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:
@@ -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, ¬Null, &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) }
|
||||
|
||||
Reference in New Issue
Block a user