fix: correct GetProperty bug, init to use .ax/, add default aliases, split e2e tests, add due date tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 01:58:48 +02:00
parent 5969a2591c
commit 921f4913f8
13 changed files with 1121 additions and 971 deletions

View File

@@ -8,12 +8,13 @@ import (
"os"
"path/filepath"
"strings"
"time"
_ "modernc.org/sqlite"
)
var migrations = []string{
`CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, title TEXT NOT NULL, content TEXT, due_date TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)`,
`CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, title TEXT NOT NULL, content TEXT, due_date DATETIME, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)`,
`CREATE TABLE IF NOT EXISTS rels (from_id TEXT NOT NULL, rel_name TEXT NOT NULL, to_id TEXT NOT NULL DEFAULT '', PRIMARY KEY (from_id, rel_name, to_id), FOREIGN KEY (from_id) REFERENCES nodes(id) ON DELETE CASCADE)`,
`CREATE INDEX IF NOT EXISTS idx_rels_from ON rels(from_id)`,
`CREATE INDEX IF NOT EXISTS idx_rels_to ON rels(to_id)`,
@@ -129,6 +130,10 @@ func NewSQLiteStore(path string) (GraphStore, error) {
db.Close()
return nil, fmt.Errorf("schema migration failed: %w", err)
}
if err := migrateDueDateColumn(db); err != nil {
db.Close()
return nil, fmt.Errorf("due_date column migration failed: %w", err)
}
if _, err := db.Exec("PRAGMA foreign_keys=ON"); err != nil {
db.Close()
return nil, err
@@ -163,6 +168,44 @@ func migrateSchema(db *sql.DB) error {
return nil
}
// migrateDueDateColumn converts the due_date column from TEXT to DATETIME affinity
// for databases created before this schema change. It is a no-op when already migrated.
func migrateDueDateColumn(db *sql.DB) error {
rows, err := db.Query("PRAGMA table_info(nodes)")
if err != nil {
return err
}
defer rows.Close()
var colType string
for rows.Next() {
var cid, notNull, pk int
var name, typ string
var dfltVal any
if err := rows.Scan(&cid, &name, &typ, &notNull, &dfltVal, &pk); err != nil {
return err
}
if name == "due_date" {
colType = strings.ToUpper(typ)
break
}
}
rows.Close()
if colType == "DATETIME" || colType == "" {
return nil // already on new schema or column missing
}
for _, stmt := range []string{
`CREATE TABLE nodes_new (id TEXT PRIMARY KEY, title TEXT NOT NULL, content TEXT, due_date DATETIME, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)`,
`INSERT INTO nodes_new SELECT id, title, content, due_date, created_at, updated_at FROM nodes`,
`DROP TABLE nodes`,
`ALTER TABLE nodes_new RENAME TO nodes`,
} {
if _, err := db.Exec(stmt); err != nil {
return err
}
}
return nil
}
// --- Transaction ---
func (s *GraphStoreSqlite) Transaction(fn func(GraphStore) error) error {
@@ -183,10 +226,10 @@ func (s *txStore) Transaction(fn func(GraphStore) error) error {
// --- Node operations ---
func addNode(q querier, id, title, content, dueDate, createdAt, updatedAt string) error {
func addNode(q querier, id, title, content string, dueDate *time.Time, createdAt, updatedAt string) error {
var dd any
if dueDate != "" {
dd = dueDate
if dueDate != nil {
dd = dueDate.UTC().Format("2006-01-02")
}
_, err := q.Exec(
"INSERT INTO nodes (id, title, content, due_date, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
@@ -197,12 +240,18 @@ func addNode(q querier, id, title, content, dueDate, createdAt, updatedAt string
func getNode(q querier, id string) (*models.Node, error) {
n := models.NewNode()
var dueDateStr string
err := q.QueryRow(
"SELECT id, title, COALESCE(content, ''), COALESCE(due_date, ''), created_at, updated_at FROM nodes WHERE id = ?", id,
).Scan(&n.ID, &n.Title, &n.Content, &n.DueDate, &n.CreatedAt, &n.UpdatedAt)
).Scan(&n.ID, &n.Title, &n.Content, &dueDateStr, &n.CreatedAt, &n.UpdatedAt)
if err != nil {
return nil, err
}
if dueDateStr != "" {
if d, err := models.ParseDate(dueDateStr); err == nil {
n.DueDate = &d
}
}
rows, err := q.Query("SELECT rel_name, to_id FROM rels WHERE from_id = ?", id)
if err != nil {
return nil, err
@@ -220,10 +269,10 @@ func getNode(q querier, id string) (*models.Node, error) {
return n, nil
}
func updateNode(q querier, id, title, content, dueDate, updatedAt string) error {
func updateNode(q querier, id, title, content string, dueDate *time.Time, updatedAt string) error {
var dd any
if dueDate != "" {
dd = dueDate
if dueDate != nil {
dd = dueDate.UTC().Format("2006-01-02")
}
_, err := q.Exec(
"UPDATE nodes SET title = ?, content = ?, due_date = ?, updated_at = ? WHERE id = ?",
@@ -243,21 +292,21 @@ func nodeExists(q querier, id string) (bool, error) {
return e, err
}
func (s *GraphStoreSqlite) AddNode(id, title, content, dueDate, createdAt, updatedAt string) error {
func (s *GraphStoreSqlite) AddNode(id, title, content string, dueDate *time.Time, createdAt, updatedAt string) error {
return addNode(s.db, id, title, content, dueDate, createdAt, updatedAt)
}
func (s *GraphStoreSqlite) GetNode(id string) (*models.Node, error) { return getNode(s.db, id) }
func (s *GraphStoreSqlite) UpdateNode(id, title, content, dueDate, updatedAt string) error {
func (s *GraphStoreSqlite) UpdateNode(id, title, content string, dueDate *time.Time, updatedAt string) error {
return updateNode(s.db, id, title, content, dueDate, updatedAt)
}
func (s *GraphStoreSqlite) DeleteNode(id string) error { return deleteNode(s.db, id) }
func (s *GraphStoreSqlite) NodeExists(id string) (bool, error) { return nodeExists(s.db, id) }
func (s *txStore) AddNode(id, title, content, dueDate, createdAt, updatedAt string) error {
func (s *txStore) AddNode(id, title, content string, dueDate *time.Time, createdAt, updatedAt string) error {
return addNode(s.tx, id, title, content, dueDate, createdAt, updatedAt)
}
func (s *txStore) GetNode(id string) (*models.Node, error) { return getNode(s.tx, id) }
func (s *txStore) UpdateNode(id, title, content, dueDate, updatedAt string) error {
func (s *txStore) UpdateNode(id, title, content string, dueDate *time.Time, updatedAt string) error {
return updateNode(s.tx, id, title, content, dueDate, updatedAt)
}
func (s *txStore) DeleteNode(id string) error { return deleteNode(s.tx, id) }