ksql

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2021 License: MIT Imports: 14 Imported by: 34

README

KissSQL

If the thing you hate the most when coding is having too much unnecessary abstractions and the second thing you hate the most is having verbose and repetitive code for routine tasks this library is probably for you.

Welcome to the KissSQL project, the "Keep It Stupid Simple" SQL client for Go.

This package was created to be used by any developer efficiently and safely. The goals were:

  • To be easy to use
  • To be hard to make mistakes
  • To have a small API so it's easy to learn
  • To be easy to mock and test (very easy)
  • And above all to be readable.

Supported Drivers:

Currently we support only the 4 most popular Golang database drivers:

  • "postgres"
  • "sqlite3"
  • "mysql"
  • "sqlserver"

If you need a new one included please open an issue or make your own implementation and submit a Pull Request.

Why KissSQL?

Note: If you want numbers see our Benchmark section below

KissSQL was created to fill a hole between the complexity we find in the tools I've seen so far, namely:

  • ORMs such as GORM that do a lot and have literally hundreds of functions that require learning, increasing the risk of interpretation errors, learning time, complicating mocking, etc.
  • Tools such as sqlx that do little but still have most of the quirks from the standard sql lib, requiring several error checks for each query which is very low level.

Besides both these examples were not created having easy tests as one of the requisites, which might cause your team to loose far more time than necessary writing the tests or worst: Opting to not writing tests since it would take too much time.

So the goal was to be high level enough that it would avoid the complications from the sql package and at the same time to be simple enough to avoid the big learning curve and complexity of the hundreds of functions offered by ORMs.

That said, KissSQL attempts to apply the Kiss principle, in order to save development time for your team, i.e.:

  • Less time spent learning (few methods to learn)
  • Less time spent testing (helper tools made to help you)
  • Less time spent debugging (simple apis are easier to debug)
  • and less time reading & understanding the code
Kiss Interface

The current interface is as follows and we plan on keeping it with as little functions as possible, so don't expect many additions:

// Provider describes the ksql public behavior
type Provider interface {
	Insert(ctx context.Context, table Table, record interface{}) error
	Update(ctx context.Context, table Table, record interface{}) error
	Delete(ctx context.Context, table Table, idsOrRecords ...interface{}) error

	Query(ctx context.Context, records interface{}, query string, params ...interface{}) error
	QueryOne(ctx context.Context, record interface{}, query string, params ...interface{}) error
	QueryChunks(ctx context.Context, parser ChunkParser) error

	Exec(ctx context.Context, query string, params ...interface{}) error
	Transaction(ctx context.Context, fn func(Provider) error) error
}
Usage examples

This example is also available here if you want to compile it yourself.

Also we have a small feature for building the "SELECT" part of the query if you rather not use SELECT * queries, you may skip to the Select Generator Feature which is very clean too.

package main

import (
	"context"
	"fmt"

	_ "github.com/mattn/go-sqlite3"
	"github.com/vingarcia/ksql"
	"github.com/vingarcia/ksql/nullable"
)

// User ...
type User struct {
	ID   int    `ksql:"id"`
	Name string `ksql:"name"`
	Age  int    `ksql:"age"`

	// This field will be saved as JSON in the database
	Address Address `ksql:"address,json"`
}

// PartialUpdateUser ...
type PartialUpdateUser struct {
	ID      int      `ksql:"id"`
	Name    *string  `ksql:"name"`
	Age     *int     `ksql:"age"`
	Address *Address `ksql:"address,json"`
}

// Address ...
type Address struct {
	State string `json:"state"`
	City  string `json:"city"`
}

// UsersTable informs ksql the name of the table and that it can
// use the default value for the primary key column name: "id"
var UsersTable = ksql.NewTable("users")

