2023-06-18 09:05:09 +03:00
package libsq_test
import (
"testing"
2023-11-20 04:06:36 +03:00
_ "github.com/mattn/go-sqlite3"
2023-11-19 03:05:48 +03:00
2023-11-20 04:06:36 +03:00
"github.com/neilotoole/sq/libsq"
2023-11-21 00:42:38 +03:00
"github.com/neilotoole/sq/libsq/source/drivertype"
2023-11-20 04:06:36 +03:00
"github.com/neilotoole/sq/testh"
2023-06-18 09:05:09 +03:00
)
2023-11-19 03:05:48 +03:00
const infoSchema = "information_schema"
// TestQuery_func tests miscellaneous functions that aren't
// tested elsewhere.
//
2023-06-18 09:05:09 +03:00
//nolint:exhaustive
func TestQuery_func ( t * testing . T ) {
testCases := [ ] queryTestCase {
{
name : "max" ,
in : ` @sakila | .actor | max(.actor_id) ` ,
wantSQL : ` SELECT max("actor_id") AS "max(.actor_id)" FROM "actor" ` ,
2024-01-25 09:29:55 +03:00
override : driverMap { drivertype . MySQL : "SELECT max(`actor_id`) AS `max(.actor_id)` FROM `actor`" } ,
2023-06-18 09:05:09 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "max(.actor_id)" ) ,
assertSinkColValue ( 0 , int64 ( 200 ) ) ,
} ,
} ,
2023-11-19 03:05:48 +03:00
{
name : "min" ,
in : ` @sakila | .actor | min(.actor_id) ` ,
wantSQL : ` SELECT min("actor_id") AS "min(.actor_id)" FROM "actor" ` ,
2024-01-25 09:29:55 +03:00
override : driverMap { drivertype . MySQL : "SELECT min(`actor_id`) AS `min(.actor_id)` FROM `actor`" } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "min(.actor_id)" ) ,
assertSinkColValue ( 0 , int64 ( 1 ) ) ,
} ,
} ,
{
name : "avg" ,
in : ` @sakila | .actor | avg(.actor_id) ` ,
wantSQL : ` SELECT avg("actor_id") AS "avg(.actor_id)" FROM "actor" ` ,
2024-01-25 09:29:55 +03:00
override : driverMap { drivertype . MySQL : "SELECT avg(`actor_id`) AS `avg(.actor_id)` FROM `actor`" } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "avg(.actor_id)" ) ,
// FIXME: The driver impls handle avg() differently. Some return
// float64, some int, some decimal (string). The SLQ impl of avg()
// needs to be modified to returned a consistent type.
// assertSinkColValue(0, float64(100.5)),
//
// See also:
// - https://github.com/golang/go/issues/30870
// - https://github.com/golang-sql/decomposer
} ,
} ,
}
for _ , tc := range testCases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
execQueryTestCase ( t , tc )
} )
}
}
func TestQuery_func_schema ( t * testing . T ) {
testCases := [ ] queryTestCase {
{
name : "sqlserver-default" ,
in : ` @sakila | schema() ` ,
wantSQL : ` SELECT SCHEMA_NAME() AS "schema()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . MSSQL } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "schema()" ) ,
assertSinkColValue ( 0 , "dbo" ) ,
} ,
} ,
{
name : "sqlserver-alt-no-change" ,
// SQL Server doesn't support changing the schema on a per-connection
// basis. So we expect the default schema to be returned.
in : ` @sakila | schema() ` ,
2024-03-06 06:19:19 +03:00
beforeRun : func ( _ queryTestCase , _ * testh . Helper , qc * libsq . QueryContext ) {
2023-11-19 03:05:48 +03:00
qc . Collection . Active ( ) . Schema = infoSchema
} ,
wantSQL : ` SELECT SCHEMA_NAME() AS "schema()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . MSSQL } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "schema()" ) ,
assertSinkColValue ( 0 , "dbo" ) ,
} ,
} ,
{
name : "postgres-default" ,
in : ` @sakila | schema() ` ,
wantSQL : ` SELECT current_schema() AS "schema()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . Pg } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "schema()" ) ,
assertSinkColValue ( 0 , "public" ) ,
} ,
} ,
{
name : "postgres-alt" ,
in : ` @sakila | schema() ` ,
2024-03-06 06:19:19 +03:00
beforeRun : func ( _ queryTestCase , _ * testh . Helper , qc * libsq . QueryContext ) {
2023-11-19 03:05:48 +03:00
qc . Collection . Active ( ) . Schema = infoSchema
} ,
wantSQL : ` SELECT current_schema() AS "schema()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . Pg } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "schema()" ) ,
assertSinkColValue ( 0 , infoSchema ) ,
} ,
} ,
{
name : "mysql-default" ,
in : ` @sakila | schema() ` ,
wantSQL : "SELECT DATABASE() AS `schema()`" ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . MySQL } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "schema()" ) ,
assertSinkColValue ( 0 , "sakila" ) ,
} ,
} ,
{
name : "mysql-alt" ,
in : ` @sakila | schema() ` ,
2024-03-06 06:19:19 +03:00
beforeRun : func ( _ queryTestCase , _ * testh . Helper , qc * libsq . QueryContext ) {
2023-11-19 03:05:48 +03:00
qc . Collection . Active ( ) . Schema = infoSchema
} ,
wantSQL : "SELECT DATABASE() AS `schema()`" ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . MySQL } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "schema()" ) ,
assertSinkColValue ( 0 , infoSchema ) ,
} ,
} ,
{
name : "sqlite-default" ,
in : ` @sakila | schema() ` ,
wantSQL : ` SELECT (SELECT name FROM pragma_database_list ORDER BY seq limit 1) AS "schema()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . SQLite } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "schema()" ) ,
assertSinkColValue ( 0 , "main" ) ,
} ,
} ,
}
for _ , tc := range testCases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
execQueryTestCase ( t , tc )
} )
}
}
func TestQuery_func_catalog ( t * testing . T ) {
testCases := [ ] queryTestCase {
{
name : "sqlserver-default" ,
in : ` @sakila | catalog() ` ,
wantSQL : ` SELECT DB_NAME() AS "catalog()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . MSSQL } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "catalog()" ) ,
assertSinkColValue ( 0 , "sakila" ) ,
} ,
} ,
{
name : "sqlserver-alt" ,
in : ` @sakila | catalog() ` ,
2024-03-06 06:19:19 +03:00
beforeRun : func ( _ queryTestCase , _ * testh . Helper , qc * libsq . QueryContext ) {
2023-11-19 03:05:48 +03:00
qc . Collection . Active ( ) . Catalog = "model"
} ,
wantSQL : ` SELECT DB_NAME() AS "catalog()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . MSSQL } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "catalog()" ) ,
assertSinkColValue ( 0 , "model" ) ,
} ,
} ,
{
name : "postgres-default" ,
in : ` @sakila | catalog() ` ,
wantSQL : ` SELECT current_database() AS "catalog()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . Pg } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "catalog()" ) ,
assertSinkColValue ( 0 , "sakila" ) ,
} ,
} ,
{
name : "postgres-alt" ,
in : ` @sakila | catalog() ` ,
2024-03-06 06:19:19 +03:00
beforeRun : func ( _ queryTestCase , _ * testh . Helper , qc * libsq . QueryContext ) {
2023-11-19 03:05:48 +03:00
qc . Collection . Active ( ) . Catalog = "postgres"
} ,
wantSQL : ` SELECT current_database() AS "catalog()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . Pg } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "catalog()" ) ,
assertSinkColValue ( 0 , "postgres" ) ,
} ,
} ,
{
name : "mysql" ,
in : ` @sakila | catalog() ` ,
wantSQL : "SELECT (SELECT CATALOG_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = DATABASE() LIMIT 1) AS `catalog()`" , //nolint:lll
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . MySQL } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "catalog()" ) ,
assertSinkColValue ( 0 , "def" ) ,
} ,
} ,
{
name : "sqlite" ,
in : ` @sakila | catalog() ` ,
// SQLite doesn't support catalogs, so we (somewhat arbitrarily)
// return the string "default". This behavior may change
// upon feedback.
wantSQL : ` SELECT (SELECT 'default') AS "catalog()" ` ,
2024-01-25 09:29:55 +03:00
onlyFor : [ ] drivertype . Type { drivertype . SQLite } ,
2023-11-19 03:05:48 +03:00
wantRecCount : 1 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "catalog()" ) ,
assertSinkColValue ( 0 , "default" ) ,
} ,
} ,
2023-06-18 09:05:09 +03:00
}
for _ , tc := range testCases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
execQueryTestCase ( t , tc )
} )
}
}
2023-11-20 09:44:36 +03:00
//nolint:lll,exhaustive
func TestQuery_func_rownum ( t * testing . T ) {
testCases := [ ] queryTestCase {
{
name : "plain" ,
in : ` @sakila | .actor | rownum() ` ,
wantSQL : ` SELECT (row_number() OVER (ORDER BY 1)) AS "rownum()" FROM "actor" ` ,
override : driverMap {
2024-01-25 09:29:55 +03:00
drivertype . MSSQL : ` SELECT (row_number() OVER (ORDER BY (SELECT NULL))) AS "rownum()" FROM "actor" ` ,
2023-11-20 09:44:36 +03:00
// We don't test the MySQL override because it uses a randomly generated variable value. E.g.
// SELECT (@row_number_dw5ch2ss:=@row_number_dw5ch2ss + 1) AS `rownum()` FROM `actor`
2024-01-25 09:29:55 +03:00
drivertype . MySQL : ` ` ,
2023-11-20 09:44:36 +03:00
} ,
wantRecCount : 200 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "rownum()" ) ,
assertSinkCellValue ( 0 , 0 , int64 ( 1 ) ) ,
assertSinkCellValue ( 199 , 0 , int64 ( 200 ) ) ,
} ,
} ,
{
name : "plus_1" ,
in : ` @sakila | .actor | rownum() + 1 ` ,
wantSQL : ` SELECT (row_number() OVER (ORDER BY 1))+1 AS "rownum()+1" FROM "actor" ` ,
override : driverMap {
2024-01-25 09:29:55 +03:00
drivertype . MSSQL : ` SELECT (row_number() OVER (ORDER BY (SELECT NULL)))+1 AS "rownum()+1" FROM "actor" ` ,
drivertype . MySQL : "" ,
2023-11-20 09:44:36 +03:00
} ,
wantRecCount : 200 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "rownum()+1" ) ,
assertSinkCellValue ( 0 , 0 , int64 ( 2 ) ) ,
assertSinkCellValue ( 199 , 0 , int64 ( 201 ) ) ,
} ,
} ,
{
name : "minus_1_alias" ,
in : ` @sakila | .actor | (rownum()-1):zero_index ` ,
wantSQL : ` SELECT ((row_number() OVER (ORDER BY 1))-1) AS "zero_index" FROM "actor" ` ,
override : driverMap {
2024-01-25 09:29:55 +03:00
drivertype . MSSQL : ` SELECT ((row_number() OVER (ORDER BY (SELECT NULL)))-1) AS "zero_index" FROM "actor" ` ,
drivertype . MySQL : "" ,
2023-11-20 09:44:36 +03:00
} ,
wantRecCount : 200 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "zero_index" ) ,
assertSinkCellValue ( 0 , 0 , int64 ( 0 ) ) ,
assertSinkCellValue ( 199 , 0 , int64 ( 199 ) ) ,
} ,
} ,
{
name : "column_orderby" ,
in : ` @sakila | .actor | rownum(), .actor_id | order_by(.actor_id) ` ,
wantSQL : ` SELECT (row_number() OVER (ORDER BY "actor_id")) AS "rownum()", "actor_id" FROM "actor" ORDER BY "actor_id" ` ,
2024-01-25 09:29:55 +03:00
override : driverMap { drivertype . MySQL : "" } ,
2023-11-20 09:44:36 +03:00
wantRecCount : 200 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "rownum()" ) ,
assertSinkCellValue ( 0 , 0 , int64 ( 1 ) ) ,
assertSinkCellValue ( 199 , 0 , int64 ( 200 ) ) ,
} ,
} ,
{
name : "double_invocation" ,
in : ` @sakila | .actor | rownum():index1, .actor_id, rownum():index2 | order_by(.actor_id) ` ,
wantSQL : ` SELECT (row_number() OVER (ORDER BY "actor_id")) AS "index1", "actor_id", (row_number() OVER (ORDER BY "actor_id")) AS "index2" FROM "actor" ORDER BY "actor_id" ` ,
2024-01-25 09:29:55 +03:00
override : driverMap { drivertype . MySQL : "" } ,
2023-11-20 09:44:36 +03:00
wantRecCount : 200 ,
sinkFns : [ ] SinkTestFunc {
assertSinkColName ( 0 , "index1" ) ,
assertSinkColName ( 2 , "index2" ) ,
assertSinkCellValue ( 0 , 0 , int64 ( 1 ) ) ,
assertSinkCellValue ( 0 , 2 , int64 ( 1 ) ) ,
assertSinkCellValue ( 199 , 0 , int64 ( 200 ) ) ,
assertSinkCellValue ( 199 , 2 , int64 ( 200 ) ) ,
} ,
} ,
}
for _ , tc := range testCases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
execQueryTestCase ( t , tc )
} )
}
}