{-# LANGUAGE FlexibleInstances #-}
-----------------------------------------------------------
-- |
-- Module      :  HaskellDB
-- Copyright   :  Daan Leijen (c) 1999, daan@cs.uu.nl
--                HWT Group (c) 2003, haskelldb-users@lists.sourceforge.net
-- License     :  BSD-style
-- 
-- Maintainer  :  "Justin Bailey" <jgbailey@gmail.com>
-- Stability   :  experimental
-- Portability :  non portable
--
-- HaskellDB is a Haskell library for expressing database queries and
-- operations in a type safe and declarative way. HaskellDB compiles a
-- relational algebra-like syntax into SQL, submits the operations to the
-- database for processing, and returns the results as ordinary Haskell
-- values.
--
-- This is the main module that the user should 
-- import. Beside this module, the user should import a
-- particular database binding (ie. "Database.HaskellDB.HSQL.ODBC") 
-- and database definitions.
--
-- HaskellDB was originally written by Daan Leijen and it's 
-- design is described in the paper Domain Specific Embedded 
-- Compilers, Daan Leijen and Erik Meijer. 2nd USENIX 
-- Conference on Domain-Specific Languages (DSL), Austin, 
-- USA, October 1999 (<http://www.usenix.org/events/dsl99/>).
--
-- This new version of HaskellDB was produced as a student project at
-- Chalmers University of Technology in Gothenburg, Sweden. The aim of the
-- project was to make HaskellDB a practically useful database library.
-- That work is described in 
-- Student Paper: HaskellDB Improved, 
-- Björn Bringert, Anders Höckersten, Conny Andersson, Martin Andersson, 
-- Mary Bergman, Victor Blomqvist and Torbjörn Martin. 
-- In Proceedings of the ACM SIGPLAN 2004 Haskell Workshop, Snowbird, Utah, 
-- USA, September 22, 2004.
-- (<http://haskelldb.sourceforge.net/haskelldb.pdf>)
--
-----------------------------------------------------------
module Database.HaskellDB
  ( Rel, Attr, Expr, ExprAggr, Table, Query, OrderExpr
  -- * Records
  , HasField, Record, Select, ( # ), ( << ), (<<-), (!), (!.)

  -- * Relational operators
  , restrict, table, project, unique
  , union, intersect, divide, minus
  , copy, copyAll, subQuery

  -- * Query expressions
  , (.==.) , (.<>.), (.<.), (.<=.), (.>.), (.>=.)
  , (.&&.) , (.||.)
  , (.*.) , (./.), (.+.), (.-.), (.%.), (.++.)
  , _not, like, _in, cat, _length
  , isNull, notNull, fromNull, fromVal
  , constant, constVal, constNull, constExpr
  , param, namedParam, Args, func
  , queryParams, Param, cast, coerce
  , literal, toStr
  , count, _sum, _max, _min, avg
  , stddev, stddevP, variance, varianceP
  , asc, desc, order
  , top , _case , _default

  -- * Database operations
  , Database				-- abstract
  , query, recCat
  , insert, delete, update, insertQuery
  , tables, describe, transaction
          
  -- * Debugging
  , showQuery, showQueryUnOpt, showSql, showSqlUnOpt
  ) where

import Database.HaskellDB.HDBRec

-- PrimQuery type is imported so that haddock can find it.
import Database.HaskellDB.PrimQuery (PrimQuery)
import Database.HaskellDB.Sql (SqlSelect(SqlSelect, SqlBin), SqlExpr(..), SqlName)
import qualified Database.HaskellDB.Sql as S (SqlSelect(..), Mark(..))
import Database.HaskellDB.Sql.Generate (sqlQuery)
import Database.HaskellDB.Sql.Default (defaultSqlGenerator)
import Database.HaskellDB.Sql.Print (ppSql)
import Database.HaskellDB.Optimize  (optimize)
import Database.HaskellDB.Query
import Database.HaskellDB.Database
import Text.PrettyPrint.HughesPJ (Doc)
import Data.Foldable (foldr')

-- | Represents a query parameter. Left parameters are indexed
-- by position, while right parameters are named.
type Param = Either Int String

-- | Shows the optimized SQL for the query.
instance Show (Query (Rel r)) where
  showsPrec _ query = shows (showSql query)

-- | Shows the optimized 'PrimQuery'.
showQuery :: Query (Rel r) -> String
showQuery = show . optimize . runQuery

-- | Shows the unoptimized 'PrimQuery'.
showQueryUnOpt :: Query (Rel r) -> String
showQueryUnOpt = show . runQuery

-- | Shows the optimized SQL query.
showSql :: Query (Rel r) -> String
showSql = show . ppSql . sqlQuery defaultSqlGenerator . optimize . runQuery 

-- | Shows the unoptimized SQL query.
showSqlUnOpt :: Query (Rel r) -> String
showSqlUnOpt = show . ppSql . sqlQuery defaultSqlGenerator . runQuery

-- | Get paramaters from a query in order.
queryParams :: Query (Rel r) -> [Param]
queryParams q = snd . indexParams . selectParams . toSelect $ q
  where
    -- Use foldr so we don't have to reverse parameter list built.
    indexParams = foldr' renumber (1, [])
    renumber (Just n) (idx, ps) = (idx, Right n : ps)
    renumber Nothing (idx, ps) = (idx + 1, Left idx : ps)
    toSelect = sqlQuery defaultSqlGenerator . optimize . runQuery 
    -- | All parameters that are in the select, in the textual order
    -- they will appear.
    selectParams :: SqlSelect -> [Maybe SqlName]
    selectParams select@(SqlSelect  { S.attrs = a, S.tables = t, S.criteria = c, S.groupby = g, S.orderby = o})
        = (attrParams a ++) . (tableParams t ++) . (criteriaParams c ++) .
            (groupByParams g ++) . orderByParams $ o
      where
        attrParams = getParams (exprParams . snd) 
        tableParams = getParams (selectParams . snd) 
        criteriaParams = getParams exprParams
        groupByParams Nothing = [] 
        groupByParams (Just S.All) = []
        groupByParams (Just (S.Columns cols)) = getParams (exprParams . snd) cols
        orderByParams = getParams (exprParams . fst) 
        getParams :: (a -> [Maybe SqlName]) -> [a] -> [Maybe SqlName]
        getParams f = concatMap f
        -- | All parameters in the expression, in the textual order
        -- in which they will appear.
        exprParams :: SqlExpr -> [Maybe SqlName]
        exprParams (ColumnSqlExpr _) = [] 
        exprParams (ConstSqlExpr _) = []
        exprParams (ParamSqlExpr p _) = [p] 
        exprParams (BinSqlExpr _ l r) = exprParams l ++ exprParams r
        exprParams (PrefixSqlExpr _ e) = exprParams e
        exprParams (PostfixSqlExpr _ e) = exprParams e
        exprParams (FunSqlExpr _ es) = (concatMap exprParams es)
        exprParams (AggrFunSqlExpr _ es) = (concatMap exprParams es)
        exprParams (CaseSqlExpr es e) =
          let caseExprs = concatMap (\(l, r) -> exprParams l ++ exprParams r) es
          in caseExprs ++ exprParams e
        exprParams (ListSqlExpr es) = concatMap exprParams es
        exprParams (ExistsSqlExpr select) = selectParams select
        exprParams PlaceHolderSqlExpr = []
        exprParams (ParensSqlExpr e) = exprParams e
        exprParams (CastSqlExpr _ e) = exprParams e
    selectParams (SqlBin _ l r) = selectParams l ++ selectParams r
    selectParams _ = []