func main() {
	ctx := context.Background()
	db, err := ksql.New("sqlite3", "/tmp/hello.sqlite", ksql.Config{
		MaxOpenConns: 1,
	})
	if err != nil {
		panic(err.Error())
	}

	// In the definition below, please note that BLOB is
	// the only type we can use in sqlite for storing JSON.
	err = db.Exec(ctx, `CREATE TABLE IF NOT EXISTS users (
	  id INTEGER PRIMARY KEY,
		age INTEGER,
		name TEXT,
		address BLOB
	)`)
	if err != nil {
		panic(err.Error())
	}

	var alison = User{
		Name: "Alison",
		Age:  22,
		Address: Address{
			State: "MG",
		},
	}
	err = db.Insert(ctx, UsersTable, &alison)
	if err != nil {
		panic(err.Error())
	}
	fmt.Println("Alison ID:", alison.ID)

	// Inserting inline:
	err = db.Insert(ctx, UsersTable, &User{
		Name: "Cristina",
		Age:  27,
		Address: Address{
			State: "SP",
		},
	})
	if err != nil {
		panic(err.Error())
	}

	// Deleting Alison:
	err = db.Delete(ctx, UsersTable, alison.ID)
	if err != nil {
		panic(err.Error())
	}

	// Retrieving Cristina:
	var cris User
	err = db.QueryOne(ctx, &cris, "SELECT * FROM users WHERE name = ? ORDER BY id", "Cristina")
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("Cristina: %#v\n", cris)

	// Updating all fields from Cristina:
	cris.Name = "Cris"
	err = db.Update(ctx, UsersTable, cris)

	// Changing the age of Cristina but not touching any other fields:

	// Partial update technique 1:
	err = db.Update(ctx, UsersTable, struct {
		ID  int `ksql:"id"`
		Age int `ksql:"age"`
	}{ID: cris.ID, Age: 28})
	if err != nil {
		panic(err.Error())
	}

	// Partial update technique 2:
	err = db.Update(ctx, UsersTable, PartialUpdateUser{
		ID:  cris.ID,
		Age: nullable.Int(28),
	})
	if err != nil {
		panic(err.Error())
	}

	// Listing first 10 users from the database
	// (each time you run this example a new Cristina is created)
	//
	// Note: Using this function it is recommended to set a LIMIT, since
	// not doing so can load too many users on your computer's memory or
	// cause an Out Of Memory Kill.
	//
	// If you need to query very big numbers of users we recommend using
	// the `QueryChunks` function.
	var users []User
	err = db.Query(ctx, &users, "SELECT * FROM users LIMIT 10")
	if err != nil {
		panic(err.Error())
	}

	// Making transactions:
	err = db.Transaction(ctx, func(db ksql.Provider) error {
		var cris2 User
		err = db.QueryOne(ctx, &cris2, "SELECT * FROM users WHERE id = ?", cris.ID)
		if err != nil {
			// This will cause an automatic rollback:
			return err
		}

		err = db.Update(ctx, UsersTable, PartialUpdateUser{
			ID:  cris2.ID,
			Age: nullable.Int(29),
		})
		if err != nil {
			// This will also cause an automatic rollback and then panic again
			// so that we don't hide the panic inside the KissSQL library
			panic(err.Error())
		}

		// Commits the transaction
		return nil
	})
	if err != nil {
		panic(err.Error())
	}

	fmt.Printf("Users: %#v\n", users)
}
Query Chunks Feature

It's very unsual for us to need to load a number of records from the database that might be too big for fitting in memory, e.g. load all the users and send them somewhere. But it might happen.

For these cases it's best to load chunks of data at a time so that we can work on a substantial amount of data at a time and never overload our memory capacity. For this use case we have a specific function called QueryChunks:

err = db.QueryChunks(ctx, ksql.ChunkParser{
	Query:     "SELECT * FROM users WHERE type = ?",
	Params:    []interface{}{usersType},
	ChunkSize: 100,
	ForEachChunk: func(users []User) error {
		err := sendUsersSomewhere(users)
		if err != nil {
			// This will abort the QueryChunks loop and return this error
			return err
		}
		return nil
	},
})
if err != nil {
	panic(err.Error())
}

It's signature is more complicated than the other two Query* methods, thus, it is adivisible to always prefer using the other two when possible reserving this one for the rare use-case where you are actually loading big sections of the database into memory.

