diff --git a/integration_test.go b/integration_test.go index 403268d..be61a3b 100644 --- a/integration_test.go +++ b/integration_test.go @@ -20,12 +20,54 @@ package gogm import ( + uuid2 "github.com/google/uuid" + "testing" "time" "github.com/stretchr/testify/require" ) +// This test is to make sure retuning raw results from neo4j actually work. This +// proves that the bug causing empty interfaces to be returned has been fixed. +func TestRawQuery(t *testing.T) { + if testing.Short() { + t.Skip() + return + } + + req := require.New(t) + + conf := Config{ + Username: "neo4j", + Password: "password", + Host: "0.0.0.0", + IsCluster: false, + Port: 7687, + PoolSize: 15, + IndexStrategy: IGNORE_INDEX, + } + + req.Nil(Init(&conf, &a{}, &b{}, &c{})) + + sess, err := NewSession(false) + req.Nil(err) + + uuid := uuid2.New().String() + + req.Nil(sess.Save(&a{ + BaseNode: BaseNode{ + UUID: uuid, + }, + })) + + raw, err := sess.QueryRaw("match (n) where n.uuid=$uuid return n", map[string]interface{}{ + "uuid": uuid, + }) + req.Nil(err) + req.NotEmpty(raw) +} + func TestIntegration(t *testing.T) { if testing.Short() { t.Skip() diff --git a/mocks/ISession.go b/mocks/ISession.go index 3ba510c..9f9f794 100644 --- a/mocks/ISession.go +++ b/mocks/ISession.go @@ -1,29 +1,13 @@ -// Copyright (c) 2020 MindStand Technologies, Inc -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v2.2.1. DO NOT EDIT. package mocks -import go_cypherdsl "github.com/mindstand/go-cypherdsl" -import gogm "github.com/mindstand/gogm" -import mock "github.com/stretchr/testify/mock" +import ( + go_cypherdsl "github.com/mindstand/go-cypherdsl" + gogm "github.com/mindstand/gogm" + + mock "github.com/stretchr/testify/mock" +) // ISession is an autogenerated mock type for the ISession type type ISession struct { diff --git a/model.go b/model.go index 73cfa4f..9975be3 100644 --- a/model.go +++ b/model.go @@ -19,6 +19,8 @@ package gogm +import "github.com/neo4j/neo4j-go-driver/neo4j" + // specifies how edges are loaded type neoEdgeConfig struct { Id int64 @@ -33,3 +35,68 @@ type neoEdgeConfig struct { Type string } + +// NodeWrap wraps the neo4j node struct because it is private +type NodeWrap struct { + Id int64 `json:"id"` + Labels []string `json:"labels"` + Props map[string]interface{} `json:"props"` +} + +func newNodeWrap(node neo4j.Node) *NodeWrap { + return &NodeWrap{ + Id: node.Id(), + Labels: node.Labels(), + Props: node.Props(), + } +} + +// PathWrap wraps the neo4j path struct because it is private +type PathWrap struct { + Nodes []*NodeWrap `json:"nodes"` + RelNodes []*RelationshipWrap `json:"rel_nodes"` +} + +func newPathWrap(path neo4j.Path) *PathWrap { + pw := new(PathWrap) + nodes := path.Nodes() + if nodes != nil && len(nodes) != 0 { + nds := make([]*NodeWrap, len(nodes), cap(nodes)) + for i, n := range nodes { + nds[i] = newNodeWrap(n) + } + + pw.Nodes = nds + } + + rels := path.Relationships() + if rels != nil && len(rels) != 0 { + newRels := make([]*RelationshipWrap, len(rels), cap(rels)) + for i, rel := range rels { + newRels[i] = newRelationshipWrap(rel) + } + + pw.RelNodes = newRels + } + + return pw +} + +// RelationshipWrap wraps the neo4j relationship struct because it is private +type RelationshipWrap struct { + Id int64 `json:"id"` + StartId int64 `json:"start_id"` + EndId int64 `json:"end_id"` + Type string `json:"type"` + Props map[string]interface{} `json:"props"` +} + +func newRelationshipWrap(rel neo4j.Relationship) *RelationshipWrap { + return &RelationshipWrap{ + Id: rel.Id(), + StartId: rel.StartId(), + EndId: rel.EndId(), + Type: rel.Type(), + Props: rel.Props(), + } +} diff --git a/session.go b/session.go index c6ae09e..75db506 100644 --- a/session.go +++ b/session.go @@ -503,8 +503,30 @@ func (s *Session) QueryRaw(query string, properties map[string]interface{}) ([][ var result [][]interface{} + // we have to wrap everything because the driver only exposes interfaces which are not serializable for res.Next() { - result = append(result, res.Record().Values()) + valLen := len(res.Record().Values()) + valCap := cap(res.Record().Values()) + if valLen != 0 { + vals := make([]interface{}, valLen, valCap) + for i, val := range res.Record().Values() { + switch val.(type) { + case neo4j.Path: + vals[i] = newPathWrap(val.(neo4j.Path)) + break + case neo4j.Relationship: + vals[i] = newRelationshipWrap(val.(neo4j.Relationship)) + break + case neo4j.Node: + vals[i] = newNodeWrap(val.(neo4j.Node)) + break + default: + vals[i] = val + continue + } + } + result = append(result, vals) + } } return result, nil diff --git a/test_util.go b/test_util.go index e00579a..f8fe595 100644 --- a/test_util.go +++ b/test_util.go @@ -77,7 +77,6 @@ func (t testPath) Relationships() []neo4j.Relationship { } type testRecord struct { - } func (t *testRecord) Keys() []string { @@ -141,7 +140,7 @@ func (t testRecord) GetByIndex(index int) interface{} { type testResult struct { empty bool - num int + num int } func (t *testResult) Keys() ([]string, error) {