Compare commits

..
96 Commits
Author SHA1 Message Date
Joe Nelson 02228b76cc Bump version 2015-04-17 16:26:27 -07:00
Joe Nelson 4d7cc3d67e Merge branch 'bulk-insert' 2015-04-17 15:55:42 -07:00
Joe Nelson ad8700e996 bulk insert in changelog 2015-04-17 15:52:57 -07:00
Joe Nelson fbc90bdb84 Tests for csv bulk import
Fixes #17
2015-04-17 15:42:58 -07:00
Joe Nelson f4c49f03f4 Allow NULL in csv field and unquote the multipart headers 2015-04-16 12:43:22 -07:00
Joe Nelson a87f13f9bb Fix original tests 2015-04-16 10:45:24 -07:00
Joe Nelson 7d03a71fed All tests but one are passing 2015-04-12 19:56:33 -07:00
Joe Nelson 70d33445db WIP: nice but doomed approach to making multipart response 2015-04-12 13:03:16 -07:00
Joe Nelson 4dbcf45555 WIP: typechecking but not yet sending back links
Rather amazing how well this actually works given
it only type checked
2015-04-11 21:41:32 -07:00
Joe Nelson 87acee924e WIP: parsing csv 2015-04-05 17:13:16 -07:00
Joe Nelson a22cf82688 Build sql for inserting multiple rows 2015-04-04 20:20:13 -07:00
Joe Nelson 532cfdff95 Merge pull request #183 from begriffs/jsonb
Filter results using properties in a jsonb column
2015-04-04 18:35:40 -07:00
Joe Nelson 5807b41997 Add jsonb to changelog 2015-04-04 18:33:08 -07:00
Joe Nelson c78d323989 Specify hasql versions exactly to help ci 2015-04-04 17:43:14 -07:00
Joe Nelson 9792b9b46a Allow filtering by values inside json columns 2015-04-04 17:38:54 -07:00
Joe Nelson ca1c524ede WIP: working on querying and ordering with jsonb paths
Affects #118
2015-03-30 00:51:52 -07:00
Joe Nelson a1822a8e08 Update whitespace 2015-03-29 12:32:14 -07:00
Joe Nelson 066cdbc697 Merge pull request #176 from brikou/patch-2
Add link to GUI demo
2015-03-23 18:35:02 -07:00
Brikou CARRE b9c3902bd8 Add link to GUI demo
You can read more about using ng-admin with postgREST here http://marmelab.com/blog/2015/03/23/using-ng-admin-with-postgrest.html
2015-03-23 15:54:34 +01:00
Joe Nelson 03613e2f8e Add link to tutorial
Thanks @prio
2015-03-22 20:27:54 -07:00
Joe Nelson de7eecb166 Document fix from pull request 2015-03-16 21:11:57 -07:00
Joe Nelson 417d98d7fa Merge pull request #172 from GaloisInc/master
Show long help output with defaults on usage errors
2015-03-16 21:09:57 -07:00
Jonathan Daugherty 6dfbe854a0 Make postgrest show long help output with defaults on usage errors
This makes optparse-applicative show the long version of help output.
Its default behavior is not to do this since the showHelpOnError
setting defaults to False.  This turns it on.
2015-03-16 13:27:22 -07:00
Joe Nelson a9bc119b82 Merge branch 'insert-nulls'
Fixes #166
2015-03-15 18:30:47 -07:00
Joe Nelson 42d3d0de6c Fix location header for inserted objects with nulls 2015-03-15 18:26:32 -07:00
Joe Nelson ddb5ba8b64 Can now post nulls, but header link is wrong
affects #166
2015-03-15 16:30:42 -07:00
Joe Nelson 185d5b1c62 Merge branch 'jcristovao-isnull' 2015-03-15 15:22:28 -07:00
Joe Nelson f7ff08edf7 Test is.null matcher 2015-03-15 15:20:31 -07:00
João CristóvãoandJoe Nelson f4e0c12cba IS/IS NOT null, true, false 2015-03-15 13:37:45 -07:00
Joe Nelson 8ed599a769 Merge branch 'jcristovao-nullsFirst' 2015-03-15 12:30:34 -07:00
Joe Nelson aeb62e75bd Add nullsfirst to changelog 2015-03-15 12:30:17 -07:00
Joe Nelson 5c7ee1effc Fix problems caused by my rebase 2015-03-15 12:24:30 -07:00
João CristóvãoandJoe Nelson 01c67ab793 Added tests for nullsfirst / nullslast 2015-03-15 11:37:06 -07:00
João CristóvãoandJoe Nelson 97c9bfe93f Added support for nulls first / nulls last 2015-03-15 10:28:21 -07:00
Joe Nelson 6e6769ea16 Bump version 2015-03-03 23:29:22 -08:00
Joe Nelson 1e417aef90 Note schema override in changelog 2015-03-03 23:20:13 -08:00
Joe Nelson 60eb826cb0 Merge branch 'v1-schema-override'
Affects #155
Affects #158
Fixes #117
2015-03-03 23:18:59 -08:00
Joe Nelson 3d3c7277c7 Allow user to override schema used for v1 of api 2015-03-03 22:29:37 -08:00
Joe Nelson 5be0b5d5ae Return inserted object from POST when Prefer: return=representation
Fixes #27
Fixes #159
2015-03-01 21:42:05 -08:00
Joe Nelson 11e5918690 Merge branch 'select-in'
Fixes #98
Affects #158
2015-03-01 19:18:21 -08:00
Joe Nelson c207e7ee30 Add IN filter to changelog 2015-03-01 18:38:36 -08:00
Joe Nelson 6474a83221 Support IN query param operator 2015-03-01 18:30:08 -08:00
Joe Nelson 9b15071f23 Log requests
Fixes #141
2015-02-28 09:31:00 -08:00
Joe Nelson 51b379d78b Clarify the --secure command line option 2015-02-19 21:28:07 -08:00
Joe Nelson cac5daca01 Merge pull request #154 from brikou/patch-1
Fix URL to video
2015-02-19 10:30:20 -08:00
Brikou CARRE 4bc66c4ca3 Fix URL to video 2015-02-19 10:04:45 +01:00
Joe Nelson eafb1a848f Bump minor version, add changelog 2015-02-18 11:39:13 -08:00
Joe Nelson 328f27e7bd Merge pull request #152 from brikou/expose_location_header
Add 'Location' to exposed headers
2015-02-18 08:41:00 -08:00
Brikou Carré a93e6c050d Add 'Location' to exposed headers 2015-02-18 10:22:24 +01:00
Joe Nelson 337b49f386 Expose Content-Range response header (and others) in CORS
Fixes #148
2015-02-16 19:35:25 -08:00
Joe Nelson 92daa7d11a Merge branch 'like'
Fixes #132
2015-02-15 17:37:40 -08:00
Joe Nelson fa48c86195 Logically simplify (i)like test cases 2015-02-15 17:34:26 -08:00
Joe Nelson b88192f95a Remove lint 2015-02-15 16:31:53 -08:00
Joe Nelson 5a1ae934b9 Put array open bracket nearer to the json 2015-02-15 16:14:25 -08:00
Joe Nelson 58f18181c2 Update order by params to new style introduced from master 2015-02-15 16:13:06 -08:00
Joe Nelson eea1bc0cac Quasiquote json to fix vim syntax highlighting 2015-02-15 16:03:31 -08:00
Joe Nelson e37d5d9b25 Style tweak 2015-02-15 15:59:49 -08:00
Adam C. BakerandJoe Nelson 5cc77709ba Add like/ilike 2015-02-15 15:23:18 -08:00
Joe Nelson 5280b9fd6d Merge pull request #138 from jcristovao/patch-1
order does not work
2015-02-12 08:41:40 -08:00
João Cristóvão 9cb1ced010 Change order test to match docs 2015-02-12 09:44:49 +00:00
João Cristóvão 0a3057e92d Update PgQuery.hs
I'm a bit baffled nobody noticed this before :P

Anyhow, without it order does not work.

Thanks,
Cheers
2015-02-11 19:49:31 +00:00
Joe Nelson 699d6d78dc Bump patch version 2015-02-07 15:27:56 -08:00
Joe Nelson 942ba52f56 Merge branch 'update-deps' 2015-02-07 14:48:26 -08:00
Joe Nelson d77b6df851 Use new optparse-applicative and bcrypt
Fixes #131 and affects #129
2015-02-07 14:21:40 -08:00
Joe Nelson 3662c1d428 Lint and check for restrictive package locks on ci 2015-02-07 13:48:04 -08:00
Joe Nelson e909ef3e62 Mention all modules in .cabal file
per #129
2015-02-04 14:50:33 -08:00
Joe Nelson 9a393c8603 Bump patch version 2015-01-31 12:48:02 -08:00
Joe Nelson ea429e077a Merge pull request #128 from begriffs/hasql-7
Upgrade to Hasql 0.7
2015-01-31 12:45:45 -08:00
Joe Nelson 90f9b00e5e Include JSON content type for errors 2015-01-31 12:33:44 -08:00
Joe Nelson f06f3e3394 Isolate tests
Credentials from AuthSpec were interfering with StructureSpec
2015-01-31 11:32:08 -08:00
Joe Nelson 88af46fbe3 Reduce test settings duplication
and better variable name
2015-01-31 11:31:11 -08:00
Joe Nelson 27450fae66 Fix NULL input problem
Relates to #127