Select Generator Feature

There are good reasons not to use SELECT * queries the most important of them is that you might end up loading more information than you are actually going to use putting more pressure in your database for no good reason.

To prevent that ksql has a feature specifically for building the SELECT part of the query using the tags from the input struct. Using it is very simple and it works with all the 3 Query* functions:

Querying a single user:

var user User
err = db.QueryOne(ctx, &user, "FROM users WHERE id = ?", userID)
if err != nil {
	panic(err.Error())
}

Querying a page of users:

var users []User
err = db.Query(ctx, &users, "FROM users WHERE type = ? ORDER BY id LIMIT ? OFFSET ?", "Cristina", limit, offset)
if err != nil {
	panic(err.Error())
}

Querying all the users, or any potentially big number of users, from the database (not usual, but supported):

err = db.QueryChunks(ctx, ksql.ChunkParser{
	Query:     "FROM users WHERE type = ?",
	Params:    []interface{}{usersType},
	ChunkSize: 100,
	ForEachChunk: func(users []User) error {
		err := sendUsersSomewhere(users)
		if err != nil {
			// This will abort the QueryChunks loop and return this error
			return err
		}
		return nil
	},
})
if err != nil {
	panic(err.Error())
}

The implementation of this feature is actually simple internally. First we check if the query is starting with the word FROM, if it is then we just get the ksql tags from the struct and then use it for building the SELECT statement.

The SELECT statement is then cached so we don't have to build it again the next time in order to keep the library efficient even when using this feature.

Select Generation with Joins

So there is one use-case that was not covered by ksql so far:

What if you want to JOIN multiple tables for which you already have structs defined? Would you need to create a new struct to represent the joined columns of the two tables? no, we actually have this covered as well.

ksql has a special feature for allowing the reuse of existing structs by using composition in an anonymous struct, and then generating the SELECT part of the query accordingly:

Querying a single joined row:

var row struct{
	User User `tablename:"u"`     // (here the tablename must match the aliased tablename in the query)
	Post Post `tablename:"posts"` // (if no alias is used you should use the actual name of the table)
}
err = db.QueryOne(ctx, &row, "FROM users as u JOIN posts ON u.id = posts.user_id WHERE u.id = ?", userID)
if err != nil {
	panic(err.Error())
}

Querying a page of joined rows:

var rows []struct{
	User User `tablename:"u"`
	Post Post `tablename:"p"`
}
err = db.Query(ctx, &rows,
	"FROM users as u JOIN posts as p ON u.id = p.user_id WHERE name = ? LIMIT ? OFFSET ?",
	"Cristina", limit, offset,
)
if err != nil {
	panic(err.Error())
}

Querying all the users, or any potentially big number of users, from the database (not usual, but supported):

err = db.QueryChunks(ctx, ksql.ChunkParser{
	Query:     "FROM users as u JOIN posts as p ON u.id = p.user_id WHERE type = ?",
	Params:    []interface{}{usersType},
	ChunkSize: 100,
	ForEachChunk: func(rows []struct{
		User User `tablename:"u"`
		Post Post `tablename:"p"`
	}) error {
		err := sendRowsSomewhere(rows)
		if err != nil {
			// This will abort the QueryChunks loop and return this error
			return err
		}
		return nil
	},
})
if err != nil {
	panic(err.Error())
}

As advanced as this feature might seem we don't do any parsing of the query, and all the work is done only once and then cached.

What actually happens is that we use the "tablename" tag to build the SELECT part of the query like this:

  • SELECT u.id, u.name, u.age, p.id, p.title

This is then cached, and when we need it again we concatenate it with the rest of the query.

This feature has two important limitations:

  1. It is not possible to use tablename tags together with normal ksql tags. Doing so will cause the tablename tags to be ignored in favor of the ksql ones.
  2. It is not possible to use it without omitting the SELECT part of the query. While in normal queries we match the selected field with the attribute by name, in queries joining multiple tables we can't use this strategy because different tables might have columns with the same name, and we don't really have access to the full name of these columns making, for example, it impossible to differentiate between u.id and p.id except by the order in which these fields were passed. Thus, it is necessary that the library itself writes the SELECT part of the query when using this technique so that we can control the order or the selected fields.

