Commits

Marcus von Appen committed d549efd

- added a simple storage interface and SqlStorage backend to ebs
- formatting changes to the ebs code files

  • Participants
  • Parent commits 5aeeec7

Comments (0)

Files changed (4)

 	Add(id Entity, values []Component)
 	Delete(id Entity)
 	AddComponentType(ctype ComponentType)
-    ComponentTypes() []ComponentType
+	ComponentTypes() []ComponentType
 	Component(id Entity, ctype ComponentType) (Component, bool)
-    Components(ctype ComponentType) []Component
+	Components(ctype ComponentType) []Component
 	SetComponent(id Entity, value Component)
 	DeleteComponent(id Entity, ctype ComponentType)
 	Systems(ctype ComponentType) []System
-    AddSystem(system System, ctypes []ComponentType)
+	AddSystem(system System, ctypes []ComponentType)
 	RemoveSystem(system System)
 }
 
-
 // Processes all components of the passed World using their associated
 // processing systems. args will be passed to the System.Process()
 // implementation.
 func ProcessLinear(world World, args ...interface{}) {
-    for _, ctype := range world.ComponentTypes() {
-        for _, system := range world.Systems(ctype) {
-            system.Process(world.Components(ctype), args...)
-        }
-    }
+	for _, ctype := range world.ComponentTypes() {
+		for _, system := range world.Systems(ctype) {
+			system.Process(world.Components(ctype), args...)
+		}
+	}
 }
 
 func process(wg *sync.WaitGroup, systems []System, components []Component,
 	args ...interface{}) {
-    for _, system := range systems {
-        system.Process(components, args...)
-    }
-    wg.Done()
+	for _, system := range systems {
+		system.Process(components, args...)
+	}
+	wg.Done()
 }
 
 // Processes all components of the passed World using their associated
 // implementation. This will spawn a separate goroutine per component
 // type that is registered with the World.
 func ProcessParallel(world World, args ...interface{}) {
-    var wg sync.WaitGroup
-    for _, ctype := range world.ComponentTypes() {
-        wg.Add(1)
-        systems := world.Systems(ctype)
+	var wg sync.WaitGroup
+	for _, ctype := range world.ComponentTypes() {
+		wg.Add(1)
+		systems := world.Systems(ctype)
 		go process(&wg, systems, world.Components(ctype), args...)
-    }
-    wg.Wait()
+	}
+	wg.Wait()
 }
 
 // Internal comparision key used by the MemWorld
 type memWorldCompKey struct {
 	ct ComponentType
-	e Entity
+	e  Entity
 }
 
 // A simple World implementation, which keeps all entities, components
 // and component types in memory.
 type MemWorld struct {
-	lastId Entity
-	componentMap map[memWorldCompKey]Component
+	lastId           Entity
+	componentMap     map[memWorldCompKey]Component
 	componentTypeMap map[ComponentType]map[Entity]bool
-	systemMap map[ComponentType][]System
+	systemMap        map[ComponentType][]System
 }
 
 // Creates a new World implementation, which keeps all entities,
 
 // Gets the component types currently associated with the MemWorld.
 func (world *MemWorld) ComponentTypes() []ComponentType {
-    ctypes := make([]ComponentType, 0)
-    for key := range world.componentTypeMap {
-        ctypes = append(ctypes, key)
-    }
-    return ctypes
+	ctypes := make([]ComponentType, 0)
+	for key := range world.componentTypeMap {
+		ctypes = append(ctypes, key)
+	}
+	return ctypes
 }
 
 // Adds a ComponentType to the MemWorld. This usually does not need to
 // not exist or the Entity is not associated with the passed
 // ComponentType, this will return nil and false. Otherwise, this will
 // return a pointer to the stored Component and true.
