This commit is contained in:
95
internal/store/sqlite.go
Normal file
95
internal/store/sqlite.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type SQLiteCounterStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewSQLiteCounterStore(path string) (*SQLiteCounterStore, error) {
|
||||
db, err := sql.Open("sqlite", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
return &SQLiteCounterStore{db: db}, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteCounterStore) Init(ctx context.Context) error {
|
||||
_, err := s.db.ExecContext(ctx, `
|
||||
CREATE TABLE IF NOT EXISTS ticks (
|
||||
slug TEXT PRIMARY KEY,
|
||||
count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SQLiteCounterStore) Increment(ctx context.Context, slug string) (int64, error) {
|
||||
if slug == "" {
|
||||
return 0, fmt.Errorf("empty slug")
|
||||
}
|
||||
|
||||
_, err := s.db.ExecContext(ctx, `
|
||||
INSERT INTO ticks(slug, count) VALUES(?, 1)
|
||||
ON CONFLICT(slug) DO UPDATE SET
|
||||
count = count + 1,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
`, slug)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.Get(ctx, slug)
|
||||
}
|
||||
|
||||
func (s *SQLiteCounterStore) Get(ctx context.Context, slug string) (int64, error) {
|
||||
var count int64
|
||||
err := s.db.QueryRowContext(ctx, `SELECT count FROM ticks WHERE slug = ?`, slug).Scan(&count)
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, nil
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (s *SQLiteCounterStore) GetMany(ctx context.Context, slugs []string) (map[string]int64, error) {
|
||||
out := make(map[string]int64, len(slugs))
|
||||
if len(slugs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
placeholders := strings.TrimRight(strings.Repeat("?,", len(slugs)), ",")
|
||||
args := make([]any, len(slugs))
|
||||
for i, slug := range slugs {
|
||||
args[i] = slug
|
||||
out[slug] = 0
|
||||
}
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT slug, count FROM ticks WHERE slug IN (`+placeholders+`)`, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var slug string
|
||||
var count int64
|
||||
if err := rows.Scan(&slug, &count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[slug] = count
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *SQLiteCounterStore) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
11
internal/store/store.go
Normal file
11
internal/store/store.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package store
|
||||
|
||||
import "context"
|
||||
|
||||
type CounterStore interface {
|
||||
Init(ctx context.Context) error
|
||||
Increment(ctx context.Context, slug string) (int64, error)
|
||||
Get(ctx context.Context, slug string) (int64, error)
|
||||
GetMany(ctx context.Context, slugs []string) (map[string]int64, error)
|
||||
Close() error
|
||||
}
|
||||
Reference in New Issue
Block a user