Ok, but what if I don't want to use this feature?

You are not forced to, and there are a few use-cases where you would prefer not to, e.g.:

var rows []struct{
	UserName string `ksql:"name"`
	PostTitle string `ksql:"title"`
}
err := db.Query(ctx, &rows, "SELECT u.name, p.title FROM users u JOIN posts p ON u.id = p.user_id LIMIT 10")
if err != nil {
	panic(err.Error())
}

In the example above, since we are only interested in a couple of columns it is far simpler and more efficient for the database to only select the columns that we actually care about, so it's better not to use composite kstructs.

Testing Examples

This library has a few helper functions for helping your tests:

  • kstructs.FillStructWith(struct interface{}, dbRow map[string]interface{}) error
  • kstructs.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error
  • kstructs.StructToMap(struct interface{}) (map[string]interface{}, error)
  • kstructs.CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) (map[string]interface{}, error)

If you want to see examples (we have examples for all the public functions) just read the example tests available on our example service

Benchmark Comparison

The benchmark is very good, the code is, in practical terms, as fast as sqlx:

$ make bench TIME=5s
go test -bench=. -benchtime=5s
goos: linux
goarch: amd64
pkg: github.com/vingarcia/ksql
cpu: Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
BenchmarkInsert/ksql-setup/insert-one-4         	    5293	    960859 ns/op
BenchmarkInsert/pgx-adapter-setup/insert-one-4  	    7982	    736973 ns/op
BenchmarkInsert/sqlx-setup/insert-one-4         	    6854	    857824 ns/op
BenchmarkQuery/ksql-setup/single-row-4          	   12596	    407116 ns/op
BenchmarkQuery/ksql-setup/multiple-rows-4       	   15883	    391135 ns/op
BenchmarkQuery/pgx-adapter-setup/single-row-4   	   34008	    165604 ns/op
BenchmarkQuery/pgx-adapter-setup/multiple-rows-4	   22579	    280673 ns/op
BenchmarkQuery/sqlx-setup/single-row-4          	   10000	    512741 ns/op
BenchmarkQuery/sqlx-setup/multiple-rows-4       	   10779	    596377 ns/op
PASS
ok  	github.com/vingarcia/ksql	94.951s
Benchmark executed at: 2021-08-01
Benchmark executed on commit: 37298e2c243f1ec66e88dd92ed7c4542f7820b4f
Running the ksql tests (for contributors)

The tests run in dockerized database instances so the easiest way to have them working is to just start them using docker-compose:

docker-compose up -d

And then for each of them you will need to run the command:

CREATE DATABASE ksql;

After that you can just run the tests by using:

make test
TODO List
  • Add tests for tables using composite keys
  • Add support for serializing structs as other formats such as YAML
  • Update kstructs.FillStructWith to work with ksql:"..,json" tagged attributes
  • Make testing easier by exposing the connection strings in an .env file
  • Make testing easier by automatically creating the ksql database
  • Create a way for users to submit user defined dialects
  • Improve error messages
  • Add support for the update function to work with maps for partial updates
  • Add support for the insert function to work with maps
  • Add support for a ksql.Array(params ...interface{}) for allowing queries like this: db.Query(ctx, &user, "SELECT * FROM user WHERE id in (?)", ksql.Array(1,2,3))
Optimization Oportunities
  • Test if using a pointer on the field info is faster or not
  • Consider passing the cached structInfo as argument for all the functions that use it, so that we don't need to get it more than once in the same call.
  • Use a cache to store all queries after they are built
  • Preload the insert method for all dialects inside ksql.NewTable()

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrAbortIteration error = fmt.Errorf("ksql: abort iteration, should only be used inside QueryChunks function")

ErrAbortIteration ...

View Source
var ErrRecordNotFound error = errors.Wrap(sql.ErrNoRows, "ksql: the query returned no results")

ErrRecordNotFound ...

