aboutsummaryrefslogtreecommitdiffstats
path: root/app/Main.hs
blob: 3747dabe526ac99b4b66be4f597bff62bebaf5cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Exception (Exception, throw)
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.List (foldl')
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Set qualified as S
import Data.String (IsString (fromString))
import Data.Text qualified as T
import Debug.Trace (trace)
import System.Directory (listDirectory)
import System.FilePath (takeExtension, (</>))
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)

debug :: Show a => String -> a -> a
debug s x = trace (printf "%s: %s" s (show x)) x

main = do
  -- query "SELECT . FROM c"
  query' $ Select ["."] "./data/c" [] []
  putStrLn ""
  -- query "SELECT id FROM c"
  query' $ Select ["id"] "./data/c" [] []
  putStrLn ""
  -- query "SELECT c.id, j.id, is_j FROM c JOIN j WHERE j.id == c.j_id"
  query' $ Select ["c.id", "j.id", "is_j"] "./data/c" ["./data/j"] [Eq "j.id" "c.j_id"]

data Query
  = Select [Field] Collection Join Where

type Field = T.Text

type Collection = FilePath

type Join = [FilePath]

type Where = [Cmp]

data Cmp
  = Eq Field Field

{-
instance IsString Query where
  fromString =
    either throw id (P.parse parser "" s)-}

query :: Query -> IO [J.Value]
query (Select fs c js ws) = do
  c' <- mapM (decodeFile . (c </>)) =<< ls c
  js' <- mapM (\j -> mapM (decodeFile . (j </>)) =<< ls j) js
  pure $ map (select fs) $ where_ ws $ combine c' js'

combine c = combine' (map (: []) c)
  where
    combine' cs [] = cs
    combine' cs (js : jss) = combine' [c ++ [j] | c <- cs, j <- js] jss

ls :: FilePath -> IO [FilePath]
ls =
  fmap (filter ((== ".json") . takeExtension)) . listDirectory

data DecodeException = DecodeException
  deriving (Show)

instance Exception DecodeException

decodeFile :: J.FromJSON a => FilePath -> IO a
decodeFile =
  fmap (fromMaybe (throw DecodeException)) . J.decodeFileStrict

select :: [T.Text] -> [J.Value] -> J.Value
select fs vs = join' $ map (select' fs) vs

select' :: [T.Text] -> J.Value -> J.Value
select' ["."] (J.Object kvs) = J.Object kvs
select' fs (J.Object kvs) =
  J.Object (JM.filterWithKey (\k _ -> JK.toText k `elem` fs) kvs)

join' :: [J.Value] -> J.Value
join' vs = foldl' merge (J.Object JM.empty) vs

where_ :: Where -> [[J.Value]] -> [[J.Value]]
where_ ws vss = filter (\vs -> all (\w -> satisfy w vs) ws) vss
  where
    satisfy (Eq f f') vs = unique f vs == unique f' vs

data DuplicateField' = DuplicateField'
  deriving (Show)

instance Exception DuplicateField'

unique :: T.Text -> [J.Value] -> J.Value
unique f as = case debug "unique" $ mapMaybe (get (debug "f" f)) (debug "as" as) of
  [x] -> x
  (_ : _) -> throw DuplicateField'

get :: T.Text -> J.Value -> Maybe J.Value
get f (J.Object kvs) = JM.lookup (JK.fromText f) kvs

data DuplicateField = DuplicateField
  deriving (Show)

instance Exception DuplicateField

merge :: J.Value -> J.Value -> J.Value
merge (J.Object kvs) (J.Object kvs') =
  case disjoint kvs kvs' of
    True -> J.Object (JM.union kvs kvs')
    False -> throw DuplicateField

disjoint kvs kvs' =
  let ks = S.fromList (JM.keys kvs)
      ks' = S.fromList (JM.keys kvs')
   in S.size ks + S.size ks' == S.size (ks `S.union` ks')

query' :: Query -> IO ()
query' q = mapM_ (LB.putStrLn . J.encode) =<< query q