Merge remote-tracking branch 'begriffs/master'

This commit is contained in:
Ruslan Talpa
2015-09-07 09:28:31 +03:00
14 changed files with 232 additions and 189 deletions
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
### Added
- Filter columns, e.g. `?select=col1,col2` - @ruslantalpa
## [0.2.11.1] - 2015-09-01
### Fixed
- Accepts `*/*` in Accept header - @diogob
## [0.2.11.0] - 2015-08-28 ## [0.2.11.0] - 2015-08-28
### Added ### Added
- Negate any filter in a uniform way, e.g. `?col=not.eq=foo` - @diogob - Negate any filter in a uniform way, e.g. `?col=not.eq=foo` - @diogob
+11 -1
View File
@@ -10,7 +10,7 @@
}, },
"POSTGREST_VER": { "POSTGREST_VER": {
"description": "Version of PostgREST to deploy", "description": "Version of PostgREST to deploy",
"value": "0.2.11.0" "value": "0.2.11.1"
}, },
"DB_NAME": { "DB_NAME": {
"description": "Database name", "description": "Database name",
@@ -41,6 +41,16 @@
"description": "Maximum number of connections in database pool", "description": "Maximum number of connections in database pool",
"required": false, "required": false,
"value": "10" "value": "10"
},
"JWT_SECRET": {
"description": "Secret used to encrypt JSON Web Tokens",
"required": false,
"value": "secret"
},
"V1SCHEMA": {
"description": "DB schema selected whe no version (or version 1) requested",
"required": false,
"value": "1"
} }
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@ name: postgrest
description: Reads the schema of a PostgreSQL database and creates RESTful routes description: Reads the schema of a PostgreSQL database and creates RESTful routes
for the tables and views, supporting all HTTP verbs that security for the tables and views, supporting all HTTP verbs that security
permits. permits.
version: 0.2.11.0 version: 0.2.11.1
synopsis: REST API for any Postgres database synopsis: REST API for any Postgres database
license: MIT license: MIT
license-file: LICENSE license-file: LICENSE
+1 -1
View File
@@ -1,6 +1,6 @@
export POSTGREST_VER=`grep ^version /app/postgrest.cabal | sed -En 's/.*\s+([0-9\.]+)/\1/p'` export POSTGREST_VER=`grep ^version /app/postgrest.cabal | sed -En 's/.*\s+([0-9\.]+)/\1/p'`
curl -L http://softlayer-ams.dl.sourceforge.net/project/s3tools/s3cmd/1.5.0-alpha1/s3cmd-1.5.0-alpha1.tar.gz | tar zx curl -L http://sourceforge.net/projects/s3tools/files/s3cmd/1.5.0-alpha1/s3cmd-1.5.0-alpha1.tar.gz | tar zx
cp /app/dist/build/postgrest/postgrest postgrest-${POSTGREST_VER} cp /app/dist/build/postgrest/postgrest postgrest-${POSTGREST_VER}
tar cJf postgrest-${POSTGREST_VER}.tar.xz postgrest-${POSTGREST_VER} tar cJf postgrest-${POSTGREST_VER}.tar.xz postgrest-${POSTGREST_VER}
+12 -10
View File
@@ -119,8 +119,7 @@ app conf reqBody req =
encode . object $ [("message", String "Failed to parse user.")] encode . object $ [("message", String "Failed to parse user.")]
Just u -> do Just u -> do
setRole authenticator setRole authenticator
login <- signInRole (cs $ userId u) login <- signInRole (cs $ userId u) (cs $ userPass u)
(cs $ userPass u)
case login of case login of
LoginSuccess role uid -> LoginSuccess role uid ->
return $ responseLBS status201 [ jsonH ] $ return $ responseLBS status201 [ jsonH ] $
@@ -271,9 +270,9 @@ contentRangeH from to total =
("Content-Range", ("Content-Range",
if total == 0 || from > total if total == 0 || from > total
then "*/" <> cs (show total) then "*/" <> cs (show total)
else cs (show from) <> "-" else cs (show from)
<> cs (show to) <> "/" <> "-" <> cs (show to)
<> cs (show total) <> "/" <> cs (show total)
) )
requestedSchema :: Text -> Maybe BS.ByteString -> Text requestedSchema :: Text -> Maybe BS.ByteString -> Text
@@ -282,7 +281,8 @@ requestedSchema v1schema accept =
Just [[_, ver]] -> if ver == "1" then v1schema else cs ver Just [[_, ver]] -> if ver == "1" then v1schema else cs ver
_ -> v1schema _ -> v1schema
where verRegex = "version[ ]*=[ ]*([0-9]+)" :: BS.ByteString where
verRegex = "version[ ]*=[ ]*([0-9]+)" :: BS.ByteString
verStr = (=~ verRegex) <$> accept :: Maybe [[BS.ByteString]] verStr = (=~ verRegex) <$> accept :: Maybe [[BS.ByteString]]
@@ -292,19 +292,21 @@ jsonMT = "application/json"
csvMT :: BS.ByteString csvMT :: BS.ByteString
csvMT = "text/csv" csvMT = "text/csv"
allMT :: BS.ByteString
allMT = "*/*"
jsonH :: Header jsonH :: Header
jsonH = (hContentType, jsonMT) jsonH = (hContentType, jsonMT)
contentTypeForAccept :: Maybe BS.ByteString -> Maybe BS.ByteString contentTypeForAccept :: Maybe BS.ByteString -> Maybe BS.ByteString
contentTypeForAccept accept contentTypeForAccept accept
| isNothing accept || hasJson = Just jsonMT | isNothing accept || has allMT || has jsonMT = Just jsonMT
| hasCsv = Just csvMT | has csvMT = Just csvMT
| otherwise = Nothing | otherwise = Nothing
where where
Just acceptH = accept Just acceptH = accept
findInAccept = flip find $ parseHttpAccept acceptH findInAccept = flip find $ parseHttpAccept acceptH
hasJson = isJust $ findInAccept $ BS.isPrefixOf jsonMT has = isJust . findInAccept . BS.isPrefixOf
hasCsv = isJust $ findInAccept $ BS.isPrefixOf csvMT
bodyForAccept :: BS.ByteString -> QualifiedIdentifier -> StatementT bodyForAccept :: BS.ByteString -> QualifiedIdentifier -> StatementT
bodyForAccept contentType table bodyForAccept contentType table
+4 -4
View File
@@ -56,10 +56,10 @@ setRole :: Text -> H.Tx P.Postgres s ()
setRole role = H.unitEx $ B.Stmt ("set local role " <> cs (pgFmtLit role)) V.empty True setRole role = H.unitEx $ B.Stmt ("set local role " <> cs (pgFmtLit role)) V.empty True
setUserId :: Text -> H.Tx P.Postgres s () setUserId :: Text -> H.Tx P.Postgres s ()
setUserId uid = if uid /= "" then setUserId uid =
H.unitEx $ B.Stmt ("set local user_vars.user_id = " <> cs (pgFmtLit uid)) V.empty True if uid /= ""
else then H.unitEx $ B.Stmt ("set local user_vars.user_id = " <> cs (pgFmtLit uid)) V.empty True
resetUserId else resetUserId
resetUserId :: H.Tx P.Postgres s () resetUserId :: H.Tx P.Postgres s ()
resetUserId = H.unitEx [H.stmt|reset user_vars.user_id|] resetUserId = H.unitEx [H.stmt|reset user_vars.user_id|]
+6 -3
View File
@@ -65,11 +65,14 @@ main = do
poolSettings <- maybe (fail "Improper session settings") return $ poolSettings <- maybe (fail "Improper session settings") return $
H.poolSettings (fromIntegral $ configPool conf) 30 H.poolSettings (fromIntegral $ configPool conf) 30
pool :: H.Pool P.Postgres pool :: H.Pool P.Postgres <- H.acquirePool pgSettings poolSettings
<- H.acquirePool pgSettings poolSettings
resOrError <- H.session pool isServerVersionSupported resOrError <- H.session pool isServerVersionSupported
either (fail . show) (\supported -> unless supported $ fail "Cannot run in this PostgreSQL version, PostgREST needs at least 9.2.0") resOrError either (fail . show)
(\supported ->
unless supported $
fail "Cannot run in this PostgreSQL version, PostgREST needs at least 9.2.0"
) resOrError
runSettings appSettings $ middle $ \req respond -> do runSettings appSettings $ middle $ \req respond -> do
body <- strictRequestBody req body <- strictRequestBody req
+13 -7
View File
@@ -105,7 +105,8 @@ asCsvWithCount :: QualifiedIdentifier -> StatementT
asCsvWithCount table = withCount . asCsv table asCsvWithCount table = withCount . asCsv table
asCsv :: QualifiedIdentifier -> StatementT asCsv :: QualifiedIdentifier -> StatementT
asCsv table s = s { B.stmtTemplate = asCsv table s = s {
B.stmtTemplate =
"(select string_agg(quote_ident(column_name::text), ',') from " "(select string_agg(quote_ident(column_name::text), ',') from "
<> "(select column_name from information_schema.columns where quote_ident(table_schema) || '.' || table_name = '" <> "(select column_name from information_schema.columns where quote_ident(table_schema) || '.' || table_name = '"
<> fromQi table <> "' order by ordinal_position) h) || '\r' || " <> fromQi table <> "' order by ordinal_position) h) || '\r' || "
@@ -116,7 +117,8 @@ asJsonWithCount :: StatementT
asJsonWithCount = withCount . asJson asJsonWithCount = withCount . asJson
asJson :: StatementT asJson :: StatementT
asJson s = s { B.stmtTemplate = asJson s = s {
B.stmtTemplate =
"array_to_json(array_agg(row_to_json(t)))::character varying from (" "array_to_json(array_agg(row_to_json(t)))::character varying from ("
<> B.stmtTemplate s <> ") t" } <> B.stmtTemplate s <> ") t" }
@@ -143,9 +145,13 @@ select table params =
selectTerm :: QualifiedIdentifier -> T.Text -> PStmt selectTerm :: QualifiedIdentifier -> T.Text -> PStmt
selectTerm table col = selectTerm table col =
case T.splitOn "::" col of case T.splitOn "::" col of
[colName,castTo] -> B.Stmt ("CAST (" <> pgFmtJsonbPath table (cs colName) <> " AS " <> castToSafe <> " )" <> asT (jsonbPath colName)) empty True [colName,castTo] ->
B.Stmt (
"CAST (" <> pgFmtJsonbPath table (cs colName) <> " AS "
<> castToSafe <> " )" <> asT (jsonbPath colName)
) empty True
where castToSafe = T.filter ( `elem` ['a'..'z'] ) castTo where castToSafe = T.filter ( `elem` ['a'..'z'] ) castTo
_-> B.Stmt (pgFmtJsonbPath table (cs col) <> asT (jsonbPath col)) empty True _ -> B.Stmt (pgFmtJsonbPath table (cs col) <> asT (jsonbPath col)) empty True
where where
jsonbPath :: T.Text -> Maybe JsonbPath jsonbPath :: T.Text -> Maybe JsonbPath
jsonbPath c = parseJsonbPath $ cs c jsonbPath c = parseJsonbPath $ cs c
@@ -216,9 +222,9 @@ wherePred table (col, predicate) =
opCode = hasNot (head rest) headPredicate opCode = hasNot (head rest) headPredicate
notOp = hasNot headPredicate "" notOp = hasNot headPredicate ""
value = hasNot (T.intercalate "." $ tail rest) (T.intercalate "." rest) value = hasNot (T.intercalate "." $ tail rest) (T.intercalate "." rest)
whiteList val = fromMaybe (cs (pgFmtLit val) <> "::unknown ") whiteList val = fromMaybe
(L.find ((==) . T.toLower $ val) (cs (pgFmtLit val) <> "::unknown ")
["null","true","false"]) (L.find ((==) . T.toLower $ val) ["null","true","false"])
star c = if c == '*' then '%' else c star c = if c == '*' then '%' else c
unknownLiteral = (<> "::unknown ") . pgFmtLit unknownLiteral = (<> "::unknown ") . pgFmtLit
+2 -1
View File
@@ -60,7 +60,8 @@ tables schema = do
and n.nspname = ? and n.nspname = ?
and ( and (
pg_has_role(c.relowner, 'USAGE'::text) pg_has_role(c.relowner, 'USAGE'::text)
or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text) or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text)
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text)
) )
order by relname order by relname
|] schema |] schema
+4 -4
View File
@@ -41,14 +41,14 @@ rangeRequested = (rangeParse =<<) . lookup hRange
rangeLimit :: NonnegRange -> Maybe Int rangeLimit :: NonnegRange -> Maybe Int
rangeLimit range = rangeLimit range =
case [rangeLower range, rangeUpper range] case [rangeLower range, rangeUpper range] of
of [BoundaryBelow from, BoundaryAbove to] -> Just (1 + to - from) [BoundaryBelow from, BoundaryAbove to] -> Just (1 + to - from)
_ -> Nothing _ -> Nothing
rangeOffset :: NonnegRange -> Int rangeOffset :: NonnegRange -> Int
rangeOffset range = rangeOffset range =
case rangeLower range case rangeLower range of
of BoundaryBelow from -> from BoundaryBelow from -> from
_ -> error "range without lower bound" -- should never happen _ -> error "range without lower bound" -- should never happen
rangeGeq :: Int -> NonnegRange rangeGeq :: Int -> NonnegRange
+6
View File
@@ -261,6 +261,12 @@ spec = afterAll_ resetDb $ around withApp $ do
liftIO $ simpleHeaders g liftIO $ simpleHeaders g
`shouldSatisfy` matchHeader "Content-Range" "0-9/10" `shouldSatisfy` matchHeader "Content-Range" "0-9/10"
it "can set a column to NULL" $ do
_ <- post "/no_pk" [json| { a: "keepme", b: "nullme" } |]
_ <- request methodPatch "/no_pk?b=eq.nullme" [] [json| { b: null } |]
get "/no_pk?a=eq.keepme" `shouldRespondWith`
[json| [{ a: "keepme", b: null }] |]
it "can update based on a computed column" $ it "can update based on a computed column" $
request methodPatch request methodPatch
"/items?always_true=eq.false" "/items?always_true=eq.false"
+5
View File
@@ -234,6 +234,11 @@ spec =
(acceptHdrs "text/unknowntype") "" (acceptHdrs "text/unknowntype") ""
`shouldRespondWith` 415 `shouldRespondWith` 415
it "should respond correctly to */* in accept header" $
request methodGet "/simple_pk"
(acceptHdrs "*/*") ""
`shouldRespondWith` 200
it "should respond correctly to multiple types in accept header" $ it "should respond correctly to multiple types in accept header" $
request methodGet "/simple_pk" request methodGet "/simple_pk"
(acceptHdrs "text/unknowntype, text/csv") "" (acceptHdrs "text/unknowntype, text/csv") ""