diff options
| author | Kujtim Hoxha <[email protected]> | 2025-04-21 19:59:35 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-04-21 19:59:35 +0200 |
| commit | f33dff87725764af0b675b5e5b2e011b21c14c90 (patch) | |
| tree | 4fe2c022305f13775f2cab3cdd80cd808259765b /internal/db | |
| parent | 6b1c64bcc75b89c530294b6a2d4404682b435d56 (diff) | |
| parent | 3a6a26981a8074b6ab0eaadb520db986e04799ff (diff) | |
| download | opencode-f33dff87725764af0b675b5e5b2e011b21c14c90.tar.gz opencode-f33dff87725764af0b675b5e5b2e011b21c14c90.zip | |
Merge pull request #27 from kujtimiihoxha/opencode
OpenCode - Initial Implementation
Diffstat (limited to 'internal/db')
| -rw-r--r-- | internal/db/connect.go | 10 | ||||
| -rw-r--r-- | internal/db/db.go | 152 | ||||
| -rw-r--r-- | internal/db/files.sql.go | 311 | ||||
| -rw-r--r-- | internal/db/messages.sql.go | 34 | ||||
| -rw-r--r-- | internal/db/migrations/000001_initial.down.sql | 2 | ||||
| -rw-r--r-- | internal/db/migrations/000001_initial.up.sql | 25 | ||||
| -rw-r--r-- | internal/db/models.go | 18 | ||||
| -rw-r--r-- | internal/db/querier.go | 10 | ||||
| -rw-r--r-- | internal/db/sql/files.sql | 71 | ||||
| -rw-r--r-- | internal/db/sql/messages.sql | 5 |
10 files changed, 592 insertions, 46 deletions
diff --git a/internal/db/connect.go b/internal/db/connect.go index 8bba9cad8..9335bfc26 100644 --- a/internal/db/connect.go +++ b/internal/db/connect.go @@ -12,8 +12,8 @@ import ( "github.com/golang-migrate/migrate/v4/database/sqlite3" _ "github.com/mattn/go-sqlite3" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/logging" ) func Connect() (*sql.DB, error) { @@ -24,7 +24,7 @@ func Connect() (*sql.DB, error) { if err := os.MkdirAll(dataDir, 0o700); err != nil { return nil, fmt.Errorf("failed to create data directory: %w", err) } - dbPath := filepath.Join(dataDir, "termai.db") + dbPath := filepath.Join(dataDir, "opencode.db") // Open the SQLite database db, err := sql.Open("sqlite3", dbPath) if err != nil { @@ -48,9 +48,9 @@ func Connect() (*sql.DB, error) { for _, pragma := range pragmas { if _, err = db.Exec(pragma); err != nil { - logging.Warn("Failed to set pragma", pragma, err) + logging.Error("Failed to set pragma", pragma, err) } else { - logging.Warn("Set pragma", "pragma", pragma) + logging.Debug("Set pragma", "pragma", pragma) } } diff --git a/internal/db/db.go b/internal/db/db.go index 75f626013..16e663804 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -24,33 +24,63 @@ func New(db DBTX) *Queries { func Prepare(ctx context.Context, db DBTX) (*Queries, error) { q := Queries{db: db} var err error + if q.createFileStmt, err = db.PrepareContext(ctx, createFile); err != nil { + return nil, fmt.Errorf("error preparing query CreateFile: %w", err) + } if q.createMessageStmt, err = db.PrepareContext(ctx, createMessage); err != nil { return nil, fmt.Errorf("error preparing query CreateMessage: %w", err) } if q.createSessionStmt, err = db.PrepareContext(ctx, createSession); err != nil { return nil, fmt.Errorf("error preparing query CreateSession: %w", err) } + if q.deleteFileStmt, err = db.PrepareContext(ctx, deleteFile); err != nil { + return nil, fmt.Errorf("error preparing query DeleteFile: %w", err) + } if q.deleteMessageStmt, err = db.PrepareContext(ctx, deleteMessage); err != nil { return nil, fmt.Errorf("error preparing query DeleteMessage: %w", err) } if q.deleteSessionStmt, err = db.PrepareContext(ctx, deleteSession); err != nil { return nil, fmt.Errorf("error preparing query DeleteSession: %w", err) } + if q.deleteSessionFilesStmt, err = db.PrepareContext(ctx, deleteSessionFiles); err != nil { + return nil, fmt.Errorf("error preparing query DeleteSessionFiles: %w", err) + } if q.deleteSessionMessagesStmt, err = db.PrepareContext(ctx, deleteSessionMessages); err != nil { return nil, fmt.Errorf("error preparing query DeleteSessionMessages: %w", err) } + if q.getFileStmt, err = db.PrepareContext(ctx, getFile); err != nil { + return nil, fmt.Errorf("error preparing query GetFile: %w", err) + } + if q.getFileByPathAndSessionStmt, err = db.PrepareContext(ctx, getFileByPathAndSession); err != nil { + return nil, fmt.Errorf("error preparing query GetFileByPathAndSession: %w", err) + } if q.getMessageStmt, err = db.PrepareContext(ctx, getMessage); err != nil { return nil, fmt.Errorf("error preparing query GetMessage: %w", err) } if q.getSessionByIDStmt, err = db.PrepareContext(ctx, getSessionByID); err != nil { return nil, fmt.Errorf("error preparing query GetSessionByID: %w", err) } + if q.listFilesByPathStmt, err = db.PrepareContext(ctx, listFilesByPath); err != nil { + return nil, fmt.Errorf("error preparing query ListFilesByPath: %w", err) + } + if q.listFilesBySessionStmt, err = db.PrepareContext(ctx, listFilesBySession); err != nil { + return nil, fmt.Errorf("error preparing query ListFilesBySession: %w", err) + } + if q.listLatestSessionFilesStmt, err = db.PrepareContext(ctx, listLatestSessionFiles); err != nil { + return nil, fmt.Errorf("error preparing query ListLatestSessionFiles: %w", err) + } if q.listMessagesBySessionStmt, err = db.PrepareContext(ctx, listMessagesBySession); err != nil { return nil, fmt.Errorf("error preparing query ListMessagesBySession: %w", err) } + if q.listNewFilesStmt, err = db.PrepareContext(ctx, listNewFiles); err != nil { + return nil, fmt.Errorf("error preparing query ListNewFiles: %w", err) + } if q.listSessionsStmt, err = db.PrepareContext(ctx, listSessions); err != nil { return nil, fmt.Errorf("error preparing query ListSessions: %w", err) } + if q.updateFileStmt, err = db.PrepareContext(ctx, updateFile); err != nil { + return nil, fmt.Errorf("error preparing query UpdateFile: %w", err) + } if q.updateMessageStmt, err = db.PrepareContext(ctx, updateMessage); err != nil { return nil, fmt.Errorf("error preparing query UpdateMessage: %w", err) } @@ -62,6 +92,11 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { func (q *Queries) Close() error { var err error + if q.createFileStmt != nil { + if cerr := q.createFileStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createFileStmt: %w", cerr) + } + } if q.createMessageStmt != nil { if cerr := q.createMessageStmt.Close(); cerr != nil { err = fmt.Errorf("error closing createMessageStmt: %w", cerr) @@ -72,6 +107,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing createSessionStmt: %w", cerr) } } + if q.deleteFileStmt != nil { + if cerr := q.deleteFileStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteFileStmt: %w", cerr) + } + } if q.deleteMessageStmt != nil { if cerr := q.deleteMessageStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deleteMessageStmt: %w", cerr) @@ -82,11 +122,26 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing deleteSessionStmt: %w", cerr) } } + if q.deleteSessionFilesStmt != nil { + if cerr := q.deleteSessionFilesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteSessionFilesStmt: %w", cerr) + } + } if q.deleteSessionMessagesStmt != nil { if cerr := q.deleteSessionMessagesStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deleteSessionMessagesStmt: %w", cerr) } } + if q.getFileStmt != nil { + if cerr := q.getFileStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getFileStmt: %w", cerr) + } + } + if q.getFileByPathAndSessionStmt != nil { + if cerr := q.getFileByPathAndSessionStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getFileByPathAndSessionStmt: %w", cerr) + } + } if q.getMessageStmt != nil { if cerr := q.getMessageStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getMessageStmt: %w", cerr) @@ -97,16 +152,41 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getSessionByIDStmt: %w", cerr) } } + if q.listFilesByPathStmt != nil { + if cerr := q.listFilesByPathStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listFilesByPathStmt: %w", cerr) + } + } + if q.listFilesBySessionStmt != nil { + if cerr := q.listFilesBySessionStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listFilesBySessionStmt: %w", cerr) + } + } + if q.listLatestSessionFilesStmt != nil { + if cerr := q.listLatestSessionFilesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listLatestSessionFilesStmt: %w", cerr) + } + } if q.listMessagesBySessionStmt != nil { if cerr := q.listMessagesBySessionStmt.Close(); cerr != nil { err = fmt.Errorf("error closing listMessagesBySessionStmt: %w", cerr) } } + if q.listNewFilesStmt != nil { + if cerr := q.listNewFilesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listNewFilesStmt: %w", cerr) + } + } if q.listSessionsStmt != nil { if cerr := q.listSessionsStmt.Close(); cerr != nil { err = fmt.Errorf("error closing listSessionsStmt: %w", cerr) } } + if q.updateFileStmt != nil { + if cerr := q.updateFileStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateFileStmt: %w", cerr) + } + } if q.updateMessageStmt != nil { if cerr := q.updateMessageStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateMessageStmt: %w", cerr) @@ -154,35 +234,55 @@ func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, ar } type Queries struct { - db DBTX - tx *sql.Tx - createMessageStmt *sql.Stmt - createSessionStmt *sql.Stmt - deleteMessageStmt *sql.Stmt - deleteSessionStmt *sql.Stmt - deleteSessionMessagesStmt *sql.Stmt - getMessageStmt *sql.Stmt - getSessionByIDStmt *sql.Stmt - listMessagesBySessionStmt *sql.Stmt - listSessionsStmt *sql.Stmt - updateMessageStmt *sql.Stmt - updateSessionStmt *sql.Stmt + db DBTX + tx *sql.Tx + createFileStmt *sql.Stmt + createMessageStmt *sql.Stmt + createSessionStmt *sql.Stmt + deleteFileStmt *sql.Stmt + deleteMessageStmt *sql.Stmt + deleteSessionStmt *sql.Stmt + deleteSessionFilesStmt *sql.Stmt + deleteSessionMessagesStmt *sql.Stmt + getFileStmt *sql.Stmt + getFileByPathAndSessionStmt *sql.Stmt + getMessageStmt *sql.Stmt + getSessionByIDStmt *sql.Stmt + listFilesByPathStmt *sql.Stmt + listFilesBySessionStmt *sql.Stmt + listLatestSessionFilesStmt *sql.Stmt + listMessagesBySessionStmt *sql.Stmt + listNewFilesStmt *sql.Stmt + listSessionsStmt *sql.Stmt + updateFileStmt *sql.Stmt + updateMessageStmt *sql.Stmt + updateSessionStmt *sql.Stmt } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, - tx: tx, - createMessageStmt: q.createMessageStmt, - createSessionStmt: q.createSessionStmt, - deleteMessageStmt: q.deleteMessageStmt, - deleteSessionStmt: q.deleteSessionStmt, - deleteSessionMessagesStmt: q.deleteSessionMessagesStmt, - getMessageStmt: q.getMessageStmt, - getSessionByIDStmt: q.getSessionByIDStmt, - listMessagesBySessionStmt: q.listMessagesBySessionStmt, - listSessionsStmt: q.listSessionsStmt, - updateMessageStmt: q.updateMessageStmt, - updateSessionStmt: q.updateSessionStmt, + db: tx, + tx: tx, + createFileStmt: q.createFileStmt, + createMessageStmt: q.createMessageStmt, + createSessionStmt: q.createSessionStmt, + deleteFileStmt: q.deleteFileStmt, + deleteMessageStmt: q.deleteMessageStmt, + deleteSessionStmt: q.deleteSessionStmt, + deleteSessionFilesStmt: q.deleteSessionFilesStmt, + deleteSessionMessagesStmt: q.deleteSessionMessagesStmt, + getFileStmt: q.getFileStmt, + getFileByPathAndSessionStmt: q.getFileByPathAndSessionStmt, + getMessageStmt: q.getMessageStmt, + getSessionByIDStmt: q.getSessionByIDStmt, + listFilesByPathStmt: q.listFilesByPathStmt, + listFilesBySessionStmt: q.listFilesBySessionStmt, + listLatestSessionFilesStmt: q.listLatestSessionFilesStmt, + listMessagesBySessionStmt: q.listMessagesBySessionStmt, + listNewFilesStmt: q.listNewFilesStmt, + listSessionsStmt: q.listSessionsStmt, + updateFileStmt: q.updateFileStmt, + updateMessageStmt: q.updateMessageStmt, + updateSessionStmt: q.updateSessionStmt, } } diff --git a/internal/db/files.sql.go b/internal/db/files.sql.go new file mode 100644 index 000000000..39def271f --- /dev/null +++ b/internal/db/files.sql.go @@ -0,0 +1,311 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: files.sql + +package db + +import ( + "context" +) + +const createFile = `-- name: CreateFile :one +INSERT INTO files ( + id, + session_id, + path, + content, + version, + created_at, + updated_at +) VALUES ( + ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') +) +RETURNING id, session_id, path, content, version, created_at, updated_at +` + +type CreateFileParams struct { + ID string `json:"id"` + SessionID string `json:"session_id"` + Path string `json:"path"` + Content string `json:"content"` + Version string `json:"version"` +} + +func (q *Queries) CreateFile(ctx context.Context, arg CreateFileParams) (File, error) { + row := q.queryRow(ctx, q.createFileStmt, createFile, + arg.ID, + arg.SessionID, + arg.Path, + arg.Content, + arg.Version, + ) + var i File + err := row.Scan( + &i.ID, + &i.SessionID, + &i.Path, + &i.Content, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const deleteFile = `-- name: DeleteFile :exec +DELETE FROM files +WHERE id = ? +` + +func (q *Queries) DeleteFile(ctx context.Context, id string) error { + _, err := q.exec(ctx, q.deleteFileStmt, deleteFile, id) + return err +} + +const deleteSessionFiles = `-- name: DeleteSessionFiles :exec +DELETE FROM files +WHERE session_id = ? +` + +func (q *Queries) DeleteSessionFiles(ctx context.Context, sessionID string) error { + _, err := q.exec(ctx, q.deleteSessionFilesStmt, deleteSessionFiles, sessionID) + return err +} + +const getFile = `-- name: GetFile :one +SELECT id, session_id, path, content, version, created_at, updated_at +FROM files +WHERE id = ? LIMIT 1 +` + +func (q *Queries) GetFile(ctx context.Context, id string) (File, error) { + row := q.queryRow(ctx, q.getFileStmt, getFile, id) + var i File + err := row.Scan( + &i.ID, + &i.SessionID, + &i.Path, + &i.Content, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getFileByPathAndSession = `-- name: GetFileByPathAndSession :one +SELECT id, session_id, path, content, version, created_at, updated_at +FROM files +WHERE path = ? AND session_id = ? +ORDER BY created_at DESC +LIMIT 1 +` + +type GetFileByPathAndSessionParams struct { + Path string `json:"path"` + SessionID string `json:"session_id"` +} + +func (q *Queries) GetFileByPathAndSession(ctx context.Context, arg GetFileByPathAndSessionParams) (File, error) { + row := q.queryRow(ctx, q.getFileByPathAndSessionStmt, getFileByPathAndSession, arg.Path, arg.SessionID) + var i File + err := row.Scan( + &i.ID, + &i.SessionID, + &i.Path, + &i.Content, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const listFilesByPath = `-- name: ListFilesByPath :many +SELECT id, session_id, path, content, version, created_at, updated_at +FROM files +WHERE path = ? +ORDER BY created_at DESC +` + +func (q *Queries) ListFilesByPath(ctx context.Context, path string) ([]File, error) { + rows, err := q.query(ctx, q.listFilesByPathStmt, listFilesByPath, path) + if err != nil { + return nil, err + } + defer rows.Close() + items := []File{} + for rows.Next() { + var i File + if err := rows.Scan( + &i.ID, + &i.SessionID, + &i.Path, + &i.Content, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listFilesBySession = `-- name: ListFilesBySession :many +SELECT id, session_id, path, content, version, created_at, updated_at +FROM files +WHERE session_id = ? +ORDER BY created_at ASC +` + +func (q *Queries) ListFilesBySession(ctx context.Context, sessionID string) ([]File, error) { + rows, err := q.query(ctx, q.listFilesBySessionStmt, listFilesBySession, sessionID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []File{} + for rows.Next() { + var i File + if err := rows.Scan( + &i.ID, + &i.SessionID, + &i.Path, + &i.Content, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listLatestSessionFiles = `-- name: ListLatestSessionFiles :many +SELECT f.id, f.session_id, f.path, f.content, f.version, f.created_at, f.updated_at +FROM files f +INNER JOIN ( + SELECT path, MAX(created_at) as max_created_at + FROM files + GROUP BY path +) latest ON f.path = latest.path AND f.created_at = latest.max_created_at +WHERE f.session_id = ? +ORDER BY f.path +` + +func (q *Queries) ListLatestSessionFiles(ctx context.Context, sessionID string) ([]File, error) { + rows, err := q.query(ctx, q.listLatestSessionFilesStmt, listLatestSessionFiles, sessionID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []File{} + for rows.Next() { + var i File + if err := rows.Scan( + &i.ID, + &i.SessionID, + &i.Path, + &i.Content, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listNewFiles = `-- name: ListNewFiles :many +SELECT id, session_id, path, content, version, created_at, updated_at +FROM files +WHERE is_new = 1 +ORDER BY created_at DESC +` + +func (q *Queries) ListNewFiles(ctx context.Context) ([]File, error) { + rows, err := q.query(ctx, q.listNewFilesStmt, listNewFiles) + if err != nil { + return nil, err + } + defer rows.Close() + items := []File{} + for rows.Next() { + var i File + if err := rows.Scan( + &i.ID, + &i.SessionID, + &i.Path, + &i.Content, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateFile = `-- name: UpdateFile :one +UPDATE files +SET + content = ?, + version = ?, + updated_at = strftime('%s', 'now') +WHERE id = ? +RETURNING id, session_id, path, content, version, created_at, updated_at +` + +type UpdateFileParams struct { + Content string `json:"content"` + Version string `json:"version"` + ID string `json:"id"` +} + +func (q *Queries) UpdateFile(ctx context.Context, arg UpdateFileParams) (File, error) { + row := q.queryRow(ctx, q.updateFileStmt, updateFile, arg.Content, arg.Version, arg.ID) + var i File + err := row.Scan( + &i.ID, + &i.SessionID, + &i.Path, + &i.Content, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/internal/db/messages.sql.go b/internal/db/messages.sql.go index 4309db181..0555b4330 100644 --- a/internal/db/messages.sql.go +++ b/internal/db/messages.sql.go @@ -7,6 +7,7 @@ package db import ( "context" + "database/sql" ) const createMessage = `-- name: CreateMessage :one @@ -15,19 +16,21 @@ INSERT INTO messages ( session_id, role, parts, + model, created_at, updated_at ) VALUES ( - ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') + ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') ) -RETURNING id, session_id, role, parts, created_at, updated_at +RETURNING id, session_id, role, parts, model, created_at, updated_at, finished_at ` type CreateMessageParams struct { - ID string `json:"id"` - SessionID string `json:"session_id"` - Role string `json:"role"` - Parts string `json:"parts"` + ID string `json:"id"` + SessionID string `json:"session_id"` + Role string `json:"role"` + Parts string `json:"parts"` + Model sql.NullString `json:"model"` } func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (Message, error) { @@ -36,6 +39,7 @@ func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (M arg.SessionID, arg.Role, arg.Parts, + arg.Model, ) var i Message err := row.Scan( @@ -43,8 +47,10 @@ func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (M &i.SessionID, &i.Role, &i.Parts, + &i.Model, &i.CreatedAt, &i.UpdatedAt, + &i.FinishedAt, ) return i, err } @@ -70,7 +76,7 @@ func (q *Queries) DeleteSessionMessages(ctx context.Context, sessionID string) e } const getMessage = `-- name: GetMessage :one -SELECT id, session_id, role, parts, created_at, updated_at +SELECT id, session_id, role, parts, model, created_at, updated_at, finished_at FROM messages WHERE id = ? LIMIT 1 ` @@ -83,14 +89,16 @@ func (q *Queries) GetMessage(ctx context.Context, id string) (Message, error) { &i.SessionID, &i.Role, &i.Parts, + &i.Model, &i.CreatedAt, &i.UpdatedAt, + &i.FinishedAt, ) return i, err } const listMessagesBySession = `-- name: ListMessagesBySession :many -SELECT id, session_id, role, parts, created_at, updated_at +SELECT id, session_id, role, parts, model, created_at, updated_at, finished_at FROM messages WHERE session_id = ? ORDER BY created_at ASC @@ -110,8 +118,10 @@ func (q *Queries) ListMessagesBySession(ctx context.Context, sessionID string) ( &i.SessionID, &i.Role, &i.Parts, + &i.Model, &i.CreatedAt, &i.UpdatedAt, + &i.FinishedAt, ); err != nil { return nil, err } @@ -130,16 +140,18 @@ const updateMessage = `-- name: UpdateMessage :exec UPDATE messages SET parts = ?, + finished_at = ?, updated_at = strftime('%s', 'now') WHERE id = ? ` type UpdateMessageParams struct { - Parts string `json:"parts"` - ID string `json:"id"` + Parts string `json:"parts"` + FinishedAt sql.NullInt64 `json:"finished_at"` + ID string `json:"id"` } func (q *Queries) UpdateMessage(ctx context.Context, arg UpdateMessageParams) error { - _, err := q.exec(ctx, q.updateMessageStmt, updateMessage, arg.Parts, arg.ID) + _, err := q.exec(ctx, q.updateMessageStmt, updateMessage, arg.Parts, arg.FinishedAt, arg.ID) return err } diff --git a/internal/db/migrations/000001_initial.down.sql b/internal/db/migrations/000001_initial.down.sql index 4f2712d86..a2b0d1321 100644 --- a/internal/db/migrations/000001_initial.down.sql +++ b/internal/db/migrations/000001_initial.down.sql @@ -1,8 +1,10 @@ DROP TRIGGER IF EXISTS update_sessions_updated_at; DROP TRIGGER IF EXISTS update_messages_updated_at; +DROP TRIGGER IF EXISTS update_files_updated_at; DROP TRIGGER IF EXISTS update_session_message_count_on_delete; DROP TRIGGER IF EXISTS update_session_message_count_on_insert; DROP TABLE IF EXISTS sessions; DROP TABLE IF EXISTS messages; +DROP TABLE IF EXISTS files; diff --git a/internal/db/migrations/000001_initial.up.sql b/internal/db/migrations/000001_initial.up.sql index 2fbe5547e..b846ec600 100644 --- a/internal/db/migrations/000001_initial.up.sql +++ b/internal/db/migrations/000001_initial.up.sql @@ -18,14 +18,39 @@ UPDATE sessions SET updated_at = strftime('%s', 'now') WHERE id = new.id; END; +-- Files +CREATE TABLE IF NOT EXISTS files ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL, + path TEXT NOT NULL, + content TEXT NOT NULL, + version TEXT NOT NULL, + created_at INTEGER NOT NULL, -- Unix timestamp in milliseconds + updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds + FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE, + UNIQUE(path, session_id, version) +); + +CREATE INDEX IF NOT EXISTS idx_files_session_id ON files (session_id); +CREATE INDEX IF NOT EXISTS idx_files_path ON files (path); + +CREATE TRIGGER IF NOT EXISTS update_files_updated_at +AFTER UPDATE ON files +BEGIN +UPDATE files SET updated_at = strftime('%s', 'now') +WHERE id = new.id; +END; + -- Messages CREATE TABLE IF NOT EXISTS messages ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL, role TEXT NOT NULL, parts TEXT NOT NULL default '[]', + model TEXT, created_at INTEGER NOT NULL, -- Unix timestamp in milliseconds updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds + finished_at INTEGER, -- Unix timestamp in milliseconds FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE ); diff --git a/internal/db/models.go b/internal/db/models.go index 1ad8607a9..f00cb6ad1 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -8,15 +8,27 @@ import ( "database/sql" ) -type Message struct { +type File struct { ID string `json:"id"` SessionID string `json:"session_id"` - Role string `json:"role"` - Parts string `json:"parts"` + Path string `json:"path"` + Content string `json:"content"` + Version string `json:"version"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } +type Message struct { + ID string `json:"id"` + SessionID string `json:"session_id"` + Role string `json:"role"` + Parts string `json:"parts"` + Model sql.NullString `json:"model"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + FinishedAt sql.NullInt64 `json:"finished_at"` +} + type Session struct { ID string `json:"id"` ParentSessionID sql.NullString `json:"parent_session_id"` diff --git a/internal/db/querier.go b/internal/db/querier.go index c9d73ec39..704a97da2 100644 --- a/internal/db/querier.go +++ b/internal/db/querier.go @@ -9,15 +9,25 @@ import ( ) type Querier interface { + CreateFile(ctx context.Context, arg CreateFileParams) (File, error) CreateMessage(ctx context.Context, arg CreateMessageParams) (Message, error) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) + DeleteFile(ctx context.Context, id string) error DeleteMessage(ctx context.Context, id string) error DeleteSession(ctx context.Context, id string) error + DeleteSessionFiles(ctx context.Context, sessionID string) error DeleteSessionMessages(ctx context.Context, sessionID string) error + GetFile(ctx context.Context, id string) (File, error) + GetFileByPathAndSession(ctx context.Context, arg GetFileByPathAndSessionParams) (File, error) GetMessage(ctx context.Context, id string) (Message, error) GetSessionByID(ctx context.Context, id string) (Session, error) + ListFilesByPath(ctx context.Context, path string) ([]File, error) + ListFilesBySession(ctx context.Context, sessionID string) ([]File, error) + ListLatestSessionFiles(ctx context.Context, sessionID string) ([]File, error) ListMessagesBySession(ctx context.Context, sessionID string) ([]Message, error) + ListNewFiles(ctx context.Context) ([]File, error) ListSessions(ctx context.Context) ([]Session, error) + UpdateFile(ctx context.Context, arg UpdateFileParams) (File, error) UpdateMessage(ctx context.Context, arg UpdateMessageParams) error UpdateSession(ctx context.Context, arg UpdateSessionParams) (Session, error) } diff --git a/internal/db/sql/files.sql b/internal/db/sql/files.sql new file mode 100644 index 000000000..aba2a6111 --- /dev/null +++ b/internal/db/sql/files.sql @@ -0,0 +1,71 @@ +-- name: GetFile :one +SELECT * +FROM files +WHERE id = ? LIMIT 1; + +-- name: GetFileByPathAndSession :one +SELECT * +FROM files +WHERE path = ? AND session_id = ? +ORDER BY created_at DESC +LIMIT 1; + +-- name: ListFilesBySession :many +SELECT * +FROM files +WHERE session_id = ? +ORDER BY created_at ASC; + +-- name: ListFilesByPath :many +SELECT * +FROM files +WHERE path = ? +ORDER BY created_at DESC; + +-- name: CreateFile :one +INSERT INTO files ( + id, + session_id, + path, + content, + version, + created_at, + updated_at +) VALUES ( + ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') +) +RETURNING *; + +-- name: UpdateFile :one +UPDATE files +SET + content = ?, + version = ?, + updated_at = strftime('%s', 'now') +WHERE id = ? +RETURNING *; + +-- name: DeleteFile :exec +DELETE FROM files +WHERE id = ?; + +-- name: DeleteSessionFiles :exec +DELETE FROM files +WHERE session_id = ?; + +-- name: ListLatestSessionFiles :many +SELECT f.* +FROM files f +INNER JOIN ( + SELECT path, MAX(created_at) as max_created_at + FROM files + GROUP BY path +) latest ON f.path = latest.path AND f.created_at = latest.max_created_at +WHERE f.session_id = ? +ORDER BY f.path; + +-- name: ListNewFiles :many +SELECT * +FROM files +WHERE is_new = 1 +ORDER BY created_at DESC; diff --git a/internal/db/sql/messages.sql b/internal/db/sql/messages.sql index 64571158f..a59cebe7d 100644 --- a/internal/db/sql/messages.sql +++ b/internal/db/sql/messages.sql @@ -15,10 +15,11 @@ INSERT INTO messages ( session_id, role, parts, + model, created_at, updated_at ) VALUES ( - ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') + ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') ) RETURNING *; @@ -26,9 +27,11 @@ RETURNING *; UPDATE messages SET parts = ?, + finished_at = ?, updated_at = strftime('%s', 'now') WHERE id = ?; + -- name: DeleteMessage :exec DELETE FROM messages WHERE id = ?; |
