diff --git a/go.mod b/go.mod index f872460..b01befb 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,14 @@ go 1.18 require ( github.com/Masterminds/squirrel v1.5.3 - github.com/dominikbraun/graph v0.16.0 - github.com/go-sql-driver/mysql v1.7.0 + github.com/dominikbraun/graph v0.23.0 + github.com/mattn/go-sqlite3 v1.14.17 + github.com/stretchr/testify v1.2.2 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 81b00c6..ee4c9b0 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,14 @@ github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvd github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dominikbraun/graph v0.16.0 h1:M8B6g/uVYHQ2TG+AdbRtyXg7/ASHZCihzzkttFUHidA= -github.com/dominikbraun/graph v0.16.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= +github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= diff --git a/store.go b/store.go index 89bf348..44f8e3a 100644 --- a/store.go +++ b/store.go @@ -79,6 +79,19 @@ func (s *Store[K, T]) AddVertex(hash K, value T, properties graph.VertexProperti return err } +// RemoveVertex implements graph.Store.RemoveVertex. +func (s *Store[K, T]) RemoveVertex(hash K) error { + _, err := sq. + Delete(s.config.VerticesTable). + Where(sq.Eq{ + "hash": hash, + }). + RunWith(s.db). + Exec() + + return err +} + // Vertex implements graph.Store.Vertex. func (s *Store[K, T]) Vertex(hash K) (T, graph.VertexProperties, error) { var ( @@ -217,6 +230,10 @@ func (s *Store[K, T]) Edge(sourceHash, targetHash K) (graph.Edge[K], error) { return edge, graph.ErrEdgeNotFound } + if err != nil { + return edge, fmt.Errorf("failed to scan row: %w", err) + } + if err = json.Unmarshal(attributesBytes, &edge.Properties.Attributes); err != nil { return edge, fmt.Errorf("failed to unmarshal attributes: %w", err) } @@ -269,3 +286,37 @@ func (s *Store[K, T]) ListEdges() ([]graph.Edge[K], error) { return edges, nil } + +// EdgeCount implements graph.Store.EdgeCount. +func (s *Store[K, T]) EdgeCount() (int, error) { + var count int + + // Please note that for some reason count(id) does not return the correct results for sqlite. + err := sq. + Select("count(source_hash)"). + From(s.config.EdgesTable). + RunWith(s.db). + QueryRow(). + Scan(&count) + + return count, err +} + +func (s *Store[K, T]) UpdateEdge(sourceHash, targetHash K, edge graph.Edge[K]) error { + + attributesBytes, err := json.Marshal(edge.Properties.Attributes) + if err != nil { + return err + } + + _, err = sq.Update(s.config.EdgesTable). + Set("weight", edge.Properties.Weight). + Set("attributes", attributesBytes). + Set("data", edge.Properties.Data). + Where("source_hash = ?", sourceHash). + Where("target_hash = ?", targetHash). + RunWith(s.db). + Exec() + + return err +} diff --git a/store_test.go b/store_test.go new file mode 100644 index 0000000..cdf9d84 --- /dev/null +++ b/store_test.go @@ -0,0 +1,159 @@ +package graphsql + +import ( + "database/sql" + "fmt" + + "testing" + "github.com/stretchr/testify/assert" + + "github.com/dominikbraun/graph" + + _ "github.com/mattn/go-sqlite3" +) + +func createStore[K comparable, T any]() (*Store[K, T], error) { + + db, err := sql.Open("sqlite3", "file::memory:") + if err != nil { + panic(err) + } + + store := New[K, T](db, DefaultConfig) + if store == nil { + return nil, fmt.Errorf("failed to create new store") + } + + if err := store.SetupTables(); err != nil { + return nil, err + } + + return store, nil +} + +func TestImplementsStoreInterface(t *testing.T) { + + var store = Store[int, int]{} + + // this will throw a compile error if graphsql.Store doesn't implement the graph.Store interface + var _ graph.Store[int, int] = (*Store[int, int])(&store) +} + +func TestEdgeCount(t *testing.T) { + + assert := assert.New(t) + + store, err := createStore[int, int]() + assert.Nil(err) + assert.NotNil(store) + + store.AddVertex(1, 1, graph.VertexProperties{}) + store.AddVertex(2, 2, graph.VertexProperties{}) + + edgeCount, err := store.EdgeCount() + assert.Nil(err) + assert.Equal(0, edgeCount) + + store.AddEdge(1, 2, graph.Edge[int]{ 1, 2, graph.EdgeProperties{} }) + + edgeCount, err = store.EdgeCount() + assert.Nil(err) + assert.Equal(1, edgeCount) + + store.AddEdge(2, 1, graph.Edge[int]{ 2, 1, graph.EdgeProperties{} }) + + edgeCount, err = store.EdgeCount() + assert.Nil(err) + assert.Equal(2, edgeCount) + + store.AddEdge(1, 1, graph.Edge[int]{ 1, 1, graph.EdgeProperties{} }) + + edgeCount, err = store.EdgeCount() + assert.Nil(err) + assert.Equal(3, edgeCount) + + store.AddEdge(2, 2, graph.Edge[int]{ 2, 2, graph.EdgeProperties{} }) + + edgeCount, err = store.EdgeCount() + assert.Nil(err) + assert.Equal(4, edgeCount) + + store.RemoveEdge(2, 2) + + edgeCount, err = store.EdgeCount() + assert.Nil(err) + assert.Equal(3, edgeCount) +} + +func TestRemoveVertex(t *testing.T) { + + assert := assert.New(t) + + store, err := createStore[int, int]() + assert.Nil(err) + assert.NotNil(store) + + store.AddVertex(1, 1, graph.VertexProperties{}) + + vertexCount, err := store.VertexCount() + assert.Nil(err) + assert.Equal(1, vertexCount) + + err = store.RemoveVertex(1) + assert.Nil(err) + + vertexCount, err = store.VertexCount() + assert.Nil(err) + assert.Equal(0, vertexCount) + + // larger graph + store.AddVertex(1, 1, graph.VertexProperties{}) + store.AddVertex(2, 2, graph.VertexProperties{}) + store.AddVertex(3, 3, graph.VertexProperties{}) + store.AddVertex(4, 4, graph.VertexProperties{}) + + vertexCount, err = store.VertexCount() + assert.Nil(err) + assert.Equal(4, vertexCount) + + err = store.RemoveVertex(3) + assert.Nil(err) + + vertexCount, err = store.VertexCount() + assert.Nil(err) + assert.Equal(3, vertexCount) + + _, _, err = store.Vertex(3) + assert.NotNil(err) +} + +func TestUpdateEdge(t *testing.T) { + + assert := assert.New(t) + + store, err := createStore[int, int]() + assert.Nil(err) + assert.NotNil(store) + + store.AddVertex(1, 1, graph.VertexProperties{}) + store.AddVertex(2, 2, graph.VertexProperties{}) + + store.AddEdge(1, 2, graph.Edge[int]{ 1, 2, graph.EdgeProperties{} }) + store.AddEdge(2, 1, graph.Edge[int]{ 2, 1, graph.EdgeProperties{} }) + store.AddEdge(1, 1, graph.Edge[int]{ 1, 1, graph.EdgeProperties{} }) + store.AddEdge(2, 2, graph.Edge[int]{ 2, 2, graph.EdgeProperties{} }) + + err = store.UpdateEdge(1, 1, graph.Edge[int]{ 1, 1, graph.EdgeProperties{ + Attributes: map[string]string{ "abc": "xyz"}, + Weight: 5, + Data: "happy", + }}) + + assert.Nil(err) + + edge, err := store.Edge(1, 1) + assert.Nil(err) + assert.Equal(5, edge.Properties.Weight) + assert.Equal("xyz", edge.Properties.Attributes["abc"]) + assert.Equal("happy", edge.Properties.Data) +}