aboutsummaryrefslogtreecommitdiffstats
path: root/app/Main.hs
diff options
context:
space:
mode:
Diffstat (limited to 'app/Main.hs')
-rw-r--r--app/Main.hs364
1 files changed, 7 insertions, 357 deletions
diff --git a/app/Main.hs b/app/Main.hs
index e7d62bf..7bf67ba 100644
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,33 +1,12 @@
-{-# LANGUAGE BlockArguments #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+module Main
+ ( main,
+ )
+where
-module Main where
-
-import Control.Exception (Exception, throw)
-import Control.Monad (void)
-import Control.Monad.Combinators.NonEmpty qualified as PN
import Data.Aeson qualified as J
-import Data.Aeson.Key qualified as JK
-import Data.Aeson.KeyMap qualified as JM
import Data.ByteString.Lazy.Char8 qualified as LB
-import Data.Char (isSpace)
-import Data.List (intercalate, isSuffixOf)
-import Data.List.NonEmpty qualified as N
-import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
-import Data.String (IsString (fromString))
-import Data.Text qualified as T
-import Data.Vector qualified as V
-import Data.Void (Void)
-import Field qualified as F
-import Record qualified as R
-import Store qualified as S
+import Query qualified as Q
import System.Directory (setCurrentDirectory)
-import System.FilePath ((</>))
-import Text.Megaparsec qualified as P
-import Text.Megaparsec.Char qualified as P
-import Text.Megaparsec.Char.Lexer qualified as P
import Text.Printf (printf)
main :: IO ()
@@ -47,334 +26,5 @@ main = do
"SELECT c.id, j FROM c EMBED j ON j.id == c.j_id"
]
-data Query
- = Select
- FieldSelector
- Collection
- (JoinClauses FilePath)
- (EmbedClauses FilePath)
- (Maybe WhereClause)
-
-instance Show Query where
- show (Select fs c js es w) =
- intercalate " " $
- catMaybes $
- [ Just "SELECT",
- Just (showFieldSelector fs),
- Just "FROM",
- Just (showCollection c),
- showJoinClauses js,
- showEmbedClauses es,
- showWhereClause w
- ]
- where
- showFieldSelector All = "*"
- showFieldSelector (Only (N.toList -> fs)) =
- intercalate ", " (map showField fs)
- showField = F.toString
- showCollection c = c
- showJoinClauses js = case map showJoinClause js of
- [] -> Nothing
- xs -> Just (intercalate " " xs)
- showJoinClause (JoinClause t c w) =
- intercalate " " $
- catMaybes $
- [ Just (showJoinType t),
- Just (showCollection c),
- Just "ON",
- showWhereClause w
- ]
- showJoinType JoinLeft = "LEFT JOIN"
- showJoinType JoinRight = "RIGHT JOIN"
- showJoinType JoinFull = "FULL JOIN"
- showEmbedClauses js = case map showEmbedClause js of
- [] -> Nothing
- xs -> Just (intercalate " " xs)
- showEmbedClause (EmbedClause c w) =
- intercalate " " $
- catMaybes $
- [ Just "EMBED",
- Just (showCollection c),
- Just "ON",
- showWhereClause w
- ]
- showWhereClause = showWhereClauseWith id
- showWhereClause' = showWhereClauseWith (\x -> "(" <> x <> ")")
- showWhereClauseWith _ Nothing = Nothing
- showWhereClauseWith wrap (Just (And ws)) = Just (wrap (intercalate "AND" (mapMaybe (showWhereClause' . Just) ws)))
- showWhereClauseWith wrap (Just (Or ws)) = Just (wrap (intercalate "OR" (mapMaybe (showWhereClause' . Just) ws)))
- showWhereClauseWith _ (Just (Where p)) = Just (showComparison p)
- showComparison (Eq a b) = intercalate " " [showField a, "==", showField b]
-
-data FieldSelector
- = All
- | Only (N.NonEmpty F.Field)
- deriving (Show)
-
-type Collection = FilePath
-
-type JoinClauses a = [JoinClause a]
-
-data JoinClause a
- = JoinClause JoinType a (Maybe WhereClause)
- deriving (Show)
-
-data JoinType
- = JoinLeft
- | JoinRight
- | JoinFull
- deriving (Show)
-
-type EmbedClauses a = [EmbedClause a]
-
-data EmbedClause a
- = EmbedClause a (Maybe WhereClause)
- deriving (Show)
-
-data WhereClause
- = And [WhereClause]
- | Or [WhereClause]
- | Where Comparison
- deriving (Show)
-
-data Comparison
- = Eq F.Field F.Field
- deriving (Show)
-
-data ParseError = ParseError String
- deriving (Show)
-
-instance Exception ParseError
-
-instance IsString Query where
- fromString =
- either (throw . ParseError . P.errorBundlePretty @String @Void) id
- . P.parse parser ""
- where
- parser = do
- void $ P.many P.space1
- select
- fs <- fieldSelector
- from
- c <- collection
- js <- joinClauses
- es <- embedClauses
- w <- P.optional do
- where_
- whereClause
- P.eof
- pure $ Select fs c js es w
-
- lexeme :: P.Parsec Void String a -> P.Parsec Void String a
- lexeme = P.lexeme $ void $ P.takeWhileP Nothing isSpace
-
- and = void $ lexeme (P.string "AND")
- comma = void $ lexeme (P.string ",")
- embed = void $ lexeme (P.string "EMBED")
- eq = void $ lexeme (P.string "==")
- from = void $ lexeme (P.string "FROM")
- on = void $ lexeme (P.string "ON")
- or = void $ lexeme (P.string "OR")
- select = void $ lexeme (P.string "SELECT")
- where_ = void $ lexeme (P.string "WHERE")
-
- collection = lexeme $ P.takeWhile1P (Just "collection") (not . isSpace)
-
- joinClauses = P.many joinClause
-
- joinClause = do
- t <- joinType
- c <- collection
- w <- P.optional do
- on
- whereClause
- pure $ JoinClause t c w
-
- embedClauses = P.many embedClause
-
- embedClause = do
- embed
- c <- collection
- w <- P.optional do
- on
- whereClause
- pure $ EmbedClause c w
-
- whereClause =
- P.choice
- [ P.try (And . map Where <$> P.sepBy1 comparison and),
- P.try (Or . map Where <$> P.sepBy1 comparison or),
- Where <$> comparison
- ]
-
- comparison = do
- a <- field
- eq
- b <- field
- pure $ Eq a b
-
- fieldSelector =
- P.choice
- [ do
- void $ lexeme $ P.string "*"
- pure All,
- do
- Only <$> PN.sepBy1 field comma
- ]
-
- field :: P.Parsec Void String F.Field
- field =
- lexeme . P.choice $
- [ P.try
- do
- F.Qualified
- <$> (fieldPart <* P.string ".")
- <*> (N.map T.pack <$> PN.sepBy1 fieldPart (P.string ".")),
- do
- F.Unqualified
- <$> (N.map T.pack <$> PN.sepBy1 fieldPart (P.string "."))
- ]
-
- fieldPart :: P.Parsec Void String String
- fieldPart = P.takeWhile1P Nothing (\c -> not (isSpace c) && c /= ',' && c /= '.')
-
- joinType :: P.Parsec Void String JoinType
- joinType =
- P.choice
- [ do
- void $ lexeme (P.string "LEFT")
- void $ lexeme (P.string "JOIN")
- pure JoinLeft,
- do
- void $ lexeme (P.string "RIGHT")
- void $ lexeme (P.string "JOIN")
- pure JoinRight,
- do
- void $ lexeme (P.string "FULL")
- void $ lexeme (P.string "JOIN")
- pure JoinFull
- ]
-
-query :: Query -> IO [J.Value]
-query (Select fs c js es w) = do
- c' <-
- mapM (fmap (R.fromValue c) . decodeFile . (c </>)) =<< ls c
- js' <-
- mapM
- ( \(JoinClause t c w) ->
- fmap (\j' -> JoinClause t (map (R.fromValue c) j') w) . mapM (decodeFile . (c </>))
- =<< ls c
- )
- js
- es' <-
- mapM
- ( \(EmbedClause c w) ->
- fmap (\e' -> EmbedClause (R.fromValue c e') w) . mapM (decodeFile . (c </>))
- =<< ls c
- )
- es
- pure $ map (select fs) $ where_ w $ embed es' $ join c' js'
- where
- ls c =
- filter (not . (isSuffixOf "/"))
- <$> S.withStore "." "HEAD" do
- S.listDirectory c
-
--- TODO use fold
-embed ::
- EmbedClauses (R.Record [J.Value]) ->
- [R.Records J.Value] ->
- [R.Records J.Value]
-embed es vss = embed' vss es
- where
- embed' vss [] = vss
- embed' vss (EmbedClause (R.Record c es) w : ess) =
- embed'
- ( map
- ( \vs ->
- let es' :: [J.Value]
- es' = filter (\e -> satisfies w (vs ++ [R.Record c e])) es
- in vs
- ++ [ R.Record
- c
- ( J.Object
- ( JM.singleton
- (JK.fromString c)
- (J.Array (V.fromList es'))
- )
- )
- ]
- )
- vss
- )
- ess
-
--- TODO use fold
-join ::
- R.Records J.Value ->
- JoinClauses (R.Records J.Value) ->
- [R.Records J.Value]
-join vs js = join' (map (: []) vs) js
- where
- join' vss [] = vss
- join' vss (JoinClause JoinLeft js w : jss) =
- join'
- ( concatMap
- ( \vs -> case filter (satisfies w) $ map (\j -> vs ++ [j]) js of
- [] -> [vs]
- vs' -> vs'
- )
- vss
- )
- jss
- join' vss (JoinClause JoinRight js w : jss) =
- join'
- ( concatMap
- ( \j -> case filter (satisfies w) $ map (\vs -> vs ++ [j]) vss of
- [] -> [[j]]
- vs' -> vs'
- )
- js
- )
- jss
- join' vss (JoinClause JoinFull js w : jss) =
- join'
- ( concatMap
- ( \vs -> case filter (satisfies w) $ map (\j -> vs ++ [j]) js of
- [] -> [vs]
- vs' -> vs'
- )
- vss
- ++ concatMap
- ( \j -> case filter (satisfies w) $ map (\vs -> vs ++ [j]) vss of
- [] -> [[j]]
- _ -> []
- )
- js
- )
- jss
-
-data DecodeException = DecodeException
- deriving (Show)
-
-instance Exception DecodeException
-
-decodeFile :: J.FromJSON a => Collection -> IO a
-decodeFile fp = S.withStore "." "HEAD" do
- fromMaybe (throw DecodeException) . J.decode <$> S.readFile fp
-
-select :: FieldSelector -> R.Records J.Value -> J.Value
-select All vs = R.disjointUnions (map R.toValue vs)
-select (Only fs) vs = R.select (N.toList fs) vs
-
-where_ :: Maybe WhereClause -> [R.Records J.Value] -> [R.Records J.Value]
-where_ w = filter (satisfies w)
-
-satisfies :: Maybe WhereClause -> R.Records J.Value -> Bool
-satisfies Nothing _ = True
-satisfies (Just (And ws)) vs = all (\w -> satisfies (Just w) vs) ws
-satisfies (Just (Or ws)) vs = any (\w -> satisfies (Just w) vs) ws
-satisfies (Just (Where (Eq f g))) vs = R.lookups f vs == R.lookups g vs
-
-query' :: Query -> IO ()
-query' q = mapM_ (LB.putStrLn . J.encode) =<< query q
+query' :: Q.Query -> IO ()
+query' q = mapM_ (LB.putStrLn . J.encode) =<< Q.query q