Merge remote-tracking branch 'begriffs/v3' into v3

This commit is contained in:
Ruslan Talpa
2015-10-16 11:34:10 +03:00
13 changed files with 202 additions and 208 deletions
+5
View File
@@ -9,6 +9,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Embed associations, e.g. `/film?select=*,director(*)` - @ruslantalpa
- Filter columns, e.g. `?select=col1,col2` - @ruslantalpa
- Does not execute the count total if header "Prefer: count=none" - @diogob
- Postgres connection string argument - @calebmer
### Removed
- API versioning feature - @calebmer
- `--db-x` command line arguments - @calebmer
### Fixed
- Tolerate a missing role in user creation - @calebmer
+15 -12
View File
@@ -24,13 +24,16 @@ your own projects.
Download the binary ([latest release](https://github.com/begriffs/postgrest/releases/latest)) and invoke like so:
```bash
postgrest --db-host localhost --db-port 5432 \
--db-name my_db --db-user postgres \
--db-pass foobar --db-pool 200 \
--anonymous postgres --port 3000 \
--v1schema public
postgrest postgres://postgres:foobar@localhost:5432/my_db \
--port 3000 \
--schema public \
--anonymous postgres \
--pool 200
```
For more information on valid connection strings see the
[Postgres docs](http://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING).
In production include the `--secure` option which redirects all
requests to HTTPS. Note that PostgREST does not handle the SSL
internally and must be put behind another server that does (such
@@ -100,13 +103,14 @@ guide](https://github.com/begriffs/postgrest/wiki/Security-and-Permissions).
### Versioning
A robust long-lived API needs the freedom to exist in multiple
versions. PostgREST supports versioning through HTTP content
negotiation. Requests for a certain version translate into switching
which database schema to search for tables. PostgreSQL schema search
paths allow tables from earlier versions to be reused verbatim in
later versions.
versions. Therefore it is a best practice that you version the database
schema exposed to PostgREST (e.g. `public1` or `api2`). This way you
future proof your API by allowing it to be backwards compatible when
you want to publish breaking API changes (e.g. a later version could
be `public2` or `api3`).
To learn more, see the [guide to versioning](https://github.com/begriffs/postgrest/wiki/API-Versioning).
For routing to different versions of a PostgREST API use a request
proxy (such as [nginx](http://nginx.org)).
### Self-documention
@@ -153,7 +157,6 @@ and the [guide to routing](https://github.com/begriffs/postgrest/wiki/Routing).
### Guides
* [Routing](https://github.com/begriffs/postgrest/wiki/Routing)
* [Versioning](https://github.com/begriffs/postgrest/wiki/API-Versioning)
* [Performance](https://github.com/begriffs/postgrest/wiki/Performance-and-Scaling)
* [Security](https://github.com/begriffs/postgrest/wiki/Security-and-Permissions)
* [Tutorial](http://blog.jonharrington.org/postgrest-introduction/) (external)
+6 -6
View File
@@ -2,12 +2,12 @@
### BEGIN INIT INFO
# Provides: postgrest
# Required-Start: $local_fs $network postgresql
# Required-Stop: $local_fs $network
# Required-Stop: $local_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: PostgreSQL REST API daemon
### END INIT INFO
. /lib/lsb/init-functions
if test -f /etc/default/postgrest; then
. /etc/default/postgrest
@@ -32,8 +32,8 @@ fi
if [ -n "$POSTGREST_DBPOOL" ]; then
POSTGREST_OPTS="$POSTGREST_OPTS --db-pool $POSTGREST_DBPOOL"
fi
POSTGREST_OPTS="$POSTGREST_OPTS --v1schema public"
POSTGREST_OPTS="$POSTGREST_OPTS --schema public"
start()
{
log_daemon_msg "Starting PostgreSQL REST API daemon" "postgrest" || true
@@ -43,7 +43,7 @@ start()
log_end_msg 1 || true
fi
}
stop()
{
log_daemon_msg "Stopping PostgreSQL REST API daemon" "postgrest" || true
@@ -53,7 +53,7 @@ stop()
log_end_msg 1 || true
fi
}
status()
{
status_of_proc $POSTGREST postgrest && exit 0 || exit $?
+1
View File
@@ -37,6 +37,7 @@ executable postgrest
, case-insensitive
, scientific, time
, aeson >= 0.8, network >= 2.6
, aeson-pretty >= 0.7 && < 0.8
, bytestring, text, split, string-conversions
, stringsearch
, containers, unordered-containers
+4 -18
View File
@@ -6,7 +6,6 @@ module PostgREST.App (
, isSqlError
, contentTypeForAccept
, jsonH
, requestedSchema
, TableOptions(..)
) where
@@ -29,7 +28,6 @@ import Data.Ranged.Ranges (emptyRange)
import qualified Data.Set as S
import Data.String.Conversions (cs)
import Data.Text (Text, replace, strip)
import Text.Regex.TDFA ((=~))
import Text.Parsec.Error
@@ -59,8 +57,8 @@ import PostgREST.Types
import Prelude
app :: DbStructure -> AppConfig -> BL.ByteString -> DbRole -> Request -> H.Tx P.Postgres s Response
app dbstructure conf reqBody dbrole req =
app :: DbStructure -> AppConfig -> DbRole -> BL.ByteString -> DbRole -> Request -> H.Tx P.Postgres s Response
app dbstructure conf authenticator reqBody dbrole req =
case (path, verb) of
([], _) -> do
@@ -216,7 +214,7 @@ app dbstructure conf reqBody dbrole req =
-- check that proc exists
-- check that arg names are all specified
-- select * from "1".proc(a := "foo"::undefined) where whereT limit limitT
-- select * from public.proc(a := "foo"::undefined) where whereT limit limitT
([table], "PUT") ->
handleJsonObj reqBody $ \obj -> do
@@ -296,8 +294,7 @@ app dbstructure conf reqBody dbrole req =
lookupHeader = flip lookup hdrs
hasPrefer val = any (\(h,v) -> h == "Prefer" && v == val) hdrs
accept = lookupHeader hAccept
schema = requestedSchema (cs $ configV1Schema conf) accept
authenticator = cs $ configDbUser conf
schema = cs $ configSchema conf
jwtSecret = cs $ configJwtSecret conf
range = rangeRequested hdrs
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header
@@ -329,17 +326,6 @@ contentRangeH from to total =
totalNotZero = fromMaybe True ((/=) 0 <$> total)
fromInRange = from <= to
requestedSchema :: Text -> Maybe BS.ByteString -> Text
requestedSchema v1schema accept =
case verStr of
Just [[_, ver]] -> if ver == "1" then v1schema else cs ver
_ -> v1schema
where
verRegex = "version[ ]*=[ ]*([0-9]+)" :: BS.ByteString
verStr = (=~ verRegex) <$> accept :: Maybe [[BS.ByteString]]
jsonMT :: BS.ByteString
jsonMT = "application/json"
+10 -19
View File
@@ -34,34 +34,25 @@ import Prelude
-- | Data type to store all command line options
data AppConfig = AppConfig {
configDbName :: String
, configDbPort :: Int
, configDbUser :: String
, configDbPass :: String
, configDbHost :: String
configDatabase :: String
, configPort :: Int
, configAnonRole :: String
, configSchema :: String
, configSecure :: Bool
, configPool :: Int
, configV1Schema :: String
, configJwtSecret :: String
, configPool :: Int
}
argParser :: Parser AppConfig
argParser = AppConfig
<$> strOption (long "db-name" <> short 'd' <> metavar "NAME" <> help "name of database")
<*> option auto (long "db-port" <> short 'P' <> metavar "PORT" <> value 5432 <> help "postgres server port" <> showDefault)
<*> strOption (long "db-user" <> short 'U' <> metavar "ROLE" <> help "postgres authenticator role")
<*> strOption (long "db-pass" <> metavar "PASS" <> value "" <> help "password for authenticator role")
<*> strOption (long "db-host" <> metavar "HOST" <> value "localhost" <> help "postgres server hostname" <> showDefault)
<$> argument str (help "database connection string" <> metavar "STRING")
<*> option auto (long "port" <> short 'p' <> metavar "PORT" <> value 3000 <> help "port number on which to run HTTP server" <> showDefault)
<*> strOption (long "anonymous" <> short 'a' <> metavar "ROLE" <> help "postgres role to use for non-authenticated requests")
<*> switch (long "secure" <> short 's' <> help "Redirect all requests to HTTPS")
<*> option auto (long "db-pool" <> metavar "COUNT" <> value 10 <> help "Max connections in database pool" <> showDefault)
<*> strOption (long "v1schema" <> metavar "NAME" <> value "1" <> help "Schema to use for nonspecified version (or explicit v1)" <> showDefault)
<*> strOption (long "jwt-secret" <> metavar "SECRET" <> value "secret" <> help "Secret used to encrypt and decrypt JWT tokens)" <> showDefault)
<*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault)
<*> strOption (long "anonymous" <> short 'a' <> help "postgres role to use for non-authenticated requests" <> metavar "ROLE")
<*> strOption (long "schema" <> short 'S' <> help "schema to use for API routes" <> metavar "NAME" <> value "1" <> showDefault)
<*> switch (long "secure" <> short 's' <> help "redirect all requests to HTTPS")
<*> strOption (long "jwt-secret" <> short 'j' <> help "secret used to encrypt and decrypt JWT tokens" <> metavar "SECRET" <> value "secret" <> showDefault)
<*> option auto (long "pool" <> short 'o' <> help "max connections in database pool" <> metavar "COUNT" <> value 10 <> showDefault)
defaultCorsPolicy :: CorsResourcePolicy
defaultCorsPolicy = CorsResourcePolicy Nothing
+28 -23
View File
@@ -1,39 +1,41 @@
module Main where
import PostgREST.App
import PostgREST.Config (AppConfig (..),
minimumPgVersion,
prettyVersion,
readOptions)
import PostgREST.Error (errResponse, PgError)
import PostgREST.Middleware
import PostgREST.PgStructure
import PostgREST.Types
import Network.Wai
import PostgREST.App
import PostgREST.Error (errResponse)
import PostgREST.Middleware
import Control.Monad (unless)
import Control.Monad.IO.Class (liftIO)
import Data.Aeson.Encode.Pretty (encodePretty)
import Data.Functor.Identity
import Data.Monoid ((<>))
import Data.String.Conversions (cs)
import Data.Text (Text)
import qualified Hasql as H
import qualified Hasql.Postgres as P
import Network.Wai
import Network.Wai.Handler.Warp hiding (Connection)
import Network.Wai.Middleware.RequestLogger (logStdout)
import System.IO (BufferMode (..),
hSetBuffering, stderr,
stdin, stdout)
import PostgREST.Config (AppConfig (..),
prettyVersion,
readOptions,
minimumPgVersion)
isServerVersionSupported :: H.Session P.Postgres IO Bool
isServerVersionSupported = do
Identity (row :: Text) <- H.tx Nothing $ H.singleEx $ [H.stmt|SHOW server_version_num|]
Identity (row :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SHOW server_version_num|]
return $ read (cs row) >= minimumPgVersion
hasqlError :: PgError -> IO a
hasqlError = error . cs . encodePretty
main :: IO ()
main = do
hSetBuffering stdout LineBuffering
@@ -50,11 +52,7 @@ main = do
Prelude.putStrLn $ "Listening on port " ++
(show $ configPort conf :: String)
let pgSettings = P.ParamSettings (cs $ configDbHost conf)
(fromIntegral $ configDbPort conf)
(cs $ configDbUser conf)
(cs $ configDbPass conf)
(cs $ configDbName conf)
let pgSettings = P.StringSettings $ cs (configDatabase conf)
appSettings = setPort port
. setServerName (cs $ "postgrest/" <> prettyVersion)
$ defaultSettings
@@ -65,12 +63,20 @@ main = do
pool :: H.Pool P.Postgres <- H.acquirePool pgSettings poolSettings
supportedOrError <- H.session pool isServerVersionSupported
either (fail . show)
either hasqlError
(\supported ->
unless supported $
fail "Cannot run in this PostgreSQL version, PostgREST needs at least 9.2.0"
error (
"Cannot run in this PostgreSQL version, PostgREST needs at least "
<> show minimumPgVersion)
) supportedOrError
roleOrError <- H.session pool $ do
Identity (role :: Text) <- H.tx Nothing $ H.singleEx
[H.stmt|SELECT SESSION_USER|]
return role
authenticator <- either hasqlError return roleOrError
let txSettings = Just (H.ReadCommitted, Just True)
metadata <- H.session pool $ H.tx txSettings $ do
tabs <- allTables
@@ -79,19 +85,18 @@ main = do
keys <- allPrimaryKeys
return (tabs, rels, cols, keys)
dbstructure <- case metadata of
Left e -> fail $ show e
Right (tabs, rels, cols, keys) ->
dbstructure <- either hasqlError
(\(tabs, rels, cols, keys) ->
return DbStructure {
tables=tabs
, columns=cols
, relations=rels
, primaryKeys=keys
}
) metadata
runSettings appSettings $ middle $ \ req respond -> do
body <- strictRequestBody req
resOrError <- liftIO $ H.session pool $ H.tx txSettings $
authenticated conf (app dbstructure conf body) req
authenticated conf authenticator (app dbstructure conf authenticator body) req
either (respond . errResponse) respond resOrError
+4 -5
View File
@@ -33,22 +33,21 @@ import PostgREST.Config (AppConfig (..), corsPolicy)
import Prelude
authenticated :: forall s. AppConfig ->
authenticated :: forall s. AppConfig -> DbRole ->
(DbRole -> Request -> H.Tx P.Postgres s Response) ->
Request -> H.Tx P.Postgres s Response
authenticated conf app req = do
authenticated conf authenticator app req = do
attempt <- httpRequesterRole (requestHeaders req)
case attempt of
MalformedAuth ->
return $ responseLBS status400 [] "Malformed basic auth header"
LoginFailed ->
return $ responseLBS status401 [] "Invalid username or password"
LoginSuccess role uid -> if role /= currentRole then runInRole role uid else app currentRole req
NoCredentials -> if anon /= currentRole then runInRole anon "" else app currentRole req
LoginSuccess role uid -> if role /= authenticator then runInRole role uid else app authenticator req
NoCredentials -> if anon /= authenticator then runInRole anon "" else app authenticator req
where
jwtSecret = cs $ configJwtSecret conf
currentRole = cs $ configDbUser conf
anon = cs $ configAnonRole conf
httpRequesterRole :: RequestHeaders -> H.Tx P.Postgres s LoginAttempt
httpRequesterRole hdrs = do
+39 -39
View File
@@ -14,28 +14,28 @@ spec = around withApp $ do
it "lists views in schema" $
request methodGet "/" [] ""
`shouldRespondWith` [json| [
{"schema":"1","name":"auto_incrementing_pk","insertable":true}
, {"schema":"1","name":"clients","insertable":true}
, {"schema":"1","name":"comments","insertable":true}
, {"schema":"1","name":"complex_items","insertable":true}
, {"schema":"1","name":"compound_pk","insertable":true}
, {"schema":"1","name":"has_count_column","insertable":false}
, {"schema":"1","name":"has_fk","insertable":true}
, {"schema":"1","name":"insertable_view_with_join","insertable":true}
, {"schema":"1","name":"items","insertable":true}
, {"schema":"1","name":"json","insertable":true}
, {"schema":"1","name":"materialized_view","insertable":false}
, {"schema":"1","name":"menagerie","insertable":true}
, {"schema":"1","name":"no_pk","insertable":true}
, {"schema":"1","name":"nullable_integer","insertable":true}
, {"schema":"1","name":"projects","insertable":true}
, {"schema":"1","name":"projects_view","insertable":true}
, {"schema":"1","name":"simple_pk","insertable":true}
, {"schema":"1","name":"tasks","insertable":true}
, {"schema":"1","name":"tsearch","insertable":true}
, {"schema":"1","name":"users","insertable":true}
, {"schema":"1","name":"users_projects","insertable":true}
, {"schema":"1","name":"users_tasks","insertable":true}
{"schema":"test","name":"auto_incrementing_pk","insertable":true}
, {"schema":"test","name":"clients","insertable":true}
, {"schema":"test","name":"comments","insertable":true}
, {"schema":"test","name":"complex_items","insertable":true}
, {"schema":"test","name":"compound_pk","insertable":true}
, {"schema":"test","name":"has_count_column","insertable":false}
, {"schema":"test","name":"has_fk","insertable":true}
, {"schema":"test","name":"insertable_view_with_join","insertable":true}
, {"schema":"test","name":"items","insertable":true}
, {"schema":"test","name":"json","insertable":true}
, {"schema":"test","name":"materialized_view","insertable":false}
, {"schema":"test","name":"menagerie","insertable":true}
, {"schema":"test","name":"no_pk","insertable":true}
, {"schema":"test","name":"nullable_integer","insertable":true}
, {"schema":"test","name":"projects","insertable":true}
, {"schema":"test","name":"projects_view","insertable":true}
, {"schema":"test","name":"simple_pk","insertable":true}
, {"schema":"test","name":"tasks","insertable":true}
, {"schema":"test","name":"tsearch","insertable":true}
, {"schema":"test","name":"users","insertable":true}
, {"schema":"test","name":"users_projects","insertable":true}
, {"schema":"test","name":"users_tasks","insertable":true}
] |]
{matchStatus = 200}
@@ -45,7 +45,7 @@ spec = around withApp $ do
request methodGet "/" [auth] ""
`shouldRespondWith` [json| [
{"schema":"1","name":"authors_only","insertable":true}
{"schema":"test","name":"authors_only","insertable":true}
] |]
{matchStatus = 200}
@@ -61,7 +61,7 @@ spec = around withApp $ do
"default": null,
"precision": 32,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "integer",
"type": "integer",
"maxLen": null,
@@ -74,7 +74,7 @@ spec = around withApp $ do
"default": null,
"precision": 53,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "double",
"type": "double precision",
"maxLen": null,
@@ -86,7 +86,7 @@ spec = around withApp $ do
"default": null,
"precision": null,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "varchar",
"type": "character varying",
"maxLen": null,
@@ -99,7 +99,7 @@ spec = around withApp $ do
"default": null,
"precision": null,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "boolean",
"type": "boolean",
"maxLen": null,
@@ -111,7 +111,7 @@ spec = around withApp $ do
"default": null,
"precision": null,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "date",
"type": "date",
"maxLen": null,
@@ -123,7 +123,7 @@ spec = around withApp $ do
"default": null,
"precision": null,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "money",
"type": "money",
"maxLen": null,
@@ -136,7 +136,7 @@ spec = around withApp $ do
"default": null,
"precision": null,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "enum",
"type": "USER-DEFINED",
"maxLen": null,
@@ -166,7 +166,7 @@ spec = around withApp $ do
"default":null,
"precision":64,
"updatable":false,
"schema":"1",
"schema":"test",
"name":"id",
"type":"bigint",
"maxLen":null,
@@ -182,7 +182,7 @@ spec = around withApp $ do
"default":null,
"precision":32,
"updatable":false,
"schema":"1",
"schema":"test",
"name":"auto_inc_fk",
"type":"integer",
"maxLen":null,
@@ -198,7 +198,7 @@ spec = around withApp $ do
"default":null,
"precision":null,
"updatable":false,
"schema":"1",
"schema":"test",
"name":"simple_fk",
"type":"character varying",
"maxLen":255,
@@ -211,7 +211,7 @@ spec = around withApp $ do
"default":null,
"precision":null,
"updatable":false,
"schema":"1",
"schema":"test",
"name":"nullable_string",
"type":"character varying",
"maxLen":null,
@@ -224,7 +224,7 @@ spec = around withApp $ do
"default":null,
"precision":null,
"updatable":false,
"schema":"1",
"schema":"test",
"name":"non_nullable_string",
"type":"character varying",
"maxLen":null,
@@ -237,7 +237,7 @@ spec = around withApp $ do
"default":null,
"precision":null,
"updatable":false,
"schema":"1",
"schema":"test",
"name":"inserted_at",
"type":"timestamp with time zone",
"maxLen":null,
@@ -261,7 +261,7 @@ spec = around withApp $ do
"default": "nextval('\"1\".has_fk_id_seq'::regclass)",
"precision": 64,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "id",
"type": "bigint",
"maxLen": null,
@@ -273,7 +273,7 @@ spec = around withApp $ do
"default": null,
"precision": 32,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "auto_inc_fk",
"type": "integer",
"maxLen": null,
@@ -285,7 +285,7 @@ spec = around withApp $ do
"default": null,
"precision": null,
"updatable": true,
"schema": "1",
"schema": "test",
"name": "simple_fk",
"type": "character varying",
"maxLen": 255,
+23 -19
View File
@@ -20,6 +20,7 @@ import Network.HTTP.Types.Header (Header, ByteRange, renderByteRange,
import Codec.Binary.Base64.String (encode)
import Data.CaseInsensitive (CI(..))
import Data.Maybe (fromMaybe)
import Data.Functor.Identity
import Text.Regex.TDFA ((=~))
import qualified Data.ByteString.Char8 as BS
import System.Process (readProcess)
@@ -33,28 +34,31 @@ import PostgREST.Error(errResponse)
import PostgREST.PgStructure
import PostgREST.Types
dbString :: String
dbString = "postgres://postgrest_test@localhost:5432/postgrest_test"
isLeft :: Either a b -> Bool
isLeft (Left _ ) = True
isLeft _ = False
cfg :: AppConfig
cfg = AppConfig "postgrest_test" 5432 "postgrest_test" "" "localhost" 3000 "postgrest_anonymous" False 10 "1" "safe"
cfg = AppConfig dbString 3000 "postgrest_anonymous" "test" False "safe" 10
testPoolOpts :: PoolSettings
testPoolOpts = fromMaybe (error "bad settings") $ H.poolSettings 1 30
pgSettings :: P.Settings
pgSettings = P.ParamSettings (cs $ configDbHost cfg)
(fromIntegral $ configDbPort cfg)
(cs $ configDbUser cfg)
(cs $ configDbPass cfg)
(cs $ configDbName cfg)
pgSettings = P.StringSettings $ cs dbString
withApp :: ActionWith Application -> IO ()
withApp perform = do
pool :: H.Pool P.Postgres
<- H.acquirePool pgSettings testPoolOpts
Right authenticator <- H.session pool $ do
Identity (role :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SELECT SESSION_USER|]
return role
let txSettings = Just (H.ReadCommitted, Just True)
metadata <- H.session pool $ H.tx txSettings $ do
tabs <- allTables
@@ -66,7 +70,7 @@ withApp perform = do
dbstructure <- case metadata of
Left e -> fail $ show e
Right (tabs, rels, cols, keys) ->
return $ DbStructure {
return DbStructure {
tables=tabs
, columns=cols
, relations=rels
@@ -76,7 +80,7 @@ withApp perform = do
perform $ middle $ \req resp -> do
body <- strictRequestBody req
result <- liftIO $ H.session pool $ H.tx txSettings
$ authenticated cfg (app dbstructure cfg body) req
$ authenticated cfg authenticator (app dbstructure cfg authenticator body) req
either (resp . errResponse) resp result
where middle = defaultMiddle False
@@ -88,7 +92,7 @@ resetDb = do
<- H.acquirePool pgSettings testPoolOpts
void . liftIO $ H.session pool $
H.tx Nothing $ do
H.unitEx [H.stmt| drop schema if exists "1" cascade |]
H.unitEx [H.stmt| drop schema if exists test cascade |]
H.unitEx [H.stmt| drop schema if exists private cascade |]
H.unitEx [H.stmt| drop schema if exists postgrest cascade |]
@@ -129,7 +133,7 @@ clearTable :: Text -> IO ()
clearTable table = do
pool <- testPool
void . liftIO $ H.session pool $ H.tx Nothing $
H.unitEx $ B.Stmt ("delete from \"1\"."<>table) V.empty True
H.unitEx $ B.Stmt ("delete from test."<>table) V.empty True
createItems :: Int -> IO ()
createItems n = do
@@ -137,7 +141,7 @@ createItems n = do
void . liftIO $ H.session pool $ H.tx Nothing txn
where
txn = mapM_ H.unitEx stmts
stmts = map [H.stmt|insert into "1".items (id) values (?)|] [1..n]
stmts = map [H.stmt|insert into test.items (id) values (?)|] [1..n]
createComplexItems :: IO ()
createComplexItems = do
@@ -145,11 +149,11 @@ createComplexItems = do
void . liftIO $ H.session pool $ H.tx Nothing txn
where
txn = mapM_ H.unitEx stmts
stmts = getZipList $ [H.stmt|insert into "1".complex_items (id, name, settings) values (?,?,?)|]
stmts = getZipList $ [H.stmt|insert into test.complex_items (id, name, settings) values (?,?,?)|]
<$> ZipList ([1..3]::[Int])
<*> ZipList (["One", "Two", "Three"]::[Text])
<*> ZipList ([jobj,jobj,jobj])
jobj = (J.object [("foo", J.object [("int", J.Number 1),("bar", J.String "baz")])])
<*> ZipList [jobj,jobj,jobj]
jobj = J.object [("foo", J.object [("int", J.Number 1),("bar", J.String "baz")])]
createNulls :: Int -> IO ()
createNulls n = do
@@ -157,14 +161,14 @@ createNulls n = do
void . liftIO $ H.session pool $ H.tx Nothing txn
where
txn = mapM_ H.unitEx (stmt':stmts)
stmt' = [H.stmt|insert into "1".no_pk (a,b) values (null,null)|]
stmts = map [H.stmt|insert into "1".no_pk (a,b) values (?,0)|] [1..n]
stmt' = [H.stmt|insert into test.no_pk (a,b) values (null,null)|]
stmts = map [H.stmt|insert into test.no_pk (a,b) values (?,0)|] [1..n]
createNullInteger :: IO ()
createNullInteger = do
pool <- testPool
void . liftIO $ H.session pool $ H.tx Nothing $
H.unitEx $ [H.stmt| insert into "1".nullable_integer (a) values (null) |]
H.unitEx $ [H.stmt| insert into "test".nullable_integer (a) values (null) |]
createLikableStrings :: IO ()
createLikableStrings = do
@@ -174,7 +178,7 @@ createLikableStrings = do
H.unitEx $ insertSimplePk "xYYx" "v"
where
insertSimplePk :: Text -> Text -> H.Stmt P.Postgres
insertSimplePk = [H.stmt|insert into "1".simple_pk (k, extra) values (?,?)|]
insertSimplePk = [H.stmt|insert into test.simple_pk (k, extra) values (?,?)|]
createJsonData :: IO ()
createJsonData = do
@@ -182,6 +186,6 @@ createJsonData = do
void . liftIO $ H.session pool $ H.tx Nothing $
H.unitEx $
[H.stmt|
insert into "1".json (data) values (?)
insert into test.json (data) values (?)
|]
(J.object [("foo", J.object [("bar", J.String "baz")])])
+5 -5
View File
@@ -32,7 +32,7 @@ spec = around dbWithSchema $ do
describe "insert" $
describe "with an auto-increment key" $ do
it "inserts and responds with a full object description" $ \conn -> do
r <- insert "1" "auto_incrementing_pk" (SqlRow [
r <- insert "test" "auto_incrementing_pk" (SqlRow [
("non_nullable_string", toSql ("a string"::String))]) conn
let returnRow = incFromList . toList $ r
incStr returnRow `shouldBe` "a string"
@@ -43,19 +43,19 @@ spec = around dbWithSchema $ do
[returnRow] `shouldBe` map incFromList tRows
it "throws an exception if the PK is not unique" $ \conn -> do
r <- insert "1" "auto_incrementing_pk" (SqlRow [
r <- insert "test" "auto_incrementing_pk" (SqlRow [
("non_nullable_string", toSql ("a string"::String))]) conn
let row = SqlRow . map (Control.Arrow.first cs) . toList $ r
insert "1" "auto_incrementing_pk" row conn `shouldThrow` \e ->
insert "test" "auto_incrementing_pk" row conn `shouldThrow` \e ->
seState e == "23505" -- uniqueness violation code
it "throws an exception if a required value is missing" $ \conn ->
insert "1" "auto_incrementing_pk" (SqlRow [
insert "test" "auto_incrementing_pk" (SqlRow [
("nullable_string", toSql ("a string"::String))]) conn
`shouldThrow` \e -> seState e == "23502"
it "generates a default values query if no data is provided" $ \c -> do
r <- insert "1" "items" (SqlRow []) c
r <- insert "test" "items" (SqlRow []) c
let [row] = toList r
quickALQuery c "select * from \"1\".items where id = ?" [snd row]
`shouldReturn` [[row]]
+4 -4
View File
@@ -12,25 +12,25 @@ spec :: Spec
spec = around dbWithSchema $ beforeWith setRole $ do
describe "tables" $
it "shows all the tables" $ \conn -> do
ts <- tables "1" conn
ts <- tables "test" conn
map tableName ts `shouldBe` ["authors_only","auto_incrementing_pk",
"compound_pk","has_fk","insertable_view_with_join","items","menagerie","no_pk", "simple_pk"]
describe "columns" $ do
it "responds with each column for the table" $ \conn -> do
cs <- columns "1" "auto_incrementing_pk" conn
cs <- columns "test" "auto_incrementing_pk" conn
map colName cs `shouldBe` ["id","nullable_string","non_nullable_string",
"inserted_at"]
it "includes foreign key data" $ \conn -> do
cs <- columns "1" "has_fk" conn
cs <- columns "test" "has_fk" conn
map colFK cs `shouldBe` [Nothing,
Just $ ForeignKey "auto_incrementing_pk" "id",
Just $ ForeignKey "simple_pk" "k"]
describe "foreignKeys" $
it "has a description of the foreign key columns" $ \conn ->
foreignKeys "1" "has_fk" conn `shouldReturn` M.fromList [
foreignKeys "test" "has_fk" conn `shouldReturn` M.fromList [
("auto_inc_fk", ForeignKey {fkTable="auto_incrementing_pk", fkCol="id"}),
("simple_fk", ForeignKey { fkTable="simple_pk", fkCol="k"})]
+58 -58
View File
@@ -5,10 +5,10 @@ SET check_function_bodies = false;
SET client_min_messages = warning;
CREATE SCHEMA "1";
CREATE SCHEMA test;
ALTER SCHEMA "1" OWNER TO postgrest_test;
ALTER SCHEMA test OWNER TO postgrest_test;
CREATE SCHEMA postgrest;
@@ -30,7 +30,7 @@ CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
SET search_path = "1", pg_catalog;
SET search_path = test, pg_catalog;
CREATE TYPE enum_menagerie_type AS ENUM (
@@ -39,7 +39,7 @@ CREATE TYPE enum_menagerie_type AS ENUM (
);
ALTER TYPE "1".enum_menagerie_type OWNER TO postgrest_test;
ALTER TYPE test.enum_menagerie_type OWNER TO postgrest_test;
SET search_path = postgrest, pg_catalog;
@@ -83,18 +83,18 @@ $$;
ALTER FUNCTION postgrest.set_authors_only_owner() OWNER TO postgrest_test;
CREATE FUNCTION "1".insert_insertable_view_with_join() RETURNS trigger
CREATE FUNCTION test.insert_insertable_view_with_join() RETURNS trigger
LANGUAGE plpgsql
AS $$
begin
INSERT INTO "1".auto_incrementing_pk (nullable_string, non_nullable_string) VALUES (NEW.nullable_string, NEW.non_nullable_string);
INSERT INTO test.auto_incrementing_pk (nullable_string, non_nullable_string) VALUES (NEW.nullable_string, NEW.non_nullable_string);
RETURN NEW;
end;
$$;
ALTER FUNCTION "1".insert_insertable_view_with_join() OWNER TO postgrest_test;
ALTER FUNCTION test.insert_insertable_view_with_join() OWNER TO postgrest_test;
SET search_path = "1", pg_catalog;
SET search_path = test, pg_catalog;
SET default_tablespace = '';
@@ -107,7 +107,7 @@ CREATE TABLE authors_only (
);
ALTER TABLE "1".authors_only OWNER TO postgrest_test_author;
ALTER TABLE test.authors_only OWNER TO postgrest_test_author;
CREATE TABLE auto_incrementing_pk (
@@ -118,7 +118,7 @@ CREATE TABLE auto_incrementing_pk (
);
ALTER TABLE "1".auto_incrementing_pk OWNER TO postgrest_test;
ALTER TABLE test.auto_incrementing_pk OWNER TO postgrest_test;
CREATE SEQUENCE auto_incrementing_pk_id_seq
@@ -129,7 +129,7 @@ CREATE SEQUENCE auto_incrementing_pk_id_seq
CACHE 1;
ALTER TABLE "1".auto_incrementing_pk_id_seq OWNER TO postgrest_test;
ALTER TABLE test.auto_incrementing_pk_id_seq OWNER TO postgrest_test;
ALTER SEQUENCE auto_incrementing_pk_id_seq OWNED BY auto_incrementing_pk.id;
@@ -143,7 +143,7 @@ CREATE TABLE compound_pk (
);
ALTER TABLE "1".compound_pk OWNER TO postgrest_test;
ALTER TABLE test.compound_pk OWNER TO postgrest_test;
CREATE TABLE has_fk (
@@ -153,7 +153,7 @@ CREATE TABLE has_fk (
);
ALTER TABLE "1".has_fk OWNER TO postgrest_test;
ALTER TABLE test.has_fk OWNER TO postgrest_test;
CREATE SEQUENCE has_fk_id_seq
@@ -164,18 +164,18 @@ CREATE SEQUENCE has_fk_id_seq
CACHE 1;
ALTER TABLE "1".has_fk_id_seq OWNER TO postgrest_test;
ALTER TABLE test.has_fk_id_seq OWNER TO postgrest_test;
ALTER SEQUENCE has_fk_id_seq OWNED BY has_fk.id;
CREATE MATERIALIZED VIEW "1".materialized_view AS
CREATE MATERIALIZED VIEW test.materialized_view AS
SELECT
version();
ALTER TABLE "1".materialized_view OWNER TO postgrest_test;
ALTER TABLE test.materialized_view OWNER TO postgrest_test;
CREATE VIEW "1".insertable_view_with_join AS
CREATE VIEW test.insertable_view_with_join AS
SELECT has_fk.id,
has_fk.auto_inc_fk,
has_fk.simple_fk,
@@ -186,12 +186,12 @@ CREATE VIEW "1".insertable_view_with_join AS
JOIN auto_incrementing_pk USING (id));
ALTER TABLE "1".insertable_view_with_join OWNER TO postgrest_test;
ALTER TABLE test.insertable_view_with_join OWNER TO postgrest_test;
CREATE VIEW "1".has_count_column AS
CREATE VIEW test.has_count_column AS
SELECT 1 AS count;
ALTER TABLE "1".insertable_view_with_join OWNER TO postgrest_test;
ALTER TABLE test.insertable_view_with_join OWNER TO postgrest_test;
CREATE TABLE items (
@@ -199,7 +199,7 @@ CREATE TABLE items (
);
ALTER TABLE "1".items OWNER TO postgrest_test;
ALTER TABLE test.items OWNER TO postgrest_test;
CREATE TABLE complex_items (
id bigint NOT NULL,
@@ -208,41 +208,41 @@ CREATE TABLE complex_items (
);
ALTER TABLE "1".complex_items OWNER TO postgrest_test;
ALTER TABLE test.complex_items OWNER TO postgrest_test;
--- Structure for testing table relations
CREATE TABLE clients(
id INT PRIMARY KEY NOT NULL,
name TEXT NOT NULL
);
ALTER TABLE "1".clients OWNER TO postgrest_test;
ALTER TABLE test.clients OWNER TO postgrest_test;
CREATE TABLE projects(
id INT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
client_id INT REFERENCES clients(id)
);
ALTER TABLE "1".projects OWNER TO postgrest_test;
ALTER TABLE test.projects OWNER TO postgrest_test;
CREATE TABLE tasks(
id INT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
project_id INT REFERENCES projects(id)
);
ALTER TABLE "1".tasks OWNER TO postgrest_test;
ALTER TABLE test.tasks OWNER TO postgrest_test;
CREATE TABLE users(
id INT PRIMARY KEY NOT NULL,
name TEXT NOT NULL
);
ALTER TABLE "1".users OWNER TO postgrest_test;
ALTER TABLE test.users OWNER TO postgrest_test;
CREATE TABLE users_tasks(
user_id INT REFERENCES users(id),
task_id INT REFERENCES tasks(id),
CONSTRAINT task_user PRIMARY KEY (task_id,user_id)
);
ALTER TABLE "1".users_tasks OWNER TO postgrest_test;
ALTER TABLE test.users_tasks OWNER TO postgrest_test;
CREATE TABLE comments(
id INT PRIMARY KEY NOT NULL,
@@ -252,22 +252,22 @@ task_id INT NOT NULL,
content TEXT NOT NULL,
FOREIGN KEY (task_id,user_id) REFERENCES users_tasks (task_id,user_id)
);
ALTER TABLE "1".comments OWNER TO postgrest_test;
ALTER TABLE test.comments OWNER TO postgrest_test;
CREATE TABLE users_projects(
user_id INT REFERENCES users(id),
project_id INT REFERENCES projects(id),
CONSTRAINT project_user PRIMARY KEY (project_id, user_id)
);
ALTER TABLE "1".users_projects OWNER TO postgrest_test;
ALTER TABLE test.users_projects OWNER TO postgrest_test;
CREATE VIEW "1".projects_view AS
CREATE VIEW test.projects_view AS
SELECT
projects.id,
projects.name,
projects.client_id
FROM projects;
ALTER TABLE "1".projects_view OWNER TO postgrest_test;
ALTER TABLE test.projects_view OWNER TO postgrest_test;
------- SAMPLE DATA -----
INSERT INTO clients VALUES (1, 'Microsoft'),(2, 'Apple');
INSERT INTO projects VALUES (1,'Windows 7', 1),(2,'Windows 10', 1),(3,'IOS', 2),(4,'OSX', 2);
@@ -286,25 +286,25 @@ CREATE SEQUENCE items_id_seq
CACHE 1;
ALTER TABLE "1".items_id_seq OWNER TO postgrest_test;
ALTER TABLE test.items_id_seq OWNER TO postgrest_test;
ALTER SEQUENCE items_id_seq OWNED BY items.id;
CREATE FUNCTION "1".getitemrange(min bigint, max bigint) RETURNS SETOF "1".items AS $$
SELECT * FROM "1".items WHERE id > $1 AND id <= $2;
CREATE FUNCTION test.getitemrange(min bigint, max bigint) RETURNS SETOF test.items AS $$
SELECT * FROM test.items WHERE id > $1 AND id <= $2;
$$ LANGUAGE SQL;
CREATE FUNCTION "1".sayhello(name text) RETURNS text AS $$
CREATE FUNCTION test.sayhello(name text) RETURNS text AS $$
SELECT 'Hello, ' || $1;
$$ LANGUAGE SQL;
CREATE FUNCTION "1".problem() RETURNS void LANGUAGE plpgsql AS
CREATE FUNCTION test.problem() RETURNS void LANGUAGE plpgsql AS
$$
BEGIN
RAISE 'bad thing';
@@ -323,7 +323,7 @@ CREATE TABLE menagerie (
);
ALTER TABLE "1".menagerie OWNER TO postgrest_test;
ALTER TABLE test.menagerie OWNER TO postgrest_test;
CREATE TABLE no_pk (
@@ -332,7 +332,7 @@ CREATE TABLE no_pk (
);
ALTER TABLE "1".no_pk OWNER TO postgrest_test;
ALTER TABLE test.no_pk OWNER TO postgrest_test;
CREATE TABLE nullable_integer (
@@ -340,7 +340,7 @@ CREATE TABLE nullable_integer (
);
ALTER TABLE "1".nullable_integer OWNER TO postgrest_test;
ALTER TABLE test.nullable_integer OWNER TO postgrest_test;
CREATE TABLE simple_pk (
@@ -349,7 +349,7 @@ CREATE TABLE simple_pk (
);
ALTER TABLE "1".simple_pk OWNER TO postgrest_test;
ALTER TABLE test.simple_pk OWNER TO postgrest_test;
CREATE TABLE json
@@ -358,14 +358,14 @@ CREATE TABLE json
);
ALTER TABLE "1".json OWNER TO postgrest_test;
ALTER TABLE test.json OWNER TO postgrest_test;
CREATE TABLE tsearch (
text_search_vector tsvector
);
ALTER TABLE "1".tsearch OWNER TO postgrest_test;
ALTER TABLE test.tsearch OWNER TO postgrest_test;
SET search_path = postgrest, pg_catalog;
@@ -406,7 +406,7 @@ ALTER TABLE private.articles_id_seq OWNER TO postgrest_test;
ALTER SEQUENCE articles_id_seq OWNED BY articles.id;
SET search_path = "1", pg_catalog;
SET search_path = test, pg_catalog;
ALTER TABLE ONLY auto_incrementing_pk ALTER COLUMN id SET DEFAULT nextval('auto_incrementing_pk_id_seq'::regclass);
@@ -426,7 +426,7 @@ SET search_path = private, pg_catalog;
ALTER TABLE ONLY articles ALTER COLUMN id SET DEFAULT nextval('articles_id_seq'::regclass);
SET search_path = "1", pg_catalog;
SET search_path = test, pg_catalog;
@@ -471,20 +471,20 @@ SET search_path = private, pg_catalog;
SELECT pg_catalog.setval('articles_id_seq', 1, false);
SET search_path = "1", pg_catalog;
SET search_path = test, pg_catalog;
CREATE FUNCTION public.always_true("1".items) RETURNS boolean
CREATE FUNCTION public.always_true(test.items) RETURNS boolean
LANGUAGE sql STABLE
AS $$ SELECT true $$;
ALTER FUNCTION public.always_true("1".items) OWNER TO postgrest_test;
ALTER FUNCTION public.always_true(test.items) OWNER TO postgrest_test;
ALTER TABLE ONLY authors_only
ADD CONSTRAINT authors_only_pkey PRIMARY KEY (secret);
CREATE TRIGGER insert_insertable_view_with_join INSTEAD OF INSERT ON "1".insertable_view_with_join FOR EACH ROW EXECUTE PROCEDURE "1".insert_insertable_view_with_join();
CREATE TRIGGER insert_insertable_view_with_join INSTEAD OF INSERT ON test.insertable_view_with_join FOR EACH ROW EXECUTE PROCEDURE test.insert_insertable_view_with_join();
CREATE TRIGGER secrets_owner_track BEFORE INSERT OR UPDATE ON authors_only FOR EACH ROW EXECUTE PROCEDURE postgrest.set_authors_only_owner();
@@ -547,7 +547,7 @@ SET search_path = private, pg_catalog;
CREATE TRIGGER articles_owner_track BEFORE INSERT OR UPDATE ON articles FOR EACH ROW EXECUTE PROCEDURE postgrest.update_owner();
SET search_path = "1", pg_catalog;
SET search_path = test, pg_catalog;
ALTER TABLE ONLY has_fk
@@ -560,11 +560,11 @@ ALTER TABLE ONLY has_fk
REVOKE ALL ON SCHEMA "1" FROM PUBLIC;
REVOKE ALL ON SCHEMA "1" FROM postgrest_test;
GRANT ALL ON SCHEMA "1" TO postgrest_test;
GRANT USAGE ON SCHEMA "1" TO postgrest_anonymous;
GRANT USAGE ON SCHEMA "1" TO postgrest_test_author;
REVOKE ALL ON SCHEMA test FROM PUBLIC;
REVOKE ALL ON SCHEMA test FROM postgrest_test;
GRANT ALL ON SCHEMA test TO postgrest_test;
GRANT USAGE ON SCHEMA test TO postgrest_anonymous;
GRANT USAGE ON SCHEMA test TO postgrest_test_author;
@@ -737,10 +737,10 @@ REVOKE ALL ON TABLE has_count_column FROM postgrest_test;
GRANT ALL ON TABLE has_count_column TO postgrest_test;
GRANT ALL ON TABLE has_count_column TO postgrest_anonymous;
REVOKE ALL ON FUNCTION public.always_true("1".items) FROM PUBLIC;
REVOKE ALL ON FUNCTION public.always_true("1".items) FROM postgrest_test;
GRANT ALL ON FUNCTION public.always_true("1".items) TO postgrest_test;
GRANT ALL ON FUNCTION public.always_true("1".items) TO postgrest_anonymous;
REVOKE ALL ON FUNCTION public.always_true(test.items) FROM PUBLIC;
REVOKE ALL ON FUNCTION public.always_true(test.items) FROM postgrest_test;
GRANT ALL ON FUNCTION public.always_true(test.items) TO postgrest_test;
GRANT ALL ON FUNCTION public.always_true(test.items) TO postgrest_anonymous;
SET search_path = postgrest, pg_catalog;