generated from alecodes/base-template
feat: add platform-collection relationship in platform.FetchCollection()
This commit is contained in:
parent
8af94161ab
commit
c1684c8dea
7 changed files with 184 additions and 79 deletions
|
|
@ -10,9 +10,13 @@ type default_collection struct {
|
|||
platform_name string
|
||||
}
|
||||
|
||||
func (collection *default_collection) ToNode() (string, []byte, error) {
|
||||
func (collection *default_collection) GetClass() string {
|
||||
platform_name := strings.ToUpper(collection.platform_name)
|
||||
return platform_name + "_DEFAULT", nil, nil
|
||||
return platform_name + "_DEFAULT"
|
||||
}
|
||||
|
||||
func (collection *default_collection) GetMetadata() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (collection *default_collection) FromNode(_class string, name string, metadata []byte) error {
|
||||
|
|
@ -46,13 +50,19 @@ func NewCollection(name string, metadata []byte) *Collection {
|
|||
}
|
||||
|
||||
// Internal RelationshipClass to handle the collection to node relationship
|
||||
type collection_relation struct{}
|
||||
|
||||
func (collection *collection_relation) ToRelationship() (string, []byte, error) {
|
||||
return "COLLECTION_HAS", nil, nil
|
||||
type collection_has_node struct {
|
||||
Relationship
|
||||
}
|
||||
|
||||
func (collection *collection_relation) FromRelationship(_class string, metadata []byte) error {
|
||||
func (col_has_node *collection_has_node) GetClass() string {
|
||||
return "COLLECTION_HAS"
|
||||
}
|
||||
|
||||
func (col_has_node *collection_has_node) GetMetadata() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (col_has_node *collection_has_node) FromRelationship(_class string, metadata []byte) error {
|
||||
if _class != "COLLECTION_HAS" {
|
||||
return fmt.Errorf("invalid class %s", _class)
|
||||
}
|
||||
|
|
@ -62,7 +72,7 @@ func (collection *collection_relation) FromRelationship(_class string, metadata
|
|||
// Adds a new child to this collection. Use the underlaying node's AddRelation
|
||||
// method.
|
||||
func (collection *Collection) AddChild(node *Node) error {
|
||||
_, err := collection.AddRelation(&collection_relation{}, node.Id)
|
||||
_, err := collection.StoreRelation(&collection_has_node{}, node.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
17
pkg/fetcher.go
Normal file
17
pkg/fetcher.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package synchronizator
|
||||
|
||||
type Fetcher = func(pagination Pagination) ([]*Collection, Pagination, error)
|
||||
|
||||
type Pagination struct {
|
||||
Total int
|
||||
HasMore bool
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
var StartPagination = Pagination{
|
||||
Total: 0,
|
||||
HasMore: false,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
}
|
||||
55
pkg/node.go
55
pkg/node.go
|
|
@ -1,10 +1,18 @@
|
|||
package synchronizator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type StandardNode interface {
|
||||
GetClass() string
|
||||
GetName() string
|
||||
GetMetadata() []byte
|
||||
|
||||
AddRelationship(relationship StandardRelationship) error
|
||||
AddRelationships(to int64, relationship []StandardRelationship)
|
||||
|
||||
SetId(int64)
|
||||
SetConnection(*DB)
|
||||
}
|
||||
|
|
@ -12,18 +20,18 @@ type StandardNode interface {
|
|||
// 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
|
||||
_conn *DB // Underlaying connection to the database.
|
||||
_class string // The class of the node, should not be modified to avoid inconsistencies.
|
||||
_relationships []StandardRelationship // 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
|
||||
}
|
||||
|
||||
func NewNode(name string, metadata []byte) *Node {
|
||||
return &Node{
|
||||
name: name,
|
||||
_class: "COLLECTION",
|
||||
_class: "NODE",
|
||||
metadata: metadata,
|
||||
Id: -1, // Use -1 to indicate not persisted
|
||||
}
|
||||
|
|
@ -41,6 +49,26 @@ func (node *Node) GetMetadata() []byte {
|
|||
return node.metadata
|
||||
}
|
||||
|
||||
func (node *Node) AddRelationship(relationship StandardRelationship) error {
|
||||
node1, node2 := relationship.GetNodes()
|
||||
|
||||
if node1 != node.Id && node2 != node.Id {
|
||||
return fmt.Errorf(
|
||||
"The current node (%v) is neither used as the source (%v), nor the destination (%v)",
|
||||
node.Id,
|
||||
node1,
|
||||
node2,
|
||||
)
|
||||
}
|
||||
node._relationships = append(node._relationships, relationship)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *Node) AddRelationships(to int64, relationships []StandardRelationship) {
|
||||
node._relationships = slices.Concat(node._relationships, relationships)
|
||||
}
|
||||
|
||||
func (node *Node) SetId(id int64) {
|
||||
node.Id = id
|
||||
}
|
||||
|
|
@ -53,7 +81,7 @@ func (node *Node) SetConnection(conn *DB) {
|
|||
// 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 StandardRelationship, to int64) (*Relationship, error) {
|
||||
func (node *Node) StoreRelation(relation StandardRelationship, to int64) (*Relationship, error) {
|
||||
return node._conn.AddRelation(node.Id, relation, to)
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +102,7 @@ func (node *Node) DeleteRelation(to int64) error {
|
|||
// 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) {
|
||||
func (node *Node) GetOutRelations() ([]StandardRelationship, error) {
|
||||
sql := `
|
||||
WITH RECURSIVE NodeRelationships AS (
|
||||
SELECT
|
||||
|
|
@ -103,13 +131,13 @@ FROM
|
|||
|
||||
defer rows.Close()
|
||||
|
||||
node._relationships = make([]*Relationship, 0)
|
||||
node._relationships = make([]StandardRelationship, 0)
|
||||
for rows.Next() {
|
||||
relationship := &Relationship{
|
||||
_conn: node._conn,
|
||||
}
|
||||
|
||||
if err := rows.Scan(&relationship.From, &relationship._class, &relationship.Metadata, &relationship.To); err != nil {
|
||||
if err := rows.Scan(&relationship.From, &relationship._class, relationship.GetMetadata(), &relationship.To); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -124,8 +152,3 @@ FROM
|
|||
func (node *Node) Delete() error {
|
||||
return node._conn.DeleteNode(node.Id)
|
||||
}
|
||||
|
||||
// Returns the class of the node
|
||||
func (relationship *Relationship) GetClass() string {
|
||||
return relationship._class
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,23 +2,6 @@ package synchronizator
|
|||
|
||||
import "slices"
|
||||
|
||||
type PlatformClass interface {
|
||||
// How to transform the struct into a node. It needs to return the class,
|
||||
// name and a []byte representation of the metadata.
|
||||
//
|
||||
// - name: A user friendly name
|
||||
// - metadata: Arbitrary data. This will be stored as a jsonb in the database
|
||||
//
|
||||
ToNode() (string, []byte, error)
|
||||
|
||||
// How to transform a node into the struct. This method should modify the
|
||||
// struct directly as it receives a pointer.
|
||||
//
|
||||
// - name: A user friendly name
|
||||
// - metadata: Arbitrary data. This is stored as a jsonb in the database
|
||||
FromNode(string, []byte) error
|
||||
}
|
||||
|
||||
// Utility struct to represent a collection of nodes, it's a [Node] itself so all
|
||||
// the node's functionality is available.
|
||||
type Platform struct {
|
||||
|
|
@ -26,22 +9,6 @@ type Platform struct {
|
|||
Collections []*Collection // Child nodes
|
||||
}
|
||||
|
||||
type Fetcher = func(pagination Pagination) ([]*Collection, Pagination, error)
|
||||
|
||||
type Pagination struct {
|
||||
Total int
|
||||
HasMore bool
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
var StartPagination = Pagination{
|
||||
Total: 0,
|
||||
HasMore: false,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
func (platform *Platform) FetchCollections(fetcher Fetcher, start_pagination Pagination) error {
|
||||
collections, pagination, err := fetcher(start_pagination)
|
||||
if err != nil {
|
||||
|
|
@ -54,6 +21,26 @@ func (platform *Platform) FetchCollections(fetcher Fetcher, start_pagination Pag
|
|||
}
|
||||
|
||||
err = BulkCreateNode(platform._conn, platform.Collections)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range platform.Collections {
|
||||
err := platform.AddRelationship(
|
||||
&Relationship{
|
||||
_class: "PLATFORM_HAS_COLLECTION",
|
||||
From: platform.Id,
|
||||
To: item.Id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = BulkCreateRelationships(platform._conn, platform._relationships)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,20 +19,11 @@ package synchronizator
|
|||
// return nil
|
||||
// }
|
||||
type StandardRelationship interface {
|
||||
// How to transform the struct into a collection. It needs to return the class,
|
||||
// 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.
|
||||
// - metadata: Arbitrary data. This will be stored as a jsonb in the database
|
||||
//
|
||||
ToRelationship() (string, []byte, error)
|
||||
GetClass() string
|
||||
GetNodes() (int64, int64)
|
||||
GetMetadata() []byte
|
||||
|
||||
// How to transform a relationship into the struct. This method should modify the
|
||||
// struct directly as it receives a pointer.
|
||||
//
|
||||
// - class: Is used for classification and query pourposes.
|
||||
// - metadata: Arbitrary data. This is stored as a jsonb in the database
|
||||
FromRelationship(string, []byte) error
|
||||
SetConnection(*DB)
|
||||
}
|
||||
|
||||
// A relationship in the database.
|
||||
|
|
@ -42,5 +33,21 @@ type Relationship struct {
|
|||
_class string // The class of the node, should not be modified to avoid inconsistencies.
|
||||
From int64 // From what node this relation comes from
|
||||
To int64 // To what node this relation goes to
|
||||
Metadata []byte // Arbitrary data. This is stored as a jsonb in the database
|
||||
metadata []byte // Arbitrary data. This is stored as a jsonb in the database
|
||||
}
|
||||
|
||||
func (relation *Relationship) GetClass() string {
|
||||
return relation._class
|
||||
}
|
||||
|
||||
func (relation *Relationship) GetMetadata() []byte {
|
||||
return relation.metadata
|
||||
}
|
||||
|
||||
func (relation *Relationship) GetNodes() (int64, int64) {
|
||||
return relation.From, relation.To
|
||||
}
|
||||
|
||||
func (relation *Relationship) SetConnection(db *DB) {
|
||||
relation._conn = db
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ func (conn *DB) NewPlatform(name string, metadata []byte) (*Platform, error) {
|
|||
Collections: []*Collection{collection},
|
||||
}
|
||||
|
||||
_, err = conn.addRelationwithTx(tx, platform.Id, &collection_relation{}, collection.Id)
|
||||
_, err = conn.addRelationwithTx(tx, platform.Id, &collection_has_node{}, collection.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -381,15 +381,13 @@ func (conn *DB) addRelationwithTx(
|
|||
data StandardRelationship,
|
||||
to int64,
|
||||
) (*Relationship, error) {
|
||||
class, metadata, err := data.ToRelationship()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
class := data.GetClass()
|
||||
metadata := data.GetMetadata()
|
||||
|
||||
relationship := Relationship{
|
||||
_conn: conn,
|
||||
_class: class,
|
||||
Metadata: metadata,
|
||||
metadata: metadata,
|
||||
From: from,
|
||||
To: to,
|
||||
}
|
||||
|
|
@ -398,7 +396,7 @@ func (conn *DB) addRelationwithTx(
|
|||
|
||||
sql := "INSERT INTO relationships (_class, node_from, node_to, metadata) VALUES ($1, $2, $3, $4) RETURNING node_from, node_to;"
|
||||
|
||||
_, err = tx.Exec(sql, relationship._class, relationship.From, relationship.To, metadata)
|
||||
_, err := tx.Exec(sql, relationship._class, relationship.From, relationship.To, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -518,3 +516,66 @@ func BulkCreateNode[T StandardNode](
|
|||
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func BulkCreateRelationships[T StandardRelationship](
|
||||
conn *DB,
|
||||
relationships []T,
|
||||
) error {
|
||||
if len(relationships) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tx, err := conn.Connection.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tx.Rollback()
|
||||
|
||||
// Build the query dynamically based on number of nodes
|
||||
valueStrings := make([]string, 0, len(relationships))
|
||||
valueArgs := make([]interface{}, 0, len(relationships)*3)
|
||||
|
||||
for i := range relationships {
|
||||
n := i * 4
|
||||
valueStrings = append(
|
||||
valueStrings,
|
||||
fmt.Sprintf("($%d, $%d, $%d, $%d) ", n+1, n+2, n+3, n+4),
|
||||
)
|
||||
|
||||
node1, node2 := relationships[i].GetNodes()
|
||||
class := relationships[i].GetClass()
|
||||
metadata := relationships[i].GetMetadata()
|
||||
relationships[i].SetConnection(conn)
|
||||
|
||||
if class == "" || node1 <= 0 || node2 <= 0 {
|
||||
return fmt.Errorf("Invalid relationship: %v, %v, %v", class, node1, node2)
|
||||
}
|
||||
|
||||
valueArgs = append(
|
||||
valueArgs,
|
||||
class,
|
||||
node1,
|
||||
node2,
|
||||
metadata,
|
||||
)
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf(`
|
||||
INSERT INTO relationships
|
||||
(_class, node_from, node_to, metadata)
|
||||
VALUES %s
|
||||
`, strings.Join(valueStrings, ","))
|
||||
|
||||
conn.log(DEBUG, "Bulk creating relationships:", sql, valueArgs)
|
||||
|
||||
// Execute and scan returned IDs
|
||||
_, err = tx.Exec(sql, valueArgs...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bulk insert failed: %w", err)
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue