sqlj

package module
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2025 License: MIT Imports: 6 Imported by: 0

README

sqlj

Go Reference

A simple struct to record mapper. Builds on top of the database/sql standard library to provide basic CRUD operations for flat structs.

Note: sqlj is in active development and should not be relied upon in production.

Install

go get github.com/JoeAxon/sqlj

Basic Usage

The library is intentionally limited with a few escape hatches for more complicated use cases. It is not intended to be an ORM but merely a convenient way to insert, update and select records from a database with minimal ceremony. To this end there is no support for marshalling records into nested structs.

Setup

The simplest way to get started with sqlj is to provide the Open function with the driver name and data source name:

package main

import (
	"fmt"
	"database/sql"

	_ "github.com/mattn/go-sqlite3"
	"github.com/JoeAxon/sqlj"
)

func main() {
	// .Open accepts the same arguments as DB.Open in the database/sql package
	db, err := sqlj.Open("sqlite3", ":memory:")

	// If you are using PostgreSQL with lib/pg, this might look like:
	// db, err = sql.Open("postgres", "host=localhost user=youruser password=yourpassword dbname=yourdb port=5432")

	defer db.Close()
}

The NewDB function can also be used to set-up sqlj. This accepts any struct that implements the DBLike interface. This is useful if you need to open the connection to the DB separately using the standard library database/sql package or if you are working with transactions.

You can also initialise the sqlj.DB manually if you find it useful to do so. I don't think it is necessary to do but I also don't intend to make it problematic if you do.

Inserting and updating records

Inserting and updating is straightforward with sqlj. As long as your structs are tagged with the corresponding db field name in the database you should just be able to call the Insert and Update methods. It's worth noting that the structs don't have to map every field in table. For Insert, only the NOT NULL fields need to be present to generate valid SQL and for Update you're free to omit whichever fields you like. This can be a powerful pattern where you create types for specific mutations.


// The "db" struct tags specify the column name in the database.
// Only fields with a "db" tag will be included in queries.
type User struct {
	ID    uint   `db:"id"`
	Name  string `db:"name"`
	Email string `db:"email"`
}

func main() {
	db, err := sqlj.Open("sqlite3", ":memory:")
	defer db.Close()

	user := User{Name: "Joe", Email: "joe@example.com"}

	// .Insert will generate and execute an "INSERT" query on the "users" table using the struct provided.
	// The newly created user will be unmarshalled into the struct.
	if err := db.Insert("users", &user); err != nil {
		fmt.Fatalf("Failed to insert user: %s\n", err.Error())
	}

	user.Name = "John"

	// .Update will generate an "UPDATE" statement on the "users" table using the struct provided.
	if err := db.Update("users", user.ID, &user); err != nil {
		fmt.Fatalf("Failed to update user: %s\n", err.Error())
	}
}

You can get a little more control over the generated SQL by using the InsertWithFields and UpdateWithFields methods. A real world example might be setting the updated_at field on a record to the current timestamp:

// The map keys should match a column in the table and the key a literal value.
// The keys and values are simply interpolated into the "UPDATE" query so be careful
// when passing dynamic strings to this method not to introduce an opportunity for SQL injection.
if err := db.UpdateWithFields("users", &user, map[string]string{"updated_at": "date()"}); err != nil {
	t.Fatalf("Failed to update user: %s\n", err.Error())
}
Retrieving records

The DB struct exposes the GetRow and SelectAll functions to allow you to marshall the results of arbitrary SQL into a struct or slice of structs respectively. It also exposes the Get function for retrieving a record by ID and, less usefully, the Select function to retrieve all records from a table.

var user User

// The .Get function is equivalent to the .GetRow call below
if err := db.Get("users", 1, &user); err != nil {
	fmt.Fatalf("Failed to retrieve user: %s\n", err.Error())
}

if err := db.GetRow("SELECT id, name, email FROM users WHERE id = $1", &user, 1); err != nil {
	fmt.Fatalf("Failed to retrieve user: %s\n", err.Error())
}

var allUsers []User

// The .Select function is equivalent to the .SelectAll call below
if err := db.Select("users", &allUsers); err != nil {
	fmt.Fatalf("Failed to retrieve users: %s\n", err.Error())
}

if err := db.SelectAll("SELECT id, name, email FROM users", &allUsers); err != nil {
	fmt.Fatalf("Failed to retrieve users: %s\n", err.Error())
}
Fluent API

There is an ergonomic API for writing queries that should hopefully suffice in most cases. Fluent interfaces get a bad rap but I believe this is a valid usecase and not too egregious:

var user User

// .From returns a QueryDB struct which allows you to chain .Where,
// .OrWhere and .Order calls before calling the .One method which
// marshalls a single record into the given struct.
if err := db.From("users").Where("name = ?", "Joe").One(&user); err != nil {
	fmt.Fatalf("Failed to retrieve user: %s\n", err.Error())
}

var allJoes []User

// .All will marshall multiple records into a slice of structs.
if err := db.From("users").Where("name = ?", "Joe").All(&allJoes); err != nil {
	fmt.Fatalf("Failed to retrieve all Joes: %s\n", err.Error())
}

var firstPage []User

// .Page will retrieve a page of records given a page number and size.
if err := db.From("users").Order("name", "ASC").Page(1, 10, &firstPage); err != nil {
	fmt.Fatalf("Failed to retrieve first page: %s\n", err.Error())
}

