2017-02-08 06:01:29 +00:00
package postgres
// error codes https://github.com/lib/pq/blob/master/error.go
import (
2018-07-07 10:17:07 +00:00
"context"
2017-02-08 06:01:29 +00:00
"database/sql"
2018-01-20 07:57:43 +00:00
sqldriver "database/sql/driver"
2017-02-08 06:01:29 +00:00
"fmt"
"io"
2018-07-07 10:17:07 +00:00
"strconv"
2018-07-09 09:39:05 +00:00
"strings"
2017-02-08 06:01:29 +00:00
"testing"
2018-11-06 06:46:46 +00:00
)
2017-02-08 06:01:29 +00:00
2019-01-08 10:20:34 +00:00
import (
"github.com/dhui/dktest"
)
2018-11-06 06:46:46 +00:00
import (
2018-10-10 22:11:48 +00:00
dt "github.com/golang-migrate/migrate/v4/database/testing"
2019-01-08 10:20:34 +00:00
"github.com/golang-migrate/migrate/v4/dktesting"
2017-02-08 06:01:29 +00:00
)
2019-01-08 10:20:34 +00:00
var (
2019-01-12 23:54:17 +00:00
opts = dktest . Options { PortRequired : true , ReadyFunc : isReady }
// Supported versions: https://www.postgresql.org/support/versioning/
2019-01-08 10:20:34 +00:00
specs = [ ] dktesting . ContainerSpec {
{ ImageName : "postgres:9.4" , Options : opts } ,
2019-01-12 23:54:17 +00:00
{ ImageName : "postgres:9.5" , Options : opts } ,
{ ImageName : "postgres:9.6" , Options : opts } ,
{ ImageName : "postgres:10" , Options : opts } ,
{ ImageName : "postgres:11" , Options : opts } ,
2019-01-08 10:20:34 +00:00
}
)
2017-02-08 06:01:29 +00:00
2019-01-08 10:20:34 +00:00
func pgConnectionString ( host , port string ) string {
return fmt . Sprintf ( "postgres://postgres@%s:%s/postgres?sslmode=disable" , host , port )
2018-07-07 10:17:07 +00:00
}
2019-01-09 06:37:15 +00:00
func isReady ( ctx context . Context , c dktest . ContainerInfo ) bool {
2019-01-08 10:20:34 +00:00
ip , port , err := c . FirstPort ( )
if err != nil {
return false
}
db , err := sql . Open ( "postgres" , pgConnectionString ( ip , port ) )
2017-02-08 06:01:29 +00:00
if err != nil {
return false
}
defer db . Close ( )
2019-01-09 06:37:15 +00:00
if err = db . PingContext ( ctx ) ; err != nil {
2018-01-20 07:57:43 +00:00
switch err {
case sqldriver . ErrBadConn , io . EOF :
2017-02-08 06:01:29 +00:00
return false
2018-01-20 07:57:43 +00:00
default :
fmt . Println ( err )
2017-02-08 06:01:29 +00:00
}
2018-01-20 07:57:43 +00:00
return false
2017-02-08 06:01:29 +00:00
}
return true
}
func Test ( t * testing . T ) {
2019-01-08 10:20:34 +00:00
dktesting . ParallelTest ( t , specs , func ( t * testing . T , c dktest . ContainerInfo ) {
ip , port , err := c . FirstPort ( )
if err != nil {
t . Fatal ( err )
}
addr := pgConnectionString ( ip , port )
p := & Postgres { }
d , err := p . Open ( addr )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer d . Close ( )
dt . Test ( t , d , [ ] byte ( "SELECT 1" ) )
} )
2017-02-08 06:01:29 +00:00
}
2017-02-17 06:10:01 +00:00
func TestMultiStatement ( t * testing . T ) {
2019-01-08 10:20:34 +00:00
dktesting . ParallelTest ( t , specs , func ( t * testing . T , c dktest . ContainerInfo ) {
ip , port , err := c . FirstPort ( )
if err != nil {
t . Fatal ( err )
}
addr := pgConnectionString ( ip , port )
p := & Postgres { }
d , err := p . Open ( addr )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer d . Close ( )
if err := d . Run ( strings . NewReader ( "CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);" ) ) ; err != nil {
t . Fatalf ( "expected err to be nil, got %v" , err )
}
// make sure second table exists
var exists bool
if err := d . ( * Postgres ) . conn . QueryRowContext ( context . Background ( ) , "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))" ) . Scan ( & exists ) ; err != nil {
t . Fatal ( err )
}
if ! exists {
t . Fatalf ( "expected table bar to exist" )
}
} )
2017-02-17 06:10:01 +00:00
}
2018-07-07 10:17:07 +00:00
func TestErrorParsing ( t * testing . T ) {
2019-01-08 10:20:34 +00:00
dktesting . ParallelTest ( t , specs , func ( t * testing . T , c dktest . ContainerInfo ) {
ip , port , err := c . FirstPort ( )
if err != nil {
t . Fatal ( err )
}
addr := pgConnectionString ( ip , port )
p := & Postgres { }
d , err := p . Open ( addr )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer d . Close ( )
wantErr := ` migration failed: syntax error at or near "TABLEE" (column 37) in line 1: CREATE TABLE foo ` +
` (foo text); CREATE TABLEE bar (bar text); (details: pq: syntax error at or near "TABLEE") `
if err := d . Run ( strings . NewReader ( "CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);" ) ) ; err == nil {
t . Fatal ( "expected err but got nil" )
} else if err . Error ( ) != wantErr {
t . Fatalf ( "expected '%s' but got '%s'" , wantErr , err . Error ( ) )
}
} )
2018-07-07 10:17:07 +00:00
}
2017-02-18 00:59:47 +00:00
func TestFilterCustomQuery ( t * testing . T ) {
2019-01-08 10:20:34 +00:00
dktesting . ParallelTest ( t , specs , func ( t * testing . T , c dktest . ContainerInfo ) {
ip , port , err := c . FirstPort ( )
if err != nil {
t . Fatal ( err )
}
addr := fmt . Sprintf ( "postgres://postgres@%v:%v/postgres?sslmode=disable&x-custom=foobar" , ip , port )
p := & Postgres { }
d , err := p . Open ( addr )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer d . Close ( )
} )
2017-02-18 00:59:47 +00:00
}
2017-02-08 06:01:29 +00:00
func TestWithSchema ( t * testing . T ) {
2019-01-08 10:20:34 +00:00
dktesting . ParallelTest ( t , specs , func ( t * testing . T , c dktest . ContainerInfo ) {
ip , port , err := c . FirstPort ( )
if err != nil {
t . Fatal ( err )
}
addr := pgConnectionString ( ip , port )
p := & Postgres { }
d , err := p . Open ( addr )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer d . Close ( )
// create foobar schema
if err := d . Run ( strings . NewReader ( "CREATE SCHEMA foobar AUTHORIZATION postgres" ) ) ; err != nil {
t . Fatal ( err )
}
if err := d . SetVersion ( 1 , false ) ; err != nil {
t . Fatal ( err )
}
// re-connect using that schema
d2 , err := p . Open ( fmt . Sprintf ( "postgres://postgres@%v:%v/postgres?sslmode=disable&search_path=foobar" , ip , port ) )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer d2 . Close ( )
version , _ , err := d2 . Version ( )
if err != nil {
t . Fatal ( err )
}
if version != - 1 {
t . Fatal ( "expected NilVersion" )
}
// now update version and compare
if err := d2 . SetVersion ( 2 , false ) ; err != nil {
t . Fatal ( err )
}
version , _ , err = d2 . Version ( )
if err != nil {
t . Fatal ( err )
}
if version != 2 {
t . Fatal ( "expected version 2" )
}
// meanwhile, the public schema still has the other version
version , _ , err = d . Version ( )
if err != nil {
t . Fatal ( err )
}
if version != 1 {
t . Fatal ( "expected version 2" )
}
} )
2017-02-08 06:01:29 +00:00
}
2018-11-05 08:27:28 +00:00
func TestParallelSchema ( t * testing . T ) {
2019-01-08 10:20:34 +00:00
dktesting . ParallelTest ( t , specs , func ( t * testing . T , c dktest . ContainerInfo ) {
ip , port , err := c . FirstPort ( )
if err != nil {
t . Fatal ( err )
}
addr := pgConnectionString ( ip , port )
p := & Postgres { }
d , err := p . Open ( addr )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer d . Close ( )
// create foo and bar schemas
if err := d . Run ( strings . NewReader ( "CREATE SCHEMA foo AUTHORIZATION postgres" ) ) ; err != nil {
t . Fatal ( err )
}
if err := d . Run ( strings . NewReader ( "CREATE SCHEMA bar AUTHORIZATION postgres" ) ) ; err != nil {
t . Fatal ( err )
}
// re-connect using that schemas
dfoo , err := p . Open ( fmt . Sprintf ( "postgres://postgres@%v:%v/postgres?sslmode=disable&search_path=foo" , ip , port ) )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer dfoo . Close ( )
dbar , err := p . Open ( fmt . Sprintf ( "postgres://postgres@%v:%v/postgres?sslmode=disable&search_path=bar" , ip , port ) )
if err != nil {
t . Fatalf ( "%v" , err )
}
defer dbar . Close ( )
if err := dfoo . Lock ( ) ; err != nil {
t . Fatal ( err )
}
if err := dbar . Lock ( ) ; err != nil {
t . Fatal ( err )
}
if err := dbar . Unlock ( ) ; err != nil {
t . Fatal ( err )
}
if err := dfoo . Unlock ( ) ; err != nil {
t . Fatal ( err )
}
} )
2018-11-05 08:27:28 +00:00
}
2017-02-08 06:01:29 +00:00
func TestWithInstance ( t * testing . T ) {
}
2017-10-27 02:56:10 +00:00
func TestPostgres_Lock ( t * testing . T ) {
2019-01-08 10:20:34 +00:00
dktesting . ParallelTest ( t , specs , func ( t * testing . T , c dktest . ContainerInfo ) {
ip , port , err := c . FirstPort ( )
if err != nil {
t . Fatal ( err )
}
addr := pgConnectionString ( ip , port )
p := & Postgres { }
d , err := p . Open ( addr )
if err != nil {
t . Fatalf ( "%v" , err )
}
dt . Test ( t , d , [ ] byte ( "SELECT 1" ) )
ps := d . ( * Postgres )
err = ps . Lock ( )
if err != nil {
t . Fatal ( err )
}
err = ps . Unlock ( )
if err != nil {
t . Fatal ( err )
}
err = ps . Lock ( )
if err != nil {
t . Fatal ( err )
}
err = ps . Unlock ( )
if err != nil {
t . Fatal ( err )
}
} )
2018-02-20 23:32:34 +00:00
}
2018-07-07 10:17:07 +00:00
2019-01-11 21:05:54 +00:00
func TestWithInstance_Concurrent ( t * testing . T ) {
dktesting . ParallelTest ( t , specs , func ( t * testing . T , c dktest . ContainerInfo ) {
ip , port , err := c . FirstPort ( )
if err != nil {
t . Fatal ( err )
}
db , err := sql . Open ( "postgres" , pgConnectionString ( ip , port ) )
if err != nil {
t . Fatal ( err )
}
defer db . Close ( )
const concurrency = 30
ch := make ( chan error , concurrency )
for i := 0 ; i < concurrency ; i ++ {
go func ( ) {
_ , err := WithInstance ( db , nil )
ch <- err
} ( )
}
for i := 0 ; i < cap ( ch ) ; i ++ {
if err := <- ch ; err != nil {
t . Error ( err )
}
}
} )
}
2018-07-07 10:17:07 +00:00
func Test_computeLineFromPos ( t * testing . T ) {
testcases := [ ] struct {
2018-07-09 09:39:05 +00:00
pos int
2018-07-07 10:17:07 +00:00
wantLine uint
wantCol uint
input string
wantOk bool
} {
{
2018-07-09 09:39:05 +00:00
15 , 2 , 6 , "SELECT *\nFROM foo" , true , // foo table does not exists
2018-07-07 10:17:07 +00:00
} ,
{
2018-07-09 09:39:05 +00:00
16 , 3 , 6 , "SELECT *\n\nFROM foo" , true , // foo table does not exists, empty line
2018-07-07 10:17:07 +00:00
} ,
{
2018-07-09 09:39:05 +00:00
25 , 3 , 7 , "SELECT *\nFROM foo\nWHERE x" , true , // x column error
2018-07-07 10:17:07 +00:00
} ,
{
2018-07-09 09:39:05 +00:00
27 , 5 , 7 , "SELECT *\n\nFROM foo\n\nWHERE x" , true , // x column error, empty lines
2018-07-07 10:17:07 +00:00
} ,
{
2018-07-09 09:39:05 +00:00
10 , 2 , 1 , "SELECT *\nFROMM foo" , true , // FROMM typo
2018-07-07 10:17:07 +00:00
} ,
{
2018-07-09 09:39:05 +00:00
11 , 3 , 1 , "SELECT *\n\nFROMM foo" , true , // FROMM typo, empty line
2018-07-07 10:17:07 +00:00
} ,
{
2018-07-09 09:39:05 +00:00
17 , 2 , 8 , "SELECT *\nFROM foo" , true , // last character
2018-07-07 10:17:07 +00:00
} ,
{
2018-07-09 09:39:05 +00:00
18 , 0 , 0 , "SELECT *\nFROM foo" , false , // invalid position
2018-07-07 10:17:07 +00:00
} ,
}
for i , tc := range testcases {
t . Run ( "tc" + strconv . Itoa ( i ) , func ( t * testing . T ) {
2018-07-09 09:39:05 +00:00
run := func ( crlf bool , nonASCII bool ) {
var name string
if crlf {
name = "crlf"
} else {
name = "lf"
}
if nonASCII {
name += "-nonascii"
} else {
name += "-ascii"
}
t . Run ( name , func ( t * testing . T ) {
input := tc . input
if crlf {
input = strings . Replace ( input , "\n" , "\r\n" , - 1 )
}
if nonASCII {
input = strings . Replace ( input , "FROM" , "FRÖM" , - 1 )
}
gotLine , gotCol , gotOK := computeLineFromPos ( input , tc . pos )
if tc . wantOk {
t . Logf ( "pos %d, want %d:%d, %#v" , tc . pos , tc . wantLine , tc . wantCol , input )
}
if gotOK != tc . wantOk {
t . Fatalf ( "expected ok %v but got %v" , tc . wantOk , gotOK )
}
if gotLine != tc . wantLine {
t . Fatalf ( "expected line %d but got %d" , tc . wantLine , gotLine )
}
if gotCol != tc . wantCol {
t . Fatalf ( "expected col %d but got %d" , tc . wantCol , gotCol )
}
} )
2018-07-07 10:17:07 +00:00
}
2018-07-09 09:39:05 +00:00
run ( false , false )
run ( true , false )
run ( false , true )
run ( true , true )
2018-07-07 10:17:07 +00:00
} )
}
}