{-

Quick tests for error messages, all the tests use the entire formatted
output of parse failures to compare, it's slightly fragile. Most of
the tests use a huge golden file which contains tons of parse error
examples.

-}


{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Language.SQL.SimpleSQL.ErrorMessages
    (errorMessageTests
    ) where

import Language.SQL.SimpleSQL.TestTypes
import Language.SQL.SimpleSQL.Parse
import qualified Language.SQL.SimpleSQL.Lex as L
import Language.SQL.SimpleSQL.TestRunners
--import Language.SQL.SimpleSQL.Syntax
import Language.SQL.SimpleSQL.Expectations
import Test.Hspec (it)
import Debug.Trace

import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Test.Hspec.Golden
    (Golden(..)
    )

import qualified Text.RawString.QQ as R
import System.FilePath ((</>))
import Text.Show.Pretty (ppShow)

errorMessageTests :: TestItem
errorMessageTests = Group "error messages"
    [gp (parseQueryExpr ansi2011 "" Nothing) prettyError [R.r|

select
a
from t
where
  something
order by 1,2,3 where

        |]
        [R.r|8:16:
  |
8 | order by 1,2,3 where
  |                ^^^^^
unexpected where
|]
   ,gp (L.lexSQL ansi2011 False "" Nothing)  L.prettyError [R.r|
           
select
a
from t
where
  something
order by 1,2,3 $@

        |]
        [R.r|8:16:
  |
8 | order by 1,2,3 $@
  |                ^
unexpected '$'
|]
   ,let fn = "expected-parse-errors"
        got = generateParseResults parseErrorData
    in GoldenErrorTest fn parseErrorData $ it "parse error regressions" $ myGolden (T.unpack fn) got
        ]
  where
    gp :: (Show a, HasCallStack) => (Text -> Either e a) -> (e -> Text) -> Text -> Text -> TestItem
    gp parse pret src err =
        GeneralParseFailTest src err $
           it (T.unpack src) $
           let f1 = parse src
               ex = shouldFailWith pret
               quickTrace =
                   case f1 of
                       Left f | pret f /= err ->
                            trace (T.unpack ("check\n[" <> pret f <>"]\n["<> err <> "]\n"))
                       _ -> id
           in quickTrace (f1 `ex` err)

------------------------------------------------------------------------------

-- golden parse error tests
    
myGolden :: String -> Text -> Golden Text
myGolden name actualOutput =
  Golden {
    output = actualOutput,
    encodePretty = show,
    writeToFile = T.writeFile,
    readFromFile = T.readFile,
    goldenFile = name </> "golden",
    actualFile = Just (name </> "actual"),
    failFirstTime = False
  }

parseErrorData :: [(Text,Text,Text)]
parseErrorData =
    concat
    [simpleExpressions1
    ,pgExprs
    ,sqlServerIden
    ,mysqliden
    ,paramvariations
    ,odbcexpr
    ,odbcqexpr
    ,queryExprExamples
    ,statementParseErrorExamples]

generateParseResults :: [(Text,Text,Text)] -> Text
generateParseResults dat =
    let testLine (parser,dialect,src) =
            let d = case dialect of
                    "ansi2011" -> ansi2011
                    "postgres" -> postgres
                    "sqlserver" -> sqlserver
                    "mysql" -> mysql
                    "params" -> ansi2011{diAtIdentifier=True, diHashIdentifier= True}
                    "odbc" -> ansi2011{diOdbc=True}
                    _ -> error $ "unknown dialect: " <> T.unpack dialect
                res = case parser of
                        "queryExpr" ->
                            either prettyError (T.pack . ppShow)
                            $ parseQueryExpr d "" Nothing src
                        "scalarExpr" ->
                            either prettyError (T.pack . ppShow)
                            $ parseScalarExpr d "" Nothing src
                        "statement" ->
                            either prettyError (T.pack . ppShow)
                            $ parseStatement d "" Nothing src
                        _ -> error $ "unknown parser: " <> T.unpack parser
                -- prepend a newline to multi line fields, so they show
                -- nice in a diff in meld or similar
                resadj = if '\n' `T.elem` res
                         then T.cons '\n' res
                         else res
            in T.unlines [parser, dialect, src, resadj]
    in T.unlines $ map testLine dat

parseExampleStrings :: Text -> [Text]
parseExampleStrings = filter (not . T.null) . map T.strip . T.splitOn ";"

simpleExpressions1 :: [(Text,Text,Text)]
simpleExpressions1 =
    concat $ flip map (parseExampleStrings simpleExprData) $ \e ->
    [("scalarExpr", "ansi2011", e)
    ,("queryExpr", "ansi2011", "select " <> e)
    ,("queryExpr", "ansi2011", "select " <> e <> ",")
    ,("queryExpr", "ansi2011", "select " <> e <> " from")]
  where
    simpleExprData = [R.r|
'test
;
'test''t
;
'test''
;
3.23e-
;
.
;
3.23e
;
a.3
;
3.a
;
3.2a
;
4iden
;
4iden.
;
iden.4iden
;
4iden.*
;
from
;
from.a
;
a.from
;
not
;
4 +
;
4 + from
;
(5
;
(5 +
;
(5 + 6
;
(5 + from)
;
case
;
case a
;
case a when b c end
;
case a when b then c
;
case a else d end
;
case a from c end
;
case a when from then to end
;
/* blah
;
/* blah /* stuff */
;
/* *
;
/* /
;
$$something$
;
$$something
;
$$something
x
;
$a$something$b$
;
$a$
;
'''
;
'''''
;
"a
;
"a""
;
"""
;
"""""
;
""
;
*/
;
:3
;
@3
;
#3
;
:::
;
|||
;
...
;
"
;
]
;
)
;
[test
;
[]
;
[[test]]
;
`open
;
```
;
``
;
}
;
mytype(4 '4';
;
app(3
;
app(
;
app(something
;
app(something,
;
count(*
;
count(* filter (where something > 5)
;
count(*) filter (where something > 5
;
count(*) filter (
;
sum(a over (order by b)
;
sum(a) over (order by b
;
sum(a) over (
;
rank(a,c within group (order by b)
;
rank(a,c) within group (order by b
;
rank(a,c) within group (
;
array[
;
(a
;
(
;
a >*
;
a >* b
;
( ( a
;
( ( a )
;
( ( a  + )
|]

pgExprs :: [(Text,Text,Text)]
pgExprs = flip map (parseExampleStrings src) $ \e ->
    ("scalarExpr", "postgres", e)
  where src = [R.r|
$$something$
;
$$something
;
$$something
x
;
$a$something$b$
;
$a$
;
:::
;
|||
;
...
;

|]

sqlServerIden :: [(Text,Text,Text)]
sqlServerIden = flip map (parseExampleStrings src) $ \e ->
    ("scalarExpr", "sqlserver", e)
  where src = [R.r|
]
;
[test
;
[]
;
[[test]]

|]

mysqliden :: [(Text,Text,Text)]
mysqliden = flip map (parseExampleStrings src) $ \e ->
    ("scalarExpr", "mysql", e)
  where src = [R.r|
`open
;
```
;
``

|]

paramvariations :: [(Text,Text,Text)]
paramvariations = flip map (parseExampleStrings src) $ \e ->
    ("scalarExpr", "params", e)
  where src = [R.r|
:3
;
@3
;
#3

|]


odbcexpr :: [(Text,Text,Text)]
odbcexpr = flip map (parseExampleStrings src) $ \e ->
    ("scalarExpr", "odbc", e)
  where src = [R.r|
{d '2000-01-01'
;
{fn CHARACTER_LENGTH(string_exp)

|]

odbcqexpr :: [(Text,Text,Text)]
odbcqexpr = flip map (parseExampleStrings src) $ \e ->
    ("queryExpr", "odbc", e)
  where src = [R.r|
select * from {oj t1 left outer join t2 on expr

|]


    
queryExprExamples :: [(Text,Text,Text)]
queryExprExamples = flip map (parseExampleStrings src) $ \e ->
    ("queryExpr", "ansi2011", e)
  where src = [R.r|
select a select
;
select a from t,
;
select a from t select
;
select a from t(a)
;
select a from (t
;
select a from (t having
;
select a from t a b
;
select a from t as
;
select a from t as having
;
select a from (1234)
;
select a from (1234
;
select a from a wrong join b
;
select a from a natural wrong join b
;
select a from a left wrong join b
;
select a from a left wrong join b
;
select a from a join b select
;
select a from a join b on select
;
select a from a join b on (1234
;
select a from a join b using(a
;
select a from a join b using(a,
;
select a from a join b using(a,)
;
select a from a join b using(1234
;
select a from t order no a
;
select a from t order by a where c
;
select 'test
'
;
select a as
;
select a as from t
;
select a as, 
;
select a,
;
select a, from t
;
select a as from
;
select a as from from
;
select a as from2 from
;
select a fromt
;
select a b fromt

;
select a from t u v
;
select a from t as
;
select a from t, 
;
select a from group by b
;
select a from t join group by a
;
select a from t join
;
select a from (@
;
select a from ()
;
select a from t left join u on
;
select a from t left join u on group by a
;
select a from t left join u using
;
select a from t left join u using (
;
select a from t left join u using (a
;
select a from t left join u using (a,
;
select a from (select a from)
;
select a from (select a

;
select a from t where
;
select a from t group by a having b where
;
select a from t where (a
;
select a from t where group by b

;
select a from t group by
;
select a from t group
;
select a from t group by a as
;
select a from t group by a,
;
select a from t group by order by
;
select a <<== b from t
;
/*
;
select * as a
;
select t.* as a
;
select 3 + *
;
select case when * then 1 end
;
select (*)
;
select * from (select a
        from t
;
select * from (select a(stuff)
        from t

;
select *
     from (select a,b
           from t
           where a = 1
                 and b > a

;
select *
     from (select a,b
           from t
           where a = 1
                 and b > a
           from t)

|]


statementParseErrorExamples :: [(Text,Text,Text)]
statementParseErrorExamples = flip map (parseExampleStrings src) $ \e ->
    ("statement", "ansi2011", e)
  where src = [R.r|
create
;
drop
;
delete this
;
delete where 7
;
delete from where t
;
truncate nothing
;
truncate nothing nothing
;
truncate table from
;
truncate table t u
;
insert t select u
;
insert into t insert
;
insert into t (1,2)
;
insert into t(
;
insert into t(1
;
insert into t(a
;
insert into t(a,
;
insert into t(a,b)
;
insert into t(a,b) values
;
insert into t(a,b) values (
;
insert into t(a,b) values (1
;
insert into t(a,b) values (1,
;
insert into t(a,b) values (1,2) and stuff
;
update set 1
;
update t u
;
update t u v
;
update t set a
;
update t set a=
;
update t set a=1,
;
update t set a=1 where
;
update t set a=1 where 1 also
;
create table
;
create table t (
  a
)
;
create table t (
  a
;
create table t (
  a,
)
;
create table t (
)
;
create table t (
;
create table t
;
create table t. (
;
truncate table t.
;
drop table t. where
;
update t. set
;
delete from t. where
;
insert into t. values
;
with a as (select * from t
select 1
;
with a as (select * from t
;
with a as (
;
with a (
;
with as (select * from t)
select 1
;
with (select * from t) as a
select 1


|]