// .Count will return a count of records for the given query.
// This is intended to be used in conjunction with the .Page method.
total, err := db.From("users").Where("name <> ?", "Joe").Count()
if err != nil {
	fmt.Fatalf("Failed to retrieve first page: %s\n", err.Error())
}

Documentation

Index

Constants

View Source
const (
	AND_TYPE = "AND"
	OR_TYPE  = "OR"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type DB

type DB struct {
	DB           DBLike
	IDColumn     string
	SkipOnInsert []string // Allows you specify db field names to skip on insert
}

func NewDB added in v0.4.0

func NewDB(db DBLike) DB

func Open added in v0.4.0

func Open(driver string, dsn string) (*DB, error)

func (*DB) Close added in v0.4.0

func (jdb *DB) Close()

func (*DB) Delete

func (jdb *DB) Delete(table string, id any) error

Deletes a row in the given table by ID.

func (*DB) From added in v0.1.0

func (jdb *DB) From(table string) QueryDB

func (*DB) Get

func (jdb *DB) Get(table string, id any, v any) error

Gets a single row from the given table with the given id. v must be a pointer to a struct.

func (*DB) GetRow

func (jdb *DB) GetRow(sql string, v any, values ...any) error

Gets a single row using the supplied SQL and values. The result will be marshalled into the v struct. v must be a pointer to a struct.

func (*DB) Insert

func (jdb *DB) Insert(table string, v any) error

Inserts a row into the specified `table` with the given struct. The new row is returned and marshalled into v. v must be a pointer to a struct.

func (*DB) InsertWithFields added in v0.4.1

func (jdb *DB) InsertWithFields(table string, v any, fieldMap map[string]string) error

Inserts a row into the specified `table` with the given struct. The new row is returned and marshalled into v. A map of column to literal string value can be included to override any values in v. v must be a pointer to a struct.

func (*DB) Select

func (jdb *DB) Select(table string, v any) error

Selects all rows from a given table. The results will be marshalled into the v slice of structs. v must be a pointer to a slice of structs.

func (*DB) SelectAll

func (jdb *DB) SelectAll(sql string, v any, values ...any) error

Selects all rows using the supplied SQL and values. The results will be marshalled into the v slice of structs. v must be a pointer to a slice of structs.

func (*DB) Update added in v0.0.4

func (jdb *DB) Update(table string, id any, v any) error

Updates a row in the specified `table` using the given struct. The updated row is returned and marshalled into v. v must be a pointer to a struct.

func (*DB) UpdateWithFields added in v0.4.1

func (jdb *DB) UpdateWithFields(table string, id any, v any, fieldMap map[string]string) error

Updates a row in the specified `table` using the given struct. The updated row is returned and marshalled into v. A map of column to literal string value can be included to override any values in v. v must be a pointer to a struct.

type DBLike added in v0.0.5

type DBLike interface {
	Exec(query string, args ...any) (sql.Result, error)
	Query(query string, args ...any) (*sql.Rows, error)
	QueryRow(query string, args ...any) *sql.Row
}

Represents a DB-like interface. This only specifies the methods used by sqlj. Both DB and Tx in the database/sql standard library fulfill this contract.

type Expr added in v0.0.7

type Expr interface {
	String() string
}

type NestedExpr added in v0.0.7

type NestedExpr struct {
	// contains filtered or unexported fields
}

func (NestedExpr) String added in v0.0.7

func (e NestedExpr) String() string

type QueryDB added in v0.0.7

type QueryDB struct {
	DB           *DB
	From         string
	OrderClauses []orderBy
	WhereClauses []WhereClause
	WhereValues  []any
}

func (QueryDB) All added in v0.2.0

func (q QueryDB) All(v any) error

Select all data from the query object. The results will be marshalled into the v slice of structs. v must be a pointer to a slice of structs.

func (QueryDB) Count added in v0.2.0

func (q QueryDB) Count() (uint, error)

Counts the number of records in the table. This is intended to be used in conjunction with .Page.

func (QueryDB) Get added in v0.0.7

func (q QueryDB) Get(id any, v any) error

Get a record by ID. This will ignore any previous calls to .Where and .OrWhere

func (QueryDB) One added in v0.0.7

func (q QueryDB) One(v any) error

Get a single record from the given table.

func (QueryDB) OrWhere added in v0.0.7

func (q QueryDB) OrWhere(expr string, values ...any) QueryDB

func (QueryDB) OrWhereExpr added in v0.0.7

func (q QueryDB) OrWhereExpr(expr Expr, values ...any) QueryDB

func (QueryDB) Order added in v0.2.0

func (q QueryDB) Order(expression string, direction string) QueryDB

func (QueryDB) Page added in v0.2.0

func (q QueryDB) Page(page uint, pageSize uint, v any) error

Selects a page of data from the given table. The options parameter allows you to specify the page and page size. The results will be marshalled into the v slice of structs. v must be a pointer to a slice of structs.

func (QueryDB) Where added in v0.0.7

func (q QueryDB) Where(expr string, values ...any) QueryDB

func (QueryDB) WhereExpr added in v0.0.7

func (q QueryDB) WhereExpr(expr Expr, values ...any) QueryDB

type SimpleExpr added in v0.0.7

type SimpleExpr struct {
	// contains filtered or unexported fields
}

func (SimpleExpr) String added in v0.0.7

func (e SimpleExpr) String() string

type WhereClause added in v0.0.7

type WhereClause struct {
	Type string
	Expr Expr
}

Jump to

Keyboard shortcuts

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