Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
747f96f
Implement generic Table for basic CRUD operations
VojtechVitek Oct 13, 2025
771a18a
Remove automatic 'deleted_at NULL' condition
VojtechVitek Oct 13, 2025
ea1e5ac
Implement .LockForUpdate() using PostgreSQL's FOR UPDATE SKIP LOCKED …
VojtechVitek Oct 13, 2025
c9fe797
Fix generic ID
VojtechVitek Oct 13, 2025
062c980
Fix naming of generic types
VojtechVitek Oct 13, 2025
7807195
Add tests for simple CRUD, complex transactions and LockForUpdate()
VojtechVitek Oct 13, 2025
fe0c370
Fix TestRecordsWithJSONStruct test, since schema changed
VojtechVitek Oct 14, 2025
d558a9c
Fix LockForUpdate test
VojtechVitek Oct 14, 2025
27e066f
Don't rely on wg.Go(), a feature from Go 1.25
VojtechVitek Oct 14, 2025
02f4e9a
LockForUpdate that can pass transaction to update fn
VojtechVitek Oct 17, 2025
2b1784c
Refactor LockForUpdate() to reuse tx if possible
VojtechVitek Oct 17, 2025
9768c84
Simplify data models further
VojtechVitek Oct 18, 2025
fdef2cc
Improve tests for async processing
VojtechVitek Oct 18, 2025
749e746
Tests: Implement in-memory worker pattern via simple WaitGroup
VojtechVitek Oct 18, 2025
9df208d
A better "dequeue" abstraction defined on reviews table
VojtechVitek Oct 18, 2025
d081629
Rename GetByIDs() to ListByIDs()
VojtechVitek Oct 20, 2025
39c8250
Fix updated_at field, thanks @shunkakinoki
VojtechVitek Oct 21, 2025
019a655
PR feedback: Improve error annotations
VojtechVitek Oct 21, 2025
d76c237
Fix tests
VojtechVitek Oct 21, 2025
d51f20b
Save multiple (#33)
david-littlefarmer Oct 24, 2025
691672b
tables
david-littlefarmer Oct 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
426 changes: 426 additions & 0 deletions table.go

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions tests/database_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package pgkit_test

import (
"context"

"github.com/jackc/pgx/v5"

"github.com/goware/pgkit/v2"
)

type Database struct {
*pgkit.DB

Accounts *accountsTable
Articles *articlesTable
Reviews *reviewsTable
}

func initDB(db *pgkit.DB) *Database {
return &Database{
DB: db,
Accounts: &accountsTable{Table: pgkit.NewTable[Account, *Account](db, "accounts")},
Articles: &articlesTable{Table: pgkit.NewTable[Article, *Article](db, "articles")},
Reviews: &reviewsTable{Table: pgkit.NewTable[Review, *Review](db, "reviews")},
}
}

func (db *Database) BeginTx(ctx context.Context, fn func(tx *Database) error) error {
return pgx.BeginFunc(ctx, db.Conn, func(pgTx pgx.Tx) error {
tx := db.WithTx(pgTx)
return fn(tx)
})
}

func (db *Database) WithTx(tx pgx.Tx) *Database {
pgkitDB := &pgkit.DB{
Conn: db.Conn,
SQL: db.SQL,
Query: db.TxQuery(tx),
}

return initDB(pgkitDB)
}

func (db *Database) Close() {
db.DB.Conn.Close()
}
12 changes: 10 additions & 2 deletions tests/pgkit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,16 @@ func TestRecordsWithJSONB(t *testing.T) {
func TestRecordsWithJSONStruct(t *testing.T) {
truncateTable(t, "articles")

account := &Account{
Name: "TestRecordsWithJSONStruct",
}
err := DB.Query.QueryRow(context.Background(), DB.SQL.InsertRecord(account).Suffix(`RETURNING "id"`)).Scan(&account.ID)
assert.NoError(t, err)
assert.True(t, account.ID > 0)

article := &Article{
Author: "Gary",
AccountID: account.ID,
Author: "Gary",
Content: Content{
Title: "How to cook pizza",
Body: "flour+water+salt+yeast+cheese",
Expand All @@ -322,7 +330,7 @@ func TestRecordsWithJSONStruct(t *testing.T) {
cols, _, err := pgkit.Map(article)
assert.NoError(t, err)
sort.Sort(sort.StringSlice(cols))
assert.Equal(t, []string{"alias", "author", "content"}, cols)
assert.Equal(t, []string{"account_id", "alias", "author", "content", "deleted_at"}, cols)

// Insert record
q1 := DB.SQL.InsertRecord(article, "articles")
Expand Down
95 changes: 76 additions & 19 deletions tests/schema_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pgkit_test

import (
"fmt"
"time"

"github.com/goware/pgkit/v2/dbtype"
Expand All @@ -11,19 +12,88 @@ type Account struct {
Name string `db:"name"`
Disabled bool `db:"disabled"`
CreatedAt time.Time `db:"created_at,omitempty"` // ,omitempty will rely on postgres DEFAULT
UpdatedAt time.Time `db:"updated_at,omitempty"` // ,omitempty will rely on postgres DEFAULT
}

func (a *Account) DBTableName() string {
return "accounts"
func (a *Account) DBTableName() string { return "accounts" }
func (a *Account) GetIDColumn() string { return "id" }
func (a *Account) GetID() int64 { return a.ID }
func (a *Account) SetUpdatedAt(t time.Time) { a.UpdatedAt = t }

func (a *Account) Validate() error {
if a.Name == "" {
return fmt.Errorf("name is required")
}

return nil
}

type Article struct {
ID uint64 `db:"id,omitempty"`
Author string `db:"author"`
Alias *string `db:"alias"`
Content Content `db:"content"` // using JSONB postgres datatype
AccountID int64 `db:"account_id"`
CreatedAt time.Time `db:"created_at,omitempty"` // ,omitempty will rely on postgres DEFAULT
UpdatedAt time.Time `db:"updated_at,omitempty"` // ,omitempty will rely on postgres DEFAULT
DeletedAt *time.Time `db:"deleted_at"`
}

func (a *Article) GetIDColumn() string { return "id" }
func (a *Article) GetID() uint64 { return a.ID }
func (a *Article) SetUpdatedAt(t time.Time) { a.UpdatedAt = t }
func (a *Article) SetDeletedAt(t time.Time) { a.DeletedAt = &t }

func (a *Article) Validate() error {
if a.Author == "" {
return fmt.Errorf("author is required")
}

return nil
}

type Content struct {
Title string `json:"title"`
Body string `json:"body"`
Views int64 `json:"views"`
}

type Review struct {
ID int64 `db:"id,omitempty"`
Name string `db:"name"`
Comments string `db:"comments"`
CreatedAt time.Time `db:"created_at"` // if unset, will store Go zero-value
ID uint64 `db:"id,omitempty"`
Comment string `db:"comment"`
Status ReviewStatus `db:"status"`
Sentiment int64 `db:"sentiment"`
AccountID int64 `db:"account_id"`
ArticleID uint64 `db:"article_id"`
ProcessedAt *time.Time `db:"processed_at"`
CreatedAt time.Time `db:"created_at,omitempty"` // ,omitempty will rely on postgres DEFAULT
UpdatedAt time.Time `db:"updated_at,omitempty"` // ,omitempty will rely on postgres DEFAULT
DeletedAt *time.Time `db:"deleted_at"`
}

func (r *Review) GetIDColumn() string { return "id" }
func (r *Review) GetID() uint64 { return r.ID }
func (r *Review) SetUpdatedAt(t time.Time) { r.UpdatedAt = t }
func (r *Review) SetDeletedAt(t time.Time) { r.DeletedAt = &t }

func (r *Review) Validate() error {
if len(r.Comment) < 3 {
return fmt.Errorf("comment too short")
}

return nil
}

type ReviewStatus int64

const (
ReviewStatusPending ReviewStatus = iota
ReviewStatusProcessing
ReviewStatusApproved
ReviewStatusRejected
ReviewStatusFailed
)

type Log struct {
ID int64 `db:"id,omitempty"`
Message string `db:"message"`
Expand All @@ -38,16 +108,3 @@ type Stat struct {
Num dbtype.BigInt `db:"big_num"` // using NUMERIC(78,0) postgres datatype
Rating dbtype.BigInt `db:"rating"` // using NUMERIC(78,0) postgres datatype
}

type Article struct {
ID int64 `db:"id,omitempty"`
Author string `db:"author"`
Alias *string `db:"alias"`
Content Content `db:"content"` // using JSONB postgres datatype
}

type Content struct {
Title string `json:"title"`
Body string `json:"body"`
Views int64 `json:"views"`
}
Loading
Loading