Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d79a6db
Implement generic Table for basic CRUD operations
VojtechVitek Oct 13, 2025
c42ba49
Remove automatic 'deleted_at NULL' condition
VojtechVitek Oct 13, 2025
89f19c2
Implement .LockForUpdate() using PostgreSQL's FOR UPDATE SKIP LOCKED …
VojtechVitek Oct 13, 2025
a5d9aed
Fix generic ID
VojtechVitek Oct 13, 2025
f12a47b
Fix naming of generic types
VojtechVitek Oct 13, 2025
3569547
Add tests for simple CRUD, complex transactions and LockForUpdate()
VojtechVitek Oct 13, 2025
1fc30a9
Fix TestRecordsWithJSONStruct test, since schema changed
VojtechVitek Oct 14, 2025
bf7f7be
Fix LockForUpdate test
VojtechVitek Oct 14, 2025
feb70f3
Don't rely on wg.Go(), a feature from Go 1.25
VojtechVitek Oct 14, 2025
b218fb5
LockForUpdate that can pass transaction to update fn
VojtechVitek Oct 17, 2025
61898a1
Refactor LockForUpdate() to reuse tx if possible
VojtechVitek Oct 17, 2025
a1f7de5
Simplify data models further
VojtechVitek Oct 18, 2025
2546562
Improve tests for async processing
VojtechVitek Oct 18, 2025
f9917e9
Tests: Implement in-memory worker pattern via simple WaitGroup
VojtechVitek Oct 18, 2025
73ba584
A better "dequeue" abstraction defined on reviews table
VojtechVitek Oct 18, 2025
6c9b200
Rename GetByIDs() to ListByIDs()
VojtechVitek Oct 20, 2025
77cb157
Fix updated_at field, thanks @shunkakinoki
VojtechVitek Oct 21, 2025
82b1e7b
PR feedback: Improve error annotations
VojtechVitek Oct 21, 2025
8bd8f1f
Fix tests
VojtechVitek Oct 21, 2025
f28591e
Save multiple (#33)
david-littlefarmer Oct 24, 2025
320b0f3
Add iterator method for accounts and update tests
klaidliadon Dec 10, 2025
b2ce7a7
Rename types for clarity
klaidliadon Mar 5, 2026
95252fc
add back truncateAllTables
klaidliadon Mar 5, 2026
a3fdad7
Add IDColumn to table context and enhance WithTx tests
klaidliadon Mar 6, 2026
cf0cd56
Add pagination support to Table with ListPaged and WithPaginator methods
klaidliadon Mar 9, 2026
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
404 changes: 404 additions & 0 deletions table.go

Large diffs are not rendered by default.

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

import (
"context"

"github.com/goware/pgkit/v2"
"github.com/jackc/pgx/v5"
)

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.Table[Account, *Account, int64]{DB: db, Name: "accounts", IDColumn: "id"}},
Articles: &articlesTable{Table: &pgkit.Table[Article, *Article, uint64]{DB: db, Name: "articles", IDColumn: "id"}},
Reviews: &reviewsTable{Table: &pgkit.Table[Review, *Review, uint64]{DB: db, Name: "reviews", IDColumn: "id"}},
}
}

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()
}
8 changes: 8 additions & 0 deletions tests/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import (
"github.com/stretchr/testify/assert"
)

func truncateAllTables(t *testing.T) {
truncateTable(t, "accounts")
truncateTable(t, "reviews")
truncateTable(t, "logs")
truncateTable(t, "stats")
truncateTable(t, "articles")
}

func truncateTable(t *testing.T, tableName string) {
_, err := DB.Conn.Exec(context.Background(), fmt.Sprintf(`TRUNCATE TABLE %q CASCADE`, tableName))
assert.NoError(t, err)
Expand Down
12 changes: 10 additions & 2 deletions tests/pgkit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,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 @@ -319,7 +327,7 @@ func TestRecordsWithJSONStruct(t *testing.T) {
cols, _, err := pgkit.Map(article)
assert.NoError(t, err)
sort.Strings(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
92 changes: 73 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,85 @@ 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) 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) 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) 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 +105,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