generated from alecodes/base-template
wip: implementing db initialization
This commit is contained in:
parent
ffd3c2014e
commit
6bc8d6d195
17 changed files with 686 additions and 0 deletions
297
pkg/synchronizator.go
Normal file
297
pkg/synchronizator.go
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
package synchronizator
|
||||
|
||||
import (
|
||||
sql "database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var DefaultOptions = &Options{
|
||||
Logger: os.Stdout,
|
||||
Log_level: INFO,
|
||||
DANGEROUSLY_DROP_TABLES: false,
|
||||
}
|
||||
|
||||
func New(connection *sql.DB, options *Options) (*db, error) {
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
}
|
||||
|
||||
conn := db{
|
||||
Connection: connection,
|
||||
logger: options.Logger,
|
||||
log_level: options.Log_level,
|
||||
drop_tables: options.DANGEROUSLY_DROP_TABLES,
|
||||
}
|
||||
|
||||
err := conn.bootstrap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &conn, nil
|
||||
}
|
||||
|
||||
func (conn *db) log(level LogLevel, args ...any) {
|
||||
// Only log if the level is the same or lower than the configured
|
||||
if level > conn.log_level {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(conn.logger, "[ %s - %-5s ]: ", time.Now().Format(time.DateTime), level.String())
|
||||
fmt.Fprintln(conn.logger, args...)
|
||||
}
|
||||
|
||||
func (conn *db) bootstrap() error {
|
||||
conn.log(INFO, "Initializing database...")
|
||||
|
||||
sql := ""
|
||||
|
||||
if conn.drop_tables {
|
||||
sql += `
|
||||
DROP TABLE IF EXISTS nodes;
|
||||
DROP TABLE IF EXISTS relationships;
|
||||
DROP INDEX IF EXISTS node_class;
|
||||
DROP INDEX IF EXISTS relationships_class;
|
||||
`
|
||||
}
|
||||
|
||||
sql += `
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
_class text NOT NULL,
|
||||
name TEXT,
|
||||
metadata jsonb DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS node_class on nodes (_class);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS relationships (
|
||||
node_from INTEGER NOT NULL,
|
||||
node_to INTEGER NOT NULL,
|
||||
_class text NOT NULL,
|
||||
metadata jsonb DEFAULT '{}',
|
||||
PRIMARY KEY (node_from, node_to),
|
||||
CHECK (node_from != node_to),
|
||||
CONSTRAINT fk_node_from_relationships FOREIGN KEY (node_from) REFERENCES nodes(id),
|
||||
CONSTRAINT fk_node_to_relationships FOREIGN KEY (node_to) REFERENCES nodes(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS relationships_class on relationships (_class);
|
||||
`
|
||||
|
||||
conn.log(DEBUG, sql)
|
||||
_, err := conn.Connection.Exec(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// func (conn *db) NewCollection(shape any) (*Node, error) {
|
||||
// collection, err := conn.NewNode("collection", shape)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// return collection, nil
|
||||
// }
|
||||
|
||||
func (conn *db) NewNode(data NodeClass) (*Node, error) {
|
||||
class, name, metadata, err := data.ToNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node := Node{
|
||||
_conn: conn,
|
||||
_class: class,
|
||||
name: name,
|
||||
metadata: metadata,
|
||||
Id: -1,
|
||||
}
|
||||
|
||||
tx, err := conn.Connection.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer tx.Rollback()
|
||||
|
||||
conn.log(DEBUG, "Creating node:", node)
|
||||
|
||||
sql := "INSERT INTO nodes (_class, name, metadata) VALUES ($1, $2, $3) RETURNING id;"
|
||||
|
||||
err = tx.QueryRow(sql, node._class, node.name, metadata).Scan(&node.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
func (conn *db) UpdateNode(id int64, data NodeClass) (*Node, error) {
|
||||
class, name, metadata, err := data.ToNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node := Node{
|
||||
_conn: conn,
|
||||
_class: class,
|
||||
name: name,
|
||||
metadata: metadata,
|
||||
Id: id,
|
||||
}
|
||||
|
||||
tx, err := conn.Connection.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
conn.log(DEBUG, "Updating node:", id, node)
|
||||
|
||||
sql := "UPDATE nodes SET _class = $1, name = $2, metadata = $3 WHERE id = $4;"
|
||||
|
||||
_, err = tx.Exec(sql, node._class, node.name, node.metadata, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
func (conn *db) GetNode(id int64) (*Node, error) {
|
||||
node := Node{Id: id}
|
||||
sql := "SELECT _class, metadata FROM nodes WHERE id = $1;"
|
||||
conn.log(DEBUG, sql)
|
||||
|
||||
var metadata []byte
|
||||
err := conn.Connection.QueryRow(sql, id).Scan(&node._class, &metadata)
|
||||
if err != nil {
|
||||
conn.log(DEBUG, err)
|
||||
return nil, fmt.Errorf("No row matching id = %v", id)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(metadata, &node.metadata); err != nil {
|
||||
conn.log(ERROR, err)
|
||||
return nil, fmt.Errorf("invalid metadata format: %w", err)
|
||||
}
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
func (conn *db) DeleteNode(id int64) error {
|
||||
tx, err := conn.Connection.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
conn.log(DEBUG, "Deleting node:", id)
|
||||
sql := "DELETE FROM nodes WHERE id = $1;"
|
||||
|
||||
_, err = tx.Exec(sql, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *db) AddRelation(from int64, metadata any, to int64) (*Relationship, error) {
|
||||
if metadata == nil {
|
||||
return nil, fmt.Errorf("metadata cannot be nil")
|
||||
}
|
||||
|
||||
json_metadata, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid metadata format: %w", err)
|
||||
}
|
||||
|
||||
relationship := Relationship{}
|
||||
relationship._class = strings.ToTitle(getTypeOfStruct(metadata))
|
||||
relationship.metadata = metadata
|
||||
|
||||
tx, err := conn.Connection.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer tx.Rollback()
|
||||
|
||||
conn.log(DEBUG, "Creating relationship:", from, relationship, to)
|
||||
|
||||
sql := "INSERT INTO relationships (_class, node_from, node_to, metadata) VALUES ($1, $2, $3, $4) RETURNING node_from, node_to;"
|
||||
|
||||
err = tx.QueryRow(sql, relationship._class, from, to, json_metadata).
|
||||
Scan(&relationship._id_from, &relationship._id_to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &relationship, nil
|
||||
}
|
||||
|
||||
func (conn *db) UpdateRelation(from int64, metadata any, to int64) error {
|
||||
if metadata == nil {
|
||||
return fmt.Errorf("metadata cannot be nil")
|
||||
}
|
||||
json_metadata, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid metadata format: %w", err)
|
||||
}
|
||||
tx, err := conn.Connection.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
conn.log(DEBUG, "Updating relationship:", from, metadata, to)
|
||||
sql := "UPDATE relationships SET metadata = $1 WHERE node_from = $2 AND node_to = $3;"
|
||||
_, err = tx.Exec(sql, json_metadata, from, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *db) DeleteRelation(from int64, to int64) error {
|
||||
tx, err := conn.Connection.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tx.Rollback()
|
||||
|
||||
sql := "DELETE FROM relationships WHERE node_from = $1 AND node_to = $2;"
|
||||
conn.log(DEBUG, "Deleting relationship:", from, to)
|
||||
_, err = tx.Exec(sql, from, to)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in a new issue