From 81d2ef12c9e00ee6f77001789b7cb47d02ac914d Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Thu, 25 May 2017 10:06:12 -0500 Subject: [PATCH] Add query/module productions (#12) * Add query/module productions * Split query/module. Fix decl parser * Modules can't end in ; --- src/SqlSquare.purs | 39 +++++ src/SqlSquare/Parser.purs | 169 +++++++++++++++------ src/SqlSquare/Signature.purs | 232 ++++++++++++++++++++++++++++- src/SqlSquare/Signature/Ident.purs | 32 ++-- test/src/Gen.purs | 18 +-- test/src/Parse.purs | 10 +- 6 files changed, 419 insertions(+), 81 deletions(-) diff --git a/src/SqlSquare.purs b/src/SqlSquare.purs index aaf63a2..0a28443 100644 --- a/src/SqlSquare.purs +++ b/src/SqlSquare.purs @@ -1,9 +1,19 @@ module SqlSquared ( Sql + , SqlQuery + , SqlModule , print + , printQuery + , printModule , encodeJson + , encodeJsonQuery + , encodeJsonModule , decodeJson + , decodeJsonQuery + , decodeJsonModule , arbitrarySqlOfSize + , arbitrarySqlQueryOfSize + , arbitrarySqlModuleOfSize , module Sig , module Lenses , module Constructors @@ -16,6 +26,7 @@ import Data.Argonaut as J import Data.Either (Either) import Data.Functor.Mu (Mu) import Data.Json.Extended as EJ +import Data.Traversable (traverse) import Matryoshka (cata, anaM) @@ -28,14 +39,42 @@ import Test.StrongCheck.Gen as Gen type Sql = Mu (Sig.SqlF EJ.EJsonF) +type SqlQuery = Sig.SqlQueryF Sql + +type SqlModule = Sig.SqlModuleF Sql + print ∷ Sql → String print = cata $ Sig.printSqlF EJ.renderEJsonF +printQuery ∷ SqlQuery → String +printQuery = Sig.printSqlQueryF <<< map print + +printModule ∷ SqlModule → String +printModule = Sig.printSqlModuleF <<< map print + encodeJson ∷ Sql → J.Json encodeJson = cata $ Sig.encodeJsonSqlF EJ.encodeJsonEJsonF +encodeJsonQuery ∷ SqlQuery → J.Json +encodeJsonQuery = Sig.encodeJsonSqlQueryF <<< map encodeJson + +encodeJsonModule ∷ SqlModule → J.Json +encodeJsonModule = Sig.encodeJsonSqlModuleF <<< map encodeJson + decodeJson ∷ J.Json → Either String Sql decodeJson = anaM $ Sig.decodeJsonSqlF EJ.decodeJsonEJsonF +decodeJsonQuery ∷ J.Json → Either String SqlQuery +decodeJsonQuery = traverse decodeJson <=< Sig.decodeJsonSqlQueryF + +decodeJsonModule ∷ J.Json → Either String SqlModule +decodeJsonModule = traverse decodeJson <=< Sig.decodeJsonSqlModuleF + arbitrarySqlOfSize ∷ Int → Gen.Gen Sql arbitrarySqlOfSize = anaM $ Sig.arbitrarySqlF EJ.arbitraryEJsonF + +arbitrarySqlQueryOfSize ∷ Int → Gen.Gen SqlQuery +arbitrarySqlQueryOfSize = traverse arbitrarySqlOfSize <=< Sig.arbitrarySqlQueryF + +arbitrarySqlModuleOfSize ∷ Int → Gen.Gen SqlModule +arbitrarySqlModuleOfSize = traverse arbitrarySqlOfSize <=< Sig.arbitrarySqlModuleF diff --git a/src/SqlSquare/Parser.purs b/src/SqlSquare/Parser.purs index 1060be1..54151a7 100644 --- a/src/SqlSquare/Parser.purs +++ b/src/SqlSquare/Parser.purs @@ -1,5 +1,7 @@ module SqlSquared.Parser ( parse + , parseQuery + , parseModule , module SqlSquared.Parser.Tokenizer ) where @@ -30,10 +32,33 @@ import Text.Parsing.Parser as P import Text.Parsing.Parser.Combinators as PC import Text.Parsing.Parser.Pos (initialPos) -parse ∷ ∀ t. Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ String → E.Either P.ParseError t -parse = - tokenize - >=> flip P.runParser (expr <* eof) +parse + ∷ ∀ t + . Corecursive t (Sig.SqlF EJ.EJsonF) + ⇒ String + → E.Either P.ParseError t +parse = tokenize >=> flip P.runParser (expr <* eof) + +parseQuery + ∷ ∀ t + . Corecursive t (Sig.SqlF EJ.EJsonF) + ⇒ String + → E.Either P.ParseError (Sig.SqlQueryF t) +parseQuery = tokenize >=> flip P.runParser (go <* eof) + where + go = + Sig.Query + <$> (PC.sepEndBy (import_ <|> functionDecl expr) $ operator ";") + <*> expr + +parseModule + ∷ ∀ t + . Corecursive t (Sig.SqlF EJ.EJsonF) + ⇒ String + → E.Either P.ParseError (Sig.SqlModuleF t) +parseModule = tokenize >=> flip P.runParser (go <* eof) + where + go = Sig.Module <$> (PC.sepBy (import_ <|> functionDecl expr) $ operator ";") token ∷ ∀ m. Monad m ⇒ P.ParserT (Array Token) m Token token = do @@ -86,28 +111,31 @@ match ∷ ∀ m. Monad m ⇒ Token → P.ParserT (Array Token) m Token match = whenTok ∘ eq expr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t expr = letExpr letExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t -letExpr = - (PC.try do - operator ":" - i ← ident - operator ":=" - bindTo ← expr - operator ";" - in_ ← expr - pure $ embed $ Sig.Let { ident: i, bindTo, in_ } ) - <|> queryExpr +letExpr = let_ <|> queryExpr + where + let_ = PC.try do + operator ":" + i ← ident + operator ":=" + bindTo ← expr + operator ";" + in_ ← expr + pure $ embed $ Sig.Let { ident: i, bindTo, in_ } queryExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t queryExpr = PC.try do @@ -129,12 +157,15 @@ queryBinop = -- TODO, add update and delete query - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t query = selectExpr -definedExpr ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) +definedExpr + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t definedExpr = PC.try do @@ -142,7 +173,8 @@ definedExpr = PC.try do embed $ Sig.Binop { lhs, rhs, op: Sig.IfUndefined } rangeExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t rangeExpr = PC.try do @@ -150,7 +182,8 @@ rangeExpr = PC.try do embed $ Sig.Binop { lhs, rhs, op: Sig.Range } orExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t orExpr = PC.try do @@ -158,7 +191,8 @@ orExpr = PC.try do embed $ Sig.Binop { lhs, rhs, op: Sig.Or } andExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t andExpr = PC.try do @@ -166,7 +200,8 @@ andExpr = PC.try do embed $ Sig.Binop { lhs, rhs, op: Sig.And } cmpExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t cmpExpr = PC.try do @@ -175,14 +210,16 @@ cmpExpr = PC.try do pure $ F.foldl (\acc fn → fn acc) e modifiers defaultExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t defaultExpr = PC.try do prod concatExpr searchLikeOp \lhs rhs op → op lhs rhs searchLikeOp - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m (t → t → t) searchLikeOp = @@ -194,7 +231,8 @@ searchLikeOp = <|> (operator "!~~" $> (\a b → _NOT $ _LIKE Nothing a b)) concatExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t concatExpr = PC.try do @@ -202,7 +240,8 @@ concatExpr = PC.try do embed $ Sig.Binop {op: Sig.Concat, lhs, rhs} addExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t addExpr = PC.try do @@ -210,7 +249,8 @@ addExpr = PC.try do embed $ Sig.Binop {op, lhs, rhs} multExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t multExpr = PC.try do @@ -221,7 +261,8 @@ multExpr = PC.try do \lhs rhs op → embed $ Sig.Binop {op, lhs, rhs} powExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t powExpr = PC.try do @@ -229,7 +270,8 @@ powExpr = PC.try do embed $ Sig.Binop {op: Sig.Pow, lhs, rhs} derefExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t derefExpr = PC.try do @@ -274,7 +316,8 @@ derefExpr = PC.try do pure \e → embed $ Sig.Binop { op: Sig.IndexDeref, lhs: e, rhs }) wildcard - ∷ ∀ m t. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ m t + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t wildcard = operator "*" $> embed (Sig.Splice Nothing) @@ -300,7 +343,8 @@ primaryExpression = <|> (ident <#> (embed ∘ Sig.Ident)) caseExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t caseExpr = @@ -335,7 +379,8 @@ cases = PC.try do pure $ cs × else_ unshiftExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t unshiftExpr = @@ -366,7 +411,8 @@ parenList = PC.try do pure $ L.fromFoldable arr unaryOperator - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t unaryOperator = PC.try do @@ -380,7 +426,8 @@ unaryOperator = PC.try do pure $ embed $ Sig.Unop { op, expr: e} functionExpr - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t functionExpr = PC.try do @@ -388,8 +435,35 @@ functionExpr = PC.try do args ← parenList pure $ embed $ Sig.InvokeFunction {name, args} +functionDecl + ∷ ∀ m a + . Monad m + ⇒ P.ParserT (Array Token) m a + → P.ParserT (Array Token) m (Sig.SqlDeclF a) +functionDecl parseExpr = PC.try do + _ ← keyword "create" + _ ← keyword "function" + name ← ident + operator "(" + args ← PC.sepBy (operator ":" *> ident) $ operator "," + operator ")" + _ ← keyword "begin" + body ← parseExpr + _ ← keyword "end" + pure $ Sig.FunctionDecl { ident: name, args, body } + +import_ + ∷ ∀ m a + . Monad m + ⇒ P.ParserT (Array Token) m (Sig.SqlDeclF a) +import_ = PC.try do + _ ← keyword "import" + s ← ident + pure $ Sig.Import s + variable - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t variable = PC.try do @@ -398,7 +472,8 @@ variable = PC.try do pure $ embed $ Sig.Vari s literal - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t literal = PC.try $ token >>= case _ of @@ -411,7 +486,8 @@ literal = PC.try $ token >>= case _ of _ → P.fail "incorrect literal" arrayLiteral - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t arrayLiteral = PC.try do @@ -421,7 +497,8 @@ arrayLiteral = PC.try do pure $ embed $ Sig.Literal $ EJ.Array $ A.fromFoldable els mapLiteral - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m t mapLiteral = PC.try do @@ -431,7 +508,8 @@ mapLiteral = PC.try do pure $ embed $ Sig.Literal $ EJ.Map $ EJ.EJsonMap $ A.fromFoldable els pair - ∷ ∀ t m. Corecursive t (Sig.SqlF EJ.EJsonF) + ∷ ∀ t m + . Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Monad m ⇒ P.ParserT (Array Token) m (t × t) pair = PC.try do @@ -489,7 +567,6 @@ likeSuffix = PC.try do defaultExpr pure $ \lhs → _LIKE mbEsc lhs rhs - relationalSuffix ∷ ∀ t m . Corecursive t (Sig.SqlF EJ.EJsonF) @@ -628,8 +705,6 @@ exprRelation = PC.try do i ← ident pure $ Sig.ExprRelation { aliasName: i, expr: e } - - stdJoinRelation ∷ ∀ t m . Corecursive t (Sig.SqlF EJ.EJsonF) @@ -660,6 +735,7 @@ stdJoinRelation = PC.try do , joinType , clause } + crossJoinRelation ∷ ∀ t m . Corecursive t (Sig.SqlF EJ.EJsonF) @@ -677,7 +753,6 @@ crossJoinRelation = PC.try do , clause: embed $ Sig.Literal $ EJ.Boolean true } - filter ∷ ∀ t m . Corecursive t (Sig.SqlF EJ.EJsonF) @@ -701,7 +776,6 @@ groupBy = PC.try do definedExpr pure $ Sig.GroupBy { keys, having } - orderBy ∷ ∀ t m . Corecursive t (Sig.SqlF EJ.EJsonF) @@ -750,8 +824,7 @@ projection = PC.try do ident pure $ Sig.Projection { expr: e, alias: a} - -_SEARCH ∷ ∀ t.Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Boolean → t → t → t +_SEARCH ∷ ∀ t. Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Boolean → t → t → t _SEARCH b lhs rhs = embed $ Sig.InvokeFunction @@ -763,7 +836,7 @@ _SEARCH b lhs rhs = : L.Nil } -_LIKE ∷ ∀ t.Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Maybe t → t → t → t +_LIKE ∷ ∀ t. Corecursive t (Sig.SqlF EJ.EJsonF) ⇒ Maybe t → t → t → t _LIKE mbEsc lhs rhs = embed $ Sig.InvokeFunction diff --git a/src/SqlSquare/Signature.purs b/src/SqlSquare/Signature.purs index e19b24b..e1aa884 100644 --- a/src/SqlSquare/Signature.purs +++ b/src/SqlSquare/Signature.purs @@ -6,11 +6,27 @@ module SqlSquared.Signature , SwitchR , LetR , SelectR + , FunctionDeclR , SqlF(..) + , SqlDeclF(..) + , SqlQueryF(..) + , SqlModuleF(..) , printSqlF + , printSqlDeclF + , printSqlQueryF + , printSqlModuleF , encodeJsonSqlF + , encodeJsonSqlDeclF + , encodeJsonSqlQueryF + , encodeJsonSqlModuleF , decodeJsonSqlF + , decodeJsonSqlDeclF + , decodeJsonSqlQueryF + , decodeJsonSqlModuleF , arbitrarySqlF + , arbitrarySqlDeclF + , arbitrarySqlQueryF + , arbitrarySqlModuleF , genSql , module SqlSquared.Utils , module OT @@ -39,6 +55,7 @@ import Data.List ((:)) import Data.HugeNum as HN import Data.Maybe (Maybe(..)) import Data.Monoid (mempty) +import Data.Newtype (class Newtype) import Data.Ord (class Ord1, compare1) import Data.String as S import Data.Traversable as T @@ -95,7 +112,7 @@ type LetR a = } type SelectR a = - { isDistinct ∷ Boolean + { isDistinct ∷ Boolean , projections ∷ L.List (PR.Projection a) , relations ∷ Maybe (RL.Relation a) , filter ∷ Maybe a @@ -103,6 +120,12 @@ type SelectR a = , orderBy ∷ Maybe (OB.OrderBy a) } +type FunctionDeclR a = + { ident ∷ String + , args ∷ L.List String + , body ∷ a + } + data SqlF literal a = SetLiteral (L.List a) | Literal (literal a) @@ -118,9 +141,29 @@ data SqlF literal a | Select (SelectR a) | Parens a +data SqlDeclF a + = Import String + | FunctionDecl (FunctionDeclR a) + +newtype SqlModuleF a = + Module (L.List (SqlDeclF a)) + +data SqlQueryF a = + Query (L.List (SqlDeclF a)) a + derive instance eqSqlF ∷ (Eq a, Eq (l a)) ⇒ Eq (SqlF l a) derive instance ordSqlF ∷ (Ord a, Ord (l a)) ⇒ Ord (SqlF l a) +derive instance eqSqlDeclF ∷ Eq a ⇒ Eq (SqlDeclF a) +derive instance ordSqlDeclF ∷ Ord a ⇒ Ord (SqlDeclF a) + +derive instance eqSqlModuleF ∷ Eq a ⇒ Eq (SqlModuleF a) +derive instance ordSqlModuleF ∷ Ord a ⇒ Ord (SqlModuleF a) +derive instance newtypeSqlModuleF ∷ Newtype (SqlModuleF a) _ + +derive instance eqSqlQueryF ∷ Eq a ⇒ Eq (SqlQueryF a) +derive instance ordSqlQueryF ∷ Ord a ⇒ Ord (SqlQueryF a) + instance eq1SqlF ∷ Eq1 l ⇒ Eq1 (SqlF l) where eq1 (SetLiteral lst) (SetLiteral llst) = eq lst llst eq1 (Literal l) (Literal ll) = eq1 l ll @@ -161,6 +204,20 @@ instance eq1SqlF ∷ Eq1 l ⇒ Eq1 (SqlF l) where && r.orderBy == rr.orderBy eq1 _ _ = false +instance eq1SqlDeclF ∷ Eq1 SqlDeclF where + eq1 (Import a) (Import b) = a == b + eq1 (FunctionDecl r) (FunctionDecl rr) = + r.ident == rr.ident + && r.args == rr.args + && r.body == rr.body + eq1 _ _ = false + +instance eq1SqlQueryF ∷ Eq1 SqlQueryF where + eq1 (Query a c) (Query b d) = a == b && c == d + +instance eq1SqlModuleF ∷ Eq1 SqlModuleF where + eq1 (Module a) (Module b) = a == b + instance ord1SqlF ∷ Ord1 l ⇒ Ord1 (SqlF l) where compare1 (Literal l) (Literal ll) = compare1 l ll compare1 (Literal _) _ = LT @@ -221,7 +278,25 @@ instance ord1SqlF ∷ Ord1 l ⇒ Ord1 (SqlF l) where <> compare r.orderBy rr.orderBy <> compare r.groupBy rr.groupBy -derive instance functorSIG ∷ Functor l ⇒ Functor (SqlF l) +instance ord1SqlDeclF ∷ Ord1 SqlDeclF where + compare1 (Import a) (Import b) = compare a b + compare1 (Import _) _ = LT + compare1 _ (Import _) = GT + compare1 (FunctionDecl r) (FunctionDecl rr) = + compare r.ident rr.ident + <> compare r.args rr.args + <> compare r.body rr.body + +instance ord1SqlQueryF ∷ Ord1 SqlQueryF where + compare1 (Query a c) (Query b d) = compare a b <> compare c d + +instance ord1SqlModuleF ∷ Ord1 SqlModuleF where + compare1 (Module a) (Module b) = compare a b + +derive instance functorSqlF ∷ Functor l ⇒ Functor (SqlF l) +derive instance functorSqlDeclF ∷ Functor SqlDeclF +derive instance functorSqlQueryF ∷ Functor SqlQueryF +derive instance functorSqlModuleF ∷ Functor SqlModuleF instance foldableSqlF ∷ F.Foldable l ⇒ F.Foldable (SqlF l) where foldMap f = case _ of @@ -298,7 +373,26 @@ instance foldableSqlF ∷ F.Foldable l ⇒ F.Foldable (SqlF l) where Parens p → f p a Literal l → F.foldr f a l +instance foldableSqlDeclF ∷ F.Foldable SqlDeclF where + foldMap f = case _ of + FunctionDecl r → f r.body + Import _ → mempty + foldl f a = case _ of + FunctionDecl r → f a r.body + Import _ → a + foldr f a = case _ of + FunctionDecl r → f r.body a + Import _ → a + +instance foldableSqlQueryF ∷ F.Foldable SqlQueryF where + foldMap f (Query r s) = F.foldMap (F.foldMap f) r <> f s + foldl f a (Query r s) = f (F.foldl (F.foldl f) a r) s + foldr f a (Query r s) = F.foldr (\x a' → F.foldr f a' x) (f s a) r +instance foldableSqlModuleF ∷ F.Foldable SqlModuleF where + foldMap f (Module r) = F.foldMap (F.foldMap f) r + foldl f a (Module r) = F.foldl (F.foldl f) a r + foldr f a (Module r) = F.foldr (\x a' → F.foldr f a' x) a r instance traversableSqlF ∷ T.Traversable l ⇒ T.Traversable (SqlF l) where traverse f = case _ of @@ -340,6 +434,23 @@ instance traversableSqlF ∷ T.Traversable l ⇒ T.Traversable (SqlF l) where <*> T.traverse (T.traverse f) orderBy sequence = T.sequenceDefault +instance traversableSqlDeclF ∷ T.Traversable SqlDeclF where + traverse f = case _ of + FunctionDecl { ident, args, body } → + map FunctionDecl + $ { ident, args, body: _ } + <$> f body + Import r → pure $ Import r + sequence = T.sequenceDefault + +instance traversableSqlQueryF ∷ T.Traversable SqlQueryF where + traverse f (Query r s) = Query <$> T.traverse (T.traverse f) r <*> f s + sequence = T.sequenceDefault + +instance traversableSqlModuleF ∷ T.Traversable SqlModuleF where + traverse f (Module r) = Module <$> T.traverse (T.traverse f) r + sequence = T.sequenceDefault + printSqlF ∷ ∀ l. Algebra l String → Algebra (SqlF l) String printSqlF printLiteralF = case _ of Splice Nothing → @@ -385,6 +496,23 @@ printSqlF printLiteralF = case _ of Parens t → "(" <> t <> ")" +printSqlDeclF ∷ Algebra SqlDeclF String +printSqlDeclF = case _ of + FunctionDecl { ident, args, body } → + "CREATE FUNCTION " + <> ID.printIdent ident + <> "(" <> F.intercalate "," (map (":" <> _) args) <> ") BEGIN " + <> body + <> " END" + Import s → + "IMPORT " <> ID.printIdent s + +printSqlQueryF ∷ Algebra SqlQueryF String +printSqlQueryF (Query decls expr) = F.intercalate "; " $ L.snoc (printSqlDeclF <$> decls) expr + +printSqlModuleF ∷ Algebra SqlModuleF String +printSqlModuleF (Module decls) = F.intercalate "; " $ printSqlDeclF <$> decls + encodeJsonSqlF ∷ ∀ l. Algebra l J.Json → Algebra (SqlF l) J.Json encodeJsonSqlF alg = case _ of SetLiteral lst → @@ -454,6 +582,31 @@ encodeJsonSqlF alg = case _ of J.~> "value" J.:= a J.~> J.jsonEmptyObject +encodeJsonSqlDeclF ∷ Algebra SqlDeclF J.Json +encodeJsonSqlDeclF = case _ of + FunctionDecl { ident, args, body } → + "tag" J.:= "create function" + J.~> "ident" J.:= ident + J.~> "args" J.:= args + J.~> "body" J.:= body + J.~> J.jsonEmptyObject + Import s → + "tag" J.:= "import" + J.~> "value" J.:= s + J.~> J.jsonEmptyObject + +encodeJsonSqlQueryF ∷ Algebra SqlQueryF J.Json +encodeJsonSqlQueryF (Query decls expr) = + "tag" J.:= "query" + J.~> "decls" J.:= (encodeJsonSqlDeclF <$> decls) + J.~> "expr" J.:= expr + J.~> J.jsonEmptyObject + +encodeJsonSqlModuleF ∷ Algebra SqlModuleF J.Json +encodeJsonSqlModuleF (Module decls) = + "tag" J.:= "module" + J.~> "decls" J.:= (encodeJsonSqlDeclF <$> decls) + J.~> J.jsonEmptyObject decodeJsonSqlF ∷ ∀ l @@ -475,7 +628,7 @@ decodeJsonSqlF coalg = J.decodeJson >=> \obj → do "vari" → decodeVari obj "select" → decodeSelect obj "parens" → decodeParens obj - _ → E.Left "This is not SqlF expression" + _ → E.Left $ "Invalid SQL^2 expression: " <> tag where decodeSetLiteral obj = do v ← obj J..? "value" @@ -544,6 +697,44 @@ decodeJsonSqlF coalg = J.decodeJson >=> \obj → do v ← obj J..? "value" pure $ Parens v +decodeJsonSqlDeclF ∷ CoalgebraM (E.Either String) SqlDeclF J.Json +decodeJsonSqlDeclF = J.decodeJson >=> \obj → do + tag ← obj J..? "tag" + case tag of + "create function" → decodeFunctionDecl obj + "import" → decodeImport obj + _ → E.Left $ "Invalid SQL^2 declaration: " <> tag + + where + decodeFunctionDecl obj = do + ident ← obj J..? "ident" + args ← obj J..? "args" + body ← obj J..? "body" + pure $ FunctionDecl { ident, args, body } + + decodeImport obj = do + v ← obj J..? "value" + pure $ Import v + +decodeJsonSqlQueryF ∷ CoalgebraM (E.Either String) SqlQueryF J.Json +decodeJsonSqlQueryF = J.decodeJson >=> \obj → do + tag ← obj J..? "tag" + case tag of + "query" → do + decls ← T.traverse decodeJsonSqlDeclF =<< obj J..? "decls" + expr ← obj J..? "expr" + pure $ Query decls expr + _ → E.Left $ "Invalid top-level SQL^2 production: " <> tag + +decodeJsonSqlModuleF ∷ CoalgebraM (E.Either String) SqlModuleF J.Json +decodeJsonSqlModuleF = J.decodeJson >=> \obj → do + tag ← obj J..? "tag" + case tag of + "module" → do + decls ← T.traverse decodeJsonSqlDeclF =<< obj J..? "decls" + pure $ Module decls + _ → E.Left $ "Invalid top-level SQL^2 production: " <> tag + arbitrarySqlF ∷ ∀ l . CoalgebraM Gen.Gen l Int @@ -570,6 +761,18 @@ arbitrarySqlF genLiteral n , genSelect n ] +arbitrarySqlDeclF ∷ CoalgebraM Gen.Gen SqlDeclF Int +arbitrarySqlDeclF n = + Gen.oneOf genImport + [ genFunctionDecl n + ] + +arbitrarySqlQueryF ∷ CoalgebraM Gen.Gen SqlQueryF Int +arbitrarySqlQueryF n = Query <$> genDecls n <*> pure n + +arbitrarySqlModuleF ∷ CoalgebraM Gen.Gen SqlModuleF Int +arbitrarySqlModuleF n = Module <$> genDecls n + genSetLiteral ∷ ∀ l. CoalgebraM Gen.Gen (SqlF l) Int genSetLiteral n = do len ← Gen.chooseInt 0 $ n - 1 @@ -664,6 +867,20 @@ genSelect n = do , orderBy } +genFunctionDecl ∷ CoalgebraM Gen.Gen SqlDeclF Int +genFunctionDecl n = do + ident ← genIdent + len ← Gen.chooseInt 0 $ n - 1 + let + foldFn acc _ = do + arg ← genIdent + pure $ arg L.: acc + args ← L.foldM foldFn L.Nil $ L.range 0 len + pure $ FunctionDecl { ident, args, body: n - 1 } + +genImport ∷ ∀ a. Gen.Gen (SqlDeclF a) +genImport = Import <$> genIdent + genIdent ∷ Gen.Gen String genIdent = do start ← @@ -673,6 +890,14 @@ genIdent = do body ← map (Int.toStringAs Int.hexadecimal) SC.arbitrary pure $ start <> body +genDecls ∷ Int → Gen.Gen (L.List (SqlDeclF Int)) +genDecls n = do + let + foldFn acc _ = do + cs ← arbitrarySqlDeclF $ n - 1 + pure $ cs L.: acc + len ← Gen.chooseInt 0 $ n - 1 + L.foldM foldFn L.Nil $ L.range 0 len -- This one is one gigantic TODO: generation Sql² AST that -- can be constructed using parsing. Since parsing is @@ -704,7 +929,6 @@ genLetP n = do in_ ← genSql n pure $ embed $ Let { ident, bindTo, in_ } - genQueryExprP ∷ ∀ t. Int → GenSql t genQueryExprP n | n < 2 = Gen.oneOf (genQueryP n) [ genDefinedExprP n ] diff --git a/src/SqlSquare/Signature/Ident.purs b/src/SqlSquare/Signature/Ident.purs index a1e728f..8297d47 100644 --- a/src/SqlSquare/Signature/Ident.purs +++ b/src/SqlSquare/Signature/Ident.purs @@ -1,16 +1,16 @@ -module SqlSquared.Signature.Ident where - -import Prelude - -import Data.String.Regex (test, replace) as Regex -import Data.String.Regex.Flags (ignoreCase, global) as Regex -import Data.String.Regex.Unsafe (unsafeRegex) as Regex - -printIdent ∷ String → String -printIdent str = - if Regex.test identifier str - then str - else "`" <> Regex.replace tick "\\`" str <> "`" - where - identifier = Regex.unsafeRegex "^[_a-z][_a-z0-9]*$" Regex.ignoreCase - tick = Regex.unsafeRegex "`" Regex.global +module SqlSquared.Signature.Ident where + +import Prelude + +import Data.String.Regex (test, replace) as Regex +import Data.String.Regex.Flags (ignoreCase, global) as Regex +import Data.String.Regex.Unsafe (unsafeRegex) as Regex + +printIdent ∷ String → String +printIdent str = + if Regex.test identifier str + then str + else "`" <> Regex.replace tick "\\`" str <> "`" + where + identifier = Regex.unsafeRegex "^[_a-z][_a-z0-9]*$" Regex.ignoreCase + tick = Regex.unsafeRegex "`" Regex.global diff --git a/test/src/Gen.purs b/test/src/Gen.purs index 53b347a..3dac8d2 100644 --- a/test/src/Gen.purs +++ b/test/src/Gen.purs @@ -15,28 +15,28 @@ import Test.StrongCheck as SC import Test.StrongCheck.Arbitrary as A import Test.Unit.Console as Console -import SqlSquared (Sql, arbitrarySqlOfSize, decodeJson, encodeJson, print, tokenize) +import SqlSquared (SqlQuery, arbitrarySqlQueryOfSize, decodeJsonQuery, encodeJsonQuery, printQuery, tokenize) -newtype ArbSql = ArbSql Sql +newtype ArbSql = ArbSql SqlQuery instance arbitraryArbSql ∷ A.Arbitrary ArbSql where - arbitrary = map ArbSql $ arbitrarySqlOfSize 3 + arbitrary = map ArbSql $ arbitrarySqlQueryOfSize 3 -newtype ParseableSql = ParseableSql Sql +newtype ParseableSql = ParseableSql SqlQuery testJsonSerialization ∷ ∀ r. Eff (TestEffects r) Unit testJsonSerialization = - SC.quickCheck' 50 \(ArbSql sql) → case decodeJson $ encodeJson sql of + SC.quickCheck' 50 \(ArbSql sql) → case decodeJsonQuery $ encodeJsonQuery sql of E.Right res → - res == sql "Mismatch:\n" <> print sql <> "\n" <> print res + res == sql "Mismatch:\n" <> printQuery sql <> "\n" <> printQuery res E.Left err → - SC.Failed $ "Argonaut codecs error: " <> err <> " \n" <> print sql + SC.Failed $ "Argonaut codecs error: " <> err <> " \n" <> printQuery sql testTokenizer ∷ ∀ r. Eff (TestEffects r) Unit testTokenizer = - SC.quickCheck' 50 \(ArbSql sql) → case tokenize $ print sql of + SC.quickCheck' 50 \(ArbSql sql) → case tokenize $ printQuery sql of E.Left err → - SC.Failed $ "Tokenizer error: " <> show err <> " \n" <> print sql + SC.Failed $ "Tokenizer error: " <> show err <> " \n" <> printQuery sql E.Right _ → SC.Success diff --git a/test/src/Parse.purs b/test/src/Parse.purs index 9db4b1f..d864100 100644 --- a/test/src/Parse.purs +++ b/test/src/Parse.purs @@ -6,7 +6,7 @@ import Control.Monad.Eff (Eff) import Data.Either as E import Data.Foldable as F -import SqlSquared (Sql, print, parse) +import SqlSquared (printQuery, parseQuery) import Test.Unit.Console as Console @@ -15,13 +15,13 @@ import Debug.Trace as DT testPrintParse ∷ ∀ e. String → Eff (testOutput ∷ Console.TESTOUTPUT|e) Unit testPrintParse s = do Console.printLabel $ "Testing: \n" <> s <> "\n" - let parsed = parse s + let parsed = parseQuery s case parsed of E.Left e → Console.printFail $ "Fail: " <> show e <> "\n" - E.Right (sql ∷ Sql) → do + E.Right sql → do Console.printPass "Success: \n" DT.traceAnyA sql - Console.printPass $ print sql + Console.printPass $ printQuery sql Console.printPass "\n" inputs ∷ Array String @@ -45,6 +45,8 @@ inputs = , """-- comment select 12 """ + , """import foo; select * from `/test`""" + , """create function foo(:bar) begin :bar + 2 end; select * from `/test` where foo = foo(42)""" ]