Functions

This section is empty.

Types

type ChunkParser

type ChunkParser struct {
	// The Query and Params are used together to build a query with
	// protection from injection, just like when using the Find function.
	Query  string
	Params []interface{}

	ChunkSize int

	// This attribute must be a function with the following signature:
	//
	// `func(chunk []<Record>) error`.
	//
	// Where the actual Record type should be of a struct
	// representing the rows you are expecting to receive.
	ForEachChunk interface{}
}

ChunkParser stores the arguments of the QueryChunks function

type Config

type Config struct {
	// MaxOpenCons defaults to 1 if not set
	MaxOpenConns int
}

Config describes the optional arguments accepted by the ksql.New() function.

type DB

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

DB represents the ksql client responsible for interfacing with the "database/sql" package implementing the KissSQL interface `ksql.Provider`.

func New

func New(
	dbDriver string,
	connectionString string,
	config Config,
) (DB, error)

New instantiates a new KissSQL client

func NewWithAdapter

func NewWithAdapter(
	db DBAdapter,
	dbDriver string,
) (DB, error)

NewWithAdapter allows the user to insert a custom implementation of the DBAdapter interface

func NewWithPGX

func NewWithPGX(
	ctx context.Context,
	connectionString string,
	config Config,
) (db DB, err error)

NewWithPGX instantiates a new KissSQL client using the pgx library in the backend

func (DB) Delete

func (c DB) Delete(
	ctx context.Context,
	table Table,
	ids ...interface{},
) error

Delete deletes one or more instances from the database by id

func (DB) Exec

func (c DB) Exec(ctx context.Context, query string, params ...interface{}) error

Exec just runs an SQL command on the database returning no rows.

func (DB) Insert

func (c DB) Insert(
	ctx context.Context,
	table Table,
	record interface{},
) error

Insert one or more instances on the database

If the original instances have been passed by reference the ID is automatically updated after insertion is completed.

func (DB) Query

func (c DB) Query(
	ctx context.Context,
	records interface{},
	query string,
	params ...interface{},
) error

Query queries several rows from the database, the input should be a slice of structs (or *struct) passed by reference and it will be filled with all the results.

Note: it is very important to make sure the query will return a small known number of results, otherwise you risk of overloading the available memory.

func (DB) QueryChunks

func (c DB) QueryChunks(
	ctx context.Context,
	parser ChunkParser,
) error

QueryChunks is meant to perform queries that returns more results than would normally fit on memory, for others cases the Query and QueryOne functions are indicated.

The ChunkParser argument has 4 attributes: (1) The Query; (2) The query args; (3) The chunk size; (4) A callback function called ForEachChunk, that will be called to process each chunk loaded from the database.

Note that the signature of the ForEachChunk callback can be any function that receives a slice of structs or a slice of pointers to struct as its only argument and that reflection will be used to instantiate this argument and to fill it with the database rows.

func (DB) QueryOne

func (c DB) QueryOne(
	ctx context.Context,
	record interface{},
	query string,
	params ...interface{},
) error

QueryOne queries one instance from the database, the input struct must be passed by reference and the query should return only one result.

QueryOne returns a ErrRecordNotFound if the query returns no results.

func (DB) Transaction

func (c DB) Transaction(ctx context.Context, fn func(Provider) error) error

Transaction just runs an SQL command on the database returning no rows.

func (DB) Update

func (c DB) Update(
	ctx context.Context,
	table Table,
	record interface{},
) error

Update updates the given instances on the database by id.

Partial updates are supported, i.e. it will ignore nil pointer attributes

type DBAdapter

type DBAdapter interface {
	ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
	QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)
}

DBAdapter is minimalistic interface to decouple our implementation from database/sql, i.e. if any struct implements the functions below with the exact same semantic as the sql package it will work with ksql.

To create a new client using this adapter use ksql.NewWithAdapter()

type Dialect

type Dialect interface {
	InsertMethod() insertMethod
	Escape(str string) string
	Placeholder(idx int) string
	DriverName() string
}

