Notes2/node.go

299 lines
5.8 KiB
Go
Raw Permalink Normal View History

2024-11-28 18:11:14 +01:00
package main
import (
// External
"github.com/jmoiron/sqlx"
// Standard
2024-12-03 06:53:31 +01:00
"database/sql"
2024-11-28 18:11:14 +01:00
"time"
)
type ChecklistItem struct {
ID int
GroupID int `db:"checklist_group_id"`
Order int
Label string
Checked bool
}
type ChecklistGroup struct {
ID int
NodeID int `db:"node_id"`
Order int
Label string
Items []ChecklistItem
}
2024-12-03 06:53:31 +01:00
type TreeNode struct {
UUID string
ParentUUID string `db:"parent_uuid"`
Name string
Created time.Time
Updated time.Time
Deleted bool
CreatedSeq uint64 `db:"created_seq"`
UpdatedSeq uint64 `db:"updated_seq"`
DeletedSeq sql.NullInt64 `db:"deleted_seq"`
}
2024-11-28 18:11:14 +01:00
type Node struct {
2024-12-18 19:12:10 +01:00
UUID string
UserID int `db:"user_id"`
ParentUUID string `db:"parent_uuid"`
Name string
Created time.Time
Updated time.Time
Deleted bool
CreatedSeq uint64 `db:"created_seq"`
UpdatedSeq uint64 `db:"updated_seq"`
DeletedSeq sql.NullInt64 `db:"deleted_seq"`
Content string
2024-11-28 18:11:14 +01:00
ContentEncrypted string `db:"content_encrypted" json:"-"`
Markdown bool
2024-12-18 19:12:10 +01:00
// CryptoKeyID int `db:"crypto_key_id"`
//Files []File
//ChecklistGroups []ChecklistGroup
2024-11-28 18:11:14 +01:00
}
2024-12-03 13:56:38 +01:00
func NodeTree(userID, offset int, synced uint64) (nodes []TreeNode, maxSeq uint64, moreRowsExist bool, err error) { // {{{
2024-12-03 22:08:45 +01:00
const LIMIT = 100
2024-11-28 18:11:14 +01:00
var rows *sqlx.Rows
rows, err = db.Queryx(`
SELECT
2024-12-03 06:53:31 +01:00
uuid,
COALESCE(parent_uuid, '') AS parent_uuid,
2024-11-28 18:11:14 +01:00
name,
2024-12-03 06:53:31 +01:00
created,
2024-11-28 18:11:14 +01:00
updated,
2024-12-03 06:53:31 +01:00
deleted IS NOT NULL AS deleted,
created_seq,
updated_seq,
deleted_seq
FROM
public.node
WHERE
2024-12-18 19:12:10 +01:00
user_id = $1 AND
NOT history AND (
2024-12-03 13:56:38 +01:00
created_seq > $4 OR
updated_seq > $4 OR
deleted_seq > $4
2024-12-03 06:53:31 +01:00
)
2024-11-28 18:11:14 +01:00
ORDER BY
2024-12-03 06:53:31 +01:00
created ASC
2024-12-03 13:56:38 +01:00
LIMIT $2 OFFSET $3
2024-11-28 18:11:14 +01:00
`,
userID,
2024-12-03 22:08:45 +01:00
LIMIT+1,
2024-12-03 13:56:38 +01:00
offset,
2024-12-03 06:53:31 +01:00
synced,
2024-11-28 18:11:14 +01:00
)
if err != nil {
return
}
defer rows.Close()
2024-12-03 06:53:31 +01:00
nodes = []TreeNode{}
2024-12-03 13:56:38 +01:00
numNodes := 0
2024-11-28 18:11:14 +01:00
for rows.Next() {
2024-12-03 13:56:38 +01:00
// Query selects up to one more row than the decided limit.
// Saves one SQL query for row counting.
// Thus if numNodes is larger than the limit, more rows exist for the next call.
numNodes++
if numNodes > LIMIT {
moreRowsExist = true
return
}
2024-12-03 06:53:31 +01:00
node := TreeNode{}
2024-11-28 18:11:14 +01:00
if err = rows.StructScan(&node); err != nil {
return
}
nodes = append(nodes, node)
2024-12-03 13:56:38 +01:00
// DeletedSeq will be 0 if invalid, and thus not be a problem for the max function.
2024-12-03 06:53:31 +01:00
maxSeq = max(maxSeq, node.CreatedSeq, node.UpdatedSeq, uint64(node.DeletedSeq.Int64))
2024-11-28 18:11:14 +01:00
}
return
} // }}}
2024-12-18 19:12:10 +01:00
func Nodes(userID, offset int, synced uint64, clientUUID string) (nodes []Node, maxSeq uint64, moreRowsExist bool, err error) { // {{{
var rows *sqlx.Rows
rows, err = db.Queryx(`
SELECT
uuid,
COALESCE(parent_uuid, '') AS parent_uuid,
name,
created,
updated,
deleted IS NOT NULL AS deleted,
created_seq,
updated_seq,
deleted_seq,
content,
content_encrypted,
markdown
FROM
public.node
WHERE
user_id = $1 AND
client != $5 AND
NOT history AND (
created_seq > $4 OR
updated_seq > $4 OR
deleted_seq > $4
)
ORDER BY
id ASC
LIMIT $2 OFFSET $3
`,
userID,
SYNC_PAGINATION+1,
offset,
synced,
clientUUID,
)
if err != nil {
return
}
defer rows.Close()
nodes = []Node{}
numNodes := 0
for rows.Next() {
// Query selects up to one more row than the decided limit.
// Saves one SQL query for row counting.
// Thus if numNodes is larger than the limit, more rows exist for the next call.
numNodes++
if numNodes > SYNC_PAGINATION {
moreRowsExist = true
return
}
node := Node{}
if err = rows.StructScan(&node); err != nil {
return
}
nodes = append(nodes, node)
// DeletedSeq will be 0 if invalid, and thus not be a problem for the max function.
maxSeq = max(maxSeq, node.CreatedSeq, node.UpdatedSeq, uint64(node.DeletedSeq.Int64))
}
return
} // }}}
2024-12-03 22:08:45 +01:00
func RetrieveNode(userID int, nodeUUID string) (node Node, err error) { // {{{
var rows *sqlx.Row
rows = db.QueryRowx(`
SELECT
uuid,
user_id,
COALESCE(parent_uuid, '') AS parent_uuid,
/*COALESCE(crypto_key_id, 0) AS crypto_key_id,*/
name,
content,
content_encrypted,
markdown,
0 AS level
FROM node
WHERE
user_id = $1 AND
uuid = $2
2024-12-01 10:21:29 +01:00
`,
userID,
2024-12-03 22:08:45 +01:00
nodeUUID,
2024-12-01 10:21:29 +01:00
)
node = Node{}
2024-12-03 22:08:45 +01:00
if err = rows.StructScan(&node); err != nil {
2024-12-01 10:21:29 +01:00
return
}
return
} // }}}
2024-12-03 22:08:45 +01:00
func NodeCrumbs(nodeUUID string) (nodes []Node, err error) { // {{{
2024-12-01 10:21:29 +01:00
var rows *sqlx.Rows
rows, err = db.Queryx(`
WITH RECURSIVE nodes AS (
SELECT
2024-12-03 22:08:45 +01:00
uuid,
COALESCE(parent_uuid, '') AS parent_uuid,
2024-12-01 10:21:29 +01:00
name
FROM node
WHERE
2024-12-03 22:08:45 +01:00
uuid = $1
2024-12-01 10:21:29 +01:00
UNION
SELECT
2024-12-03 22:08:45 +01:00
n.uuid,
COALESCE(n.parent_uuid, 0) AS parent_uuid,
2024-12-01 10:21:29 +01:00
n.name
FROM node n
2024-12-03 22:08:45 +01:00
INNER JOIN nodes nr ON n.uuid = nr.parent_uuid
2024-12-01 10:21:29 +01:00
)
SELECT * FROM nodes
2024-12-03 22:08:45 +01:00
`, nodeUUID)
2024-12-01 10:21:29 +01:00
if err != nil {
return
}
defer rows.Close()
nodes = []Node{}
for rows.Next() {
node := Node{}
if err = rows.StructScan(&node); err != nil {
return
}
nodes = append(nodes, node)
}
return
} // }}}
2024-12-03 22:08:45 +01:00
func TestData() (err error) {
for range 10 {
hash1, name1, _ := generateOneTestNode("", "G")
for range 10 {
hash2, name2, _ := generateOneTestNode(hash1, name1)
for range 10 {
hash3, name3, _ := generateOneTestNode(hash2, name2)
for range 10 {
generateOneTestNode(hash3, name3)
}
}
}
}
return
}
func generateOneTestNode(parentUUID, parentPath string) (hash, name string, err error) {
var sqlParentUUID sql.NullString
if parentUUID != "" {
sqlParentUUID.String = parentUUID
sqlParentUUID.Valid = true
}
query := `
INSERT INTO node(user_id, parent_uuid, name)
VALUES(
1,
$1,
CONCAT(
$2::text,
'-',
LPAD(nextval('test_data')::text, 4, '0')
)
)
RETURNING uuid, name`
var row *sql.Row
row = db.QueryRow(query, sqlParentUUID, parentPath)
err = row.Scan(&hash, &name)
if err != nil {
return
}
return
}