Compare commits
3 Commits
a199e34730
...
88ab880a8c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88ab880a8c | ||
|
|
e6d9e356ca | ||
|
|
b85df7bd31 |
@@ -28,6 +28,7 @@ func One(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_ = feeds
|
||||||
|
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/db/db.go
42
src/db/db.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func QueryOne[T any](ctx context.Context, q string, args ...any) (T, error) {
|
func QueryOne[T any](ctx context.Context, q string, args ...any) (T, error) {
|
||||||
@@ -33,12 +34,33 @@ func Query[T any](ctx context.Context, q string, args ...any) ([]T, error) {
|
|||||||
}
|
}
|
||||||
scanners := func(columns []string) ([]any, error) {
|
scanners := func(columns []string) ([]any, error) {
|
||||||
s := make([]any, len(columns))
|
s := make([]any, len(columns))
|
||||||
for i, k := range columns {
|
i := 0
|
||||||
v, ok := m[k]
|
for i < len(columns) {
|
||||||
|
k := columns[i]
|
||||||
|
if strings.Contains(k, ".") {
|
||||||
|
columns := strings.SplitN(k, ".", 2)
|
||||||
|
m2, ok := m[columns[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("cannot scan column %s to %T (%+v)", k, a, m)
|
return nil, fmt.Errorf("no column %s in %T (%+v)", columns[0], a, m)
|
||||||
|
}
|
||||||
|
m3, ok := m2.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot scan subfield %s of %s of %T (%+v)", columns[1], columns[0], a, m)
|
||||||
|
}
|
||||||
|
v, ok := m3[columns[1]]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no subfield %s of %s of %T (%+v)", columns[1], columns[0], a, m)
|
||||||
}
|
}
|
||||||
s[i] = &v
|
s[i] = &v
|
||||||
|
i += 1
|
||||||
|
} else {
|
||||||
|
v, ok := m[k]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no column %s in %T (%+v)", k, a, m)
|
||||||
|
}
|
||||||
|
s[i] = &v
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@@ -67,7 +89,21 @@ func Query[T any](ctx context.Context, q string, args ...any) ([]T, error) {
|
|||||||
|
|
||||||
m := map[string]any{}
|
m := map[string]any{}
|
||||||
for i, column := range columns {
|
for i, column := range columns {
|
||||||
|
if !strings.Contains(column, ".") {
|
||||||
m[column] = scanners[i]
|
m[column] = scanners[i]
|
||||||
|
} else {
|
||||||
|
columns := strings.SplitN(column, ".", 2)
|
||||||
|
m2, ok := m[columns[0]]
|
||||||
|
if !ok {
|
||||||
|
m2 = map[string]any{}
|
||||||
|
}
|
||||||
|
m3, ok := m2.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s is not a submap", columns[0])
|
||||||
|
}
|
||||||
|
m3[columns[1]] = scanners[i]
|
||||||
|
m[columns[0]] = m3
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var a T
|
var a T
|
||||||
|
|||||||
@@ -46,4 +46,15 @@ func TestDB(t *testing.T) {
|
|||||||
} else if gots[1].K != "b" {
|
} else if gots[1].K != "b" {
|
||||||
t.Errorf("expected [1]='b' but got %q", gots[1].K)
|
t.Errorf("expected [1]='b' but got %q", gots[1].K)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NestedResult struct {
|
||||||
|
Nest struct {
|
||||||
|
K string `json:"k"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got, err := db.QueryOne[NestedResult](ctx, `SELECT k AS "Nest.k" FROM test WHERE k='a'`); err != nil {
|
||||||
|
t.Errorf("failed nested query one: %v", err)
|
||||||
|
} else if got.Nest.K != "a" {
|
||||||
|
t.Errorf("bad nested query one: %+v", got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,22 +196,21 @@ func (f *Feeds) oldGet(ctx context.Context, id string) (Feed, error) {
|
|||||||
func (f *Feeds) Insert(ctx context.Context, url, cron string) (string, error) {
|
func (f *Feeds) Insert(ctx context.Context, url, cron string) (string, error) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
id := uuid.New().String()
|
id := uuid.New().String()
|
||||||
q := `
|
return id, db.Exec(ctx, `
|
||||||
BEGIN;
|
BEGIN;
|
||||||
INSERT INTO "feed.entries" (
|
INSERT INTO "feed.entries" (
|
||||||
id,
|
id,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (?, ?, ?);
|
) VALUES ($1, $2, $3);
|
||||||
INSERT INTO "feed.versions" (
|
INSERT INTO "feed.versions" (
|
||||||
entries_id,
|
entries_id,
|
||||||
created_at,
|
created_at,
|
||||||
url,
|
url,
|
||||||
cron
|
cron
|
||||||
) VALUES (?, ?, ?, ?);
|
) VALUES ($4, $5, $6, $7);
|
||||||
COMMIT;
|
COMMIT;
|
||||||
`
|
`,
|
||||||
return id, db.Exec(ctx, q,
|
|
||||||
id, now, now,
|
id, now, now,
|
||||||
id, now, url, cron,
|
id, now, url, cron,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -51,6 +51,36 @@ func TestFeeds(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("cannot get:", err)
|
t.Fatal("cannot get:", err)
|
||||||
}
|
}
|
||||||
t.Errorf("%+v", got)
|
t.Logf("%+v", got)
|
||||||
|
|
||||||
|
if got.Entry.ID == "" {
|
||||||
|
t.Error("no entry.id")
|
||||||
|
}
|
||||||
|
if got.Entry.Created.IsZero() {
|
||||||
|
t.Error("no entry.created")
|
||||||
|
}
|
||||||
|
if got.Entry.Updated.IsZero() {
|
||||||
|
t.Error("no entry.updated")
|
||||||
|
}
|
||||||
|
if !got.Entry.Deleted.IsZero() {
|
||||||
|
t.Error("entry.deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
if got.Version.Created.IsZero() {
|
||||||
|
t.Error("no version.created")
|
||||||
|
}
|
||||||
|
if got.Version.URL != "url" {
|
||||||
|
t.Error("no version.url")
|
||||||
|
}
|
||||||
|
if got.Version.Cron != "cron" {
|
||||||
|
t.Error("no version.cron")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !got.Execution.Executed.IsZero() {
|
||||||
|
t.Error("execution.executed")
|
||||||
|
}
|
||||||
|
if !got.Execution.Version.IsZero() {
|
||||||
|
t.Error("execution.version")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user