Merge remote-tracking branch 'begriffs/master'
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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,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
@@ -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
|
||||||
|
|||||||
@@ -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|]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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") ""
|
||||||
|
|||||||
Reference in New Issue
Block a user