sq/libsq/driver/driver_test.go

393 lines
10 KiB
Go
Raw Normal View History

2020-08-06 20:58:47 +03:00
package driver_test
import (
"testing"
"github.com/neilotoole/sq/drivers/csv"
"github.com/neilotoole/sq/drivers/mysql"
"github.com/neilotoole/sq/drivers/postgres"
"github.com/neilotoole/sq/drivers/sqlite3"
"github.com/neilotoole/sq/drivers/sqlserver"
"github.com/neilotoole/sq/drivers/xlsx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/neilotoole/sq/libsq/driver"
"github.com/neilotoole/sq/libsq/source"
"github.com/neilotoole/sq/libsq/sqlmodel"
"github.com/neilotoole/sq/libsq/stringz"
"github.com/neilotoole/sq/testh"
"github.com/neilotoole/sq/testh/fixt"
"github.com/neilotoole/sq/testh/sakila"
)
func TestDriver_DropTable(t *testing.T) {
for _, handle := range sakila.SQLAll {
handle := handle
t.Run(handle, func(t *testing.T) {
th := testh.New(t)
src := th.Source(handle)
targetTable := stringz.UniqSuffix(sakila.TblActor)
drvr, err := th.Registry().DriverFor(src.Type)
require.NoError(t, err)
sqlDrvr := drvr.(driver.SQLDriver)
db := th.Open(src).DB()
// Copy a table that we can play with
th.CopyTable(false, src, sakila.TblActor, targetTable, false)
require.NoError(t, sqlDrvr.DropTable(th.Context, db, targetTable, true))
// Copy the table again so we can drop it again
th.CopyTable(false, src, sakila.TblActor, targetTable, false)
// test with ifExists = false
require.NoError(t, sqlDrvr.DropTable(th.Context, db, targetTable, false))
// Check that we get the expected behavior when the table doesn't exist
require.NoError(t, sqlDrvr.DropTable(th.Context, db, stringz.UniqSuffix("not_a_table"), true), "should be no error when ifExists is true")
require.Error(t, sqlDrvr.DropTable(th.Context, db, stringz.UniqSuffix("not_a_table"), false), "error expected when ifExists is false")
})
}
}
func TestDriver_CopyTable(t *testing.T) {
for _, handle := range sakila.SQLAll {
handle := handle
t.Run(handle, func(t *testing.T) {
// t.Parallel()
th := testh.New(t)
src := th.Source(handle)
drvr, err := th.Registry().DriverFor(src.Type)
require.NoError(t, err)
sqlDrvr := drvr.(driver.SQLDriver)
db := th.Open(src).DB()
require.EqualValues(t, sakila.TblActorCount, th.RowCount(src, sakila.TblActor), "fromTable should have ActorCount rows beforehand")
toTable := stringz.UniqSuffix(sakila.TblActor)
// First, test with copyData = true
copied, err := sqlDrvr.CopyTable(th.Context, db, sakila.TblActor, toTable, true)
require.NoError(t, err)
require.Equal(t, int64(sakila.TblActorCount), copied)
require.EqualValues(t, sakila.TblActorCount, th.RowCount(src, toTable))
th.DropTable(src, toTable)
// Then, with copyData = false
copied, err = sqlDrvr.CopyTable(th.Context, db, sakila.TblActor, toTable, false)
require.NoError(t, err)
require.Equal(t, int64(0), copied)
require.EqualValues(t, 0, th.RowCount(src, toTable))
th.DropTable(src, toTable)
})
}
}
// TestDriver_CreateTable_Minimal tests the minimal functionality
// of SQLDriver.CreateTable impls.
func TestDriver_CreateTable_Minimal(t *testing.T) {
t.Parallel()
testCases := sakila.SQLAll
for _, handle := range testCases {
handle := handle
t.Run(handle, func(t *testing.T) {
t.Parallel()
th, src, dbase, drvr := testh.NewWith(t, handle)
tblName := stringz.UniqTableName(t.Name())
colNames, colKinds := fixt.ColNamePerKind(drvr.Dialect().IntBool, false, false)
tblDef := sqlmodel.NewTableDef(tblName, colNames, colKinds)
err := drvr.CreateTable(th.Context, dbase.DB(), tblDef)
require.NoError(t, err)
t.Cleanup(func() { th.DropTable(src, tblName) })
colTypes, err := drvr.TableColumnTypes(th.Context, dbase.DB(), tblName, colNames)
require.NoError(t, err)
require.Equal(t, len(colNames), len(colTypes))
recMeta, _, err := drvr.RecordMeta(colTypes)
require.NoError(t, err)
require.Equal(t, colNames, recMeta.Names())
require.Equal(t, colKinds, recMeta.Kinds())
})
}
}
func TestDriver_TableColumnTypes(t *testing.T) {
t.Parallel()
testCases := sakila.SQLAll
for _, handle := range testCases {
handle := handle
t.Run(handle, func(t *testing.T) {
testh.SkipShort(t, handle == sakila.XLSX)
t.Parallel()
th := testh.New(t)
src := th.Source(handle)
dbase := th.Open(src)
drvr, db := dbase.SQLDriver(), dbase.DB()
// Run the test both with and without data in the target table.
// Some driver implementations of rows.ColumnTypes behave
// differently depending upon whether the query returns rows
// or not.
for _, copyData := range []bool{false, true} {
actualTblName := th.CopyTable(false, src, sakila.TblActor, "", copyData)
t.Cleanup(func() { th.DropTable(src, actualTblName) })
// Note nil colNames, should still get all columns
// as if the query was (SELECT * FROM actualTblName)
colTypes, err := drvr.TableColumnTypes(th.Context, db, actualTblName, nil)
require.NoError(t, err)
require.Equal(t, len(sakila.TblActorCols), len(colTypes))
for i := range colTypes {
require.Equal(t, sakila.TblActorCols[i], colTypes[i].Name())
}
// Try again, but requesting specific col names
wantColNames := []string{sakila.TblActorCols[0], sakila.TblActorCols[2]}
colTypes, err = drvr.TableColumnTypes(th.Context, db, actualTblName, wantColNames)
require.NoError(t, err)
require.Equal(t, len(wantColNames), len(colTypes))
for i := range colTypes {
require.Equal(t, wantColNames[i], colTypes[i].Name())
}
}
})
}
}
func TestSQLDriver_PrepareUpdateStmt(t *testing.T) {
t.Parallel()
testCases := sakila.SQLAll
for _, handle := range testCases {
handle := handle
t.Run(handle, func(t *testing.T) {
testh.SkipShort(t, handle == sakila.XLSX)
t.Parallel()
th, src, dbase, drvr := testh.NewWith(t, handle)
actualTblName := th.CopyTable(false, src, sakila.TblActor, "", true)
t.Cleanup(func() { th.DropTable(src, actualTblName) })
const (
actorID int64 = 1
whereClause string = "actor_id = ?"
)
var (
destCols = []string{"first_name", "last_name"}
wantVals = []interface{}{"Kubla", "Khan"}
args = append(wantVals, actorID)
)
stmtExecer, err := drvr.PrepareUpdateStmt(th.Context, dbase.DB(), actualTblName, destCols, whereClause)
require.NoError(t, err)
require.Equal(t, destCols, stmtExecer.DestMeta().Names())
require.NoError(t, stmtExecer.Munge(wantVals))
affected, err := stmtExecer.Exec(th.Context, args...)
require.NoError(t, err)
assert.Equal(t, int64(1), affected)
sink, err := th.QuerySQL(src, "SELECT * FROM "+actualTblName+" WHERE actor_id = 1")
require.NoError(t, err)
require.Equal(t, 1, len(sink.Recs))
require.Equal(t, actorID, testh.Val(sink.Recs[0][0]))
require.Equal(t, wantVals[0], testh.Val(sink.Recs[0][1]))
require.Equal(t, wantVals[1], testh.Val(sink.Recs[0][2]))
})
}
}
func TestDriver_Ping(t *testing.T) {
t.Parallel()
testCases := append(sakila.All, sakila.CSVActor, sakila.CSVActorHTTP)
for _, handle := range testCases {
handle := handle
t.Run(handle, func(t *testing.T) {
testh.SkipShort(t, handle == sakila.XLSX)
t.Parallel()
th := testh.New(t)
src := th.Source(handle)
drvr := th.DriverFor(src)
err := drvr.Ping(th.Context, src)
require.NoError(t, err)
})
}
}
func TestDriver_Open(t *testing.T) {
t.Parallel()
testCases := append(sakila.All, sakila.CSVActor, sakila.CSVActorHTTP)
for _, handle := range testCases {
handle := handle
t.Run(handle, func(t *testing.T) {
testh.SkipShort(t, handle == sakila.XLSX)
t.Parallel()
th := testh.New(t)
src := th.Source(handle)
drvr := th.DriverFor(src)
dbase, err := drvr.Open(th.Context, src)
require.NoError(t, err)
require.NoError(t, dbase.DB().PingContext(th.Context))
require.NoError(t, dbase.Close())
})
}
}
// coreDrivers is a slice of the core driver types.
var coreDrivers = []source.Type{
postgres.Type,
sqlserver.Type,
sqlite3.Type,
mysql.Type,
csv.TypeCSV,
csv.TypeTSV,
xlsx.Type,
}
// sqlDrivers is a slice of the SQL driver types.
var sqlDrivers = []source.Type{
postgres.Type,
sqlserver.Type,
sqlite3.Type,
mysql.Type,
}
// docDrivers is a slice of the doc driver types.
var docDrivers = []source.Type{
csv.TypeCSV,
csv.TypeTSV,
xlsx.Type,
}
func TestRegistry_DriversMetadata_All(t *testing.T) {
t.Parallel()
reg := testh.New(t).Registry()
metas := reg.DriversMetadata()
require.Equal(t, len(metas), len(reg.Drivers()))
m := map[source.Type]driver.Metadata{}
for i := range metas {
m[metas[i].Type] = metas[i]
}
for _, typ := range coreDrivers {
md, ok := m[typ]
require.True(t, ok)
require.Equal(t, typ, md.Type)
}
}
func TestRegistry_DriversMetadata_SQL(t *testing.T) {
t.Parallel()
for _, typ := range sqlDrivers {
typ := typ
t.Run(typ.String(), func(t *testing.T) {
t.Parallel()
th := testh.New(t)
reg := th.Registry()
drvr, err := reg.DriverFor(typ)
require.NoError(t, err)
meta := drvr.DriverMetadata()
require.Equal(t, typ, meta.Type)
require.True(t, meta.IsSQL)
require.False(t, meta.Monotable, "can't be monotable and SQL")
sqlDrvr, ok := drvr.(driver.SQLDriver)
require.True(t, ok)
dialect := sqlDrvr.Dialect()
require.Equal(t, typ, dialect.Type)
require.NotEmpty(t, dialect.Quote)
require.NotNil(t, dialect.Placeholders)
})
}
}
func TestRegistry_DriversMetadata_Doc(t *testing.T) {
t.Parallel()
for _, typ := range docDrivers {
typ := typ
t.Run(typ.String(), func(t *testing.T) {
t.Parallel()
th := testh.New(t)
reg := th.Registry()
drvr, err := reg.DriverFor(typ)
require.NoError(t, err)
_, ok := drvr.(driver.SQLDriver)
require.False(t, ok)
meta := drvr.DriverMetadata()
require.Equal(t, typ, meta.Type)
require.False(t, meta.IsSQL)
})
}
}
func TestDatabase_TableMetadata(t *testing.T) {
t.Parallel()
for _, handle := range sakila.SQLAll {
handle := handle
t.Run(handle, func(t *testing.T) {
t.Parallel()
th, _, dbase, _ := testh.NewWith(t, handle)
tblMeta, err := dbase.TableMetadata(th.Context, sakila.TblActor)
require.NoError(t, err)
require.Equal(t, sakila.TblActor, tblMeta.Name)
})
}
}
func TestDatabase_SourceMetadata(t *testing.T) {
t.Parallel()
for _, handle := range []string{sakila.Pg9} {
handle := handle
t.Run(handle, func(t *testing.T) {
t.Parallel()
th, _, dbase, _ := testh.NewWith(t, handle)
md, err := dbase.SourceMetadata(th.Context)
require.NoError(t, err)
require.Equal(t, "sakila", md.Name)
})
}
}