Build escaped queries in the app itself
universally replace String with Text
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: dbapi
|
name: dbapi
|
||||||
version: 0.2.4.3
|
version: 0.2.4.4
|
||||||
synopsis: The database is your api
|
synopsis: The database is your api
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
|
|||||||
+5
-5
@@ -16,7 +16,7 @@ import Data.Map (intersection, fromList, toList, Map)
|
|||||||
import Data.List (sort)
|
import Data.List (sort)
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import Data.Convertible.Base (convert)
|
import Data.Convertible.Base (convert)
|
||||||
import Data.Text (strip)
|
import Data.Text (strip, Text)
|
||||||
|
|
||||||
import Network.HTTP.Types.Status
|
import Network.HTTP.Types.Status
|
||||||
import Network.HTTP.Types.Header
|
import Network.HTTP.Types.Header
|
||||||
@@ -125,7 +125,7 @@ app conn req respond =
|
|||||||
([table], "POST") ->
|
([table], "POST") ->
|
||||||
jsonBodyAction req (\row -> do
|
jsonBodyAction req (\row -> do
|
||||||
allvals <- insert ver table row conn
|
allvals <- insert ver table row conn
|
||||||
keys <- primaryKeyColumns ver (cs table) conn
|
keys <- map cs <$> primaryKeyColumns ver (cs table) conn
|
||||||
let params = urlEncodeVars $ map (\t -> (fst t, "eq." <> convert (snd t) :: String)) $ toList $ filterByKeys allvals keys
|
let params = urlEncodeVars $ map (\t -> (fst t, "eq." <> convert (snd t) :: String)) $ toList $ filterByKeys allvals keys
|
||||||
return $ responseLBS status201
|
return $ responseLBS status201
|
||||||
[ jsonContentType
|
[ jsonContentType
|
||||||
@@ -210,15 +210,15 @@ respondWithRangedResult rr =
|
|||||||
| (1 + to - from) < total = status206
|
| (1 + to - from) < total = status206
|
||||||
| otherwise = status200
|
| otherwise = status200
|
||||||
|
|
||||||
requestedVersion :: RequestHeaders -> Maybe String
|
requestedVersion :: RequestHeaders -> Maybe Text
|
||||||
requestedVersion hdrs =
|
requestedVersion hdrs =
|
||||||
case verStr of
|
case verStr of
|
||||||
Just [[_, ver]] -> Just ver
|
Just [[_, ver]] -> Just ver
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
where verRegex = "version[ ]*=[ ]*([0-9]+)" :: String
|
where verRegex = "version[ ]*=[ ]*([0-9]+)" :: String
|
||||||
accept = cs <$> lookup hAccept hdrs :: Maybe String
|
accept = cs <$> lookup hAccept hdrs :: Maybe Text
|
||||||
verStr = (=~ verRegex) <$> accept :: Maybe [[String]]
|
verStr = (=~ verRegex) <$> accept :: Maybe [[Text]]
|
||||||
|
|
||||||
|
|
||||||
addHeaders :: ResponseHeaders -> Response -> Response
|
addHeaders :: ResponseHeaders -> Response -> Response
|
||||||
|
|||||||
+55
-69
@@ -11,8 +11,8 @@ module PgQuery (
|
|||||||
, setRole
|
, setRole
|
||||||
, resetRole
|
, resetRole
|
||||||
, checkPass
|
, checkPass
|
||||||
, pgFormatIdentifier
|
, pgFmtIdent
|
||||||
, pgFormatLiteral
|
, pgFmtLit
|
||||||
, RangedResult(..)
|
, RangedResult(..)
|
||||||
, LoginAttempt(..)
|
, LoginAttempt(..)
|
||||||
, DbRole
|
, DbRole
|
||||||
@@ -52,8 +52,7 @@ data RangedResult = RangedResult {
|
|||||||
, rrBody :: BL.ByteString
|
, rrBody :: BL.ByteString
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
type QuotedSql = (Text, [SqlValue])
|
type Schema = Text
|
||||||
type Schema = String
|
|
||||||
type DbRole = BS.ByteString
|
type DbRole = BS.ByteString
|
||||||
|
|
||||||
data LoginAttempt =
|
data LoginAttempt =
|
||||||
@@ -63,15 +62,8 @@ data LoginAttempt =
|
|||||||
| LoginSuccess DbRole
|
| LoginSuccess DbRole
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
getRows :: Schema -> String -> Net.Query -> Maybe R.NonnegRange -> Connection -> IO RangedResult
|
getRows :: Schema -> Text -> Net.Query -> Maybe R.NonnegRange -> Connection -> IO RangedResult
|
||||||
getRows schema table qq range conn = do
|
getRows schema table qq range conn = do
|
||||||
query <- populateSql conn
|
|
||||||
$ globalAndLimitedCounts schema table qq <>
|
|
||||||
jsonArrayRows
|
|
||||||
(selectStarClause schema table
|
|
||||||
<> whereClause qq
|
|
||||||
<> orderClause qq
|
|
||||||
<> limitClause range)
|
|
||||||
r <- quickQuery conn (cs query) []
|
r <- quickQuery conn (cs query) []
|
||||||
|
|
||||||
return $ case r of
|
return $ case r of
|
||||||
@@ -83,26 +75,31 @@ getRows schema table qq range conn = do
|
|||||||
|
|
||||||
where
|
where
|
||||||
offset = fromMaybe 0 $ R.offset <$> range
|
offset = fromMaybe 0 $ R.offset <$> range
|
||||||
|
query = globalAndLimitedCounts schema table qq <> jsonArrayRows (
|
||||||
|
selectStarClause schema table
|
||||||
|
<> whereClause qq
|
||||||
|
<> orderClause qq
|
||||||
|
<> limitClause range)
|
||||||
|
|
||||||
|
|
||||||
whereClause :: Net.Query -> QuotedSql
|
whereClause :: Net.Query -> Text
|
||||||
whereClause qs =
|
whereClause qs =
|
||||||
if null qs then ("", []) else (" where ", []) <> conjunction
|
if null qs then "" else " where " <> conjunction
|
||||||
|
|
||||||
where
|
where
|
||||||
cols = [ col | col <- qs, fst col `notElem` ["order"] ]
|
cols = [ col | col <- qs, fst col `notElem` ["order"] ]
|
||||||
conjunction = mconcat $ L.intersperse (" and ", []) (map wherePred cols)
|
conjunction = mconcat $ L.intersperse " and " (map wherePred cols)
|
||||||
|
|
||||||
|
|
||||||
orderClause :: Net.Query -> QuotedSql
|
orderClause :: Net.Query -> Text
|
||||||
orderClause qs = do
|
orderClause qs = do
|
||||||
let order = fromMaybe "" $ join $ lookup "order" qs
|
let order = fromMaybe "" $ join $ lookup "order" qs
|
||||||
terms = mapMaybe parseOrderTerm $ splitOn "," $ cs order
|
terms = mapMaybe parseOrderTerm $ splitOn "," $ cs order
|
||||||
termPred = mconcat $ L.intersperse (", ", []) (map orderTermSql terms)
|
termPred = mconcat $ L.intersperse ", " (map orderTermSql terms)
|
||||||
|
|
||||||
if null terms
|
if null terms
|
||||||
then ("", [])
|
then ""
|
||||||
else (" order by ", []) <> termPred
|
else " order by " <> termPred
|
||||||
|
|
||||||
where
|
where
|
||||||
parseOrderTerm :: Text -> Maybe OrderTerm
|
parseOrderTerm :: Text -> Maybe OrderTerm
|
||||||
@@ -114,9 +111,8 @@ orderClause qs = do
|
|||||||
else Nothing
|
else Nothing
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
orderTermSql :: OrderTerm -> QuotedSql
|
orderTermSql :: OrderTerm -> Text
|
||||||
orderTermSql t =
|
orderTermSql t = pgFmtIdent (otColumn t) <> " " <> otDirection t
|
||||||
("%I " <> otDirection t, [toSql $ otColumn t])
|
|
||||||
|
|
||||||
|
|
||||||
data OrderTerm = OrderTerm {
|
data OrderTerm = OrderTerm {
|
||||||
@@ -125,9 +121,9 @@ data OrderTerm = OrderTerm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
wherePred :: Net.QueryItem -> QuotedSql
|
wherePred :: Net.QueryItem -> Text
|
||||||
wherePred (column, predicate) =
|
wherePred (column, predicate) =
|
||||||
("%I " <> op <> "%L", map toSql [column, value])
|
pgFmtIdent (cs column) <> " " <> op <> " " <> pgFmtLit (cs value)
|
||||||
|
|
||||||
where
|
where
|
||||||
opCode:rest = BS.split '.' $ fromMaybe "." predicate
|
opCode:rest = BS.split '.' $ fromMaybe "." predicate
|
||||||
@@ -141,37 +137,38 @@ wherePred (column, predicate) =
|
|||||||
"neq" -> "<>"
|
"neq" -> "<>"
|
||||||
_ -> "="
|
_ -> "="
|
||||||
|
|
||||||
limitClause :: Maybe R.NonnegRange -> QuotedSql
|
limitClause :: Maybe R.NonnegRange -> Text
|
||||||
limitClause range =
|
limitClause range =
|
||||||
(" LIMIT %s OFFSET %s ", [toSql limit, toSql offset])
|
cs $ " LIMIT " <> limit <> " OFFSET " <> show offset <> " "
|
||||||
|
|
||||||
where
|
where
|
||||||
limit = fromMaybe "ALL" $ show <$> (R.limit =<< range)
|
limit = fromMaybe "ALL" $ show <$> (R.limit =<< range)
|
||||||
offset = fromMaybe 0 $ R.offset <$> range
|
offset = fromMaybe 0 $ R.offset <$> range
|
||||||
|
|
||||||
globalAndLimitedCounts :: Schema -> String -> Net.Query -> QuotedSql
|
globalAndLimitedCounts :: Schema -> Text -> Net.Query -> Text
|
||||||
globalAndLimitedCounts schema table qq =
|
globalAndLimitedCounts schema table qq =
|
||||||
(" select ", [])
|
" select "
|
||||||
<> ("(select count(1) from %I.%I ", map toSql [schema, table])
|
<> "(select count(1) from " <> pgFmtIdent schema <> "." <> pgFmtIdent table <> " "
|
||||||
<> whereClause qq
|
<> whereClause qq
|
||||||
<> ("), count(t), ", [])
|
<> "), count(t), "
|
||||||
|
|
||||||
selectStarClause :: Schema -> String -> QuotedSql
|
selectStarClause :: Schema -> Text -> Text
|
||||||
selectStarClause schema table =
|
selectStarClause schema table =
|
||||||
(" select * from %I.%I ", map toSql [schema, table])
|
" select * from " <> pgFmtIdent schema <> "." <> pgFmtIdent table <> " "
|
||||||
|
|
||||||
jsonArrayRows :: QuotedSql -> QuotedSql
|
jsonArrayRows :: Text -> Text
|
||||||
jsonArrayRows q =
|
jsonArrayRows q =
|
||||||
("array_to_json(array_agg(row_to_json(t))) from (", []) <> q <> (") t", [])
|
"array_to_json(array_agg(row_to_json(t))) from (" <> q <> ") t"
|
||||||
|
|
||||||
insert :: Schema -> Text -> SqlRow -> Connection -> IO (M.Map String SqlValue)
|
insert :: Schema -> Text -> SqlRow -> Connection -> IO (M.Map String SqlValue)
|
||||||
insert schema table row conn = do
|
insert schema table row conn = do
|
||||||
sql <- populateSql conn $ insertClause schema table row
|
|
||||||
stmt <- prepare conn $ cs sql
|
stmt <- prepare conn $ cs sql
|
||||||
_ <- execute stmt $ sqlRowValues row
|
_ <- execute stmt $ sqlRowValues row
|
||||||
Just m <- fetchRowMap stmt
|
Just m <- fetchRowMap stmt
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
where sql = insertClause schema table row
|
||||||
|
|
||||||
addUser :: BS.ByteString -> BS.ByteString -> BS.ByteString -> Connection -> IO ()
|
addUser :: BS.ByteString -> BS.ByteString -> BS.ByteString -> Connection -> IO ()
|
||||||
addUser identity pass role conn = do
|
addUser identity pass role conn = do
|
||||||
Just hashed <- hashPasswordUsingPolicy fastBcryptHashingPolicy $ cs pass
|
Just hashed <- hashPasswordUsingPolicy fastBcryptHashingPolicy $ cs pass
|
||||||
@@ -195,56 +192,45 @@ checkPass = validatePassword
|
|||||||
|
|
||||||
upsert :: Schema -> Text -> SqlRow -> Net.Query -> Connection -> IO (M.Map String SqlValue)
|
upsert :: Schema -> Text -> SqlRow -> Net.Query -> Connection -> IO (M.Map String SqlValue)
|
||||||
upsert schema table row qq conn = do
|
upsert schema table row qq conn = do
|
||||||
sql <- populateSql conn $ upsertClause schema table row qq
|
|
||||||
stmt <- prepare conn $ cs sql
|
stmt <- prepare conn $ cs sql
|
||||||
_ <- execute stmt $ join $ replicate 2 $ sqlRowValues row
|
_ <- execute stmt $ join $ replicate 2 $ sqlRowValues row
|
||||||
Just m <- fetchRowMap stmt
|
Just m <- fetchRowMap stmt
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
where sql = upsertClause schema table row qq
|
||||||
|
|
||||||
placeholders :: Text -> SqlRow -> Text
|
placeholders :: Text -> SqlRow -> Text
|
||||||
placeholders symbol = intercalate ", " . map (const symbol) . getRow
|
placeholders symbol = intercalate ", " . map (const symbol) . getRow
|
||||||
|
|
||||||
insertClause :: Schema -> Text -> SqlRow -> QuotedSql
|
insertClause :: Schema -> Text -> SqlRow -> Text
|
||||||
insertClause schema table (SqlRow []) =
|
insertClause schema table (SqlRow []) =
|
||||||
("insert into %I.%I default values returning *", [toSql schema, toSql table])
|
"insert into " <> pgFmtIdent schema <> "." <> pgFmtIdent table <> " default values returning *"
|
||||||
insertClause schema table row =
|
insertClause schema table row =
|
||||||
("insert into %I.%I (" <> placeholders "%I" row <> ")",
|
"insert into " <> pgFmtIdent schema <> "." <> pgFmtIdent table <> " (" <>
|
||||||
map toSql $ cs schema : table : sqlRowColumns row)
|
intercalate ", " (map pgFmtIdent (sqlRowColumns row))
|
||||||
<> (" values (" <> placeholders "?" row <> ") returning *", sqlRowValues row)
|
<> ") values (" <> placeholders "?" row <> ") returning *"
|
||||||
|
|
||||||
|
insertClauseViaSelect :: Schema -> Text -> SqlRow -> Text
|
||||||
insertClauseViaSelect :: Schema -> Text -> SqlRow -> QuotedSql
|
|
||||||
insertClauseViaSelect schema table row =
|
insertClauseViaSelect schema table row =
|
||||||
("insert into %I.%I (" <> placeholders "%I" row <> ")",
|
"insert into " <> pgFmtIdent schema <> "." <> pgFmtIdent table <> " (" <>
|
||||||
map toSql $ cs schema : table : sqlRowColumns row)
|
intercalate ", " (map pgFmtIdent (sqlRowColumns row))
|
||||||
<> (" select " <> placeholders "?" row, sqlRowValues row)
|
<> ") select " <> placeholders "?" row
|
||||||
|
|
||||||
updateClause :: Schema -> Text -> SqlRow -> QuotedSql
|
updateClause :: Schema -> Text -> SqlRow -> Text
|
||||||
updateClause schema table row =
|
updateClause schema table row =
|
||||||
("update %I.%I set (" <> placeholders "%I" row <> ")",
|
"update " <> pgFmtIdent schema <> "." <> pgFmtIdent table <> " set (" <>
|
||||||
map toSql $ cs schema : table : sqlRowColumns row)
|
intercalate ", " (map pgFmtIdent (sqlRowColumns row))
|
||||||
<> (" = (" <> placeholders "?" row <> ")", [])
|
<> ") = (" <> placeholders "?" row <> ")"
|
||||||
|
|
||||||
upsertClause :: Schema -> Text -> SqlRow -> Net.Query -> QuotedSql
|
upsertClause :: Schema -> Text -> SqlRow -> Net.Query -> Text
|
||||||
upsertClause schema table row qq =
|
upsertClause schema table row qq =
|
||||||
("with upsert as (", []) <> updateClause schema table row
|
"with upsert as (" <> updateClause schema table row
|
||||||
<> whereClause qq
|
<> whereClause qq
|
||||||
<> (" returning *) ", []) <> insertClauseViaSelect schema table row
|
<> " returning *) " <> insertClauseViaSelect schema table row
|
||||||
<> (" where not exists (select * from upsert) returning *", [])
|
<> " where not exists (select * from upsert) returning *"
|
||||||
|
|
||||||
populateSql :: Connection -> QuotedSql -> IO Text
|
pgFmtIdent :: Text -> Text
|
||||||
populateSql conn sql = do
|
pgFmtIdent x =
|
||||||
[[escaped]] <- quickQuery conn (cs q) (snd sql)
|
|
||||||
return $ fromSql escaped
|
|
||||||
|
|
||||||
where
|
|
||||||
q = mconcat [ "select format('", fst sql, "', ", ph (snd sql), ")" ]
|
|
||||||
|
|
||||||
ph :: [a] -> Text
|
|
||||||
ph = intercalate ", " . map (const "?::varchar")
|
|
||||||
|
|
||||||
pgFormatIdentifier :: Text -> Text
|
|
||||||
pgFormatIdentifier x =
|
|
||||||
let escaped = replace "\"" "\"\"" (trimNullChars x) in
|
let escaped = replace "\"" "\"\"" (trimNullChars x) in
|
||||||
if escaped =~ danger
|
if escaped =~ danger
|
||||||
then "\"" <> escaped <> "\""
|
then "\"" <> escaped <> "\""
|
||||||
@@ -252,8 +238,8 @@ pgFormatIdentifier x =
|
|||||||
|
|
||||||
where danger = "^$|^[^a-z_]|[^a-z_0-9]" :: Text
|
where danger = "^$|^[^a-z_]|[^a-z_0-9]" :: Text
|
||||||
|
|
||||||
pgFormatLiteral :: Text -> Text
|
pgFmtLit :: Text -> Text
|
||||||
pgFormatLiteral x =
|
pgFmtLit x =
|
||||||
let trimmed = trimNullChars x
|
let trimmed = trimNullChars x
|
||||||
escaped = "'" <> replace "'" "''" trimmed <> "'"
|
escaped = "'" <> replace "'" "''" trimmed <> "'"
|
||||||
slashed = replace "\\" "\\\\" escaped in
|
slashed = replace "\\" "\\\\" escaped in
|
||||||
|
|||||||
+24
-22
@@ -4,11 +4,13 @@ module PgStructure where
|
|||||||
|
|
||||||
import Data.Functor ( (<$>) )
|
import Data.Functor ( (<$>) )
|
||||||
import Data.Maybe (mapMaybe)
|
import Data.Maybe (mapMaybe)
|
||||||
|
import Data.Text hiding (foldl, map, zipWith, concat)
|
||||||
|
import Data.Monoid ((<>))
|
||||||
|
import Data.String.Conversions (cs)
|
||||||
|
|
||||||
import Control.Applicative ( (<*>) )
|
import Control.Applicative ( (<*>) )
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as BL
|
import qualified Data.ByteString.Lazy as BL
|
||||||
import Data.List.Split (splitOn)
|
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
@@ -19,8 +21,8 @@ import Database.HDBC.PostgreSQL
|
|||||||
import Data.Aeson ((.=))
|
import Data.Aeson ((.=))
|
||||||
|
|
||||||
data Table = Table {
|
data Table = Table {
|
||||||
tableSchema :: String
|
tableSchema :: Text
|
||||||
, tableName :: String
|
, tableName :: Text
|
||||||
, tableInsertable :: Bool
|
, tableInsertable :: Bool
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
@@ -30,17 +32,17 @@ instance JSON.ToJSON Table where
|
|||||||
, "name" .= tableName v
|
, "name" .= tableName v
|
||||||
, "insertable" .= tableInsertable v ]
|
, "insertable" .= tableInsertable v ]
|
||||||
|
|
||||||
toBool :: String -> Bool
|
toBool :: Text -> Bool
|
||||||
toBool = (== "YES")
|
toBool = (== "YES")
|
||||||
|
|
||||||
data ForeignKey = ForeignKey {
|
data ForeignKey = ForeignKey {
|
||||||
fkTable::String, fkCol::String
|
fkTable::Text, fkCol::Text
|
||||||
} deriving (Eq, Show)
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
instance JSON.ToJSON ForeignKey where
|
instance JSON.ToJSON ForeignKey where
|
||||||
toJSON fk = JSON.object ["table".=fkTable fk, "column".=fkCol fk]
|
toJSON fk = JSON.object ["table".=fkTable fk, "column".=fkCol fk]
|
||||||
|
|
||||||
foreignKeys :: String -> String -> Connection -> IO (Map.Map String ForeignKey)
|
foreignKeys :: Text -> Text -> Connection -> IO (Map.Map Text ForeignKey)
|
||||||
foreignKeys schema table conn = do
|
foreignKeys schema table conn = do
|
||||||
r <- quickQuery conn
|
r <- quickQuery conn
|
||||||
"select kcu.column_name, ccu.table_name AS foreign_table_name,\
|
"select kcu.column_name, ccu.table_name AS foreign_table_name,\
|
||||||
@@ -59,17 +61,17 @@ foreignKeys schema table conn = do
|
|||||||
addKey m _ = m --should never happen
|
addKey m _ = m --should never happen
|
||||||
|
|
||||||
data Column = Column {
|
data Column = Column {
|
||||||
colSchema :: String
|
colSchema :: Text
|
||||||
, colTable :: String
|
, colTable :: Text
|
||||||
, colName :: String
|
, colName :: Text
|
||||||
, colPosition :: Int
|
, colPosition :: Int
|
||||||
, colNullable :: Bool
|
, colNullable :: Bool
|
||||||
, colType :: String
|
, colType :: Text
|
||||||
, colUpdatable :: Bool
|
, colUpdatable :: Bool
|
||||||
, colMaxLen :: Maybe Int
|
, colMaxLen :: Maybe Int
|
||||||
, colPrecision :: Maybe Int
|
, colPrecision :: Maybe Int
|
||||||
, colDefault :: Maybe String
|
, colDefault :: Maybe Text
|
||||||
, colEnum :: Maybe [String]
|
, colEnum :: Maybe [Text]
|
||||||
, colFK :: Maybe ForeignKey
|
, colFK :: Maybe ForeignKey
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
@@ -89,7 +91,7 @@ instance JSON.ToJSON Column where
|
|||||||
|
|
||||||
data TableOptions = TableOptions {
|
data TableOptions = TableOptions {
|
||||||
tblOptcolumns :: [Column]
|
tblOptcolumns :: [Column]
|
||||||
, tblOptpkey :: [String]
|
, tblOptpkey :: [Text]
|
||||||
}
|
}
|
||||||
|
|
||||||
instance JSON.ToJSON TableOptions where
|
instance JSON.ToJSON TableOptions where
|
||||||
@@ -97,7 +99,7 @@ instance JSON.ToJSON TableOptions where
|
|||||||
"columns" .= tblOptcolumns t
|
"columns" .= tblOptcolumns t
|
||||||
, "pkey" .= tblOptpkey t ]
|
, "pkey" .= tblOptpkey t ]
|
||||||
|
|
||||||
tables :: String -> Connection -> IO [Table]
|
tables :: Text -> Connection -> IO [Table]
|
||||||
tables s conn = do
|
tables s conn = do
|
||||||
r <- quickQuery conn
|
r <- quickQuery conn
|
||||||
"select table_schema, table_name,\
|
"select table_schema, table_name,\
|
||||||
@@ -114,7 +116,7 @@ tables s conn = do
|
|||||||
(toBool (fromSql insertable))
|
(toBool (fromSql insertable))
|
||||||
mkTable _ = Nothing
|
mkTable _ = Nothing
|
||||||
|
|
||||||
columns :: String -> String -> Connection -> IO [Column]
|
columns :: Text -> Text -> Connection -> IO [Column]
|
||||||
columns s t conn = do
|
columns s t conn = do
|
||||||
r <- quickQuery conn
|
r <- quickQuery conn
|
||||||
"select info.table_schema as schema, info.table_name as table_name, \
|
"select info.table_schema as schema, info.table_name as table_name, \
|
||||||
@@ -161,23 +163,23 @@ columns s t conn = do
|
|||||||
(fromSql maxlen)
|
(fromSql maxlen)
|
||||||
(fromSql precision)
|
(fromSql precision)
|
||||||
(fromSql defVal)
|
(fromSql defVal)
|
||||||
(splitOn "," <$> fromSql enum)
|
(Data.Text.splitOn "," <$> fromSql enum)
|
||||||
mkColumn _ = error $ "Incomplete column data received for table " ++
|
mkColumn _ = error $ "Incomplete column data received for table " <>
|
||||||
t ++ " in schema " ++ s ++ "."
|
cs t <> " in schema " <> cs s <> "."
|
||||||
|
|
||||||
printTables :: String -> Connection -> IO BL.ByteString
|
printTables :: Text -> Connection -> IO BL.ByteString
|
||||||
printTables schema conn = JSON.encode <$> tables schema conn
|
printTables schema conn = JSON.encode <$> tables schema conn
|
||||||
|
|
||||||
printColumns :: String -> String -> Connection -> IO BL.ByteString
|
printColumns :: Text -> Text -> Connection -> IO BL.ByteString
|
||||||
printColumns schema table conn =
|
printColumns schema table conn =
|
||||||
JSON.encode <$> (TableOptions <$> cols <*> pkey)
|
JSON.encode <$> (TableOptions <$> cols <*> pkey)
|
||||||
where
|
where
|
||||||
cols :: IO [Column]
|
cols :: IO [Column]
|
||||||
cols = columns schema table conn
|
cols = columns schema table conn
|
||||||
pkey :: IO [String]
|
pkey :: IO [Text]
|
||||||
pkey = primaryKeyColumns schema table conn
|
pkey = primaryKeyColumns schema table conn
|
||||||
|
|
||||||
primaryKeyColumns :: String -> String -> Connection -> IO [String]
|
primaryKeyColumns :: Text -> Text -> Connection -> IO [Text]
|
||||||
primaryKeyColumns s t conn = do
|
primaryKeyColumns s t conn = do
|
||||||
r <- quickQuery conn
|
r <- quickQuery conn
|
||||||
"select kc.column_name \
|
"select kc.column_name \
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Database.HDBC (IConnection, SqlValue, toSql, prepare,
|
|||||||
quickQuery, fromSql, execute, seState, fetchAllRowsAL)
|
quickQuery, fromSql, execute, seState, fetchAllRowsAL)
|
||||||
|
|
||||||
import PgQuery (LoginAttempt(..), insert, addUser, signInRole, checkPass
|
import PgQuery (LoginAttempt(..), insert, addUser, signInRole, checkPass
|
||||||
, pgFormatIdentifier, pgFormatLiteral)
|
, pgFmtIdent, pgFmtLit)
|
||||||
import Types (SqlRow(SqlRow))
|
import Types (SqlRow(SqlRow))
|
||||||
import TestTypes (fromList, incStr, incNullableStr, incInsert, incId)
|
import TestTypes (fromList, incStr, incNullableStr, incInsert, incId)
|
||||||
import Data.Map (toList)
|
import Data.Map (toList)
|
||||||
@@ -87,16 +87,16 @@ spec = around dbWithSchema $ do
|
|||||||
signInRole "not-a-user" pass conn `shouldReturn` LoginFailed
|
signInRole "not-a-user" pass conn `shouldReturn` LoginFailed
|
||||||
signInRole user (pass <> "crap") conn `shouldReturn` LoginFailed
|
signInRole user (pass <> "crap") conn `shouldReturn` LoginFailed
|
||||||
|
|
||||||
describe "pgFormatIdentifier" $
|
describe "pgFmtIdent" $
|
||||||
it "Does what format %I would do" $ \conn ->
|
it "Does what format %I would do" $ \conn ->
|
||||||
property $ monadicIO $ do
|
property $ monadicIO $ do
|
||||||
fuzz <- pick arbitrary
|
fuzz <- pick arbitrary
|
||||||
[[row]] <- run $ quickALQuery conn "select format('%I', ? :: varchar)" [toSql (fuzz :: String)]
|
[[row]] <- run $ quickALQuery conn "select format('%I', ? :: varchar)" [toSql (fuzz :: String)]
|
||||||
assert $ fromSql (snd row) == pgFormatIdentifier (cs fuzz)
|
assert $ fromSql (snd row) == pgFmtIdent (cs fuzz)
|
||||||
|
|
||||||
describe "pgFormatLiteral" $
|
describe "pgFmtLit" $
|
||||||
it "Does what format %L would do" $ \conn ->
|
it "Does what format %L would do" $ \conn ->
|
||||||
property $ monadicIO $ do
|
property $ monadicIO $ do
|
||||||
fuzz <- pick arbitrary
|
fuzz <- pick arbitrary
|
||||||
[[row]] <- run $ quickALQuery conn "select format('%L', ? :: varchar)" [toSql (fuzz :: String)]
|
[[row]] <- run $ quickALQuery conn "select format('%L', ? :: varchar)" [toSql (fuzz :: String)]
|
||||||
assert $ fromSql (snd row) == pgFormatLiteral (cs fuzz)
|
assert $ fromSql (snd row) == pgFmtLit (cs fuzz)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
module Unit.PgStructureSpec where
|
module Unit.PgStructureSpec where
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|||||||
Reference in New Issue
Block a user