-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSelectQueryQ.hs
378 lines (326 loc) · 15.3 KB
/
SelectQueryQ.hs
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
{-# LANGUAGE LambdaCase #-}
module SelectQueryQ (
parseQuery,
parseQueryMap,
parseNamedQuery,
parseSelectQuery,
typeCheckSelectQuery,
combinePrimaryKeys,
extractSelect,
extractTrefs,
extractWhereExpr,
extractWhere,
extractJoinTables,
isSupportedAggregOp)
where
import Control.Monad
import Database.HsSqlPpp.Annotation
import Database.HsSqlPpp.Catalog
import Database.HsSqlPpp.Dialect
import Database.HsSqlPpp.Parse
import Database.HsSqlPpp.Syntax
import Database.HsSqlPpp.TypeCheck
import Data.Char
import Data.Generics.Uniplate.Data
import Data.IORef
import Data.List
import Data.Maybe
import System.Exit
import Text.Printf
import Debug.Trace
import qualified Data.Map as M
import qualified Data.Text.Lazy as T
import SchemaQ -- TODO: workaround
import LoggingQ
import ExprQ
import AexprQ
import QueryQ
import ErrorMsg
data Query
= P [String] [Function] (M.Map TableAlias TableName) [AExpr VarName]
deriving (Show)
parseQueryMap :: String -> String -> IO (TableName, M.Map TableName QueryQ.Query)
parseQueryMap defaultOutputName s = do
queries <- parseNamedQuery defaultOutputName s
let outputTableName = fst $ last queries
let subQueryMaps = map (\(name,query) -> constructSubQueries name query) queries
-- traceIO $ show subQueryMaps
-- if there are several anonymous tables, the output table by default is be the last one
-- the previous anonymous tables will be discarded
let queryMap = foldr M.union M.empty $ reverse subQueryMaps
return (outputTableName, queryMap)
constructSubQueries :: TableName -> QueryExpr -> (M.Map TableName QueryQ.Query)
constructSubQueries tableName queryExpr =
let (tableAliasMap, subQueries) = extractTrefs queryExpr in
let funs = extractSelect tableName queryExpr in
let filters = extractWhere queryExpr in
let groups = extractGroups queryExpr in
let subQuery = QueryQ.P groups funs tableAliasMap filters in
M.insert tableName subQuery subQueries
parseNamedQuery :: String -> String -> IO [(String, QueryExpr)]
parseNamedQuery defaultOutputName s = do
xs <- parseSchemas s
return $ map (extractNameAndQuery defaultOutputName) xs
extractNameAndQuery :: String -> Statement -> (String, QueryExpr)
extractNameAndQuery _ (Insert _ (Name _ [Nmc name]) _ query _) = (name, query)
extractNameAndQuery defaultOutputName (QueryStatement _ query) = (defaultOutputName, query)
parseQuery :: String -> IO (QueryExpr, [QueryExpr])
parseQuery s =
let dialect = postgresDialect in
let src = T.pack s in
parseSelectQuery dialect "errorlog.txt" src
parseSelectQuery :: Dialect -> FilePath -> T.Text -> IO (QueryExpr, [QueryExpr])
parseSelectQuery dialect fp src =
case parseQueryExpr parseFlags fp Nothing src of
Left err -> fatal (show err)
Right query -> do
qs <- extractQueries query
return (query, qs)
where
parseFlags = defaultParseFlags { pfDialect = dialect }
extractQueries query@Select{} = return [query]
extractQueries (CombineQueryExpr _ cqt q1 q2) | cqt `elem` [Intersect,Union,Except] = do
qs1 <- extractQueries q1
qs2 <- extractQueries q2
return (qs1 ++ qs2)
extractQueries _ = fatal "Unsupported query type. Expecting basic SELECT query."
combinePrimaryKeys :: QueryExpr -> [[Bool]] -> [Bool]
combinePrimaryKeys q pks = fst $ f q pks
where
f Select{} (pk:pks) = (pk,pks)
f (CombineQueryExpr _ cqt q1 q2) pks =
let
(pk1,pks') = f q1 pks
(pk2,pks'') = f q2 pks'
pk =
case cqt of
Intersect -> zipWith (||) pk1 pk2
Union -> map (const False) pk1
Except -> pk1
in
(pk,pks'')
type Reason = String
type Loc = Maybe SourcePosition -- using location of hssqlppp
prettyLoc :: String -> Loc -> String
prettyLoc msg Nothing = msg
prettyLoc msg (Just (fp, r, c)) = printf "%s. Error at %s:%d:%d" msg fp r c
unsupportedClauses :: Bool -> QueryExpr -> [Reason]
unsupportedClauses False query =
-- ["ALL with non-aggregating expressions and without GROUP BY"
-- | selDistinct query == All && not (isSelectListOnlyAggregExprs (selSelectList query)) && null (selGroupBy query) ] ++
["LIMIT" | isJust $ selLimit query] ++
["OFFSET" | isJust $ selOffset query] ++
["HAVING" | isJust $ selHaving query]
unsupportedClauses True query =
["LIMIT" | isJust $ selLimit query] ++
["OFFSET" | isJust $ selOffset query] ++
["HAVING" | isJust $ selHaving query]
unsupportedFrom :: Bool -> QueryExpr -> [(Loc, Reason)]
unsupportedFrom local query =
[(anSrc a, "Subquery") | not local, SubTref a _ <- trefs] ++
[(anSrc a, "Function") | FunTref a _ <- trefs] ++
[(anSrc a, "???") | OdbcTableRef a _ <- trefs] ++
[(anSrc a, showJoin j) | JoinTref a _ _ j _ _ _ <- trefs, j `notElem` [Inner, Cross]] ++
[(anSrc a, "Join USING") | JoinTref _ _ _ _ _ _ (Just (JoinUsing a _)) <- trefs] ++ -- TODO: handle these properly
[(anSrc a, "Full alias") | FullAlias a _ _ _ <- trefs] -- TODO: also handle this properly
where
trefs = universeBi query
showJoin j = headToUpper (map toLower (show j)) ++ " join"
headToUpper [] = []
headToUpper (x : xs) = toUpper x : xs
-- ^ Get locations of unsupported expressions.
-- ^ Both from WHERE clause and joins.
-- TODO: Dont descend under already unsupported expressions?
unsupportedWhere :: Bool -> QueryExpr -> [Loc]
unsupportedWhere local query =
map (anSrc.getAnnotation) $
filter (not.isSupportedWhereExpr) $
if local
then universeBi (selWhere query)
else universeBi (selWhere query) ++ universeBi (selTref query)
isSupportedWhereExpr :: ScalarExpr -> Bool
isSupportedWhereExpr = \case
NumberLit{} -> True
StringLit{} -> True
-- NullLit{} -> True
BooleanLit{} -> True
Identifier{} -> True
Parens{} -> True
PrefixOp _ n _ -> nameToStr n `elem` ops
BinaryOp _ n _ _ -> nameToStr n `elem` ops
SpecialOp _ n _ -> nameToStr n == "between"
App _ n _ -> nameToStr n `elem` apps
InPredicate _ _ True (InList _ _) -> True
_ -> False
where
ops = ["=", "<", ">", "<=", ">=", "<>", "<@>", "and", "or", "xor", "+", "-", "*", "/", "%", "^", "not"]
apps = ["log", "exp", "abs", "least", "greatest","point"]
isSupportedAggregOp :: Name -> Bool
isSupportedAggregOp op = nameToStr op `elem` ["count", "sum", "avg", "min", "max"]
nameToStr :: Name -> String
nameToStr (Name _ ns) = intercalate "." (map ncStr ns)
nameToStr AntiName{} = ice "Unexpected AntiName."
extractSelect :: String -> QueryExpr -> [Function]
extractSelect tableName query =
let y = tableName in
let (SelectList _ xs) = selSelectList query in
zipWith (extractTableExpr tableName) xs [0..length xs - 1]
extractTableExpr :: String -> SelectItem -> Int -> Function
extractTableExpr _ (SelExp _ (App _ (Name _ [Nmc aggrOp]) [expr])) i =
let y = "y" ++ (show i) in
let arg = extractScalarExpr expr in
let aggrOp2 = map toLower aggrOp in
case aggrOp2 of
"count" -> F arg (SelectCount y)
"sum" -> F arg (SelectSum y)
"avg" -> F arg (SelectAvg y)
"min" -> F arg (SelectMin y)
"max" -> F arg (SelectMax y)
_ -> error $ error_queryExpr_aggrFinal aggrOp
extractTableExpr _ (SelectItem _ (App _ (Name _ [Nmc aggrOp]) [expr]) (Nmc colName)) _ =
let y = colName in
let arg = extractScalarExpr expr in
let aggrOp2 = map toLower aggrOp in
case aggrOp2 of
"count" -> F arg (SelectCount y)
"sum" -> F arg (SelectSum y)
"avg" -> F arg (SelectAvg y)
"min" -> F arg (SelectMin y)
"max" -> F arg (SelectMax y)
_ -> error $ error_queryExpr_aggrFinal aggrOp
-- TODO prefix would be needed if we treat all subqueries as subtables
-- currently, we treat plain select statements in a different way
extractTableExpr _ (SelectItem _ expr (Nmc colName)) _ =
let arg = extractScalarExpr expr in
F arg (SelectPlain colName)
extractTableExpr _ (SelExp _ (Identifier _ (Name _ [Nmc tableName, Nmc colName]))) _ =
let varName = tableName ++ "." ++ colName in
F (AVar varName) (SelectPlain varName)
extractTableExpr _ (SelExp _ (Identifier _ (Name _ [Nmc colName]))) _ = error $ error_queryExpr_unnamed colName
extractTableExpr _ q _ = error $ error_queryExpr q
extractTrefs :: QueryExpr -> (M.Map TableName TableAlias, M.Map TableName QueryQ.Query)
extractTrefs query = extractTrefsRec (selTref query) M.empty M.empty
extractTrefsRec :: [TableRef] -> M.Map TableName TableAlias -> M.Map TableName QueryQ.Query -> (M.Map TableName TableAlias, M.Map TableName QueryQ.Query)
extractTrefsRec [] ts qs = (ts, qs)
extractTrefsRec (TableAlias _ (Nmc tableAlias) (Tref _ (Name _ [Nmc tableName])):xs) ts qs =
let (ts', qs') = extractTrefsRec xs ts qs in
(M.insert tableAlias tableName ts', qs')
extractTrefsRec (Tref _ (Name _ [Nmc tableName]):xs) ts qs =
let (ts', qs') = extractTrefsRec xs ts qs in
(M.insert tableName tableName ts', qs')
extractTrefsRec (TableAlias _ (Nmc tableName) (SubTref _ queryExpr):xs) ts qs =
let (ts', qs1) = extractTrefsRec xs ts qs in
let qs2 = constructSubQueries tableName queryExpr in
(M.insert tableName tableName ts', M.union qs1 qs2)
extractTrefsRec (JoinTref _ x1 _ _ _ x2 _:xs) ts qs =
extractTrefsRec (x1:x2:xs) ts qs
--extractTrefsRec t _ _ = error $ error_queryExpr t
extractWhere :: QueryExpr -> [AExpr String]
extractWhere query =
let scalarExprs = extractWhereExpr query in
let aexprs = map extractScalarExpr scalarExprs in
concat $ map applyAexprNormalize aexprs
extractGroups :: QueryExpr -> [String]
extractGroups query =
let scalarExprList = selGroupBy query in
map (\ (Identifier _ (Name _ nmcs)) -> intercalate "." (map (\(Nmc x) -> x) nmcs)) scalarExprList
applyAexprNormalize :: AExpr String -> [AExpr String]
applyAexprNormalize bexpr =
let aexpr = aexprNormalize bexpr in
-- how many filters we actually have if we split them by "and"?
let xs = case aexpr of
AAnds ys -> ys
_ -> []
in if length xs == 0 then [aexpr] else xs
-- TODO make all operations case insensitive
extractScalarExpr :: ScalarExpr -> AExpr String
extractScalarExpr expr =
--trace (show expr) $
case expr of
Identifier _ (Name _ nmcs) -> AVar $ intercalate "." (map (\(Nmc x) -> x) nmcs)
NumberLit _ s -> AConst (read s)
StringLit _ s -> AText ("\'" ++ s ++ "\'")
BinaryOp _ (Name _ [Nmc "<>"]) x1 x2 -> AUnary ANot $ ABinary AEQ (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "<@>"]) x1 x2 -> ABinary ADistance (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "<="]) x1 x2 -> ABinary ALE (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "<"]) x1 x2 -> ABinary ALT (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "="]) x1 x2 -> ABinary AEQ (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "!="]) x1 x2 -> AUnary ANot $ ABinary AEQ (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc ">="]) x1 x2 -> ABinary AGE (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc ">"]) x1 x2 -> ABinary AGT (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "like"]) x1 x2 -> ABinary ALike (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "and"]) x1 x2 -> ABinary AAnd (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "or"]) x1 x2 -> ABinary AOr (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "xor"]) x1 x2 -> ABinary AXor (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "*"]) x1 x2 -> ABinary AMult (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "/"]) x1 x2 -> ABinary ADiv (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "+"]) x1 x2 -> ABinary AAdd (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "-"]) x1 x2 -> ABinary ASub (extractScalarExpr x1) (extractScalarExpr x2)
BinaryOp _ (Name _ [Nmc "^"]) x1 x2 ->
let z2 = extractScalarExpr x2 in
case z2 of
AConst c -> AUnary (APower c) (extractScalarExpr x1)
_ -> AError $ error_queryExpr expr
PrefixOp _ (Name _ [Nmc "not"]) x -> AUnary ANot (extractScalarExpr x)
PrefixOp _ (Name _ [Nmc "-"]) x -> AUnary ANeg (extractScalarExpr x)
App _ (Name _ [Nmc opName]) [x] ->
let opName2 = map toLower opName in
case opName2 of
"abs" -> AAbs (extractScalarExpr x)
"log" -> AUnary ALn (extractScalarExpr x)
"floor" -> AUnary AFloor (extractScalarExpr x)
"ceil" -> AUnary ACeil (extractScalarExpr x)
"exp" -> AUnary (AExp 1.0) (extractScalarExpr x)
_ -> AError $ error_queryExpr expr
App _ (Name _ [Nmc opName]) xs ->
let opName2 = map toLower opName in
case opName2 of
"least" -> AMins (map extractScalarExpr xs)
"greatest" -> AMaxs (map extractScalarExpr xs)
"point" -> AVector (map extractScalarExpr xs)
_ -> AError opName2
Star _ -> AConst 0.0
Parens _ x -> extractScalarExpr x
SpecialOp _ (Name _ [Nmc "between"]) [x,x1,x2] -> ABinary AAnd (ABinary AGE (extractScalarExpr x) (extractScalarExpr x1)) (ABinary AGE (extractScalarExpr x2) (extractScalarExpr x))
InPredicate _ x True (InList _ xs) -> foldr (\ae aes -> ABinary AOr (ABinary AEQ b ae) aes) (ABinary AEQ b a) as
where (a:as) = map extractScalarExpr xs
b = extractScalarExpr x
_ -> AError $ error_queryExpr expr
extractWhereExpr :: QueryExpr -> [ScalarExpr]
extractWhereExpr query =
maybeToList (selWhere query) ++
[e | JoinTref a _ _ _ _ _ (Just (JoinOn _ e)) <- universeBi query]
extractJoinTables :: QueryExpr -> [TableRef]
extractJoinTables = concatMap go . selTref
where
go (JoinTref _ l _ _ _ r _) = go l ++ go r
go t = [t]
typeCheckSelectQuery :: Dialect -> Bool -> Bool -> FilePath -> Catalog -> QueryExpr -> IO QueryExpr
typeCheckSelectQuery dialect local checkUnsupporteds fp catalog query = do
query <- return $ typeCheckQueryExpr typeCheckFlags catalog query
queryErrs <- checkAndReportErrors query
when queryErrs exitFailure -- dont bail?
-- Because type checker may rewrite queries to a different form
-- we perform feature check late.
bailRef <- newIORef False
when checkUnsupporteds $ do
forM_ (unsupportedClauses local query) $ \str -> do
bailRef `writeIORef` True
err $ str ++ " clause is not supported"
forM_ (unsupportedFrom local query) $ \ (loc, str) -> do
bailRef `writeIORef` True
err $ prettyLoc (printf "%s not supported in FROM clause." str) loc
forM_ (unsupportedWhere local query) $ \loc -> do
bailRef `writeIORef` True
err $ prettyLoc "Unsupported expression in WHERE clause or join." loc
bail <- readIORef bailRef
when bail exitFailure
return query
where
typeCheckFlags = defaultTypeCheckFlags {
tcfAddQualifiers = True,
-- tcfAddFullTablerefAliases = True,
tcfAddSelectItemAliases = True,
tcfExpandStars = True,
tcfDialect = dialect
}