sqltest

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Mar 18, 2025 License: MIT Imports: 10 Imported by: 1

README

sqltest

Check database schema of your Go app behaves as expected.

Example usage

In example we use DefaultFileSet, which finds .sql files in testdata directory.

Structure of files in this example:

migrations/schema_test.go
migrations/initial.sql
migrations/testdata/emp_log_salary.sql
// migrations/schema_test.go
package migrations

import (
	"testing"

	"github.com/shagohead/sqltest"
	"github.com/shagohead/sqltestpgx"
)

// TestSchema tests database schema with queries from testdata/*.sql
func TestSchema(t *testing.T) {
	set, err := sqltest.DefaultFileSet()
	if err != nil {
		t.Fatal(err)
	}
	for name, test := range set.All() {
		t.Run(name, func(t *testing.T) {
			// dbtest.StartTx is a helper which creates and rollbacks transactions for tests.
			err := test.Run(sqltestpgx.Tx(dbtest.StartTx(t)))
			if err != nil {
				t.Fatal(err)
			}
		})
	}
}

Database migration in which table emp_log populated by trigger on writes in emp table.

-- migrations/initial.sql
CREATE TABLE emp (
  user_id integer PRIMARY KEY GENERATED AS DEFAULT BY IDENTITY,
  salary integer NOT NULL
);

CREATE TABLE emp_log (
  id bigint PRIMARY KEY GENERATED AS DEFAULT BY IDENTITY,
  user_id integer NOT NULL,
  salary integer NOT NULL
);

CREATE FUNCTION log_emp() RETURNS trigger AS $$
BEGIN
    IF (TG_OP = 'INSERT' OR OLD.salary <> NEW.salary) THEN
        INSERT INTO emp_log (user_id, salary) VALUES (NEW.user_id, NEW.salary);
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER log_emp AFTER INSERT OR UPDATE OF salary ON log_emp FOR EACH ROW EXECUTE FUNCTION log_emp();

Test file contains queries separated by ;\n.

For testing purposes we have two statements: define and assert. First one declares and defines named query. Second one calls that query by its name and compare representation of results.

If query do not use that keywords, it just invokes and checks for error occur.

-- migrations/emp_log_salary.sql
define get_last_log
SELECT user_id, salary FROM emp_log ORDER BY id DESC;

INSERT INTO emp VALUES (1, 125800);
assert get_last_log [1 125800];

INSERT INTO emp VALUES (2, 220000);
assert get_last_log [2 220000];

UPDATE emp SET salary 157000 WHERE user_id = 1;
assert get_last_log [1 157000];

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func WithLimit

func WithLimit(limit int) option

Overwrite parsing cycles limit.

Types

type Querier

type Querier interface {
	// Query called in Test.Run. Querier implementation do some query and check it results.
	Query(ctx context.Context, tx Tx) error
}

Querier is query'like object. Which whould be called in Test.Run.

type QueryDelimiter

type QueryDelimiter func([]byte) position

type QueryParser

type QueryParser interface {
	// Parse is trying to parse query source to Querier object or updated context.
	//
	// If query source is not related to QueryParser implementation, it should return nils.
	// That is, Parse returns either the context, or the Querier, or nothing.
	// If it returns not nil, then the processing of the current query will be stopped at this QueryParser.
	Parse(context.Context, []byte) (context.Context, Querier, error)
}

type Rows

type Rows interface {
	Close()
	Next() bool
	Err() error

	// String returns string representation of row values.
	String() (string, error)
}

Rows returned by Tx.Query.

type Set

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

func DefaultFileSet

func DefaultFileSet(opts ...option) (*Set, error)

func NewFileSet

func NewFileSet(pattern string, opts ...option) (*Set, error)

func NewSet

func NewSet(tp iter.Seq2[string, io.Reader], opts ...option) (*Set, error)

NewSet creates set for tests provided by tp.

func (*Set) All

func (set *Set) All() iter.Seq2[string, TestRunner]

type Test

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

func New

func New(reader io.Reader, opts ...option) (*Test, error)

Create new Test with queries from reader delimited by ;\n

func (*Test) Run

func (test *Test) Run(tx Tx) error

type TestRunner

type TestRunner interface {
	Run(tx Tx) error
}

type Tx

type Tx interface {
	// Exec query omitting results.
	Exec(ctx context.Context, sql string, args ...any) error

	// Query [Rows] from database.
	Query(ctx context.Context, sql string, args ...any) (Rows, error)
}

Tx declares database interaction interface.

Jump to

Keyboard shortcuts

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