-func (world *MemWorld) Component(id Entity,	ctype ComponentType) (Component, bool) {
+func (world *MemWorld) Component(id Entity, ctype ComponentType) (Component, bool) {
 	c, ok := world.componentMap[memWorldCompKey{ctype, id}]
 	return c, ok
 }
 // Gets all existing components for the specific component type. If no such
 // component type exists, nil is returned.
 func (world *MemWorld) Components(ctype ComponentType) []Component {
-    entities, ok := world.componentTypeMap[ctype]
-    if ok {
-        comps := make([]Component, 0)
-        for id := range entities {
-            comps = append(comps,
-                world.componentMap[memWorldCompKey{ctype, id}])
-        }
-        return comps
-    }
-    return nil
+	entities, ok := world.componentTypeMap[ctype]
+	if ok {
+		comps := make([]Component, 0)
+		for id := range entities {
+			comps = append(comps,
+				world.componentMap[memWorldCompKey{ctype, id}])
+		}
+		return comps
+	}
+	return nil
 }
 
 // Sets a specific Component for the Entity.
 // Gets the processing systems associated with a specific component type in
 // the order, in which they were added.
 func (world *MemWorld) Systems(ctype ComponentType) []System {
-    return world.systemMap[ctype]
+	return world.systemMap[ctype]
 }
 
 // Adds a System to the MemWorld. If the System was added already, this
 		}
 		if todelete != -1 {
 			world.systemMap[ctype] = append(list[:todelete],
-                list[todelete+1:]...)
+				list[todelete+1:]...)
 		}
 	}
 }
 package ebs
 
 import (
-	"testing"
+	"fmt"
 	"math/rand"
 	"strconv"
-	"fmt"
+	"testing"
 )
 
 var _ = fmt.Printf
 const (
 	ENTITYCOUNT = 40000
 	// Component types
-	MOVEABLE = 0x01
+	MOVEABLE   = 0x01
 	MODIFYABLE = 0x02
-	TESTABLE = 0x03
+	TESTABLE   = 0x03
 )
 
 // A simple moveable type. It features a 2D coordinate as well as a
 // velocity for that coordinate.
 type Moveable struct {
-	X float32
-	Y float32
+	X    float32
+	Y    float32
 	VelX float32
 	VelY float32
 }
+
 // The type definition for World implementations and processing systems.
 func (m *Moveable) Type() ComponentType {
 	return MOVEABLE
 }
+
 // Creates a new Moveable component. We are creating pointers here,
 // since we want the processing systems of the World to operate on the
 // components rather than on value copies of them.
 	return &Moveable{rand.Float32() * 10, rand.Float32() * 10,
 		rand.Float32(), rand.Float32()}
 }
+
 // A simple processing system, which moves the Moveable by its velocity.
 type MovementSystem int
