package synchronizator // A user representation of a node, this interface should be implemented by the user // to provide the ability to parse the database node into a user defined // struct that fulfills it's requirements. // // Example usage: // // type Library struct { // Name string `json:"name"` // Category string `json:"category"` // Metadata map[string]interface{} `json:"metadata"` // } // // func (library *Library) ToNode() (string, string, []byte, error) { // metadata, err := json.Marshal(library.Metadata) // if err != nil { // return "", "", nil, err // } // return "LIBRARY", library.Name, metadata, nil // } // // func (library *Library) FromNode(_class string, name string, metadata []byte) error { // if _class != "LIBRARY" { // return fmt.Errorf("invalid class %s", _class) // } // if err := json.Unmarshal(metadata, &library.Metadata); err != nil { // return err // } // library.Name = name // return nil // } type NodeClass interface { // How to transform the struct into a node. It needs to return the class, // name and a []byte representation of the metadata. // // - class: Is used for classification and query pourposes. It's recomended to provide a constante string to increase consistency. // - name: A user friendly name // - metadata: Arbitrary data. This will be stored as a jsonb in the database // ToNode() (string, string, []byte, error) // How to transform a node into the struct. This method should modify the // struct directly as it receives a pointer. // // - class: Is used for classification and query pourposes. // - name: A user friendly name // - metadata: Arbitrary data. This is stored as a jsonb in the database FromNode(string, string, []byte) error } // A node in the database. // It adds some helper methods to easily manage the node. type Node struct { _conn *DB // Underlaying connection to the database. _class string // The class of the node, should not be modified to avoid inconsistencies. _relationships []*Relationship // Relationships of the node Id int64 // The id of the node name string // The name of the node metadata []byte // Arbitrary data. This is stored as a jsonb in the database } // Creates a new relation of type RelationshipClass to the node with the // provided id. An error is returned if the relation already exists. // // This method is a wrapper around the AddRelation method of the connection. func (node *Node) AddRelation(relation RelationshipClass, to int64) (*Relationship, error) { return node._conn.AddRelation(node.Id, relation, to) } // Update a relation with the node of the provided id. // // This method is a wrapper around the UpdateRelation method of the connection. func (node *Node) UpdateRelation(metadata any, to int64) error { return node._conn.UpdateRelation(node.Id, metadata, to) } // Delete a relation with the node of the provided id. // // This method is a wrapper around the DeleteRelation method of the connection. func (node *Node) DeleteRelation(to int64) error { return node._conn.DeleteRelation(node.Id, to) } // Fetch all the outgoing relations for this node. This method will return a // slice of relationships pointers and also store them in the node for further // use. func (node *Node) GetOutRelations() ([]*Relationship, error) { sql := ` WITH RECURSIVE NodeRelationships AS ( SELECT *, relationships._class AS relationship_class, relationships.metadata AS relationship_metadata FROM nodes as src JOIN relationships ON src.id = relationships.node_from WHERE src.id = $1 ) SELECT NodeRelationships.id as src_node, NodeRelationships.relationship_class, NodeRelationships.relationship_metadata, dst.id as dst_id FROM NodeRelationships JOIN nodes as dst ON dst.id = NodeRelationships.node_to ` rows, err := node._conn.Query(sql, node.Id) if err != nil { return nil, err } defer rows.Close() node._relationships = make([]*Relationship, 0) for rows.Next() { relationship := &Relationship{ _conn: node._conn, } if err := rows.Scan(&relationship.From, &relationship._class, &relationship.Metadata, &relationship.To); err != nil { return nil, err } node._relationships = append(node._relationships, relationship) } return node._relationships, nil } // Deletes this node from the database. // This method is a wrapper around the DeleteNode method of the connection. func (node *Node) Delete() error { return node._conn.DeleteNode(node.Id) } // Allows to retreive the saved information back into the user struct. This // method will call the [NodeClass.FromNode] of the provided struct. // // Example: // // data := &Library{} // if err := node.Unmarshall(data); err != nil { // println(err) // } func (node *Node) Unmarshall(dst NodeClass) error { return dst.FromNode(node._class, node.name, node.metadata) }