Dialect is used to represent the different ways of writing SQL queries used by each SQL driver.

func GetDriverDialect

func GetDriverDialect(driver string) (Dialect, error)

GetDriverDialect instantiantes the dialect for the provided driver string, if the drive is not supported it returns an error

type Mock

type Mock struct {
	InsertFn func(ctx context.Context, table Table, record interface{}) error
	UpdateFn func(ctx context.Context, table Table, record interface{}) error
	DeleteFn func(ctx context.Context, table Table, ids ...interface{}) error

	QueryFn       func(ctx context.Context, records interface{}, query string, params ...interface{}) error
	QueryOneFn    func(ctx context.Context, record interface{}, query string, params ...interface{}) error
	QueryChunksFn func(ctx context.Context, parser ChunkParser) error

	ExecFn        func(ctx context.Context, query string, params ...interface{}) error
	TransactionFn func(ctx context.Context, fn func(db Provider) error) error
}

Mock ...

func (Mock) Delete

func (m Mock) Delete(ctx context.Context, table Table, ids ...interface{}) error

Delete ...

func (Mock) Exec

func (m Mock) Exec(ctx context.Context, query string, params ...interface{}) error

Exec ...

func (Mock) Insert

func (m Mock) Insert(ctx context.Context, table Table, record interface{}) error

Insert ...

func (Mock) Query

func (m Mock) Query(ctx context.Context, records interface{}, query string, params ...interface{}) error

Query ...

func (Mock) QueryChunks

func (m Mock) QueryChunks(ctx context.Context, parser ChunkParser) error

QueryChunks ...

func (Mock) QueryOne

func (m Mock) QueryOne(ctx context.Context, record interface{}, query string, params ...interface{}) error

QueryOne ...

func (Mock) SetFallbackDatabase

func (m Mock) SetFallbackDatabase(db Provider) Mock

SetFallbackDatabase will set all the Fn attributes to use the function from the input database.

SetFallbackDatabase is useful when you only want to overwrite some of the operations, e.g. for testing errors or if you want to use the same setup for making unit tests and integration tests, this way instead of creating a new server with a real database and another with a mocked one you can start the server once and run both types of tests.

Example Usage:

db, err := ksql.New(...)
if err != nil {
	t.Fatal(err.Error())
}

mockdb := ksql.Mock{
	UpdateFn: func(_ context.Context, _ ksql.Table, record interface{}) error {
		return ksql.ErrRecordNotFound
	},
}.SetFallbackDatabase(db)

// Passing the address to the service so
// you can change it for each test
myService := myservice.New(..., &mockdb, ...)

func (Mock) Transaction

func (m Mock) Transaction(ctx context.Context, fn func(db Provider) error) error

Transaction ...

func (Mock) Update

func (m Mock) Update(ctx context.Context, table Table, record interface{}) error

Update ...

type PGXAdapter

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

PGXAdapter adapts the sql.DB type to be compatible with the `DBAdapter` interface

func (PGXAdapter) BeginTx

func (p PGXAdapter) BeginTx(ctx context.Context) (Tx, error)

BeginTx implements the Tx interface

func (PGXAdapter) ExecContext

func (p PGXAdapter) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)

ExecContext implements the DBAdapter interface

func (PGXAdapter) QueryContext

func (p PGXAdapter) QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)

QueryContext implements the DBAdapter interface

type PGXResult

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

PGXResult is used to implement the DBAdapter interface and implements the Result interface

func (PGXResult) LastInsertId

func (p PGXResult) LastInsertId() (int64, error)

LastInsertId implements the Result interface

func (PGXResult) RowsAffected

func (p PGXResult) RowsAffected() (int64, error)

RowsAffected implements the Result interface

type PGXRows

type PGXRows struct {
	pgx.Rows
}

PGXRows implements the Rows interface and is used to help the PGXAdapter to implement the DBAdapter interface.

func (PGXRows) Close

func (p PGXRows) Close() error

Close implements the Rows interface

func (PGXRows) Columns

func (p PGXRows) Columns() ([]string, error)

Columns implements the Rows interface