Still auth problems though
2015-01-28 23:58:17 -08:00
Joe Nelson da6318f5a2 Fix warnings, lint, and ambiguous hasql imports 2015-01-28 20:37:19 -08:00
Adam C. Baker 3151aa2ebc fix table parsing 2015-01-26 18:42:46 -08:00
Adam C. Baker e1ad5cb1ae handle errors in the app too 2015-01-26 18:07:38 -08:00
Adam C. Baker ead346816b reporting errors, but not passing tests. 2015-01-26 17:31:59 -08:00
Joe Nelson b504473790 WIP: Upgrade to hasql 7
Still fails handling query errors
2015-01-25 18:33:50 -08:00
Joe Nelson 8073c84809 More informative cabal file for hackage release 2015-01-17 11:08:46 -08:00
Joe Nelson d28dee41a1 Heroku deployment button on README 2015-01-17 10:46:58 -08:00
Joe Nelson 003685ff46 Add support for DELETE verb
Fixes #125
2015-01-17 00:48:49 -08:00
Joe Nelson 61f8f41a36 Temporarily remove heroku button while I get it working properly 2015-01-07 23:45:58 -08:00
Joe Nelson 55f6318dcd Deploy button and thanks 2015-01-07 23:09:10 -08:00
Joe Nelson 96d108d724 Merge pull request #121 from begriffs/fix-order-by
Do not add WHERE clause if only param is order
2015-01-05 23:07:29 -08:00
Joe Nelson d77924a9d1 Update links to binaries 2015-01-05 23:03:51 -08:00
Joe Nelson 4c74e54ae1 Do not add WHERE clause if only param is order
Fixes #119
2015-01-05 22:37:49 -08:00
Joe Nelson 591f0eb78d Video link 2014-12-30 14:52:58 -08:00
Adam C. Baker c9b960f955 only create schema once 2014-12-29 17:57:31 -08:00
Adam C. Baker 23a75aefcc bump hspec version.
use new version for before_
2014-12-29 17:57:31 -08:00
Adam C. Baker 3756309b22 create items in test, not in the schema. 2014-12-29 17:57:31 -08:00
Adam C. Baker 2ae99daa12 resetDb once for each set of tests. 2014-12-29 17:57:31 -08:00
Joe Nelson 796de39762 More prominent demo server link 2014-12-29 16:12:17 -08:00
Joe Nelson c541f83cef Link to demo server and its schema 2014-12-29 15:27:29 -08:00
Joe Nelson a142128915 Fix logo typo 2014-12-29 15:07:09 -08:00
Joe Nelson ee8f754d7a Consolidate guides to make them easier to spot 2014-12-29 11:45:43 -08:00
Joe Nelson 3d02abc844 Link to binaries 2014-12-29 11:18:09 -08:00
Joe Nelson a049a8d5cd Update performance stats 2014-12-29 10:31:19 -08:00
28 changed files with 993 additions and 938 deletions
+33
View File
@@ -0,0 +1,33 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [0.2.8.0] - 2015-04-17
### Added
- Option to specify nulls first or last, eg `/people?order=age.desc.nullsfirst`
- Filter nulls, `?col=is.null` and `?col=isnot.null`
- Filter within jsonb, `?col->a->>b=eq.c`
- Accept CSV in post body for bulk inserts
### Fixed
- Allow NULL values in posts
- Show full command line usage on param errors
## [0.2.7.0] - 2015-03-03
### Added
- Server response logging
- Filter IN values, e.g. `?col=in.1,2,3`
- Return POSTed resource if header "Prefer: return=representation"
- Allow override of default (v1) schema
## [0.2.6.0] - 2015-02-18
### Added
- A changelog
- Filter by substring match, e.g. `?col=like.*hello*` (or ilike for
case insensitivity).
- Access-Control-Expose-Headers for CORS
### Fixed
- Make filter position match docs, e.g. `?order=col.asc` rather
than `?order=asc.col`.
+56 -31
View File
@@ -1,56 +1,47 @@
![Logo](static/logo.png "Logo")
[![Build Status](https://circleci.com/gh/begriffs/postgrest.png?circle-token=f723c01686abf0364de1e2eaae5aff1f68bd3ff2)](https://circleci.com/gh/begriffs/postgrest/tree/master)
<a href="https://heroku.com/deploy?template=https://github.com/begriffs/postgrest">
<img src="static/heroku.png" alt="Deploy">
</a>
PostgREST serves a fully RESTful API from any existing PostgreSQL
database. It provides a cleaner, more standards-compliant, faster
API than you are likely to write from scratch.
### Demo
### Demo [postgrest.herokuapp.com](https://postgrest.herokuapp.com) | Watch [Video](http://begriffs.com/posts/2014-12-30-intro-to-postgrest.html) | [GUI Demo](http://marmelab.com/ng-admin-postgrest)
Try making requests to the live [demo server] with an HTTP client
such as [postman](http://www.getpostman.com/).
[video placeholder]
Try making requests to the live demo server with an HTTP client
such as [postman](http://www.getpostman.com/). The structure of the
demo database is defined by
[begriffs/postgrest-example](https://github.com/begriffs/postgrest-example).
You can use it as inspiration for test-driven server migrations in
your own projects.
### Usage
Download [binaries for your platform] and invoke the program like so:
Download the binary ([OS X](http://bin.begriffs.com/dbapi/osx/postgrest-0.2.8.0.tar.xz) / [Linux](http://bin.begriffs.com/dbapi/heroku/postgrest-0.2.8.0.tar.xz)) 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 --secure \
--port 3000
--anonymous postgres --port 3000 \
--v1schema public
```
### Security
PostgREST handles authentication (HTTP Basic over SSL) and delegates
authorization to the role information defined in the database. This
ensures there is a single declarative source of truth for security.
When dealing with the database the server assumes the identity of
the currently authenticated user, and for the duration of the
connection cannot do anything the user themselves couldn't.
Postgres 9.5 will soon support true [row-level
security](http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-row-level-security/).
In the meantime what isn't yet implemented can be simulated with
triggers and security-barrier views. Because the possible queries
to the database are limited to certain templates using
[leakproof](http://blog.2ndquadrant.com/how-do-postgresql-security_barrier-views-work/)
functions, the trigger workaround does not compromise row-level
security.
For example security patterns see the [security
guide](https://github.com/begriffs/postgrest/wiki/Security-and-Permissions).
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
as nginx or the Heroku load balancer).
### Performance
TLDR; subsecond response times for up to 2000 requests/sec on Heroku free tier. ([see the load test](https://github.com/begriffs/postgrest/wiki/Performance-and-Scaling))
If you're used to servers written in interpreted languages (or named
after precious gems), prepare to be pleasantly surprised by PostgREST
performance. ([see the load test](https://github.com/begriffs/postgrest/wiki/Performance-and-Scaling))
performance.
Three factors contribute to the speed. First the server is written
in [Haskell](https://new-www.haskell.org/) using the
@@ -82,6 +73,27 @@ the [performance guide](https://github.com/begriffs/postgrest/wiki/Performance-a
Other optimizations are possible, and some are outlined in the
[Future Features](#future-features).
### Security
PostgREST handles authentication (HTTP Basic over SSL) and delegates
authorization to the role information defined in the database. This
ensures there is a single declarative source of truth for security.
When dealing with the database the server assumes the identity of
the currently authenticated user, and for the duration of the
connection cannot do anything the user themselves couldn't.
Postgres 9.5 will soon support true [row-level
security](http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-row-level-security/).
In the meantime what isn't yet implemented can be simulated with
triggers and security-barrier views. Because the possible queries
to the database are limited to certain templates using
[leakproof](http://blog.2ndquadrant.com/how-do-postgresql-security_barrier-views-work/)
functions, the trigger workaround does not compromise row-level
security.
For example security patterns see the [security
guide](https://github.com/begriffs/postgrest/wiki/Security-and-Permissions).
### Versioning
A robust long-lived API needs the freedom to exist in multiple
@@ -135,7 +147,20 @@ and the [guide to routing](https://github.com/begriffs/postgrest/wiki/Routing).
* Add two-legged auth with OAuth 1.0a(?)
* ... the other [issues](https://github.com/begriffs/postgrest/issues)
### 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)
### Thanks
Thanks to [Adam Baker](https://github.com/adambaker) for code
contributions and many fundamental design discussions.
* [Adam Baker](https://github.com/adambaker) for code
contributions and many fundamental design discussions
* [Nikita Volkov](https://github.com/nikita-volkov) for writing the
wonderful [Hasql](https://github.com/nikita-volkov/hasql) library
and helping me use it
* [Mikey Casalaina](https://github.com/casalaina) for the cool logo
* [Jonathan Harrington](https://github.com/prio) for writing a nice tutorial
+46
View File
@@ -0,0 +1,46 @@
{
"name": "PostgREST",
"description": "RESTful API for any PostgreSQL database.",
"logo": "https://halcyon.sh/logo.svg",
"repository": "https://github.com/begriffs/postgrest",
"env": {
"BUILDPACK_URL": {
"description": "Heroku buildpack for deploying Haskell applications",
"value": "https://github.com/begriffs/postgrest-heroku"
},
"POSTGREST_VER": {
"description": "Version of PostgREST to deploy",
"value": "0.2.8.0"
},
"DB_NAME": {
"description": "Database name",
"required": true
},
"AUTH_ROLE": {
"description": "Database role to use checking client authentication",
"required": true
},
"AUTH_PASS": {
"description": "Authentication password",
"required": false
},
"ANONYMOUS_ROLE": {
"description": "Database role for non-authenticated requests",
"required": true
},
"DB_HOST": {
"description": "Database server hostname",
"required": true
},
"DB_PORT": {
"description": "Database server port",
"required": false,
"value": "5432"
},
"DB_POOL": {
"description": "Maximum number of connections in database pool",
"required": false,
"value": "10"
}
}
}
+4
View File
@@ -4,3 +4,7 @@ machine:
- createdb -O postgrest_test -U ubuntu postgrest_test
ghc:
version: 7.8.3
test:
post:
- cabal exec hlint -- -X QuasiQuotes src/*.hs test/**/*.hs
- cabal exec packdeps postgrest.cabal
+46 -31
View File
@@ -1,9 +1,13 @@
name: postgrest
version: 0.2.4.9
synopsis: The database is your api
description: Reads the schema of a PostgreSQL database and creates RESTful routes
for the tables and views, supporting all HTTP verbs that security
permits.
version: 0.2.8.0
synopsis: REST API for any Postgres database
license: MIT
license-file: LICENSE
author: Joe Nelson, Adam Baker
homepage: https://github.com/begriffs/postgrest
maintainer: cred+github@begriffs.com
category: Web
build-type: Simple
@@ -11,13 +15,12 @@ cabal-version: >=1.10
executable postgrest
main-is: Main.hs
ghc-options: -Wall -W -Werror -O2
ghc-options: -Wall -W -O2
default-language: Haskell2010
default-extensions: OverloadedStrings
other-extensions: QuasiQuotes
default-extensions: OverloadedStrings, ScopedTypeVariables, QuasiQuotes
build-depends: base >=4.6 && <5
, hasql == 0.4.*, hasql-backend
, hasql-postgres == 0.8.*
, hasql == 0.7.3, hasql-backend == 0.4.1
, hasql-postgres == 0.10.3
, warp >= 3.0.2, wai >= 3.0.1
, wai-extra, wai-cors
, wai-middleware-static >= 0.6.0
@@ -26,64 +29,76 @@ executable postgrest
, scientific, time
, aeson, network >= 2.6
, bytestring, text, split, string-conversions
, stringsearch, parsec
, stringsearch
, containers, unordered-containers
, optparse-applicative >= 0.9.1 && < 0.10
, optparse-applicative == 0.11.*
, regex-base, regex-tdfa
, regex-tdfa-text
, Ranged-sets
, transformers
, bcrypt, base64-string
, transformers, MissingH
, bcrypt >= 0.0.6, base64-string
, network-uri >= 2.6
, resource-pool
, blaze-builder
, vector
, mtl
, cassava
Other-Modules: App
, Auth
, Config
, PgStructure
, PgQuery
, PgError
, RangeQuery
, Error
, Middleware
, PgQuery
, PgStructure
, RangeQuery
, Types
hs-source-dirs: src
Test-Suite spec
Type: exitcode-stdio-1.0
Default-Language: Haskell2010
default-extensions: OverloadedStrings
other-extensions: QuasiQuotes
default-extensions: OverloadedStrings, ScopedTypeVariables, QuasiQuotes
Hs-Source-Dirs: test, src
ghc-options: -Wall -W -Werror
Main-Is: Spec.hs
Other-Modules: App, Auth, Config, Spec, SpecHelper
Build-Depends: base, hspec >= 2.0, QuickCheck
Main-Is: Main.hs
Other-Modules: App
, Auth
, Config
, Error
, Middleware
, PgQuery
, PgStructure
, RangeQuery
, Types
, Spec
, SpecHelper
Build-Depends: base, hspec >= 2.1.2, QuickCheck
, hspec-wai >= 0.5.0, hspec-wai-json
, hasql == 0.4.*, hasql-backend
, hasql-postgres == 0.8.*
, warp >= 3.0.2, wai >= 3.0.1
, hasql == 0.7.3, hasql-backend == 0.4.1
, hasql-postgres == 0.10.3
, warp, wai
, packdeps, hlint
, HTTP, convertible
, case-insensitive
, wai-extra, wai-cors, containers
, wai-middleware-static >= 0.6.0
, wai-middleware-static
, http-types, scientific, time
, bytestring, aeson, network >= 2.6
, bytestring, aeson, network
, text, optparse-applicative
, stringsearch, parsec
, stringsearch
, unordered-containers
, regex-base
, string-conversions
, http-media, regex-tdfa
, regex-tdfa-text
, Ranged-sets
, transformers
, bcrypt
, base64-string
, split
, network-uri >= 2.6
, transformers, MissingH, split
, bcrypt, base64-string
, network-uri
, resource-pool
, blaze-builder
, vector
, mtl
, cassava
, process
, heredoc
+97 -74
View File
@@ -2,41 +2,45 @@
module App (app, sqlError, isSqlError) where
import Control.Monad (join)
import Control.Arrow ((***))
import Control.Arrow ((***), second)
import Control.Applicative
import Data.Text hiding (map)
import Data.Maybe (fromMaybe)
import Data.Maybe (fromMaybe, mapMaybe)
import Text.Regex.TDFA ((=~))
import Data.Ord (comparing)
import Data.Ranged.Ranges (emptyRange)
import Data.HashMap.Strict (keys, elems, filterWithKey, toList)
import qualified Data.HashMap.Strict as M
import Data.String.Conversions (cs)
import Data.CaseInsensitive (original)
import Data.List (sortBy)
import Data.Functor.Identity
import qualified Data.Set as S
import qualified Data.ByteString.Lazy as BL
import qualified Blaze.ByteString.Builder as BB
import qualified Data.Csv as CSV
import Network.HTTP.Types.Status
import Network.HTTP.Types.Header
import Network.HTTP.Types.URI (parseSimpleQuery)
import Network.HTTP.Base (urlEncodeVars)
import Network.Wai
import Network.Wai.Internal (Response(..))
import Data.Aeson
import Data.Monoid
import qualified Data.Vector as V
import qualified Hasql as H
import qualified Hasql.Postgres as H
import qualified Hasql.Backend as B
import qualified Hasql.Postgres as P
import Auth
import PgQuery
import RangeQuery
import PgStructure
import PgError
import Text.Parsec hiding (Column)
app :: BL.ByteString -> Request -> H.Tx H.Postgres s Response
app reqBody req =
app :: Text -> BL.ByteString -> Request -> H.Tx P.Postgres s Response
app v1schema reqBody req =
case (path, verb) of
([], _) -> do
body <- encode <$> tables (cs schema)
@@ -54,8 +58,7 @@ app reqBody req =
then return $ responseLBS status416 [] "HTTP Range error"
else do
let qt = QualifiedTable schema (cs table)
let select = coerce $
("select ",[],mempty) <>
let select = B.Stmt "select " V.empty True <>
parentheticT (
whereT qq $ countRows qt
) <> commaq <> (
@@ -65,7 +68,7 @@ app reqBody req =
. whereT qq
$ selectStar qt
)
row <- H.single select
row <- H.maybeEx select
let (tableTotal, queryTotal, body) =
fromMaybe (0, 0, Just "" :: Maybe Text) row
from = fromMaybe 0 $ rangeOffset <$> range
@@ -99,26 +102,40 @@ app reqBody req =
, (hLocation, "/postgrest/users?id=eq." <> cs (userId u))
] ""
([table], "POST") ->
handleJsonObj reqBody $ \obj -> do
let qt = QualifiedTable schema (cs table)
query = coerce $
insertInto qt (map cs $ keys obj) (elems obj)
row <- H.single query
let (Identity insertedJson) = fromMaybe (Identity "{}" :: Identity Text) row
Just inserted = decode (cs insertedJson) :: Maybe Object
primaryKeys <- map cs <$> primaryKeyColumns qt
let primaries = if Prelude.null primaryKeys
then inserted
else filterWithKey (const . (`elem` primaryKeys)) inserted
let params = urlEncodeVars
$ map (\t -> (cs $ fst t, "eq." <> cs (unquoted $ snd t)))
$ sortBy (comparing fst) $ toList primaries
return $ responseLBS status201
[ jsonH
, (hLocation, "/" <> cs table <> "?" <> cs params)
] ""
([table], "POST") -> do
let qt = QualifiedTable schema (cs table)
echoRequested = lookup "Prefer" hdrs == Just "return=representation"
parsed :: Either String (V.Vector Text, V.Vector (V.Vector Value))
parsed = if lookup "Content-Type" hdrs == Just "text/csv"
then do
rows <- CSV.decode CSV.NoHeader reqBody
if V.null rows then Left "CSV requires header"
else Right (V.head rows, (V.map $ V.map $ parseCsvCell . cs) (V.tail rows))
else eitherDecode reqBody >>= \val ->
case val of
Object obj -> Right . second V.singleton . V.unzip . V.fromList $
M.toList obj
_ -> Left "Expecting single JSON object or CSV rows"
case parsed of
Left err -> return $ responseLBS status400 [] $
encode . object $ [("message", String $ "Failed to parse JSON payload. " <> cs err)]
Right toBeInserted -> do
rows :: [Identity Text] <- H.listEx $ uncurry (insertInto qt) toBeInserted
let inserted :: [Object] = mapMaybe (decode . cs . runIdentity) rows
primaryKeys <- primaryKeyColumns qt
let responses = flip map inserted $ \obj -> do
let primaries =
if Prelude.null primaryKeys
then obj
else M.filterWithKey (const . (`elem` primaryKeys)) obj
let params = urlEncodeVars
$ map (\t -> (cs $ fst t, cs (paramFilter $ snd t)))
$ sortBy (comparing fst) $ M.toList primaries
responseLBS status201
[ jsonH
, (hLocation, "/" <> cs table <> "?" <> cs params)
] $ if echoRequested then encode obj else ""
return $ multipart status201 responses
([table], "PUT") ->
handleJsonObj reqBody $ \obj -> do
@@ -130,11 +147,11 @@ app reqBody req =
"You must speficy all and only primary keys as params"
else do
tableCols <- map (cs . colName) <$> columns qt
let cols = map cs $ keys obj
let cols = map cs $ M.keys obj
if S.fromList tableCols == S.fromList cols
then do
let vals = elems obj
H.unit . coerce $ iffNotT
let vals = M.elems obj
H.unitEx $ iffNotT
(whereT qq $ update qt cols vals)
(insertSelect qt cols vals)
return $ responseLBS status204 [ jsonH ] ""
@@ -147,12 +164,23 @@ app reqBody req =
([table], "PATCH") ->
handleJsonObj reqBody $ \obj -> do
let qt = QualifiedTable schema (cs table)
H.unit
$ coerce
H.unitEx
$ whereT qq
$ update qt (map cs $ keys obj) (elems obj)
$ update qt (map cs $ M.keys obj) (M.elems obj)
return $ responseLBS status204 [ jsonH ] ""
([table], "DELETE") -> do
let qt = QualifiedTable schema (cs table)
let del = countT
. returningStarT
. whereT qq
$ deleteFrom qt
row <- H.maybeEx del
let (Identity deletedCount) = fromMaybe (Identity 0 :: Identity Int) row
return $ if deletedCount == 0
then responseLBS status404 [] ""
else responseLBS status204 [("Content-Range", "*/"<> cs (show deletedCount))] ""
(_, _) ->
return $ responseLBS status404 [] ""
@@ -161,41 +189,15 @@ app reqBody req =
verb = requestMethod req
qq = queryString req
hdrs = requestHeaders req
schema = requestedSchema hdrs
schema = requestedSchema v1schema hdrs
range = rangeRequested hdrs
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header
coerce (q, args, All b) = (q, args, b)
sqlError :: t
sqlError = undefined
isSqlError :: H.Error -> Maybe H.Error
isSqlError = Just
sqlError :: H.Error -> Response
sqlError err =
let inside = case err of
H.CantConnect _ ->
"Message: \"Cannot connect to postgres server\""
H.ConnectionLost t -> t
H.ErroneousResult t -> t
H.UnexpectedResult t -> t
H.UnparsableTemplate t -> t
H.UnparsableRow t -> t
H.NotInTransaction -> "An operation which requires a"
<> "database transaction was executed without one" in
either
(\hint ->
responseLBS status500
[(hContentType, "application/json")]
(cs . encode . object $ [
("message", String $
"Failed to parse exception:" <> inside)
, ("hint", String . cs . show $ hint)]))
(\msg ->
responseLBS (httpStatus msg)
[(hContentType, "application/json")]
(encode msg))
(parse message "" inside)
isSqlError :: t
isSqlError = undefined
rangeStatus :: Int -> Int -> Int -> Status
rangeStatus from to total
@@ -213,11 +215,11 @@ contentRangeH from to total =
<> cs (show total)
)
requestedSchema :: RequestHeaders -> Text
requestedSchema hdrs =
requestedSchema :: Text -> RequestHeaders -> Text
requestedSchema v1schema hdrs =
case verStr of
Just [[_, ver]] -> ver
_ -> "1"
Just [[_, ver]] -> if ver == "1" then v1schema else ver
_ -> v1schema
where verRegex = "version[ ]*=[ ]*([0-9]+)" :: String
accept = cs <$> lookup hAccept hdrs :: Maybe Text
@@ -226,8 +228,8 @@ requestedSchema hdrs =
jsonH :: Header
jsonH = (hContentType, "application/json")
handleJsonObj :: BL.ByteString -> (Object -> H.Tx H.Postgres s Response)
-> H.Tx H.Postgres s Response
handleJsonObj :: BL.ByteString -> (Object -> H.Tx P.Postgres s Response)
-> H.Tx P.Postgres s Response
handleJsonObj reqBody handler = do
let p = eitherDecode reqBody
case p of
@@ -243,6 +245,27 @@ handleJsonObj reqBody handler = do
jErr = encode . object $
[("message", String "Expecting a JSON object")]
parseCsvCell :: BL.ByteString -> Value
parseCsvCell s = if s == "NULL" then Null else String $ cs s
multipart :: Status -> [Response] -> Response
multipart _ [] = responseLBS status204 [] ""
multipart _ [r] = r
multipart s rs =
responseLBS s [(hContentType, "multipart/mixed; boundary=\"postgrest_boundary\"")] $
BL.intercalate "\n--postgrest_boundary\n" (map renderResponseBody rs)
where
renderHeader :: Header -> BL.ByteString
renderHeader (k, v) = cs (original k) <> ": " <> cs v
renderResponseBody :: Response -> BL.ByteString
renderResponseBody (ResponseBuilder _ headers b) =
BL.intercalate "\n" (map renderHeader headers)
<> "\n\n" <> BB.toLazyByteString b
renderResponseBody _ = error
"Unable to create multipart response from non-ResponseBuilder"
data TableOptions = TableOptions {
tblOptcolumns :: [Column]
, tblOptpkey :: [Text]
+12 -10
View File
@@ -7,8 +7,10 @@ import Control.Applicative ( (<*>), (<$>) )
import Crypto.BCrypt
import Data.Text
import Data.Monoid
import qualified Data.Vector as V
import qualified Hasql as H
import qualified Hasql.Postgres as H
import qualified Hasql.Backend as B
import qualified Hasql.Postgres as P
import Data.String.Conversions (cs)
import PgQuery (pgFmtLit)
@@ -45,22 +47,22 @@ data LoginAttempt =
checkPass :: Text -> Text -> Bool
checkPass = (. cs) . validatePassword . cs
setRole :: Text -> H.Tx H.Postgres s ()
setRole role = H.unit ("set role " <> cs (pgFmtLit role), [], True)
setRole :: Text -> H.Tx P.Postgres s ()
setRole role = H.unitEx $ B.Stmt ("set role " <> cs (pgFmtLit role)) V.empty True
resetRole :: H.Tx H.Postgres s ()
resetRole = H.unit [H.q|reset role|]
resetRole :: H.Tx P.Postgres s ()
resetRole = H.unitEx [H.stmt|reset role|]
addUser :: Text -> Text -> Text -> H.Tx H.Postgres s ()
addUser :: Text -> Text -> Text -> H.Tx P.Postgres s ()
addUser identity pass role = do
let Just hashed = unsafePerformIO $ hashPasswordUsingPolicy fastBcryptHashingPolicy (cs pass)
H.unit $
[H.q|insert into postgrest.auth (id, pass, rolname) values (?, ?, ?)|]
H.unitEx $
[H.stmt|insert into postgrest.auth (id, pass, rolname) values (?, ?, ?)|]
identity (cs hashed :: Text) role
signInRole :: Text -> Text -> H.Tx H.Postgres s LoginAttempt
signInRole :: Text -> Text -> H.Tx P.Postgres s LoginAttempt
signInRole user pass = do
u <- H.single $ [H.q|select pass, rolname from postgrest.auth where id = ?|] user
u <- H.maybeEx $ [H.stmt|select pass, rolname from postgrest.auth where id = ?|] user
return $ maybe LoginFailed (\r ->
let (hashed, role) = r in
if checkPass hashed pass
+15 -9
View File
@@ -20,20 +20,22 @@ data AppConfig = AppConfig {
, configAnonRole :: String
, configSecure :: Bool
, configPool :: Int
, configV1Schema :: String
}
argParser :: Parser AppConfig
argParser = AppConfig
<$> strOption (long "db-name" <> short 'd' <> help "name of database")
<*> option (long "db-port" <> short 'P' <> value 5432 <> help "postgres server port")
<*> strOption (long "db-user" <> short 'U' <> help "postgres authenticator role")
<*> strOption (long "db-pass" <> value "" <> help "password for authenticator role")
<*> strOption (long "db-host" <> short 'h' <> value "localhost" <> help "postgres server hostname")
<$> 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)
<*> option (long "port" <> short 'p' <> value 3000 <> help "port number on which to run HTTP server")
<*> strOption (long "anonymous" <> short 'a' <> help "postgres role to use for non-authenticated requests")
<*> switch (long "secure" <> short 's' <> help "Redirect all requests to HTTPS")
<*> option (long "db-pool" <> value 10 <> help "Max connections in database pool")
<*> 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)
defaultCorsPolicy :: CorsResourcePolicy
defaultCorsPolicy = CorsResourcePolicy Nothing
@@ -45,6 +47,10 @@ corsPolicy req = case lookup "origin" headers of
Just origin -> Just defaultCorsPolicy {
corsOrigins = Just ([origin], True)
, corsRequestHeaders = "Authentication":accHeaders
, corsExposedHeaders = Just [
"Content-Encoding", "Content-Location", "Content-Range", "Content-Type"
, "Date", "Location", "Server", "Transfer-Encoding", "Range-Unit"
]
}
Nothing -> Nothing
where
+71
View File
@@ -0,0 +1,71 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
module Error (PgError, errResponse) where
import qualified Hasql as H
import qualified Hasql.Postgres as P
import qualified Network.HTTP.Types.Status as HT
import qualified Data.Aeson as JSON
import qualified Data.Text as T
import Data.Aeson ((.=))
import Data.String.Conversions (cs)
import Data.String.Utils(replace)
import Network.Wai(Response, responseLBS)
import Network.HTTP.Types.Header
type PgError = H.SessionError P.Postgres
errResponse :: PgError -> Response
errResponse e = responseLBS (httpStatus e)
[(hContentType, "application/json")] (JSON.encode e)
instance JSON.ToJSON PgError where
toJSON (H.TxError (P.ErroneousResult c m d h)) = JSON.object [
"code" .= (cs c::T.Text),
"message" .= (cs m::T.Text),
"details" .= (fmap cs d::Maybe T.Text),
"hint" .= (fmap cs h::Maybe T.Text)]
toJSON (H.TxError (P.NoResult d)) = JSON.object [
"message" .= ("No response from server"::T.Text),
"details" .= (fmap cs d::Maybe T.Text)]
toJSON (H.TxError (P.UnexpectedResult m)) = JSON.object ["message" .= m]
toJSON (H.TxError P.NotInTransaction) = JSON.object [
"message" .= ("Not in transaction"::T.Text)]
toJSON (H.CxError (P.CantConnect d)) = JSON.object [
"message" .= ("Can't connect to the database"::T.Text),
"details" .= (fmap cs d::Maybe T.Text)]
toJSON (H.CxError (P.UnsupportedVersion v)) = JSON.object [
"message" .= ("Postgres version "++version++" is not supported") ]
where version = replace "0" "." (show v)
toJSON (H.ResultError m) = JSON.object ["message" .= m]
httpStatus :: PgError -> HT.Status
httpStatus (H.TxError (P.ErroneousResult codeBS _ _ _)) =
let code = cs codeBS in
case code of
'0':'8':_ -> HT.status503 -- pg connection err
'0':'9':_ -> HT.status500 -- triggered action exception
'0':'L':_ -> HT.status403 -- invalid grantor
'0':'P':_ -> HT.status403 -- invalid role specification
'2':'5':_ -> HT.status500 -- invalid tx state
'2':'8':_ -> HT.status403 -- invalid auth specification
'2':'D':_ -> HT.status500 -- invalid tx termination
'3':'8':_ -> HT.status500 -- external routine exception
'3':'9':_ -> HT.status500 -- external routine invocation
'3':'B':_ -> HT.status500 -- savepoint exception
'4':'0':_ -> HT.status500 -- tx rollback
'5':'3':_ -> HT.status503 -- insufficient resources
'5':'4':_ -> HT.status413 -- too complex
'5':'5':_ -> HT.status500 -- obj not on prereq state
'5':'7':_ -> HT.status500 -- operator intervention
'5':'8':_ -> HT.status500 -- system error
'F':'0':_ -> HT.status500 -- conf file error
'H':'V':_ -> HT.status500 -- foreign data wrapper error
'P':'0':_ -> HT.status500 -- PL/pgSQL Error
'X':'X':_ -> HT.status500 -- internal Error
"42P01" -> HT.status404 -- undefined table
"42501" -> HT.status404 -- insufficient privilege
_ -> HT.status400
httpStatus (H.TxError (P.NoResult _)) = HT.status503
httpStatus _ = HT.status500
+26 -19
View File
@@ -4,27 +4,36 @@ import Paths_postgrest (version)
import App
import Middleware
import Error(errResponse)
import Control.Monad (unless)
import Control.Monad.IO.Class (liftIO)
import Control.Exception
import Data.String.Conversions (cs)
import Network.Wai (strictRequestBody)
import Network.Wai.Middleware.Cors (cors)
import Network.Wai.Handler.Warp hiding (Connection)
import Network.Wai.Middleware.Gzip (gzip, def)
import Network.Wai.Middleware.Static (staticPolicy, only)
import Network.Wai.Middleware.RequestLogger (logStdout)
import Data.List (intercalate)
import Data.Version (versionBranch)
import qualified Hasql as H
import qualified Hasql.Postgres as H
import qualified Hasql.Postgres as P
import Options.Applicative hiding (columns)
import Config (AppConfig(..), argParser, corsPolicy)
main :: IO ()
main = do
conf <- execParser (info (helper <*> argParser) describe)
let opts = info (helper <*> argParser) $
fullDesc
<> progDesc (
"PostgREST "
<> prettyVersion
<> " / create a REST API to an existing Postgres database"
)
parserPrefs = prefs showHelpOnError
conf <- customExecParser parserPrefs opts
let port = configPort conf
unless (configSecure conf) $
@@ -32,33 +41,31 @@ main = do
Prelude.putStrLn $ "Listening on port " ++
(show $ configPort conf :: String)
let pgSettings = H.ParamSettings (cs $ configDbHost conf)
let pgSettings = P.ParamSettings (cs $ configDbHost conf)
(fromIntegral $ configDbPort conf)
(cs $ configDbUser conf)
(cs $ configDbPass conf)
(cs $ configDbName conf)
sessSettings <- maybe (fail "Improper session settings") return $
H.sessionSettings (fromIntegral $ configPool conf) 30
let appSettings = setPort port
appSettings = setPort port
. setServerName (cs $ "postgrest/" <> prettyVersion)
$ defaultSettings
middle =
(if configSecure conf then redirectInsecure else id)
middle = logStdout
. (if configSecure conf then redirectInsecure else id)
. gzip def . cors corsPolicy
. staticPolicy (only [("favicon.ico", "static/favicon.ico")])
anonRole = cs $ configAnonRole conf
currRole = cs $ configDbUser conf
H.session pgSettings sessSettings $ H.sessionUnlifter >>= \unlift ->
liftIO $ runSettings appSettings $ middle $ \req respond -> do
body <- strictRequestBody req
respond =<< catchJust isSqlError
(unlift $ H.tx Nothing
$ authenticated currRole anonRole (app body) req)
(return . sqlError)
poolSettings <- maybe (fail "Improper session settings") return $
H.poolSettings (fromIntegral $ configPool conf) 30
pool :: H.Pool P.Postgres
<- H.acquirePool pgSettings poolSettings
runSettings appSettings $ middle $ \req respond -> do
body <- strictRequestBody req
resOrError <- liftIO $ H.session pool $ H.tx Nothing $
authenticated currRole anonRole (app (cs $ configV1Schema conf) body) req
either (respond . errResponse) respond resOrError
where
describe = progDesc "create a REST API to an existing Postgres database"
prettyVersion = intercalate "." $ map show $ versionBranch version
+5 -5
View File
@@ -9,7 +9,7 @@ import Data.Text
-- import Data.Pool(withResource, Pool)
import qualified Hasql as H
import qualified Hasql.Postgres as H
import qualified Hasql.Postgres as P
import Data.String.Conversions(cs)
import Network.HTTP.Types.Header (hLocation, hAuthorization)
@@ -23,8 +23,8 @@ import Auth (LoginAttempt(..), signInRole, setRole, resetRole)
import Codec.Binary.Base64.String (decode)
authenticated :: forall s. Text -> Text ->
(Request -> H.Tx H.Postgres s Response) ->
Request -> H.Tx H.Postgres s Response
(Request -> H.Tx P.Postgres s Response) ->
Request -> H.Tx P.Postgres s Response
authenticated currentRole anon app req = do
attempt <- httpRequesterRole (requestHeaders req)
case attempt of
@@ -36,7 +36,7 @@ authenticated currentRole anon app req = do
NoCredentials -> if anon /= currentRole then runInRole anon else app req
where
httpRequesterRole :: RequestHeaders -> H.Tx H.Postgres s LoginAttempt
httpRequesterRole :: RequestHeaders -> H.Tx P.Postgres s LoginAttempt
httpRequesterRole hdrs = do
let auth = fromMaybe "" $ lookup hAuthorization hdrs
case split (==' ') (cs auth) of
@@ -46,7 +46,7 @@ authenticated currentRole anon app req = do
_ -> return MalformedAuth
_ -> return NoCredentials
runInRole :: Text -> H.Tx H.Postgres s Response
runInRole :: Text -> H.Tx P.Postgres s Response
runInRole r = do
setRole r
res <- app req
-86
View File
@@ -1,86 +0,0 @@
{-# LANGUAGE OverloadedStrings #-}
module PgError (Message(..), message, httpStatus) where
import Text.Parsec
import Text.Parsec.Text
import qualified Data.Map as M
import Text.Regex.TDFA.Text ()
import Data.Text hiding (drop, concat, head)
import Data.Aeson
import Data.Maybe
import Control.Monad (void)
import Data.String.Conversions (cs)
import Data.CaseInsensitive (CI, mk)
import Network.HTTP.Types.Status
data Message = Message {
msgStatus :: Maybe Text
, msgCode :: Text
, msgText :: Maybe Text
, msgHint :: Maybe Text
} deriving (Show, Eq)
message :: Parser Message
message = do
ps <- sepBy valPair (char ';')
let m = M.fromList ps
return $ Message
(M.lookup "status" m)
(fromMaybe "" $ M.lookup "code" m)
(M.lookup "message" m)
(M.lookup "hint" m)
valPair :: Parser (CI Text, Text)
valPair = do
_ <- spaces
name <- many1 letter
_ <- char ':'
spaces
_ <- many $ char '"'
val <- manyTill anyChar $
try
(void $ many (char '"') >> (
(void . lookAhead $ (char ';'))
<|> ((optional $ char '.') >> eof)
))
return (mk (cs name), cs val)
instance ToJSON Message where
toJSON t = object [
"message" .= msgText t
, "code" .= msgCode t
, "status" .= msgStatus t
, "hint" .= msgHint t
]
httpStatus :: Message -> Status
httpStatus m =
let code = cs $ msgCode m :: String in
case code of
'0' : '8' : _ -> status503 -- pg connection err
'0' : '9' : _ -> status500 -- triggered action exception
'0' : 'L' : _ -> status403 -- invalid grantor
'0' : 'P' : _ -> status403 -- invalid role specification
'2' : '5' : _ -> status500 -- invalid tx state
'2' : '8' : _ -> status403 -- invalid auth specification
'2' : 'D' : _ -> status500 -- invalid tx termination
'3' : '8' : _ -> status500 -- external routine exception
'3' : '9' : _ -> status500 -- external routine invocation
'3' : 'B' : _ -> status500 -- savepoint exception
'4' : '0' : _ -> status500 -- tx rollback
'5' : '3' : _ -> status503 -- insufficient resources
'5' : '4' : _ -> status413 -- too complex
'5' : '5' : _ -> status500 -- obj not on prereq state
'5' : '7' : _ -> status500 -- operator intervention
'5' : '8' : _ -> status500 -- system error
'F' : '0' : _ -> status500 -- conf file error
'H' : 'V' : _ -> status500 -- foreign data wrapper error
'P' : '0' : _ -> status500 -- PL/pgSQL Error
'X' : 'X' : _ -> status500 -- internal Error
"42P01" -> status404 -- undefined table
"42501" -> status404 -- insufficient privilege
_ -> status400
+181 -119
View File
@@ -1,51 +1,60 @@
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, MultiWayIf #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module PgQuery where
import RangeQuery
import qualified Hasql.Postgres as H
import qualified Hasql.Backend as H
import qualified Hasql as H
import qualified Hasql.Postgres as P
import qualified Hasql.Backend as B
import Data.Text hiding (map)
import qualified Data.Text as T
import Text.Regex.TDFA ( (=~) )
import Text.Regex.TDFA.Text ()
import qualified Network.HTTP.Types.URI as Net
import qualified Data.ByteString.Char8 as BS
import Data.Monoid
import Data.Vector (empty)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Functor ( (<$>) )
import Control.Monad (join)
import Data.String.Conversions (cs)
import qualified Data.Aeson as JSON
import qualified Data.List as L
import qualified Data.Vector as V
import Data.Scientific (isInteger, formatScientific, FPFormat(..))
type DynamicSQL = (BS.ByteString, [H.StatementArgument H.Postgres], All)
type StatementT = DynamicSQL -> DynamicSQL
type PStmt = H.Stmt P.Postgres
instance Monoid PStmt where
mappend (B.Stmt query params prep) (B.Stmt query' params' prep') =
B.Stmt (query <> query') (params <> params') (prep && prep')
mempty = B.Stmt "" empty True
type StatementT = PStmt -> PStmt
data QualifiedTable = QualifiedTable {
qtSchema :: Text
, qtName :: Text
qtSchema :: T.Text
, qtName :: T.Text
} deriving (Show)
data OrderTerm = OrderTerm {
otTerm :: Text
otTerm :: T.Text
, otDirection :: BS.ByteString
, otNullOrder :: Maybe BS.ByteString
}
limitT :: Maybe NonnegRange -> StatementT
limitT r q =
q <> (" LIMIT " <> limit <> " OFFSET " <> offset <> " ", [], mempty)
q <> B.Stmt (" LIMIT " <> limit <> " OFFSET " <> offset <> " ") empty True
where
limit = maybe "ALL" (cs . show) $ join $ rangeLimit <$> r
offset = cs . show $ fromMaybe 0 $ rangeOffset <$> r
whereT :: Net.Query -> StatementT
whereT params q =
if L.null params
if L.null cols
then q
else q <> (" where ",[],mempty) <> conjunction
else q <> B.Stmt " where " empty True <> conjunction
where
cols = [ col | col <- params, fst col `notElem` ["order"] ]
conjunction = mconcat $ L.intersperse andq (map wherePred cols)
@@ -54,98 +63,112 @@ orderT :: [OrderTerm] -> StatementT
orderT ts q =
if L.null ts
then q
else q <> (" order by ",[],mempty) <> clause
else q <> B.Stmt " order by " empty True <> clause
where
clause = mconcat $ L.intersperse commaq (map queryTerm ts)
queryTerm :: OrderTerm -> DynamicSQL
queryTerm t = (" " <> cs (pgFmtIdent $ otTerm t) <> " "
<> otDirection t <> " "
, [], mempty)
queryTerm :: OrderTerm -> PStmt
queryTerm t = B.Stmt
(" " <> cs (pgFmtIdent $ otTerm t) <> " "
<> cs (otDirection t) <> " "
<> maybe "" cs (otNullOrder t) <> " ")
empty True
parentheticT :: StatementT
parentheticT (sql, params, pre) =
(" (" <> sql <> ") ", params, pre)
parentheticT s =
s { B.stmtTemplate = " (" <> B.stmtTemplate s <> ") " }
iffNotT :: DynamicSQL -> StatementT
iffNotT (aq, ap, apre) (bq, bp, bpre) =
("WITH aaa AS (" <> aq <> " returning *) " <>
bq <> " WHERE NOT EXISTS (SELECT * FROM aaa)"
, ap ++ bp
, All $ getAll apre && getAll bpre
)
iffNotT :: PStmt -> StatementT
iffNotT (B.Stmt aq ap apre) (B.Stmt bq bp bpre) =
B.Stmt
("WITH aaa AS (" <> aq <> " returning *) " <>
bq <> " WHERE NOT EXISTS (SELECT * FROM aaa)")
(ap <> bp)
(apre && bpre)
countRows :: QualifiedTable -> DynamicSQL
countRows t =
("select count(1) from " <> fromQt t, [], mempty)
countT :: StatementT
countT s =
s { B.stmtTemplate = "WITH qqq AS (" <> B.stmtTemplate s <> ") SELECT count(1) FROM qqq" }
countRows :: QualifiedTable -> PStmt
countRows t = B.Stmt ("select count(1) from " <> fromQt t) empty True
asJsonWithCount :: StatementT
asJsonWithCount (sql, params, pre) = (
"count(t), array_to_json(array_agg(row_to_json(t)))::character varying from (" <> sql <> ") t"
, params, pre
)
asJsonWithCount s = s { B.stmtTemplate =
"count(t), array_to_json(array_agg(row_to_json(t)))::character varying from ("
<> B.stmtTemplate s <> ") t" }
asJsonRow :: StatementT
asJsonRow (sql, params, pre) = (
"row_to_json(t) from (" <> sql <> ") t", params, pre
)
asJsonRow s = s { B.stmtTemplate = "row_to_json(t) from (" <> B.stmtTemplate s <> ") t" }
selectStar :: QualifiedTable -> DynamicSQL
selectStar t =
("select * from " <> fromQt t, [], mempty)
selectStar :: QualifiedTable -> PStmt
selectStar t = B.Stmt ("select * from " <> fromQt t) empty True
insertInto :: QualifiedTable -> [Text] -> [JSON.Value] -> DynamicSQL
insertInto t [] _ =
("insert into " <> fromQt t <> " default values returning *", [], mempty)
insertInto t cols vals =
("insert into " <> fromQt t <> " (" <>
cs (intercalate ", " (map pgFmtIdent cols)) <>
") values (" <>
cs (
intercalate ", " (map
((<> "::unknown") . pgFmtLit . unquoted)
vals)
) <> ") returning row_to_json(" <> fromQt t <> ".*)"
, []
, mempty
)
returningStarT :: StatementT
returningStarT s = s { B.stmtTemplate = B.stmtTemplate s <> " RETURNING *" }
insertSelect :: QualifiedTable -> [Text] -> [JSON.Value] -> DynamicSQL
insertSelect t [] _ =
("insert into " <> fromQt t <> " default values returning *", [], mempty)
insertSelect t cols vals =
("insert into " <> fromQt t <> " (" <>
cs (intercalate ", " (map pgFmtIdent cols)) <>
") select " <>
cs (
intercalate ", " (map
((<> "::unknown") . pgFmtLit . unquoted)
vals)
)
, []
, mempty
)
deleteFrom :: QualifiedTable -> PStmt
deleteFrom t = B.Stmt ("delete from " <> fromQt t) empty True
update :: QualifiedTable -> [Text] -> [JSON.Value] -> DynamicSQL
update t cols vals =
("update " <> fromQt t <> " set (" <>
cs (intercalate ", " (map pgFmtIdent cols)) <>
") = (" <>
cs (
intercalate ", " (map
((<> "::unknown") . pgFmtLit . unquoted)
vals)
) <> ")"
, []
, mempty
)
insertInto :: QualifiedTable
-> V.Vector T.Text
-> V.Vector (V.Vector JSON.Value)
-> PStmt
insertInto t cols vals
| V.null cols = B.Stmt ("insert into " <> fromQt t <> " default values returning *") empty True
| otherwise = B.Stmt
("insert into " <> fromQt t <> " (" <>
T.intercalate ", " (V.toList $ V.map pgFmtIdent cols) <>
") values "
<> T.intercalate ", "
(V.toList $ V.map (\v -> "("
<> T.intercalate ", " (V.toList $ V.map insertableValue v)
<> ")"
) vals
)
<> " returning row_to_json(" <> fromQt t <> ".*)")
empty True
wherePred :: Net.QueryItem -> DynamicSQL
insertSelect :: QualifiedTable -> [T.Text] -> [JSON.Value] -> PStmt
insertSelect t [] _ = B.Stmt
("insert into " <> fromQt t <> " default values returning *") empty True
insertSelect t cols vals = B.Stmt
("insert into " <> fromQt t <> " ("
<> T.intercalate ", " (map pgFmtIdent cols)
<> ") select "
<> T.intercalate ", " (map insertableValue vals))
empty True
update :: QualifiedTable -> [T.Text] -> [JSON.Value] -> PStmt
update t cols vals = B.Stmt
("update " <> fromQt t <> " set ("
<> T.intercalate ", " (map pgFmtIdent cols)
<> ") = ("
<> T.intercalate ", " (map insertableValue vals)
<> ")")
empty True
wherePred :: Net.QueryItem -> PStmt
wherePred (col, predicate) =
(" " <> cs (pgFmtIdent $ cs col) <> " " <> op <> " " <> cs (pgFmtLit value) <> "::unknown ", [], mempty)
B.Stmt (" " <> pgFmtJsonbPath (cs col) <> " " <> op <> " " <>
if opCode `elem` ["is","isnot"] then whiteList value
else cs sqlValue)
empty True
where
opCode:rest = split (=='.') $ cs $ fromMaybe "." predicate
value = intercalate "." rest
opCode:rest = T.split (=='.') $ cs $ fromMaybe "." predicate
value = T.intercalate "." rest
whiteList val = fromMaybe (cs (pgFmtLit val) <> "::unknown ")
(L.find ((==) . T.toLower $ val)
["null","true","false"])
star c = if c == '*' then '%' else c
unknownLiteral = (<> "::unknown ") . pgFmtLit
sqlValue = case opCode of
"like" -> unknownLiteral $ T.map star value
"ilike" -> unknownLiteral $ T.map star value
"in" -> "(" <> T.intercalate ", " (map unknownLiteral $ T.split (==',') value) <> ") "
_ -> unknownLiteral value
op = case opCode of
"eq" -> "="
"gt" -> ">"
@@ -153,68 +176,107 @@ wherePred (col, predicate) =
"gte" -> ">="
"lte" -> "<="
"neq" -> "<>"
"like"-> "like"
"ilike"-> "ilike"
"in" -> "in"
"is" -> "is"
"isnot" -> "is not"
_ -> "="
orderParse :: Net.Query -> [OrderTerm]
orderParse q =
mapMaybe orderParseTerm . split (==',') $ cs order
mapMaybe orderParseTerm . T.split (==',') $ cs order
where
order = fromMaybe "" $ join (lookup "order" q)
orderParseTerm :: Text -> Maybe OrderTerm
orderParseTerm :: T.Text -> Maybe OrderTerm
orderParseTerm s =
case split (=='.') s of
[d,c] ->
case T.split (=='.') s of
(c:d:nls) ->
if d `elem` ["asc", "desc"]
then Just $ OrderTerm c $
if d == "asc" then "asc" else "desc"
then Just $ OrderTerm c
( if d == "asc" then "asc" else "desc" )
( case nls of
[n] -> if | n == "nullsfirst" -> Just "nulls first"
| n == "nullslast" -> Just "nulls last"
| otherwise -> Nothing
_ -> Nothing
)
else Nothing
_ -> Nothing
commaq :: DynamicSQL
commaq = (", ", [], mempty)
commaq :: PStmt
commaq = B.Stmt ", " empty True
andq :: DynamicSQL
andq = (" and ", [], mempty)
andq :: PStmt
andq = B.Stmt " and " empty True
pgFmtIdent :: Text -> Text
data JsonbPath =
ColIdentifier T.Text
| KeyIdentifier T.Text
| SingleArrow JsonbPath JsonbPath
| DoubleArrow JsonbPath JsonbPath
deriving (Show)
parseJsonbPath :: T.Text -> Maybe JsonbPath
parseJsonbPath p =
case T.splitOn "->>" p of
[a,b] ->
let i:is = T.splitOn "->" a in
Just $ DoubleArrow
(foldl SingleArrow (ColIdentifier i) (map KeyIdentifier is))
(KeyIdentifier b)
_ -> Nothing
pgFmtJsonbPath :: T.Text -> T.Text
pgFmtJsonbPath p =
pgFmtJsonbPath' $ fromMaybe (ColIdentifier p) (parseJsonbPath p)
where
pgFmtJsonbPath' (ColIdentifier i) = pgFmtIdent i
pgFmtJsonbPath' (KeyIdentifier i) = pgFmtLit i
pgFmtJsonbPath' (SingleArrow a b) =
pgFmtJsonbPath' a <> "->" <> pgFmtJsonbPath' b
pgFmtJsonbPath' (DoubleArrow a b) =
pgFmtJsonbPath' a <> "->>" <> pgFmtJsonbPath' b
pgFmtIdent :: T.Text -> T.Text
pgFmtIdent x =
let escaped = replace "\"" "\"\"" (trimNullChars $ cs x) in
let escaped = T.replace "\"" "\"\"" (trimNullChars $ cs x) in
if escaped =~ danger
then "\"" <> escaped <> "\""
else escaped
where danger = "^$|^[^a-z_]|[^a-z_0-9]" :: Text
where danger = "^$|^[^a-z_]|[^a-z_0-9]" :: T.Text
pgFmtLit :: Text -> Text
pgFmtLit :: T.Text -> T.Text
pgFmtLit x =
let trimmed = trimNullChars x
escaped = "'" <> replace "'" "''" trimmed <> "'"
slashed = replace "\\" "\\\\" escaped in
cs $ if escaped =~ ("\\\\" :: Text)
escaped = "'" <> T.replace "'" "''" trimmed <> "'"
slashed = T.replace "\\" "\\\\" escaped in
cs $ if escaped =~ ("\\\\" :: T.Text)
then "E" <> slashed
else slashed
trimNullChars :: Text -> Text
trimNullChars = Data.Text.takeWhile (/= '\x0')
trimNullChars :: T.Text -> T.Text
trimNullChars = T.takeWhile (/= '\x0')
fromQt :: QualifiedTable -> BS.ByteString
fromQt t = cs $ pgFmtIdent (qtSchema t) <> "." <> pgFmtIdent (qtName t)
fromQt :: QualifiedTable -> T.Text
fromQt t = pgFmtIdent (qtSchema t) <> "." <> pgFmtIdent (qtName t)
unquoted :: JSON.Value -> Text
unquoted :: JSON.Value -> T.Text
unquoted (JSON.String t) = t
unquoted (JSON.Number n) =
cs $ formatScientific Fixed (if isInteger n then Just 0 else Nothing) n
unquoted (JSON.Bool b) = cs . show $ b
unquoted _ = ""
pgParam :: JSON.Value -> H.StatementArgument H.Postgres
pgParam (JSON.Number n) = H.renderValue
(cs $ formatScientific Fixed
(if isInteger n then Just 0 else Nothing) n :: Text)
pgParam (JSON.String s) = H.renderValue s
pgParam (JSON.Bool b) = H.renderValue $
if b then "t" else "f" :: Text
pgParam JSON.Null = H.renderValue (Nothing :: Maybe Text)
pgParam (JSON.Object o) = H.renderValue $ JSON.encode o
pgParam (JSON.Array a) = H.renderValue $ JSON.encode a
insertableText :: T.Text -> T.Text
insertableText = (<> "::unknown") . pgFmtLit
insertableValue :: JSON.Value -> T.Text
insertableValue JSON.Null = "null"
insertableValue v = insertableText $ unquoted v
paramFilter :: JSON.Value -> T.Text
paramFilter JSON.Null = "is.null"
paramFilter v = "eq." <> unquoted v
+61 -77
View File
@@ -3,25 +3,21 @@
module PgStructure where
import PgQuery (QualifiedTable(..))
import Data.Functor ( (<$>) )
import Data.Text hiding (foldl, map, zipWith, concat)
import Data.Aeson
import Data.Functor.Identity
import qualified Data.Vector as V
import Data.String.Conversions (cs)
import Data.Maybe (fromMaybe)
import Control.Applicative ( (<$>) )
import Control.Applicative ( (<*>) )
import qualified Data.List as L
import qualified Data.Map as Map
import qualified Hasql as H
import qualified Hasql.Backend as H
import qualified Hasql.Postgres as H
import qualified Hasql.Postgres as P
foreignKeys :: QualifiedTable -> H.Tx H.Postgres s (Map.Map Text ForeignKey)
foreignKeys :: QualifiedTable -> H.Tx P.Postgres s (Map.Map Text ForeignKey)
foreignKeys table = do
r :: [(Text, Text, Text)] <- H.list $ [H.q|
r <- H.listEx $ [H.stmt|
select kcu.column_name, ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
from information_schema.table_constraints AS tc
@@ -36,58 +32,65 @@ foreignKeys table = do
return $ foldl addKey Map.empty r
where
addKey m (col, ftab, fcol) = Map.insert col (ForeignKey (cs ftab) (cs fcol)) m
addKey :: Map.Map Text ForeignKey -> (Text, Text, Text) -> Map.Map Text ForeignKey
addKey m (col, ftab, fcol) = Map.insert col (ForeignKey ftab fcol) m
tables :: Text -> H.Tx H.Postgres s [Table]
tables schema =
H.list $ [H.q|
tables :: Text -> H.Tx P.Postgres s [Table]
tables schema = do
rows <- H.listEx $
[H.stmt|
select table_schema, table_name,
is_insertable_into
from information_schema.tables
where table_schema = ?
order by table_name
|] schema
return $ map tableFromRow rows
columns :: QualifiedTable -> H.Tx H.Postgres s [Column]
columns :: QualifiedTable -> H.Tx P.Postgres s [Column]
columns table = do
cols <- H.list $ [H.q|
select info.table_schema as schema, info.table_name as table_name,
info.column_name as name, info.ordinal_position as position,
info.is_nullable as nullable, info.data_type as col_type,
info.is_updatable as updatable,
info.character_maximum_length as max_len,
info.numeric_precision as precision,
info.column_default as default_value,
array_to_string(enum_info.vals, ',') as enum
from (
select table_schema, table_name, column_name, ordinal_position,
is_nullable, data_type, is_updatable,
character_maximum_length, numeric_precision,
column_default, udt_name
from information_schema.columns
where table_schema = ? and table_name = ?
) as info
left outer join (
select n.nspname as s,
t.typname as n,
array_agg(e.enumlabel ORDER BY e.enumsortorder) as vals
from pg_type t
join pg_enum e on t.oid = e.enumtypid
join pg_catalog.pg_namespace n ON n.oid = t.typnamespace
group by s, n
) as enum_info
on (info.udt_name = enum_info.n)
order by position |] (qtSchema table) (qtName table)
cols <- H.listEx $ [H.stmt|
select info.table_schema as schema, info.table_name as table_name,
info.column_name as name, info.ordinal_position as position,
info.is_nullable as nullable, info.data_type as col_type,
info.is_updatable as updatable,
info.character_maximum_length as max_len,
info.numeric_precision as precision,
info.column_default as default_value,
array_to_string(enum_info.vals, ',') as enum
from (
select table_schema, table_name, column_name, ordinal_position,
is_nullable, data_type, is_updatable,
character_maximum_length, numeric_precision,
column_default, udt_name
from information_schema.columns
where table_schema = ? and table_name = ?
) as info
left outer join (
select n.nspname as s,
t.typname as n,
array_agg(e.enumlabel ORDER BY e.enumsortorder) as vals
from pg_type t
join pg_enum e on t.oid = e.enumtypid
join pg_catalog.pg_namespace n ON n.oid = t.typnamespace
group by s, n
) as enum_info
on (info.udt_name = enum_info.n)
order by position |]
(qtSchema table) (qtName table)
fks <- foreignKeys table
return $ map (\col -> col { colFK = Map.lookup (cs . colName $ col) fks }) cols
return $ map (addFK fks . columnFromRow) cols
where
addFK fks col = col { colFK = Map.lookup (cs . colName $ col) fks }
primaryKeyColumns :: QualifiedTable -> H.Tx H.Postgres s [Text]
primaryKeyColumns :: QualifiedTable -> H.Tx P.Postgres s [Text]
primaryKeyColumns table = do
r :: [Identity Text] <- H.list $ [H.q|
r <- H.listEx $ [H.stmt|
select kc.column_name
from
information_schema.table_constraints tc,
@@ -101,9 +104,6 @@ primaryKeyColumns table = do
return $ map runIdentity r
vanishNull :: [a] -> Maybe [a]
vanishNull xs = if L.null xs then Nothing else Just xs
toBool :: Text -> Bool
toBool = (== "YES")
@@ -132,37 +132,21 @@ data Column = Column {
, colFK :: Maybe ForeignKey
} deriving (Show)
instance H.RowParser H.Postgres Column where
parseRow r =
let schema = H.parseResult $ r V.! 0
table = H.parseResult $ r V.! 1
name = H.parseResult $ r V.! 2
position = H.parseResult $ r V.! 3
nullable = toBool <$> (H.parseResult $ r V.! 4 :: Either Text Text)
typ = H.parseResult $ r V.! 5
updatable = toBool <$> (H.parseResult $ r V.! 6 :: Either Text Text)
maxLen = H.parseResult $ r V.! 7
precision = H.parseResult $ r V.! 8
defValue = H.parseResult $ r V.! 9
enum = either (const $ Right []) (Right . split (==','))
(H.parseResult $ r V.! 10 :: Either Text Text)
in
if V.length r /= 11
then Left "Wrong number of fields in Column"
else Column <$> schema <*> table <*> name <*> position <*> nullable
<*> typ <*> updatable <*> maxLen <*> precision
<*> defValue <*> enum
<*> return Nothing
tableFromRow :: (Text, Text, Text) -> Table
tableFromRow (s, n, i) = Table s n (toBool i)
columnFromRow :: (Text, Text, Text,
Int, Text, Text,
Text, Maybe Int, Maybe Int,
Maybe Text, Maybe Text)
-> Column
columnFromRow (s, t, n, pos, nul, typ, u, l, p, d, e) =
Column s t n pos (toBool nul) typ (toBool u) l p d (parseEnum e) Nothing
where
parseEnum :: Maybe Text -> [Text]
parseEnum str = fromMaybe [] $ split (==',') <$> str
instance H.RowParser H.Postgres Table where
parseRow r =
let schema = H.parseResult $ r V.! 0
name = H.parseResult $ r V.! 1
insertable = toBool <$> (H.parseResult $ r V.! 2 :: Either Text Text) in
if V.length r /= 3
then Left "Wrong number of fields in Table"
else Table <$> schema <*> name <*> insertable
instance ToJSON Column where
toJSON c = object [
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 36 KiB

+18 -14
View File
@@ -1,4 +1,3 @@
{-# LANGUAGE QuasiQuotes #-}
module Feature.AuthSpec where
-- {{{ Imports
@@ -11,16 +10,21 @@ import SpecHelper
-- }}}
spec :: Spec
spec = before resetDb $ around withApp $
describe "authorization" $ do
it "hides tables that anonymous does not own" $
get "/authors_only" `shouldRespondWith` 404
it "indicates login failure" $ do
let auth = authHeader "postgrest_test_author" "fakefake"
request methodGet "/authors_only" [auth] ""
`shouldRespondWith` 401
it "allows users with permissions to see their tables" $ do
_ <- post "/postgrest/users" [json| { "id":"jdoe", "pass": "1234", "role": "postgrest_test_author" } |]
let auth = authHeader "jdoe" "1234"
request methodGet "/authors_only" [auth] ""
`shouldRespondWith` 200
spec = beforeAll
(clearTable "postgrest.auth") . afterAll_ (clearTable "postgrest.auth")
$ around withApp
$ describe "authorization" $ do
it "hides tables that anonymous does not own" $
get "/authors_only" `shouldRespondWith` 404
it "indicates login failure" $ do
let auth = authHeader "postgrest_test_author" "fakefake"
request methodGet "/authors_only" [auth] ""
`shouldRespondWith` 401
it "allows users with permissions to see their tables" $ do
_ <- post "/postgrest/users" [json| { "id":"jdoe", "pass": "1234", "role": "postgrest_test_author" } |]
let auth = authHeader "jdoe" "1234"
request methodGet "/authors_only" [auth] ""
`shouldRespondWith` 200
+9 -2
View File
@@ -12,8 +12,7 @@ import Network.HTTP.Types
-- }}}
spec :: Spec
spec = before resetDb $ around withApp $
describe "CORS" $ do
spec = around withApp $ describe "CORS" $ do
let preflightHeaders = [
("Accept", "*/*"),
("Origin", "http://example.com"),
@@ -54,6 +53,14 @@ spec = before resetDb $ around withApp $
r <- request methodOptions "/" preflightHeaders ""
liftIO $ simpleBody r `shouldBe` ""
describe "regular request" $
it "exposes necesssary response headers" $ do
r <- request methodGet "/items" [("Origin", "http://example.com")] ""
liftIO $ simpleHeaders r `shouldSatisfy` matchHeader
"Access-Control-Expose-Headers"
"Content-Encoding, Content-Location, Content-Range, Content-Type, \
\Date, Location, Server, Transfer-Encoding, Range-Unit"
describe "postflight request" $
it "allows INFO body through even with CORS request headers present" $ do
r <- request methodOptions "/items" normalCors ""
+37
View File
@@ -0,0 +1,37 @@
module Feature.DeleteSpec where
import Test.Hspec
import Test.Hspec.Wai
import SpecHelper
import Network.HTTP.Types
spec :: Spec
spec = beforeAll (clearTable "items" >> createItems 15) . afterAll_ (clearTable "items")
. around withApp $
describe "Deleting" $ do
context "existing record" $ do
it "succeeds with 204 and deletion count" $
request methodDelete "/items?id=eq.1" [] ""
`shouldRespondWith` ResponseMatcher {
matchBody = Nothing
, matchStatus = 204
, matchHeaders = ["Content-Range" <:> "*/1"]
}
it "actually clears items ouf the db" $ do
_ <- request methodDelete "/items?id=lt.15" [] ""
get "/items"
`shouldRespondWith` ResponseMatcher {
matchBody = Just "[{\"id\":15}]"
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-0/1"]
}
context "known route, unknown record" $
it "fails with 404" $
request methodDelete "/items?id=eq.101" [] "" `shouldRespondWith` 404
context "totally unknown route" $
it "fails with 404" $
request methodDelete "/foozle?id=eq.101" [] "" `shouldRespondWith` 404
+72 -11
View File
@@ -1,4 +1,3 @@
{-# LANGUAGE QuasiQuotes #-}
module Feature.InsertSpec where
import Test.Hspec
@@ -10,18 +9,17 @@ import SpecHelper
import qualified Data.Aeson as JSON
import Data.Maybe (fromJust)
import Text.Heredoc
import Network.HTTP.Types.Header
import Network.HTTP.Types
import Control.Monad (replicateM_)
import TestTypes(IncPK(..), CompoundPK(..))
--import Debug.Trace
spec :: Spec
spec = before resetDb $ around withApp $ do
spec = afterAll_ resetDb $ around withApp $ do
describe "Posting new record" $ do
it "accepts disparate json types" $ do
after_ (clearTable "menagerie") . it "accepts disparate json types" $ do
p <- post "/menagerie"
[json| {
"integer": 13, "double": 3.14159, "varchar": "testing!"
@@ -33,7 +31,7 @@ spec = before resetDb $ around withApp $ do
simpleStatus p `shouldBe` created201
context "with no pk supplied" $ do
context "into a table with auto-incrementing pk" $
context "into a table with auto-incrementing pk" . after_ (clearTable "auto_incrementing_pk") $
it "succeeds with 201 and link" $ do
p <- post "/auto_incrementing_pk" [json| { "non_nullable_string":"not null"} |]
liftIO $ do
@@ -52,7 +50,7 @@ spec = before resetDb $ around withApp $ do
post "/simple_pk" [json| { "extra":"foo"} |]
`shouldRespondWith` 400
context "into a table with no pk" $
context "into a table with no pk" . after_ (clearTable "no_pk") $ do
it "succeeds with 201 and a link including all fields" $ do
p <- post "/no_pk" [json| { "a":"foo", "b":"bar" } |]
liftIO $ do
@@ -60,7 +58,25 @@ spec = before resetDb $ around withApp $ do
simpleHeaders p `shouldSatisfy` matchHeader hLocation "/no_pk\\?a=eq.foo&b=eq.bar"
simpleStatus p `shouldBe` created201
context "with compound pk supplied" $
it "returns full details of inserted record if asked" $ do
p <- request methodPost "/no_pk"
[("Prefer", "return=representation")]
[json| { "a":"bar", "b":"baz" } |]
liftIO $ do
simpleBody p `shouldBe` [json| { "a":"bar", "b":"baz" } |]
simpleHeaders p `shouldSatisfy` matchHeader hLocation "/no_pk\\?a=eq.bar&b=eq.baz"
simpleStatus p `shouldBe` created201
it "can post nulls" $ do
p <- request methodPost "/no_pk"
[("Prefer", "return=representation")]
[json| { "a":null, "b":"foo" } |]
liftIO $ do
simpleBody p `shouldBe` [json| { "a":null, "b":"foo" } |]
simpleHeaders p `shouldSatisfy` matchHeader hLocation "/no_pk\\?a=is.null&b=eq.foo"
simpleStatus p `shouldBe` created201
context "with compound pk supplied" . after_ (clearTable "compound_pk") $
it "builds response location header appropriately" $
post "/compound_pk" [json| { "k1":12, "k2":42 } |]
`shouldRespondWith` ResponseMatcher {
@@ -78,6 +94,50 @@ spec = before resetDb $ around withApp $ do
, matchHeaders = []
}
describe "CSV insert" $ do
after_ (clearTable "menagerie") . context "disparate csv types" $
it "succeeds with multipart response" $ do
p <- request methodPost "/menagerie" [("Content-Type", "text/csv")]
[str|integer,double,varchar,boolean,date,money,enum
|13,3.14159,testing!,false,1900-01-01,$3.99,foo
|12,0.1,a string,true,1929-10-01,12,bar
|]
liftIO $ do
simpleBody p `shouldBe` "Content-Type: application/json\nLocation: /menagerie?integer=eq.13\n\n\n--postgrest_boundary\nContent-Type: application/json\nLocation: /menagerie?integer=eq.12\n\n"
simpleStatus p `shouldBe` created201
after_ (clearTable "no_pk") . context "requesting full representation" $ do
it "returns full details of inserted record" $
request methodPost "/no_pk"
[("Content-Type", "text/csv"), ("Prefer", "return=representation")]
"a,b\nbar,baz"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| { "a":"bar", "b":"baz" } |]
, matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json",
"Location" <:> "/no_pk?a=eq.bar&b=eq.baz"]
}
it "can post nulls" $
request methodPost "/no_pk"
[("Content-Type", "text/csv"), ("Prefer", "return=representation")]
"a,b\nNULL,foo"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| { "a":null, "b":"foo" } |]
, matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json",
"Location" <:> "/no_pk?a=is.null&b=eq.foo"]
}
after_ (clearTable "no_pk") . context "with wrong number of columns" $ do
it "fails for too few" $ do
p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz"
liftIO $ simpleStatus p `shouldBe` badRequest400
it "fails for too many" $ do
p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz,bat,bad"
liftIO $ simpleStatus p `shouldBe` badRequest400
describe "Putting record" $ do
context "to unkonwn uri" $
@@ -101,7 +161,7 @@ spec = before resetDb $ around withApp $ do
[json| { "k1":12, "k2":42 } |]
`shouldRespondWith` 400
context "specifying every column in the table" $ do
context "specifying every column in the table" . after_ (clearTable "compound_pk") $ do
it "can create a new record" $ do
p <- request methodPut "/compound_pk?k1=eq.12&k2=eq.42" []
[json| { "k1":12, "k2":42, "extra":3 } |]
@@ -131,7 +191,7 @@ spec = before resetDb $ around withApp $ do
let record = head rows
compoundExtra record `shouldBe` Just 5
context "with an auto-incrementing primary key" $
context "with an auto-incrementing primary key" . after_ (clearTable "auto_incrementing_pk") $
it "succeeds with 204" $
request methodPut "/auto_incrementing_pk?id=eq.1" []
@@ -161,7 +221,8 @@ spec = before resetDb $ around withApp $ do
[json| { "extra":20 } |]
`shouldRespondWith` 204
context "in a nonempty table" $ do
context "in a nonempty table" . before_ (clearTable "items" >> createItems 15) .
after_ (clearTable "items") $ do
it "can update a single item" $ do
g <- get "/items?id=eq.42"
liftIO $ simpleHeaders g
+94 -21
View File
@@ -2,42 +2,109 @@ module Feature.QuerySpec where
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Network.Wai.Test (SResponse(simpleHeaders))
import SpecHelper
spec :: Spec
spec = before resetDb $ around withApp $ do
spec =
beforeAll (clearTable "items" >> createItems 15)
. beforeAll (
clearTable "no_pk" >>
createNulls 2 >>
createLikableStrings >>
createJsonData)
. afterAll_ (clearTable "items" >> clearTable "no_pk" >> clearTable "simple_pk")
. around withApp $ do
describe "Querying a nonexistent table" $
it "causes a 404" $
get "/faketable" `shouldRespondWith` 404
describe "Filtering response" $
context "column equality" $
describe "Filtering response" $ do
it "matches with equality" $
get "/items?id=eq.5"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":5}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-0/1"]
}
it "matches the predicate" $
get "/items?id=eq.5"
`shouldRespondWith` ResponseMatcher {
matchBody = Just "[{\"id\":5}]"
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-0/1"]
}
it "matches items IN" $
get "/items?id=in.1,3,5"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
}
it "matches nulls" $
get "/no_pk?a=is.null" `shouldRespondWith`
[json| [{"a": null, "b": null}] |]
it "matches with like" $ do
get "/simple_pk?k=like.*yx" `shouldRespondWith`
"[{\"k\":\"xyyx\",\"extra\":\"u\"}]"
get "/simple_pk?k=like.xy*" `shouldRespondWith`
"[{\"k\":\"xyyx\",\"extra\":\"u\"}]"
get "/simple_pk?k=like.*YY*" `shouldRespondWith`
"[{\"k\":\"xYYx\",\"extra\":\"v\"}]"
it "matches with ilike" $ do
get "/simple_pk?k=ilike.xy*&order=extra.asc" `shouldRespondWith`
"[{\"k\":\"xyyx\",\"extra\":\"u\"},{\"k\":\"xYYx\",\"extra\":\"v\"}]"
get "/simple_pk?k=ilike.*YY*&order=extra.asc" `shouldRespondWith`
"[{\"k\":\"xyyx\",\"extra\":\"u\"},{\"k\":\"xYYx\",\"extra\":\"v\"}]"
describe "ordering response" $ do
it "by a column asc" $
get "/items?id=lte.2&order=asc.id"
get "/items?id=lte.2&order=id.asc"
`shouldRespondWith` ResponseMatcher {
matchBody = Just "[{\"id\":1},{\"id\":2}]"
matchBody = Just [json| [{"id":1},{"id":2}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/2"]
}
it "by a column desc" $
get "/items?id=lte.2&order=desc.id"
get "/items?id=lte.2&order=id.desc"
`shouldRespondWith` ResponseMatcher {
matchBody = Just "[{\"id\":2},{\"id\":1}]"
matchBody = Just [json| [{"id":2},{"id":1}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/2"]
}
it "by a column asc with nulls last" $
get "/no_pk?order=a.asc.nullslast"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"a":"1","b":"0"},
{"a":"2","b":"0"},
{"a":null,"b":null}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
}
it "by a column desc with nulls first" $
get "/no_pk?order=a.desc.nullsfirst"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"a":null,"b":null},
{"a":"2","b":"0"},
{"a":"1","b":"0"}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
}
it "by a column desc with nulls last" $
get "/no_pk?order=a.desc.nullslast"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"a":"2","b":"0"},
{"a":"1","b":"0"},
{"a":null,"b":null}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
}
it "without other constraints" $
get "/items?order=asc.id" `shouldRespondWith` 200
describe "Canonical location" $ do
it "Sets Content-Location with alphabetized params" $
get "/no_pk?b=eq.1&a=eq.1"
@@ -47,10 +114,16 @@ spec = before resetDb $ around withApp $ do
, matchHeaders = ["Content-Location" <:> "/no_pk?a=eq.1&b=eq.1"]
}
it "Omits question mark when there are no params" $
get "/no_pk"
`shouldRespondWith` ResponseMatcher {
matchBody = Just "[]"
, matchStatus = 200
, matchHeaders = ["Content-Location" <:> "/no_pk"]
}
it "Omits question mark when there are no params" $ do
r <- get "/simple_pk"
liftIO $ do
let respHeaders = simpleHeaders r
respHeaders `shouldSatisfy` matchHeader
"Content-Location" "/simple_pk"
describe "jsonb" $
it "can filter by properties inside json column" $ do
get "/json?data->foo->>bar=eq.baz" `shouldRespondWith`
[json| [{"data": {"foo": {"bar": "baz"}}}] |]
get "/json?data->foo->>bar=eq.fake" `shouldRespondWith`
[json| [] |]
+2 -1
View File
@@ -8,7 +8,8 @@ import Network.Wai.Test (SResponse(simpleHeaders,simpleStatus))
import SpecHelper
spec :: Spec
spec = before resetDb $ around withApp $
spec = beforeAll (clearTable "items" >> createItems 15) . afterAll_ (clearTable "items")
. around withApp $
describe "GET /items" $ do
context "without range headers" $
+2 -2
View File
@@ -1,4 +1,3 @@
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
module Feature.StructureSpec where
import Test.Hspec hiding (pendingWith)
@@ -10,7 +9,7 @@ import SpecHelper
import Network.HTTP.Types
spec :: Spec
spec = before resetDb $ around withApp $ do
spec = around withApp $ do
describe "GET /" $ do
it "lists views in schema" $
request methodGet "/" [] ""
@@ -19,6 +18,7 @@ spec = before resetDb $ around withApp $ do
, {"schema":"1","name":"compound_pk","insertable":true}
, {"schema":"1","name":"has_fk","insertable":true}
, {"schema":"1","name":"items","insertable":true}
, {"schema":"1","name":"json","insertable":true}
, {"schema":"1","name":"menagerie","insertable":true}
, {"schema":"1","name":"no_pk","insertable":true}
, {"schema":"1","name":"simple_pk","insertable":true}
+8
View File
@@ -0,0 +1,8 @@
module Main where
import Test.Hspec
import SpecHelper
import Spec
main :: IO ()
main = resetDb >> hspec spec
+1 -1
View File
@@ -1 +1 @@
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --no-main #-}
+78 -32
View File
@@ -1,5 +1,3 @@
{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
module SpecHelper where
import Network.Wai
@@ -7,12 +5,14 @@ import Test.Hspec
import Test.Hspec.Wai
import Hasql as H
import Hasql.Postgres as H
import Hasql.Backend as B
import Hasql.Postgres as P
import Data.String.Conversions (cs)
-- import Control.Exception.Base (bracket, finally)
import Data.Monoid
import Data.Text hiding (map)
import qualified Data.Vector as V
import Control.Monad (void)
import Control.Exception
import Network.HTTP.Types.Header (Header, ByteRange, renderByteRange,
hRange, hAuthorization)
@@ -24,47 +24,55 @@ import qualified Data.ByteString.Char8 as BS
import Network.Wai.Middleware.Cors (cors)
import System.Process (readProcess)
import App (app, sqlError, isSqlError)
import qualified Data.Aeson.Types as J
import App (app)
import Config (AppConfig(..), corsPolicy)
import Middleware
-- import Auth (addUser)
import Error(errResponse)
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
cfg = AppConfig "postgrest_test" 5432 "postgrest_test" "" "localhost" 3000 "postgrest_anonymous" False 10 "1"
testSettings :: SessionSettings
testSettings = fromMaybe (error "bad settings") $ H.sessionSettings 1 30
testPoolOpts :: PoolSettings
testPoolOpts = fromMaybe (error "bad settings") $ H.poolSettings 1 30
pgSettings :: Postgres
pgSettings = H.ParamSettings "localhost" 5432 "postgrest_test" "" "postgrest_test"
pgSettings :: P.Settings
pgSettings = P.ParamSettings (cs $ configDbHost cfg)
(fromIntegral $ configDbPort cfg)
(cs $ configDbUser cfg)
(cs $ configDbPass cfg)
(cs $ configDbName cfg)
withApp :: ActionWith Application -> IO ()
withApp perform =
withApp perform = do
let anonRole = cs $ configAnonRole cfg
currRole = cs $ configDbUser cfg in
perform $ middle $ \req resp ->
H.session pgSettings testSettings $ H.sessionUnlifter >>= \unlift ->
liftIO $ do
body <- strictRequestBody req
resp =<< catchJust isSqlError
(unlift $ H.tx Nothing
$ authenticated currRole anonRole (app body) req)
(return . sqlError)
currRole = cs $ configDbUser cfg
pool :: H.Pool P.Postgres
<- H.acquirePool pgSettings testPoolOpts
perform $ middle $ \req resp -> do
body <- strictRequestBody req
result <- liftIO $ H.session pool $ H.tx Nothing
$ authenticated currRole anonRole (app (cs $ configV1Schema cfg) body) req
either (resp . errResponse) resp result
where middle = cors corsPolicy
resetDb :: IO ()
resetDb = do
H.session pgSettings testSettings $
pool :: H.Pool P.Postgres
<- H.acquirePool pgSettings testPoolOpts
void . liftIO $ H.session pool $
H.tx Nothing $ do
H.unit [H.q| drop schema if exists "1" cascade |]
H.unit [H.q| drop schema if exists private cascade |]
H.unit [H.q| drop schema if exists postgrest cascade |]
H.unitEx [H.stmt| drop schema if exists "1" cascade |]
H.unitEx [H.stmt| drop schema if exists private cascade |]
H.unitEx [H.stmt| drop schema if exists postgrest cascade |]
loadFixture "roles"
loadFixture "schema"
@@ -89,10 +97,48 @@ authHeader :: String -> String -> Header
authHeader u p =
(hAuthorization, cs $ "Basic " ++ encode (u ++ ":" ++ p))
-- for hspec-wai
pending_ :: WaiSession ()
pending_ = liftIO Test.Hspec.pending
testPool :: IO(H.Pool P.Postgres)
testPool = H.acquirePool pgSettings testPoolOpts
-- for hspec-wai
pendingWith_ :: String -> WaiSession ()
pendingWith_ = liftIO . Test.Hspec.pendingWith
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
createItems :: Int -> IO ()
createItems n = do
pool <- testPool
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]
createNulls :: Int -> IO ()
createNulls n = do
pool <- testPool
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]
createLikableStrings :: IO ()
createLikableStrings = do
pool <- testPool
void . liftIO $ H.session pool $ H.tx Nothing $ do
H.unitEx $ insertSimplePk "xyyx" "u"
H.unitEx $ insertSimplePk "xYYx" "v"
where
insertSimplePk :: Text -> Text -> H.Stmt P.Postgres
insertSimplePk = [H.stmt|insert into "1".simple_pk (k, extra) values (?,?)|]
createJsonData :: IO ()
createJsonData = do
pool <- testPool
void . liftIO $ H.session pool $ H.tx Nothing $
H.unitEx $
[H.stmt|
insert into "1".json (data) values (?)
|]
(J.object [("foo", J.object [("bar", J.String "baz")])])
-41
View File
@@ -1,41 +0,0 @@
module Unit.ErrorsSpec where
import Test.Hspec
import Text.Parsec
import PgError
import Data.Either (rights)
spec :: Spec
spec =
describe "Parsing Hasql errors" $ do
it "can handle status and code" $
let p = parse message "" "Status: \"foo\"; Code: \"abc\"." in
rights [p] `shouldBe` [
Message (Just "foo") "abc" Nothing Nothing
]
it "can handle weird redundant quotes in status" $
let p = parse message "" "Status: \"\"foo\"\"; Code: \"abc\"." in
rights [p] `shouldBe` [
Message (Just "foo") "abc" Nothing Nothing
]
it "can handle text and code" $
let p = parse message "" "Message: \"foo\"; Code: \"abc\"." in
rights [p] `shouldBe` [
Message Nothing "abc" (Just "foo") Nothing
]
it "can handle status, text and code" $
let p = parse message "" "Status: \"hi\"; Message: \"foo\"; Code: \"abc\"." in
rights [p] `shouldBe` [
Message (Just "hi") "abc" (Just "foo") Nothing
]
it "can handle unescaped quotes in message" $
let p = parse message "" "Status: \"hi\"; Message: \"unknown \"foo\"!\"; Code: \"abc\"." in
rights [p] `shouldBe` [
Message (Just "hi") "abc" (Just "unknown \"foo\"!") Nothing
]
it "can handle periods in message" $
let p = parse message "" "Message: \"unknown \"foo\".bar\"; Code: \"42P01\"." in
rights [p] `shouldBe` [
Message Nothing "42P01" (Just "unknown \"foo\".bar") Nothing
]
+19 -352
View File
@@ -1,70 +1,37 @@
--
-- PostgreSQL database dump
--
-- Dumped from database version 9.3.5
-- Dumped by pg_dump version 9.3.5
-- Started on 2014-10-21 15:12:44 PDT
SET statement_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
--
-- TOC entry 20 (class 2615 OID 337536)
-- Name: 1; Type: SCHEMA; Schema: -; Owner: postgrest_test
--
CREATE SCHEMA "1";
ALTER SCHEMA "1" OWNER TO postgrest_test;
--
-- TOC entry 19 (class 2615 OID 337537)
-- Name: postgrest; Type: SCHEMA; Schema: -; Owner: postgrest_test
--
CREATE SCHEMA postgrest;
ALTER SCHEMA postgrest OWNER TO postgrest_test;
--
-- TOC entry 16 (class 2615 OID 337538)
-- Name: private; Type: SCHEMA; Schema: -; Owner: postgrest_test
--
CREATE SCHEMA private;
ALTER SCHEMA private OWNER TO postgrest_test;
--
-- TOC entry 205 (class 3079 OID 12018)
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
--
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
--
-- TOC entry 2321 (class 0 OID 0)
-- Dependencies: 205
-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner:
--
COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
SET search_path = "1", pg_catalog;
--
-- TOC entry 553 (class 1247 OID 337540)
-- Name: enum_menagerie_type; Type: TYPE; Schema: 1; Owner: postgrest_test
--
CREATE TYPE enum_menagerie_type AS ENUM (
'foo',
@@ -76,10 +43,6 @@ ALTER TYPE "1".enum_menagerie_type OWNER TO postgrest_test;
SET search_path = postgrest, pg_catalog;
--
-- TOC entry 218 (class 1255 OID 337545)
-- Name: check_role_exists(); Type: FUNCTION; Schema: postgrest; Owner: postgrest_test
--
CREATE FUNCTION check_role_exists() RETURNS trigger
LANGUAGE plpgsql
@@ -96,10 +59,6 @@ $$;
ALTER FUNCTION postgrest.check_role_exists() OWNER TO postgrest_test;
--
-- TOC entry 219 (class 1255 OID 337546)
-- Name: update_owner(); Type: FUNCTION; Schema: postgrest; Owner: postgrest_test
--
CREATE FUNCTION update_owner() RETURNS trigger
LANGUAGE plpgsql
@@ -119,10 +78,6 @@ SET default_tablespace = '';
SET default_with_oids = false;
--
-- TOC entry 191 (class 1259 OID 337547)
-- Name: authors_only; Type: TABLE; Schema: 1; Owner: postgrest_test_author; Tablespace:
--
CREATE TABLE authors_only (
secret character varying NOT NULL
@@ -131,10 +86,6 @@ CREATE TABLE authors_only (
ALTER TABLE "1".authors_only OWNER TO postgrest_test_author;
--
-- TOC entry 192 (class 1259 OID 337553)
-- Name: auto_incrementing_pk; Type: TABLE; Schema: 1; Owner: postgrest_test; Tablespace:
--
CREATE TABLE auto_incrementing_pk (
id integer NOT NULL,
@@ -146,10 +97,6 @@ CREATE TABLE auto_incrementing_pk (
ALTER TABLE "1".auto_incrementing_pk OWNER TO postgrest_test;
--
-- TOC entry 193 (class 1259 OID 337560)
-- Name: auto_incrementing_pk_id_seq; Type: SEQUENCE; Schema: 1; Owner: postgrest_test
--
CREATE SEQUENCE auto_incrementing_pk_id_seq
START WITH 1
@@ -161,19 +108,10 @@ CREATE SEQUENCE auto_incrementing_pk_id_seq
ALTER TABLE "1".auto_incrementing_pk_id_seq OWNER TO postgrest_test;
--
-- TOC entry 2324 (class 0 OID 0)
-- Dependencies: 193
-- Name: auto_incrementing_pk_id_seq; Type: SEQUENCE OWNED BY; Schema: 1; Owner: postgrest_test
--
ALTER SEQUENCE auto_incrementing_pk_id_seq OWNED BY auto_incrementing_pk.id;
--
-- TOC entry 194 (class 1259 OID 337562)
-- Name: compound_pk; Type: TABLE; Schema: 1; Owner: postgrest_test; Tablespace:
--
CREATE TABLE compound_pk (
k1 integer NOT NULL,
@@ -184,10 +122,6 @@ CREATE TABLE compound_pk (
ALTER TABLE "1".compound_pk OWNER TO postgrest_test;
--
-- TOC entry 195 (class 1259 OID 337565)
-- Name: has_fk; Type: TABLE; Schema: 1; Owner: postgrest_test; Tablespace:
--
CREATE TABLE has_fk (
id bigint NOT NULL,
@@ -198,10 +132,6 @@ CREATE TABLE has_fk (
ALTER TABLE "1".has_fk OWNER TO postgrest_test;
--
-- TOC entry 196 (class 1259 OID 337568)
-- Name: has_fk_id_seq; Type: SEQUENCE; Schema: 1; Owner: postgrest_test
--
CREATE SEQUENCE has_fk_id_seq
START WITH 1
@@ -213,19 +143,10 @@ CREATE SEQUENCE has_fk_id_seq
ALTER TABLE "1".has_fk_id_seq OWNER TO postgrest_test;
--
-- TOC entry 2327 (class 0 OID 0)
-- Dependencies: 196
-- Name: has_fk_id_seq; Type: SEQUENCE OWNED BY; Schema: 1; Owner: postgrest_test
--
ALTER SEQUENCE has_fk_id_seq OWNED BY has_fk.id;
--
-- TOC entry 197 (class 1259 OID 337570)
-- Name: items; Type: TABLE; Schema: 1; Owner: postgrest_test; Tablespace:
--
CREATE TABLE items (
id bigint NOT NULL
@@ -234,10 +155,6 @@ CREATE TABLE items (
ALTER TABLE "1".items OWNER TO postgrest_test;
--
-- TOC entry 198 (class 1259 OID 337573)
-- Name: items_id_seq; Type: SEQUENCE; Schema: 1; Owner: postgrest_test
--
CREATE SEQUENCE items_id_seq
START WITH 1
@@ -249,19 +166,10 @@ CREATE SEQUENCE items_id_seq
ALTER TABLE "1".items_id_seq OWNER TO postgrest_test;
--
-- TOC entry 2329 (class 0 OID 0)
-- Dependencies: 198
-- Name: items_id_seq; Type: SEQUENCE OWNED BY; Schema: 1; Owner: postgrest_test
--
ALTER SEQUENCE items_id_seq OWNED BY items.id;
--
-- TOC entry 199 (class 1259 OID 337575)
-- Name: menagerie; Type: TABLE; Schema: 1; Owner: postgrest_test; Tablespace:
--
CREATE TABLE menagerie (
"integer" integer NOT NULL,
@@ -276,10 +184,6 @@ CREATE TABLE menagerie (
ALTER TABLE "1".menagerie OWNER TO postgrest_test;
--
-- TOC entry 200 (class 1259 OID 337581)
-- Name: no_pk; Type: TABLE; Schema: 1; Owner: postgrest_test; Tablespace:
--
CREATE TABLE no_pk (
a character varying,
@@ -289,10 +193,6 @@ CREATE TABLE no_pk (
ALTER TABLE "1".no_pk OWNER TO postgrest_test;
--
-- TOC entry 201 (class 1259 OID 337587)
-- Name: simple_pk; Type: TABLE; Schema: 1; Owner: postgrest_test; Tablespace:
--
CREATE TABLE simple_pk (
k character varying NOT NULL,
@@ -302,12 +202,18 @@ CREATE TABLE simple_pk (
ALTER TABLE "1".simple_pk OWNER TO postgrest_test;
CREATE TABLE json
(
data json
);
ALTER TABLE "1".json OWNER TO postgrest_test;
SET search_path = postgrest, pg_catalog;
--
-- TOC entry 202 (class 1259 OID 337593)
-- Name: auth; Type: TABLE; Schema: postgrest; Owner: postgrest_test; Tablespace:
--
CREATE TABLE auth (
id character varying NOT NULL,
@@ -320,10 +226,6 @@ ALTER TABLE postgrest.auth OWNER TO postgrest_test;
SET search_path = private, pg_catalog;
--
-- TOC entry 203 (class 1259 OID 337599)
-- Name: articles; Type: TABLE; Schema: private; Owner: postgrest_test; Tablespace:
--
CREATE TABLE articles (
body text,
@@ -334,10 +236,6 @@ CREATE TABLE articles (
ALTER TABLE private.articles OWNER TO postgrest_test;
--
-- TOC entry 204 (class 1259 OID 337605)
-- Name: articles_id_seq; Type: SEQUENCE; Schema: private; Owner: postgrest_test
--
CREATE SEQUENCE articles_id_seq
START WITH 1
@@ -349,248 +247,113 @@ CREATE SEQUENCE articles_id_seq
ALTER TABLE private.articles_id_seq OWNER TO postgrest_test;
--
-- TOC entry 2336 (class 0 OID 0)
-- Dependencies: 204
-- Name: articles_id_seq; Type: SEQUENCE OWNED BY; Schema: private; Owner: postgrest_test
--
ALTER SEQUENCE articles_id_seq OWNED BY articles.id;
SET search_path = "1", pg_catalog;
--
-- TOC entry 2165 (class 2604 OID 337607)
-- Name: id; Type: DEFAULT; Schema: 1; Owner: postgrest_test
--
ALTER TABLE ONLY auto_incrementing_pk ALTER COLUMN id SET DEFAULT nextval('auto_incrementing_pk_id_seq'::regclass);
--
-- TOC entry 2166 (class 2604 OID 337608)
-- Name: id; Type: DEFAULT; Schema: 1; Owner: postgrest_test
--
ALTER TABLE ONLY has_fk ALTER COLUMN id SET DEFAULT nextval('has_fk_id_seq'::regclass);
--
-- TOC entry 2167 (class 2604 OID 337609)
-- Name: id; Type: DEFAULT; Schema: 1; Owner: postgrest_test
--
ALTER TABLE ONLY items ALTER COLUMN id SET DEFAULT nextval('items_id_seq'::regclass);
SET search_path = private, pg_catalog;
--
-- TOC entry 2168 (class 2604 OID 337610)
-- Name: id; Type: DEFAULT; Schema: private; Owner: postgrest_test
--
ALTER TABLE ONLY articles ALTER COLUMN id SET DEFAULT nextval('articles_id_seq'::regclass);
SET search_path = "1", pg_catalog;
--
-- TOC entry 2298 (class 0 OID 337547)
-- Dependencies: 191
-- Data for Name: authors_only; Type: TABLE DATA; Schema: 1; Owner: postgrest_test_author
--
--
-- TOC entry 2299 (class 0 OID 337553)
-- Dependencies: 192
-- Data for Name: auto_incrementing_pk; Type: TABLE DATA; Schema: 1; Owner: postgrest_test
--
--
-- TOC entry 2337 (class 0 OID 0)
-- Dependencies: 193
-- Name: auto_incrementing_pk_id_seq; Type: SEQUENCE SET; Schema: 1; Owner: postgrest_test
--
SELECT pg_catalog.setval('auto_incrementing_pk_id_seq', 54, true);
SELECT pg_catalog.setval('auto_incrementing_pk_id_seq', 1, true);
--
-- TOC entry 2301 (class 0 OID 337562)
-- Dependencies: 194
-- Data for Name: compound_pk; Type: TABLE DATA; Schema: 1; Owner: postgrest_test
--
--
-- TOC entry 2302 (class 0 OID 337565)
-- Dependencies: 195
-- Data for Name: has_fk; Type: TABLE DATA; Schema: 1; Owner: postgrest_test
--
--
-- TOC entry 2338 (class 0 OID 0)
-- Dependencies: 196
-- Name: has_fk_id_seq; Type: SEQUENCE SET; Schema: 1; Owner: postgrest_test
--
SELECT pg_catalog.setval('has_fk_id_seq', 1, false);
--
-- TOC entry 2304 (class 0 OID 337570)
-- Dependencies: 197
-- Data for Name: items; Type: TABLE DATA; Schema: 1; Owner: postgrest_test
--
INSERT INTO items (id) VALUES (1);
INSERT INTO items (id) VALUES (2);
INSERT INTO items (id) VALUES (3);
INSERT INTO items (id) VALUES (4);
INSERT INTO items (id) VALUES (5);
INSERT INTO items (id) VALUES (6);
INSERT INTO items (id) VALUES (7);
INSERT INTO items (id) VALUES (8);
INSERT INTO items (id) VALUES (9);
INSERT INTO items (id) VALUES (10);
INSERT INTO items (id) VALUES (11);
INSERT INTO items (id) VALUES (12);
INSERT INTO items (id) VALUES (13);
INSERT INTO items (id) VALUES (14);
INSERT INTO items (id) VALUES (15);
SELECT pg_catalog.setval('items_id_seq', 1, true);
--
-- TOC entry 2339 (class 0 OID 0)
-- Dependencies: 198
-- Name: items_id_seq; Type: SEQUENCE SET; Schema: 1; Owner: postgrest_test
--
SELECT pg_catalog.setval('items_id_seq', 19, true);
--
-- TOC entry 2306 (class 0 OID 337575)
-- Dependencies: 199
-- Data for Name: menagerie; Type: TABLE DATA; Schema: 1; Owner: postgrest_test
--
--
-- TOC entry 2307 (class 0 OID 337581)
-- Dependencies: 200
-- Data for Name: no_pk; Type: TABLE DATA; Schema: 1; Owner: postgrest_test
--
--
-- TOC entry 2308 (class 0 OID 337587)
-- Dependencies: 201
-- Data for Name: simple_pk; Type: TABLE DATA; Schema: 1; Owner: postgrest_test
--
SET search_path = postgrest, pg_catalog;
--
-- TOC entry 2309 (class 0 OID 337593)
-- Dependencies: 202
-- Data for Name: auth; Type: TABLE DATA; Schema: postgrest; Owner: postgrest_test
--
SET search_path = private, pg_catalog;
--
-- TOC entry 2310 (class 0 OID 337599)
-- Dependencies: 203
-- Data for Name: articles; Type: TABLE DATA; Schema: private; Owner: postgrest_test
--
--
-- TOC entry 2340 (class 0 OID 0)
-- Dependencies: 204
-- Name: articles_id_seq; Type: SEQUENCE SET; Schema: private; Owner: postgrest_test
--
SELECT pg_catalog.setval('articles_id_seq', 1, false);
SET search_path = "1", pg_catalog;
--
-- TOC entry 2170 (class 2606 OID 337612)
-- Name: authors_only_pkey; Type: CONSTRAINT; Schema: 1; Owner: postgrest_test_author; Tablespace:
--
ALTER TABLE ONLY authors_only
ADD CONSTRAINT authors_only_pkey PRIMARY KEY (secret);
--
-- TOC entry 2172 (class 2606 OID 337614)
-- Name: auto_incrementing_pk_pkey; Type: CONSTRAINT; Schema: 1; Owner: postgrest_test; Tablespace:
--
ALTER TABLE ONLY auto_incrementing_pk
ADD CONSTRAINT auto_incrementing_pk_pkey PRIMARY KEY (id);
--
-- TOC entry 2174 (class 2606 OID 337616)
-- Name: compound_pk_pkey; Type: CONSTRAINT; Schema: 1; Owner: postgrest_test; Tablespace:
--
ALTER TABLE ONLY compound_pk
ADD CONSTRAINT compound_pk_pkey PRIMARY KEY (k1, k2);
--
-- TOC entry 2182 (class 2606 OID 337618)
-- Name: contacts_pkey; Type: CONSTRAINT; Schema: 1; Owner: postgrest_test; Tablespace:
--
ALTER TABLE ONLY simple_pk
ADD CONSTRAINT contacts_pkey PRIMARY KEY (k);
--
-- TOC entry 2176 (class 2606 OID 337620)
-- Name: has_fk_pkey; Type: CONSTRAINT; Schema: 1; Owner: postgrest_test; Tablespace:
--
ALTER TABLE ONLY has_fk
ADD CONSTRAINT has_fk_pkey PRIMARY KEY (id);
--
-- TOC entry 2178 (class 2606 OID 337622)
-- Name: items_pkey; Type: CONSTRAINT; Schema: 1; Owner: postgrest_test; Tablespace:
--
ALTER TABLE ONLY items
ADD CONSTRAINT items_pkey PRIMARY KEY (id);
--
-- TOC entry 2180 (class 2606 OID 337624)
-- Name: menagerie_pkey; Type: CONSTRAINT; Schema: 1; Owner: postgrest_test; Tablespace:
--
ALTER TABLE ONLY menagerie
ADD CONSTRAINT menagerie_pkey PRIMARY KEY ("integer");
@@ -598,10 +361,6 @@ ALTER TABLE ONLY menagerie
SET search_path = postgrest, pg_catalog;
--
-- TOC entry 2184 (class 2606 OID 337626)
-- Name: auth_pkey; Type: CONSTRAINT; Schema: postgrest; Owner: postgrest_test; Tablespace:
--
ALTER TABLE ONLY auth
ADD CONSTRAINT auth_pkey PRIMARY KEY (id);
@@ -609,10 +368,6 @@ ALTER TABLE ONLY auth
SET search_path = private, pg_catalog;
--
-- TOC entry 2186 (class 2606 OID 337628)
-- Name: articles_pkey; Type: CONSTRAINT; Schema: private; Owner: postgrest_test; Tablespace:
--
ALTER TABLE ONLY articles
ADD CONSTRAINT articles_pkey PRIMARY KEY (id);
@@ -620,49 +375,28 @@ ALTER TABLE ONLY articles
SET search_path = postgrest, pg_catalog;
--
-- TOC entry 2189 (class 2620 OID 337630)
-- Name: ensure_auth_role_exists; Type: TRIGGER; Schema: postgrest; Owner: postgrest_test
--
CREATE CONSTRAINT TRIGGER ensure_auth_role_exists AFTER INSERT OR UPDATE ON auth NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW EXECUTE PROCEDURE check_role_exists();
SET search_path = private, pg_catalog;
--
-- TOC entry 2190 (class 2620 OID 337631)
-- Name: articles_owner_track; Type: TRIGGER; Schema: private; Owner: postgrest_test
--
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;
--
-- TOC entry 2187 (class 2606 OID 337632)
-- Name: has_fk_fk_fkey; Type: FK CONSTRAINT; Schema: 1; Owner: postgrest_test
--
ALTER TABLE ONLY has_fk
ADD CONSTRAINT has_fk_fk_fkey FOREIGN KEY (auto_inc_fk) REFERENCES auto_incrementing_pk(id);
--
-- TOC entry 2188 (class 2606 OID 337637)
-- Name: has_fk_simple_fk_fkey; Type: FK CONSTRAINT; Schema: 1; Owner: postgrest_test
--
ALTER TABLE ONLY has_fk
ADD CONSTRAINT has_fk_simple_fk_fkey FOREIGN KEY (simple_fk) REFERENCES simple_pk(k);
--
-- TOC entry 2317 (class 0 OID 0)
-- Dependencies: 20
-- Name: 1; Type: ACL; Schema: -; Owner: postgrest_test
--
REVOKE ALL ON SCHEMA "1" FROM PUBLIC;
REVOKE ALL ON SCHEMA "1" FROM postgrest_test;
@@ -671,11 +405,6 @@ GRANT USAGE ON SCHEMA "1" TO postgrest_anonymous;
GRANT USAGE ON SCHEMA "1" TO postgrest_test_author;
--
-- TOC entry 2318 (class 0 OID 0)
-- Dependencies: 19
-- Name: postgrest; Type: ACL; Schema: -; Owner: postgrest_test
--
REVOKE ALL ON SCHEMA postgrest FROM PUBLIC;
REVOKE ALL ON SCHEMA postgrest FROM postgrest_test;
@@ -683,33 +412,17 @@ GRANT ALL ON SCHEMA postgrest TO postgrest_test;
GRANT USAGE ON SCHEMA postgrest TO postgrest_anonymous;
--
-- TOC entry 2320 (class 0 OID 0)
-- Dependencies: 21
-- Name: public; Type: ACL; Schema: -; Owner: postgres
--
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO PUBLIC;
--
-- TOC entry 2322 (class 0 OID 0)
-- Dependencies: 191
-- Name: authors_only; Type: ACL; Schema: 1; Owner: postgrest_test_author
--
REVOKE ALL ON TABLE authors_only FROM PUBLIC;
REVOKE ALL ON TABLE authors_only FROM postgrest_test_author;
GRANT ALL ON TABLE authors_only TO postgrest_test_author;
--
-- TOC entry 2323 (class 0 OID 0)
-- Dependencies: 192
-- Name: auto_incrementing_pk; Type: ACL; Schema: 1; Owner: postgrest_test
--
REVOKE ALL ON TABLE auto_incrementing_pk FROM PUBLIC;
REVOKE ALL ON TABLE auto_incrementing_pk FROM postgrest_test;
@@ -717,11 +430,6 @@ GRANT ALL ON TABLE auto_incrementing_pk TO postgrest_test;
GRANT ALL ON TABLE auto_incrementing_pk TO postgrest_anonymous;
--
-- TOC entry 2325 (class 0 OID 0)
-- Dependencies: 193
-- Name: auto_incrementing_pk_id_seq; Type: ACL; Schema: 1; Owner: postgrest_test
--
REVOKE ALL ON SEQUENCE auto_incrementing_pk_id_seq FROM PUBLIC;
REVOKE ALL ON SEQUENCE auto_incrementing_pk_id_seq FROM postgrest_test;
@@ -729,11 +437,6 @@ GRANT ALL ON SEQUENCE auto_incrementing_pk_id_seq TO postgrest_test;
GRANT USAGE ON SEQUENCE auto_incrementing_pk_id_seq TO postgrest_anonymous;
--
-- TOC entry 2326 (class 0 OID 0)
-- Dependencies: 194
-- Name: compound_pk; Type: ACL; Schema: 1; Owner: postgrest_test
--
REVOKE ALL ON TABLE compound_pk FROM PUBLIC;
REVOKE ALL ON TABLE compound_pk FROM postgrest_test;
@@ -746,11 +449,6 @@ REVOKE ALL ON TABLE has_fk FROM postgrest_test;
GRANT ALL ON TABLE has_fk TO postgrest_test;
GRANT ALL ON TABLE has_fk TO postgrest_anonymous;
--
-- TOC entry 2328 (class 0 OID 0)
-- Dependencies: 197
-- Name: items; Type: ACL; Schema: 1; Owner: postgrest_test
--
REVOKE ALL ON TABLE items FROM PUBLIC;
REVOKE ALL ON TABLE items FROM postgrest_test;
@@ -758,11 +456,6 @@ GRANT ALL ON TABLE items TO postgrest_test;
GRANT ALL ON TABLE items TO postgrest_anonymous;
--
-- TOC entry 2330 (class 0 OID 0)
-- Dependencies: 198
-- Name: items_id_seq; Type: ACL; Schema: 1; Owner: postgrest_test
--
REVOKE ALL ON SEQUENCE items_id_seq FROM PUBLIC;
REVOKE ALL ON SEQUENCE items_id_seq FROM postgrest_test;
@@ -770,11 +463,6 @@ GRANT ALL ON SEQUENCE items_id_seq TO postgrest_test;
GRANT USAGE ON SEQUENCE items_id_seq TO postgrest_anonymous;
--
-- TOC entry 2331 (class 0 OID 0)
-- Dependencies: 199
-- Name: menagerie; Type: ACL; Schema: 1; Owner: postgrest_test
--
REVOKE ALL ON TABLE menagerie FROM PUBLIC;
REVOKE ALL ON TABLE menagerie FROM postgrest_test;
@@ -782,11 +470,6 @@ GRANT ALL ON TABLE menagerie TO postgrest_test;
GRANT ALL ON TABLE menagerie TO postgrest_anonymous;
--
-- TOC entry 2332 (class 0 OID 0)
-- Dependencies: 200
-- Name: no_pk; Type: ACL; Schema: 1; Owner: postgrest_test
--
REVOKE ALL ON TABLE no_pk FROM PUBLIC;
REVOKE ALL ON TABLE no_pk FROM postgrest_test;
@@ -794,11 +477,6 @@ GRANT ALL ON TABLE no_pk TO postgrest_test;
GRANT ALL ON TABLE no_pk TO postgrest_anonymous;
--
-- TOC entry 2333 (class 0 OID 0)
-- Dependencies: 201
-- Name: simple_pk; Type: ACL; Schema: 1; Owner: postgrest_test
--
REVOKE ALL ON TABLE simple_pk FROM PUBLIC;
REVOKE ALL ON TABLE simple_pk FROM postgrest_test;
@@ -806,13 +484,15 @@ GRANT ALL ON TABLE simple_pk TO postgrest_test;
GRANT ALL ON TABLE simple_pk TO postgrest_anonymous;
REVOKE ALL ON TABLE json FROM PUBLIC;
REVOKE ALL ON TABLE json FROM postgrest_test;
GRANT ALL ON TABLE json TO postgrest_test;
GRANT ALL ON TABLE json TO postgrest_anonymous;
SET search_path = postgrest, pg_catalog;
--
-- TOC entry 2334 (class 0 OID 0)
-- Dependencies: 202
-- Name: auth; Type: ACL; Schema: postgrest; Owner: postgrest_test
--
REVOKE ALL ON TABLE auth FROM PUBLIC;
REVOKE ALL ON TABLE auth FROM postgrest_test;
@@ -822,20 +502,7 @@ GRANT INSERT ON TABLE auth TO postgrest_anonymous;
SET search_path = private, pg_catalog;
--
-- TOC entry 2335 (class 0 OID 0)
-- Dependencies: 203
-- Name: articles; Type: ACL; Schema: private; Owner: postgrest_test
--
REVOKE ALL ON TABLE articles FROM PUBLIC;
REVOKE ALL ON TABLE articles FROM postgrest_test;
GRANT ALL ON TABLE articles TO postgrest_test;
-- Completed on 2014-10-21 15:12:44 PDT
--
-- PostgreSQL database dump complete
--