Documentation ¶
Overview ¶
regresql package implements the RegreSQL commands.
The main entry point of this package is a Suite data structure instance, which can be obtained with the Walk() function:
func List(dir string) { suite := Walk(dir) suite.Println() }
That's the simplest you can do with a suite instance, and that's the whole implementation of the exported List function too.
One you have a test suite instance, for more interesting things you usually want to read regresql configuration which is created by the Init() command and stores the PostgreSQL connection string, in the format expected by the github.com/lib/pq library:
suite := Walk(root) config, err := suite.readConfig() if err != nil { fmt.Printf(err.Error()) os.Exit(3) } if err := TestConnectionString(config.PgUri); err != nil { fmt.Printf(err.Error()) os.Exit(2) }
Now that you have a test suite and a valid PostgreSQL connection string, it's possible to run the SQL queries. A query is typically as in the following example:
-- name: list-albums-by-artist -- List the album titles and duration of a given artist select album.title as album, sum(milliseconds) * interval '1 ms' as duration from album join artist using(artistid) left join track using(albumid) where artist.name = :name group by album order by album;
This is parsed to find out the parameters, spelled in a `psql` compatible way as documented in https://www.postgresql.org/docs/9.6/static/app-psql.html#APP-PSQL-VARIABLES.
To be able to run regression tests against such a query with parameters, we need parameter values. That's to be found in a Plan file. A plan file is a YAML file associated with a query, such as the following:
"1": name: "Red Hot Chili Peppers"
In this file we find a single implementation of the query parameters, named "1" (that's automatically filled in by the Init() function). Our user edited the file to fill in "Red Hot Chili Peppers" from an empty string, as created by the Init() function.
The Init() function created a YAML plan file for each query, using the https://github.com/spf13/viper library. The user is expected to edit the YAML files. Once the parameters are edited it's possible to run the queries.
Update() runs the queries and stores their results in an expected file.
Test() runs the queries, stores their results in an out file (the actual output) and compares this actual result set with the expected one.
Index ¶
- func DiffFiles(a string, b string, c int) (string, error)
- func DiffLines(from string, to string, a []string, b []string, c int) string
- func Init(root string, pguri string)
- func List(dir string)
- func PlanQueries(root string)
- func Test(root string)
- func TestConnectionString(pguri string) error
- func Update(root string)
- type Folder
- type Plan
- type Query
- type ResultSet
- type Suite
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DiffLines ¶
DiffLines compares two lists of strings and reports differences in the unified format. from and to are the files to report in the diff output.
func Init ¶
Init initializes a code repository for RegreSQL processing.
That means creating the ./regresql/ directory, walking the code repository in search of *.sql files, and creating the associated empty plan files. If the plan files already exists, we simply skip them, thus allowing to run init again on an existing repository to create missing plan files.
func List ¶
func List(dir string)
List walks a repository, builds a Suite instance and pretty prints it.
func PlanQueries ¶
func PlanQueries(root string)
PlanQueries create query plans for queries found in the root repository
func Test ¶
func Test(root string)
Test runs the queries and compare their results to the previously created expected files (see Update()), reporting a TAP output to standard output.
func TestConnectionString ¶
TestConnectionString connects to PostgreSQL with pguri and issue a single query (select 1"), because some errors (such as missing SSL certificates) only happen at query time.
Types ¶
type Folder ¶
Folder implements a directory from the source repository wherein we found some SQL files. Folder are only implemented as part of a Suite instance.
type Plan ¶
type Plan struct { Query *Query Path string // the file path where we read the Plan from Names []string Bindings []map[string]string ResultSets []ResultSet }
A query plan associates a Query parsed from a Path (name of the file on disk) and a list of set of parameters used to run the query. Each set of parameters as a name in Names[i] and a list of bindings in Bindings[i]. When the query is executed we store its output in ResultSets[i].
func (*Plan) CompareResultSets ¶
CompareResultsSets load the expected result set and compares it with the given Plan's ResultSet, and fills in a tap.T test output.
The test is considered passed when the diff is empty.
Rather than returning an error in case something wrong happens, we register a diagnostic against the tap output and fail the test case.
func (*Plan) Execute ¶
Executes a plan and returns the filepath where the output has been written, for later comparing
func (*Plan) WriteResultSets ¶
WriteResultSets serialize the result of running a query, as a Pretty Printed output (comparable to a simplified `psql` output)
type Query ¶
type Query struct { Path string Text string // original query text Query string // "normalized" SQL query for lib/pq Vars []string // variable names used in the query text Params []string // ordered list of params used in the query }
A query instances represents an SQL query, read from Path filename and stored raw as the Text slot. The query text is “parsed” into the Query slot, and parameters are extracted into both the Vars slot and the Params slot.
SELECT * FROM foo WHERE a = :a and b between :a and :b;
In the previous query, we would have Vars = [a b] and Params = [a a b].
func (*Query) CreateEmptyPlan ¶
CreateEmptyPlan creates a YAML file where to store the set of parameters associated with a query.
type ResultSet ¶
A ResultSet stores the result of a Query in Filename, with Cols and Rows separated.
func (*ResultSet) PrettyPrint ¶
PrettyPrint pretty prints a result set and returns it as a string
type Suite ¶
type Suite struct { Root string RegressDir string Dirs []Folder PlanDir string ExpectedDir string OutDir string }
Suite implements a test suite, which is found in the Root directory and contains a list of Dirs folders, each containing a list of SQL query files. The RegressDir slot contains the directory where regresql stores its files: the query plans with bound parameters, their expected outputs and the actual results obtained when running `regresql test`.
Rather than handling a fully recursive data structure, which isn't necessary for our endeavours, we maintain a fixed two-levels data structure. The Printf() method dipatched on a Suite method is callable from the main command and shows our structure organisation:
$ regresql list . src/sql/ album-by-artist.sql album-tracks.sql artist.sql genre-topn.sql genre-tracks.sql