+
 func (system *MovementSystem) Process(components []Component,
 	args ...interface{}) {
 	for _, c := range components {
 type Modifyable struct {
 	changed bool
 }
+
 // The type definition for World implementations and processing systems .
-func (m * Modifyable) Type() ComponentType {
+func (m *Modifyable) Type() ComponentType {
 	return MODIFYABLE
 }
+
 // A simple processing system, which changes the Modifyable.
 type ModifySystem int
+
 func (system *ModifySystem) Process(components []Component,
 	args ...interface{}) {
 	for _, c := range components {
 type Testable struct {
 	tested string
 }
+
 // The type definition for World implementations and processing systems .
-func (m * Testable) Type() ComponentType {
+func (m *Testable) Type() ComponentType {
 	return TESTABLE
 }
+
 // A simple processing system, which changes the Testable.
 type TestSystem int
+
 func (system *TestSystem) Process(components []Component,
 	args ...interface{}) {
 	for _, c := range components {
 	world := NewMemWorld()
 	// Create lots of cars
 	for i := 0; i < ENTITYCOUNT; i++ {
-	 	id := world.NewEntity()
+		id := world.NewEntity()
 		move := NewMoveable()
 		mod := &Modifyable{false}
 		ttest := &Testable{"0"}
 	world := NewMemWorld()
 	for i := 0; i < 100; i++ {
 		id := world.NewEntity()
-		if Entity(i + 1) != id {
+		if Entity(i+1) != id {
 			t.Errorf("MemWorld.NewEntity() creates wrong entities")
 		}
 	}
 		t.Errorf("MemWorld.Add() - MOVEABLE component not referenced")
 	}
 	c, ok = world.Component(entity, MODIFYABLE)
-	if  !ok {
+	if !ok {
 		t.Errorf("MemWorld.Add() - MODIFYABLE component not set")
 	} else if c != component2 {
 		t.Errorf("MemWorld.Add() - MODIFYABLE component not referenced")
+package ebs
+
+import (
+	"database/sql"
+	"errors"
+	"log"
+)
+
+const (
+	SQL_COUNT_ENTITIES      = `SELECT COUNT(id) FROM entities`
+	SQL_QUERY_ENTITY_IDS    = `SELECT id FROM entities`
+	SQL_QUERY_LASTENTITY_ID = `SELECT COALESCE(MAX(id), 0) FROM entities`
+	SQL_INSERT_ENTITY       = `INSERT INTO entities VALUES (?)`
+	SQL_QUERY_COMPTYPE_ID   = `
+SELECT componenttype_id FROM entity_components WHERE entity_id = ?`
+	SQL_INSERT_ENTCOMPS = `
+INSERT INTO entity_components VALUES (?, ?)`
+	SQL_QUERY_ENTITY_BY_ID_AND_COMP = `
+SELECT entity_id FROM entity_components
+WHERE entity_id = ? AND componenttype_id = ?`
+	SQL_DELETE_ENTCOMPS = `
+DELETE FROM entity_components
+WHERE entity_id = ? AND componenttype_id = ?`
+)
+
+// Interface to mark a Component as storable.
+type Storable interface {
+	Version() int
+}
+
+// A simple storage interface for the ebs framework.
+// It can be used to persist and load component data from an underlying data
+// storage.
+type Storage interface {
+	Register(ComponentType, Storable) error
+	Entities() ([]Entity, error)
+	NewEntity() (Entity, error)
+	LoadEntity(id Entity) ([]Component, error)
+	SaveEntity(id Entity, values []Component) error
+	UpdateComponent(id Entity, value Component) error
+	DeleteComponent(id Entity, value Component) error
+}
+
+// A generic SQL-based Storage implementation.
+type SqlStorage struct {
+	*sql.DB
+	*log.Logger
+	storableTypes map[ComponentType]SqlStorable
+}
+
+// Interface to mark a Component as storable for a SqlStorage.
+type SqlStorable interface {
+	Storable
+	CreateTable(tx *sql.Tx) error
+	Get(tx *sql.Tx, id uint64) (Component, error)
+	Insert(tx *sql.Tx, id Entity) error
+	Update(tx *sql.Tx, id Entity) error
+	Delete(tx *sql.Tx, id Entity) error
+}
+
+// Creates a new SqlStorage for a specific SQL database.
+func NewSqlStorage(db *sql.DB, logger *log.Logger) (*SqlStorage, error) {
+	if err := db.Ping(); err != nil {
+		logger.Printf("ERROR: Could not connect to database: %v\n", err)
+		return nil, err
+	}
+	stypes := make(map[ComponentType]SqlStorable)
+	return &SqlStorage{db, logger, stypes}, nil
+}
+
+func (storage *SqlStorage) Register(ctype ComponentType, st Storable) error {
+	storable, ok := st.(SqlStorable)
+	if !ok {
+		storage.Logger.Println("ERROR: Storable is not a SqlStorable")
+		return errors.New("Storable is not a SqlStorable")
+	}
+
+	var tx *sql.Tx
+	tx, err := storage.DB.Begin()
+	if err != nil {
+		storage.Logger.Printf("ERROR: Could not open transaction: %v\n", err)
+		return err
+	}
+	storable.CreateTable(tx)
+	if err = tx.Commit(); err != nil {
+		storage.Logger.Printf("ERROR: Could not commit transaction: %v\n", err)
+		return err
+	}
+	storage.storableTypes[ctype] = storable
+	return nil
+}
+
+func (storage *SqlStorage) Entities() ([]Entity, error) {
+	var ecount uint64
+	row := storage.DB.QueryRow(SQL_COUNT_ENTITIES)
+	if err := row.Scan(&ecount); err != nil {
+		storage.Logger.Printf("ERROR: Could not get entity amount: %v\n", err)
+		return nil, err
+	}
+	rows, err := storage.DB.Query(SQL_QUERY_ENTITY_IDS)
+	if err != nil {
+		storage.Logger.Printf("ERROR: Could not query entities: %v\n", err)
+		return nil, err
+	}
+	entities := make([]Entity, ecount)
+	for idx := 0; rows.Next(); idx++ {
+		if err := rows.Scan(&entities[idx]); err != nil {
+			storage.Logger.Printf("ERROR: Could not retrieve id: %v\n", err)
+			return nil, err
+		}
+	}
+	err = rows.Err()
+	if err != nil {
+		storage.Logger.Printf("ERROR: Could not query entities: %v\n", err)
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Creates a new Entity
+func (storage *SqlStorage) NewEntity() (Entity, error) {
+	var id Entity
+	row := storage.DB.QueryRow(SQL_QUERY_LASTENTITY_ID)
+	if err := row.Scan(&id); err != nil {
+		storage.Logger.Printf("ERROR: Could not get last entity id: %v\n", err)
+		return 0, err
+	}
+	id += 1
+	if _, err := storage.DB.Exec(SQL_INSERT_ENTITY, id); err != nil {
+		storage.Logger.Printf("ERROR: Could not insert new entity id: %v\n", err)
+		return 0, err
+	}
+	return id, nil
+}
+
+func (storage *SqlStorage) LoadEntity(id Entity) ([]Component, error) {
+	var tx *sql.Tx
+	tx, err := storage.DB.Begin()
+	if err != nil {
+		storage.Logger.Printf("ERROR: Could not open transaction: %v\n", err)
+		return nil, err
+	}
+	rows, err := tx.Query(SQL_QUERY_COMPTYPE_ID, id)
+	if err != nil {
+		storage.Logger.Printf("ERROR: Could not retrieve components for Entity: %v\n", err)
+		return nil, err
+	}
+	components := make([]Component, 0)
+	for idx := 0; rows.Next(); idx++ {
+		var ctypeid ComponentType
+		if err := rows.Scan(&ctypeid); err != nil {
+			storage.Logger.Printf("ERROR: Could not retrieve component type: %v\n", err)
+			continue
+		}
+		if st, ok := storage.storableTypes[ctypeid]; !ok {
+			storage.Logger.Printf("ERROR: Could not find registered component type: %v\n", err)
+			continue
+		} else {
+			comp, err := st.Get(tx, uint64(id))
+			if err != nil {
+				storage.Logger.Printf("ERROR: Could not create component: %v\n", err)
+				continue
+			}
+			components = append(components, comp)
+		}
+	}
+	if err := rows.Err(); err != nil {
+		storage.Logger.Printf("ERROR: Could not query components: %v\n", err)
+		return nil, err
+	}
+	return components, nil
+}
+
+func (storage *SqlStorage) SaveEntity(id Entity, values []Component) error {
+	var tx *sql.Tx
+	var lasterr error = nil
+	tx, err := storage.DB.Begin()
+	if err != nil {
+		storage.Logger.Printf("ERROR: Could not open transaction: %v\n", err)
+		return err
+	}
+	for _, value := range values {
+		stv, ok := ((interface{})(value)).(SqlStorable)
+		if !ok {
+			storage.Logger.Println("ERROR: Component does not implement SqlStorable")
+			continue
+		}
+		ctypeid := value.Type()
+		var tmp uint64
+		if err := tx.QueryRow(SQL_QUERY_ENTITY_BY_ID_AND_COMP, id, ctypeid).Scan(&tmp); err != nil {
+			// No row found, use INSERT
+			if err := stv.Insert(tx, id); err != nil {
+				lasterr = err
+				storage.Logger.Printf("ERROR: Could not insert component: %v\n", err)
+			}
+			if _, err := tx.Exec(SQL_INSERT_ENTCOMPS, id, ctypeid); err != nil {
+				lasterr = err
+				storage.Logger.Printf("ERROR: Could not reference inserted component: %v\n", err)
+			}
+		} else {
+			// Row found, use UPDATE
+			if err = stv.Update(tx, id); err != nil {
+				lasterr = err
+				storage.Logger.Printf("ERROR: Could not update component: %v\n", err)
+			}
+		}
+	}
+	if lasterr == nil {
+		if err = tx.Commit(); err != nil {
+			storage.Logger.Printf("ERROR: Could not commit transaction: %v\n", err)
+			return err
+		}
+	} else {
+		tx.Rollback()
+	}
+	return lasterr
+}
+
+func (storage *SqlStorage) UpdateComponent(id Entity, value Component) error {
+	var tx *sql.Tx
+	tx, err := storage.DB.Begin()
+	if err != nil {
+		storage.Logger.Printf("ERROR: Could not open transaction: %v\n", err)
+		return err
+	}
+	stv, ok := ((interface{})(value)).(SqlStorable)
+	if !ok {
+		storage.Logger.Println("ERROR: Component does not implement SqlStorable")
+		return errors.New("Component does not implement SqlStorable")
+	}
+	if err = stv.Update(tx, id); err != nil {
+		storage.Logger.Printf("ERROR: Could not update component: %v\n", err)
+		return err
+	}
+	if err = tx.Commit(); err != nil {
+		storage.Logger.Printf("ERROR: Could not commit transaction: %v\n", err)
+		return err
+	}
+	return nil
+}
+
+func (storage *SqlStorage) DeleteComponent(id Entity, value Component) error {
+	var tx *sql.Tx
+	tx, err := storage.DB.Begin()
+	if err != nil {
+		storage.Logger.Printf("ERROR: Could not open transaction: %v\n", err)
+		return err
+	}
+	stv, ok := ((interface{})(value)).(SqlStorable)
+	if !ok {
+		storage.Logger.Println("ERROR: Component does not implement SqlStorable")
+		return errors.New("Component does not implement SqlStorable")
+	}
+	if err = stv.Delete(tx, id); err != nil {
+		storage.Logger.Printf("ERROR: Could not delete component: %v\n", err)
+		return err
+	}
+	if _, err := tx.Exec(SQL_DELETE_ENTCOMPS, id, value.Type()); err != nil {
+		storage.Logger.Printf("ERROR: Could not delete component reference: %v\n", err)
+	}
+	if err = tx.Commit(); err != nil {
+		storage.Logger.Printf("ERROR: Could not commit transaction: %v\n", err)
+		return err
+	}
+	return nil
+}

ebs/storage_test.go

+package ebs
+
+import (
+	_ "code.google.com/p/go-sqlite/go1/sqlite3"
+	"database/sql"
+	"fmt"
+	"log"
+	"os"
+	"testing"
+)
+
+var _ = fmt.Printf
+var _ = fmt.Println
+
+const (
+	// Component types
+	VECTOR              = 0x01
+	NOSQLSTORE          = 0x02
+	SQL_CREATE_ENTITIES = `
+CREATE TABLE entities (
+	"id" INTEGER PRIMARY KEY NOT NULL
+)`
+	SQL_CREATE_CTYPE = `
+CREATE TABLE componenttypes (
+	"id" INTEGER PRIMARY KEY NOT NULL,
+	"type" INTEGER
+)`
+	SQL_CREATE_ECOMP = `
+CREATE TABLE entity_components (
+	"entity_id" INTEGER NOT NULL,
+	"componenttype_id" INTEGER NOT NULL,
+	PRIMARY KEY(entity_id, componenttype_id)
+)`
+	SQL_CREATE_VECTOR = `
+CREATE TABLE IF NOT EXISTS vector (
+	"entity_id" INTEGER PRIMARY KEY NOT NULL,
+	"x" FLOAT NOT NULL,
+	"y" FLOAT NOT NULL
+)`
+)
+
+// A simple 2D vector
+type Vector struct {
+	X float32
+	Y float32
+}
+
+// Make the Vector Component-aware
+func (v *Vector) Type() ComponentType {
+	return VECTOR
+}
+
+// Storage interface implementation
+func (v *Vector) Version() int {
+	return 1
+}
+
+// SqlStorage interface implementation
+func (v *Vector) CreateTable(tx *sql.Tx) error {
+	if _, err := tx.Exec(SQL_CREATE_VECTOR); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (v *Vector) Get(tx *sql.Tx, id uint64) (Component, error) {
+	row := tx.QueryRow("SELECT x, y FROM vector WHERE entity_id = ?", id)
+	if err := row.Scan(&v.X, &v.Y); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+func (v *Vector) Insert(tx *sql.Tx, id Entity) error {
+	_, err := tx.Exec("INSERT OR REPLACE INTO vector VALUES (?, ?, ?)", id, v.X, v.Y)
+	return err
+}
+
+func (v *Vector) Update(tx *sql.Tx, id Entity) error {
+	return v.Insert(tx, id)
+}
+
+func (v *Vector) Delete(tx *sql.Tx, id Entity) error {
+	_, err := tx.Exec("DELETE FROM vector WHERE entity_id = ?", id)
+	return err
+}
+
+type NonSqlStorable struct {
+	A float32
+}
+
+func (v *NonSqlStorable) Type() ComponentType {
+	return NOSQLSTORE
+}
+
+// Storage interface implementation
+func (v *NonSqlStorable) Version() int {
+	return 1
+}
+
+func TestVectorStorable(t *testing.T) {
+	var v interface{} = &Vector{1, 2}
+	if v.(*Vector).Version() != 1 {
+		t.Errorf("Vector.Version() did not work")
+	}
+	stv := v.(Storable)
+	if stv.Version() != 1 {
+		t.Errorf("Storable.Version() did not work")
+	}
+}
+
+func createSqlStorage() (*sql.DB, *SqlStorage, error) {
+	conn, err := sql.Open("sqlite3", ":memory:")
+	if err != nil {
+		return nil, nil, err
+	}
+
+	logger := log.New(os.Stderr, "", log.LstdFlags)
+	if storage, err := NewSqlStorage(conn, logger); err != nil {
+		conn.Close()
+		return nil, nil, err
+	} else {
+		if _, err := conn.Exec(SQL_CREATE_ENTITIES); err != nil {
+			conn.Close()
+			return nil, nil, err
+		}
+		if _, err := conn.Exec(SQL_CREATE_CTYPE); err != nil {
+			conn.Close()
+			return nil, nil, err
+		}
+		if _, err := conn.Exec(SQL_CREATE_ECOMP); err != nil {
+			conn.Close()
+			return nil, nil, err
+		}
+		return conn, storage, nil
+	}
+}
+
+func TestNewSqlStorage(t *testing.T) {
+	if db, _, err := createSqlStorage(); err != nil {
+		t.Fatalf("Could not create SqlStorage: %v", err)
+	} else {
+		db.Close()
+	}
+}
+
+func TestSqlStorageRegister(t *testing.T) {
+	db, storage, err := createSqlStorage()
+	if err != nil {
+		t.Fatalf("Could not create SqlStorage: %v", err)
+	}
+	defer db.Close()
+
+	if err := storage.Register(VECTOR, &Vector{}); err != nil {
+		t.Errorf("Could not call SqlStorage.Register(): %v", err)
+	}
+	if err := storage.Register(NOSQLSTORE, &NonSqlStorable{}); err == nil {
+		t.Error("SqlStorage.Register() must not allow non-SqlStorage types")
+	}
+}
+
+func TestSqlStorageNewEntityEntities(t *testing.T) {
+	db, storage, err := createSqlStorage()
+	if err != nil {
+		t.Fatalf("Could not create SqlStorage: %v", err)
+	}
+	defer db.Close()
+	for i := 1; i <= 10; i++ {
+		if e, err := storage.NewEntity(); err != nil {
+			t.Errorf("SqlStorage.NewEntity() failed: %v", err)
+		} else if e != Entity(i) {
+			t.Errorf("SqlStorage.NewEntity() invalid id: %v", e)
+		}
+	}
+	if entities, err := storage.Entities(); err != nil {
+		t.Errorf("SqlStorage.Entities() failed: %v", err)
+	} else {
+		if len(entities) != 10 {
+			t.Errorf("SqlStorage.Entities() count - expted 10, got %v",
+				len(entities))
+		}
+	}
+}
+
+func TestSqlStorageSaveEntity(t *testing.T) {
+	db, storage, err := createSqlStorage()
+	if err != nil {
+		t.Fatalf("Could not create SqlStorage: %v", err)
+	}
+	defer db.Close()
+	if err := storage.Register(VECTOR, &Vector{}); err != nil {
+		t.Errorf("Could not call SqlStorage.Register(): %v", err)
+	}
+	entity, err := storage.NewEntity()
+	if err != nil {
+		t.Errorf("SqlStorage.NewEntity() failed: %v", err)
+	}
+	component := &Vector{1, 2}
+	if err := storage.SaveEntity(entity, []Component{component}); err != nil {
+		t.Errorf("SqlStorage.SaveEntity() failed: %v", err)
+	}
+}
+
+func TestSqlStorageLoadEntity(t *testing.T) {
+	db, storage, err := createSqlStorage()
+	if err != nil {
+		t.Fatalf("Could not create SqlStorage: %v", err)
+	}
+	defer db.Close()
+	if err := storage.Register(VECTOR, &Vector{}); err != nil {
+		t.Errorf("Could not call SqlStorage.Register(): %v", err)
+	}
+	entity, err := storage.NewEntity()
+	if err != nil {
+		t.Errorf("SqlStorage.NewEntity() failed: %v", err)
+	}
+	component := &Vector{1, 2}
+	if err := storage.SaveEntity(entity, []Component{component}); err != nil {
+		t.Errorf("SqlStorage.SaveEntity() failed: %v", err)
+	}
+	if comps, err := storage.LoadEntity(entity); err != nil {
+		t.Errorf("SqlStorage.LoadEntity() failed: %v", err)
+	} else {
+		if len(comps) != 1 {
+			t.Errorf("SqlStorage.LoadEntity() failed - invalid component count: %v", len(comps))
+		} else {
+			v := comps[0].(*Vector)
+			if v.X != 1 || v.Y != 2 {
+				t.Errorf("SqlStorage.LoadEntity() failed - component values invalid")
+			}
+		}
+	}
+}
+
+func TestSqlStorageUpdateComponent(t *testing.T) {
+	db, storage, err := createSqlStorage()
+	if err != nil {
+		t.Fatalf("Could not create SqlStorage: %v", err)
+	}
+	defer db.Close()
+	if err := storage.Register(VECTOR, &Vector{}); err != nil {
+		t.Errorf("Could not call SqlStorage.Register(): %v", err)
+	}
+	entity, err := storage.NewEntity()
+	if err != nil {
+		t.Errorf("SqlStorage.NewEntity() failed: %v", err)
+	}
+	component := &Vector{1, 2}
+	if err := storage.SaveEntity(entity, []Component{component}); err != nil {
+		t.Errorf("SqlStorage.SaveEntity() failed: %v", err)
+	}
+
+	newcomp := &Vector{3, 4}
+	if err := storage.UpdateComponent(entity, newcomp); err != nil {
+		t.Errorf("SqlStorage.UpdateComponent() failed: %v", err)
+	}
+
+	if comps, err := storage.LoadEntity(entity); err != nil {
+		t.Errorf("SqlStorage.LoadEntity() failed: %v", err)
+	} else {
+		if len(comps) != 1 {
+			t.Errorf("SqlStorage.UpdateComponent() failed - invalid component count: %v", len(comps))
+		} else {
+			v := comps[0].(*Vector)
+			if v.X != 3 || v.Y != 4 {
+				t.Errorf("SqlStorage.UpdateComponent() failed - component values invalid")
+			}
+		}
+	}
+}
+
+func TestSqlStorageDeleteComponent(t *testing.T) {
+	db, storage, err := createSqlStorage()
+	if err != nil {
+		t.Fatalf("Could not create SqlStorage: %v", err)
+	}
+	defer db.Close()
+	if err := storage.Register(VECTOR, &Vector{}); err != nil {
+		t.Errorf("Could not call SqlStorage.Register(): %v", err)
+	}
+	entity, err := storage.NewEntity()
+	if err != nil {
+		t.Errorf("SqlStorage.NewEntity() failed: %v", err)
+	}
+	component := &Vector{1, 2}
+	if err := storage.SaveEntity(entity, []Component{component}); err != nil {
+		t.Errorf("SqlStorage.SaveEntity() failed: %v", err)
+	}
+
+	if err := storage.DeleteComponent(entity, component); err != nil {
+		t.Errorf("SqlStorage.DeleteComponent() failed: %v", err)
+	}
+	if comps, err := storage.LoadEntity(entity); err != nil {
+		t.Errorf("SqlStorage.LoadEntity() failed: %v", err)
+	} else if len(comps) != 0 {
+		t.Errorf("SqlStorage.DeleteComponent() failed - invalid component count: %v", len(comps))
+	}
+}