type PGXTx

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

PGXTx is used to implement the DBAdapter interface and implements the Tx interface

func (PGXTx) Commit

func (p PGXTx) Commit(ctx context.Context) error

Commit implements the Tx interface

func (PGXTx) ExecContext

func (p PGXTx) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)

ExecContext implements the Tx interface

func (PGXTx) QueryContext

func (p PGXTx) QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)

QueryContext implements the Tx interface

func (PGXTx) Rollback

func (p PGXTx) Rollback(ctx context.Context) error

Rollback implements the Tx interface

type Provider

type Provider interface {
	Insert(ctx context.Context, table Table, record interface{}) error
	Update(ctx context.Context, table Table, record interface{}) error
	Delete(ctx context.Context, table Table, idsOrRecords ...interface{}) error

	Query(ctx context.Context, records interface{}, query string, params ...interface{}) error
	QueryOne(ctx context.Context, record interface{}, query string, params ...interface{}) error
	QueryChunks(ctx context.Context, parser ChunkParser) error

	Exec(ctx context.Context, query string, params ...interface{}) error
	Transaction(ctx context.Context, fn func(Provider) error) error
}

Provider describes the ksql public behavior

type Result

type Result interface {
	LastInsertId() (int64, error)
	RowsAffected() (int64, error)
}

Result stores information about the result of an Exec query

type Rows

type Rows interface {
	Scan(...interface{}) error
	Close() error
	Next() bool
	Err() error
	Columns() ([]string, error)
}

Rows represents the results from a call to Query()

type SQLAdapter

type SQLAdapter struct {
	*sql.DB
}

SQLAdapter adapts the sql.DB type to be compatible with the `DBAdapter` interface

func (SQLAdapter) BeginTx

func (s SQLAdapter) BeginTx(ctx context.Context) (Tx, error)

BeginTx implements the Tx interface

func (SQLAdapter) ExecContext

func (s SQLAdapter) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)

ExecContext implements the DBAdapter interface

func (SQLAdapter) QueryContext

func (s SQLAdapter) QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)

QueryContext implements the DBAdapter interface

type SQLTx

type SQLTx struct {
	*sql.Tx
}

SQLTx is used to implement the DBAdapter interface and implements the Tx interface

func (SQLTx) Commit

func (s SQLTx) Commit(ctx context.Context) error

Commit implements the Tx interface

func (SQLTx) ExecContext

func (s SQLTx) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)

ExecContext implements the Tx interface

func (SQLTx) QueryContext

func (s SQLTx) QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)

QueryContext implements the Tx interface

func (SQLTx) Rollback

func (s SQLTx) Rollback(ctx context.Context) error

Rollback implements the Tx interface

type Table

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

Table describes the required information for inserting, updating and deleting entities from the database by ID using the 3 helper functions created for that purpose.

func NewTable

func NewTable(tableName string, ids ...string) Table

NewTable returns a Table instance that stores the tablename and the names of columns used as ID, if no column name is passed it defaults to using the `"id"` column.

This Table is required only for using the helper methods:

- Insert - Update - Delete

Passing multiple ID columns will be interpreted as a single composite key, if you want to use the helper functions with different keys you'll need to create multiple Table instances for the same database table, each with a different set of ID columns, but this is usually not necessary.

type Tx

type Tx interface {
	ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
	QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)
	Rollback(ctx context.Context) error
	Commit(ctx context.Context) error
}

Tx represents a transaction and is expected to be returned by the DBAdapter.BeginTx function

type TxBeginner

type TxBeginner interface {
	BeginTx(ctx context.Context) (Tx, error)
}

TxBeginner needs to be implemented by the DBAdapter in order to make it possible to use the `ksql.Transaction()` function.

Directories

Path Synopsis
adapters
kmysql module
kpgx module
kpgx5 module
kpostgres module
ksqlite3 module
ksqlserver module
benchmarks module
examples
crud command
example_service
Package exampleservice is a generated GoMock package.
Package exampleservice is a generated GoMock package.

Jump to

Keyboard shortcuts

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