From 1a1b53ece94e0c27402b1646b8b6a6b9d5f1fbcb Mon Sep 17 00:00:00 2001 From: Avi Avni Date: Sun, 7 Apr 2024 16:48:51 +0300 Subject: [PATCH] address some review comments --- README.md | 2 +- client_test.go | 2 +- example_graph_test.go | 2 +- .../falkordb_tls_client.go | 3 +- falkordb.go | 13 +- graph.go | 28 ++-- graph_schema.go | 67 +++++---- query_result.go | 129 ++++++++++++------ 8 files changed, 154 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 653dffe..65ed0da 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ import ( ) func main() { - db, _ := falkordb.FalkorDBNew("0.0.0.0:6379", &falkordb.ConnectionOption{}) + db, _ := falkordb.FalkorDBNew(&falkordb.ConnectionOption{Addr: "0.0.0.0:6379"}) graph := db.SelectGraph("social") diff --git a/client_test.go b/client_test.go index 5479c5f..5a76a2d 100644 --- a/client_test.go +++ b/client_test.go @@ -10,7 +10,7 @@ import ( var graph *Graph func createGraph() { - db, _ := FalkorDBNew("0.0.0.0:6379", &ConnectionOption{}) + db, _ := FalkorDBNew(&ConnectionOption{Addr: "0.0.0.0:6379"}) graph = db.SelectGraph("social") graph.Delete() diff --git a/example_graph_test.go b/example_graph_test.go index 94c0fb2..032d0e1 100644 --- a/example_graph_test.go +++ b/example_graph_test.go @@ -7,7 +7,7 @@ import ( ) func ExampleSelectGraph() { - db, _ := falkordb.FalkorDBNew("0.0.0.0:6379", &falkordb.ConnectionOption{}) + db, _ := falkordb.FalkorDBNew(&falkordb.ConnectionOption{Addr: "0.0.0.0:6379"}) graph := db.SelectGraph("social") diff --git a/examples/falkordb_tls_client/falkordb_tls_client.go b/examples/falkordb_tls_client/falkordb_tls_client.go index 814db75..b02dcc5 100644 --- a/examples/falkordb_tls_client/falkordb_tls_client.go +++ b/examples/falkordb_tls_client/falkordb_tls_client.go @@ -67,7 +67,8 @@ func main() { // This should be used only for testing. clientTLSConfig.InsecureSkipVerify = true - db, _ := falkordb.FalkorDBNew(*host, &falkordb.ConnectionOption{ + db, _ := falkordb.FalkorDBNew(&falkordb.ConnectionOption{ + Addr: *host, Password: *password, TLSConfig: clientTLSConfig, }) diff --git a/falkordb.go b/falkordb.go index 40fffe6..4a34730 100644 --- a/falkordb.go +++ b/falkordb.go @@ -20,7 +20,8 @@ func isSentinel(conn *redis.Client) bool { return info["server"]["redis_mode"] == "sentinel" } -func FalkorDBNew(address string, options *ConnectionOption) (*FalkorDB, error) { +// FalkorDB Class for interacting with a FalkorDB server. +func FalkorDBNew(options *ConnectionOption) (*FalkorDB, error) { db := redis.NewClient(options) if isSentinel(db) { @@ -34,12 +35,13 @@ func FalkorDBNew(address string, options *ConnectionOption) (*FalkorDB, error) { masterName := masters.([]interface{})[0].(map[string]interface{})["name"].(string) db = redis.NewFailoverClient(&redis.FailoverOptions{ MasterName: masterName, - SentinelAddrs: []string{address}, + SentinelAddrs: []string{options.Addr}, }) } return &FalkorDB{Conn: db}, nil } +// Creates a new FalkorDB instance from a URL. func FromURL(url string) (*FalkorDB, error) { options, err := redis.ParseURL(url) if err != nil { @@ -63,18 +65,25 @@ func FromURL(url string) (*FalkorDB, error) { return &FalkorDB{Conn: db}, nil } +// Selects a graph by creating a new Graph instance. func (db *FalkorDB) SelectGraph(graphName string) *Graph { return graphNew(graphName, db.Conn) } +// List all graph names. +// See: https://docs.falkordb.com/commands/graph.list.html func (db *FalkorDB) ListGraphs() ([]string, error) { return db.Conn.Do(ctx, "GRAPH.LIST").StringSlice() } +// Retrieve a DB level configuration. +// For a list of available configurations see: https://docs.falkordb.com/configuration.html#falkordb-configuration-parameters func (db *FalkorDB) ConfigGet(key string) string { return db.Conn.Do(ctx, "GRAPH.CONFIG", "GET", key).String() } +// Update a DB level configuration. +// For a list of available configurations see: https://docs.falkordb.com/configuration.html#falkordb-configuration-parameters func (db *FalkorDB) ConfigSet(key, value string) error { return db.Conn.Do(ctx, "GRAPH.CONFIG", "SET", key).Err() } diff --git a/graph.go b/graph.go index 3764070..316c8d1 100644 --- a/graph.go +++ b/graph.go @@ -61,17 +61,16 @@ func (options *QueryOptions) GetTimeout() int { return options.timeout } -// Query executes a query against the graph. -func (g *Graph) Query(q string, params map[string]interface{}, options *QueryOptions) (*QueryResult, error) { +func (g *Graph) query(command string, q string, params map[string]interface{}, options *QueryOptions) (*QueryResult, error) { if params != nil { q = BuildParamsHeader(params) + q } var r interface{} var err error if options != nil && options.timeout >= 0 { - r, err = g.Conn.Do(ctx, "GRAPH.QUERY", g.Id, q, "--compact", "timeout", options.timeout).Result() + r, err = g.Conn.Do(ctx, command, g.Id, q, "--compact", "timeout", options.timeout).Result() } else { - r, err = g.Conn.Do(ctx, "GRAPH.QUERY", g.Id, q, "--compact").Result() + r, err = g.Conn.Do(ctx, command, g.Id, q, "--compact").Result() } if err != nil { return nil, err @@ -80,23 +79,14 @@ func (g *Graph) Query(q string, params map[string]interface{}, options *QueryOpt return QueryResultNew(g, r) } +// Query executes a query against the graph. +func (g *Graph) Query(q string, params map[string]interface{}, options *QueryOptions) (*QueryResult, error) { + return g.query("GRAPH.QUERY", q, params, options) +} + // ROQuery executes a read only query against the graph. func (g *Graph) ROQuery(q string, params map[string]interface{}, options *QueryOptions) (*QueryResult, error) { - if params != nil { - q = BuildParamsHeader(params) + q - } - var r interface{} - var err error - if options != nil && options.timeout >= 0 { - r, err = g.Conn.Do(ctx, "GRAPH.RO_QUERY", g.Id, q, "--compact", "timeout", options.timeout).Result() - } else { - r, err = g.Conn.Do(ctx, "GRAPH.RO_QUERY", g.Id, q, "--compact").Result() - } - if err != nil { - return nil, err - } - - return QueryResultNew(g, r) + return g.query("GRAPH.RO_QUERY", q, params, options) } // Procedures diff --git a/graph_schema.go b/graph_schema.go index 65d8ac0..0b8f294 100644 --- a/graph_schema.go +++ b/graph_schema.go @@ -1,5 +1,7 @@ package falkordb +import "errors" + type GraphSchema struct { graph *Graph version int @@ -24,72 +26,87 @@ func (gs *GraphSchema) clear() { gs.properties = []string{} } -func (gs *GraphSchema) refresh_labels() { - qr, _ := gs.graph.CallProcedure("db.labels", nil) +func (gs *GraphSchema) refresh_labels() error { + qr, err := gs.graph.CallProcedure("db.labels", nil) + if err != nil { + return err + } gs.labels = make([]string, len(qr.results)) for idx, r := range qr.results { gs.labels[idx] = r.GetByIndex(0).(string) } + return nil } -func (gs *GraphSchema) refresh_relationships() { - qr, _ := gs.graph.CallProcedure("db.relationshipTypes", nil) +func (gs *GraphSchema) refresh_relationships() error { + qr, err := gs.graph.CallProcedure("db.relationshipTypes", nil) + if err != nil { + return err + } gs.relationships = make([]string, len(qr.results)) for idx, r := range qr.results { gs.relationships[idx] = r.GetByIndex(0).(string) } + return nil } -func (gs *GraphSchema) refresh_properties() { - qr, _ := gs.graph.CallProcedure("db.propertyKeys", nil) +func (gs *GraphSchema) refresh_properties() error { + qr, err := gs.graph.CallProcedure("db.propertyKeys", nil) + if err != nil { + return err + } gs.properties = make([]string, len(qr.results)) for idx, r := range qr.results { gs.properties[idx] = r.GetByIndex(0).(string) } + return nil } -func (gs *GraphSchema) getLabel(lblIdx int) string { +func (gs *GraphSchema) getLabel(lblIdx int) (string, error) { if lblIdx >= len(gs.labels) { - gs.refresh_labels() - // Retry. + err := gs.refresh_labels() + if err != nil { + return "", err + } if lblIdx >= len(gs.labels) { - // Error! - panic("Unknown label index.") + return "", errors.New("Unknown label index.") + } } - return gs.labels[lblIdx] + return gs.labels[lblIdx], nil } -func (gs *GraphSchema) getRelation(relIdx int) string { +func (gs *GraphSchema) getRelation(relIdx int) (string, error) { if relIdx >= len(gs.relationships) { - gs.refresh_relationships() - // Retry. + err := gs.refresh_relationships() + if err != nil { + return "", err + } if relIdx >= len(gs.relationships) { - // Error! - panic("Unknown relation type index.") + return "", errors.New("Unknown label index.") } } - return gs.relationships[relIdx] + return gs.relationships[relIdx], nil } -func (gs *GraphSchema) getProperty(propIdx int) string { +func (gs *GraphSchema) getProperty(propIdx int) (string, error) { if propIdx >= len(gs.properties) { - gs.refresh_properties() - - // Retry. + err := gs.refresh_properties() + if err != nil { + return "", err + } if propIdx >= len(gs.properties) { - // Error! - panic("Unknown property index.") + return "", errors.New("Unknown property index.") } } - return gs.properties[propIdx] + return gs.properties[propIdx], nil } diff --git a/query_result.go b/query_result.go index 165af8f..f868885 100644 --- a/query_result.go +++ b/query_result.go @@ -1,6 +1,7 @@ package falkordb import ( + "errors" "fmt" "os" "strconv" @@ -119,7 +120,7 @@ func (qr *QueryResult) parseHeader(raw_header interface{}) { } } -func (qr *QueryResult) parseRecords(raw_result_set []interface{}) { +func (qr *QueryResult) parseRecords(raw_result_set []interface{}) error { records := raw_result_set[1].([]interface{}) qr.results = make([]*Record, len(records)) @@ -131,35 +132,53 @@ func (qr *QueryResult) parseRecords(raw_result_set []interface{}) { t := qr.header.column_types[idx] switch t { case COLUMN_SCALAR: - s := c.([]interface{}) - values[idx] = qr.parseScalar(s) + s, err := qr.parseScalar(c.([]interface{})) + if err != nil { + return err + } + values[idx] = s case COLUMN_NODE: - values[idx] = qr.parseNode(c) + v, err := qr.parseNode(c) + if err != nil { + return err + } + values[idx] = v case COLUMN_RELATION: - values[idx] = qr.parseEdge(c) + v, err := qr.parseEdge(c) + if err != nil { + return err + } + values[idx] = v default: - panic("Unknown column type.") + return errors.New("unknown column type") } } qr.results[i] = recordNew(values, qr.header.column_names) } + return nil } -func (qr *QueryResult) parseProperties(props []interface{}) map[string]interface{} { +func (qr *QueryResult) parseProperties(props []interface{}) (map[string]interface{}, error) { // [[name, value type, value] X N] properties := make(map[string]interface{}) for _, prop := range props { p := prop.([]interface{}) idx := p[0].(int64) - prop_name := qr.graph.schema.getProperty(int(idx)) - prop_value := qr.parseScalar(p[1:]) + prop_name, err := qr.graph.schema.getProperty(int(idx)) + if err != nil { + return nil, err + } + prop_value, err := qr.parseScalar(p[1:]) + if err != nil { + return nil, err + } properties[prop_name] = prop_value } - return properties + return properties, nil } -func (qr *QueryResult) parseNode(cell interface{}) *Node { +func (qr *QueryResult) parseNode(cell interface{}) (*Node, error) { // Node ID (integer), // [label string offset (integer)], // [[name, value type, value] X N] @@ -169,18 +188,25 @@ func (qr *QueryResult) parseNode(cell interface{}) *Node { labelIds := c[1].([]interface{}) labels := make([]string, len(labelIds)) for i := 0; i < len(labelIds); i++ { - labels[i] = qr.graph.schema.getLabel(int(labelIds[i].(int64))) + label, err := qr.graph.schema.getLabel(int(labelIds[i].(int64))) + if err != nil { + return nil, err + } + labels[i] = label } rawProps := c[2].([]interface{}) - properties := qr.parseProperties(rawProps) + properties, err := qr.parseProperties(rawProps) + if err != nil { + return nil, err + } n := NodeNew(labels, "", properties) n.ID = uint64(id) - return n + return n, nil } -func (qr *QueryResult) parseEdge(cell interface{}) *Edge { +func (qr *QueryResult) parseEdge(cell interface{}) (*Edge, error) { // Edge ID (integer), // reltype string offset (integer), // src node ID offset (integer), @@ -190,89 +216,108 @@ func (qr *QueryResult) parseEdge(cell interface{}) *Edge { c := cell.([]interface{}) id := c[0].(int64) r := c[1].(int64) - relation := qr.graph.schema.getRelation(int(r)) + relation, err := qr.graph.schema.getRelation(int(r)) + if err != nil { + return nil, err + } src_node_id := c[2].(int64) dest_node_id := c[3].(int64) rawProps := c[4].([]interface{}) - properties := qr.parseProperties(rawProps) + properties, err := qr.parseProperties(rawProps) + if err != nil { + return nil, err + } e := EdgeNew(relation, nil, nil, properties) e.ID = uint64(id) e.srcNodeID = uint64(src_node_id) e.destNodeID = uint64(dest_node_id) - return e + return e, nil } -func (qr *QueryResult) parseArray(cell interface{}) []interface{} { +func (qr *QueryResult) parseArray(cell interface{}) ([]interface{}, error) { var array = cell.([]interface{}) var arrayLength = len(array) for i := 0; i < arrayLength; i++ { - array[i] = qr.parseScalar(array[i].([]interface{})) + s, err := qr.parseScalar(array[i].([]interface{})) + if err != nil { + return nil, err + } + array[i] = s } - return array + return array, nil } -func (qr *QueryResult) parsePath(cell interface{}) Path { +func (qr *QueryResult) parsePath(cell interface{}) (Path, error) { arrays := cell.([]interface{}) - nodes := qr.parseScalar(arrays[0].([]interface{})) - edges := qr.parseScalar(arrays[1].([]interface{})) - return PathNew(nodes.([]interface{}), edges.([]interface{})) + nodes, err := qr.parseScalar(arrays[0].([]interface{})) + if err != nil { + return Path{}, err + } + edges, err := qr.parseScalar(arrays[1].([]interface{})) + if err != nil { + return Path{}, err + } + return PathNew(nodes.([]interface{}), edges.([]interface{})), nil } -func (qr *QueryResult) parseMap(cell interface{}) map[string]interface{} { +func (qr *QueryResult) parseMap(cell interface{}) (map[string]interface{}, error) { var raw_map = cell.([]interface{}) var mapLength = len(raw_map) var parsed_map = make(map[string]interface{}) for i := 0; i < mapLength; i += 2 { key := raw_map[i].(string) - parsed_map[key] = qr.parseScalar(raw_map[i+1].([]interface{})) + s, err := qr.parseScalar(raw_map[i+1].([]interface{})) + if err != nil { + return nil, err + } + parsed_map[key] = s } - return parsed_map + return parsed_map, nil } -func (qr *QueryResult) parseScalar(cell []interface{}) interface{} { +func (qr *QueryResult) parseScalar(cell []interface{}) (interface{}, error) { t := cell[0].(int64) v := cell[1] - var s interface{} switch ResultSetScalarTypes(t) { case VALUE_NULL: - return nil + return nil, nil case VALUE_STRING: - s = v.(string) + return v.(string), nil case VALUE_INTEGER: - s = v.(int64) + return v.(int64), nil case VALUE_BOOLEAN: - s = v.(string) == "true" + return v.(string) == "true", nil case VALUE_DOUBLE: - s, _ = strconv.ParseFloat(v.(string), 64) + return strconv.ParseFloat(v.(string), 64) case VALUE_ARRAY: - s = qr.parseArray(v) + return qr.parseArray(v) case VALUE_EDGE: - s = qr.parseEdge(v) + return qr.parseEdge(v) case VALUE_NODE: - s = qr.parseNode(v) + return qr.parseNode(v) case VALUE_PATH: - s = qr.parsePath(v) + return qr.parsePath(v) case VALUE_MAP: - s = qr.parseMap(v) + return qr.parseMap(v) case VALUE_UNKNOWN: - panic("Unknown scalar type\n") + return nil, errors.New("unknown scalar type") } - return s + return nil, errors.New("unknown scalar type") } func (qr *QueryResult) getStat(stat string) float64 {