Skip to content

add: accept application/vnd.pgrst.sql requests - #5054

Open
wolfgangwalther wants to merge 2 commits into
PostgREST:mainfrom
wolfgangwalther:accept-sql
Open

add: accept application/vnd.pgrst.sql requests#5054
wolfgangwalther wants to merge 2 commits into
PostgREST:mainfrom
wolfgangwalther:accept-sql

Conversation

@wolfgangwalther

@wolfgangwalther wolfgangwalther commented Jun 27, 2026

Copy link
Copy Markdown
Member

This allows returning the request's SQL query by setting the Accept: application/vnd.pgrst.sql header on the client-side. I opted to use a mimetype in our vendor namespace, because this needs to support a for= parameter from the beginning - queries differ depending on which mimetype would normally be requested.

I mostly copied my way along the implementation of the application/vnd.pgrst.plan header and made adjustments where necessary.

This still has a few rough edges. Test coverage is not very meaningful, yet - ideally we'd do some snapshot/golden tests for these basic queries, but I couldn't get hspec-golden to work, yet. Also, some parts of the code theoretically support this, for example, on OpenAPI requests as well, but others don't, so that's a bit inconsistent.

Compared to #4012, this does not attempt to return parameter values. It's highly valuable without, I think.

For now, this should allow me to run the loadtests with it to see whether that gives us more stable results.

@wolfgangwalther wolfgangwalther linked an issue Jun 27, 2026 that may be closed by this pull request
This allows returning the request's SQL query by setting the `Accept:
application/vnd.pgrst.sql` header on the client-side. I opted to use a
mimetype in our vendor namespace, because this needs to support a `for=`
parameter from the beginning - queries differ depending on which
mimetype would *normally* be requested.

I mostly copied my way along the implementation of the
`application/vnd.pgrst.plan` header and made adjustments where
necessary.

Resolves PostgREST#3580
Comment thread src/PostgREST/MainTx.hs
Comment on lines +121 to +129
-- TODO: maybe patch upstream hasql-dynamic-statements so we have a less hackish way to convert
-- the SQL.Snippet or maybe don't use hasql-dynamic-statements and resort to plain strings for the queries and use regular hasql
renderSnippet :: SQL.Snippet -> ByteString
renderSnippet snippet =
let SQL.Statement sql _ _ _ = SQL.dynamicallyParameterized snippet decoder False
decoder = HD.noResult -- unused
in
sql

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This function is copied entirely from Logger.hs, since it is meant to be temporary anyway.

We could move it to our vendored hasql, once we get there.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wolfgangwalther Could vendoring hasql-dynamic-statements be a good first step towards #4823? It seems it'd be simplest step and there's a need here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could vendoring hasql-dynamic-statements be a good first step towards #4823?

Done in #5084.

Comment on lines +75 to +76
-- TODO the plan should have its own MediaHandler instead of relying on MediaType
m@(MTVndSQL mType) -> mtPlanToNothing $ ((,) . fst <$> lookupHandler mType) <*> pure m

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO copied 1:1 from above.

Comment thread src/PostgREST/Plan.hs
MTVndPlan{} -> DbCrud True pl
_ -> DbCrud False pl
MTVndPlan{} -> DbCrud True pl
MTVndSQL MTVndPlan{} -> SQLOnly $ DbCrud True pl

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Should probably add a test-case for the "return the SQL of an EXPLAIN" scenario.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't seem that useful, which makes me think perhaps we should reuse the vnd.pgrst.plan type?

It could be vnd.pgrst.plan+sql?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that, but the first challenge will be to match it on the MTVndPlan type, which supports all kinds of options and such - none of which make any sense for the sql case. Essentially this shows that this is just a different thing in reality as well and should not be modeled as the same type.

Returning the SQL or running an EXPLAIN are two very different things, imho. I'd go as far as saying: We should not even tie application/sql to db-plan-enabled at all. We should just allow it unconditionally. I mean - what's the downside? Our security model is supposed to be very similar to "we expect the user to be able to access the SQL database directly anyway, we're just providing the HTTP API for it", so I don't see a risk in letting any user know which SQL query we'd run.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean - what's the downside?

I don't think all users would be fine with that. Recently we had some that didn't want the hints #4667 and this feature does give much more information about implementation details.

We should not even tie application/sql to db-plan-enabled at all

I do agree in that they're different, but I don't see any other option than adding another config 😞 .

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I assumed you were going to say that. That's why I tied it to db-plan-enabled, like you suggested earlier - it doesn't hurt either. There is very little use-case for "I want to enable one of the two, but not the other" anyway.

I only mentioned it to make it clear that I think this feature is distinct enough from explain/plan, that we should not do more than putting it behind the same config option. We should treat both media types separately otherwise.

Comment thread src/PostgREST/MainTx.hs
Comment on lines +136 to +139
actionResult MainQuery{..} (SQLOnly (DbCrud _ plan)) _ _ _ =
pure $ SQLResult (pMedia plan) $ BS.intercalate "\n\n" $ filter (/= mempty) snipts
where
snipts = renderSnippet <$> [mqTxVars, fromMaybe mempty mqPreReq, mqMain, fromMaybe mempty mqExplain]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to add support for OpenAPI requests.

Needs a test-case and some more pattern patching elsewhere as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be fine if openapi rejects this media type.

Even with postgrest-openapi I'm not sure if we'd always hit the db.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be fine if openapi rejects this media type.

I'll actually need the openapi endpoint to respond with SQL, otherwise #5055 is almost pointless - the openapi requests just dominate the loadtest right now, they would do so even more when all others don't hit the DB anymore. But once the openapi requests don't hit the DB either, the mixed loadtests are actually much more balanced between the requests. This might, after all, be the biggest effect that PR will have.

Comment on lines +25 to +29
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.sql; for=\"application/json\"; charset=utf-8")
resHeaders `shouldSatisfy` notZeroContentLength
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not snapshot-testing, these tests should at least test key components of the query string in the body.

SQL query
---------

You can get the raw SQL query of a request by adding the ``Accept: application/sql`` header.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header is outdated?

Also I get the need of a vendor mimetype but perhaps we can add application/sql for the default case? (Should be the json handler)

Not adamant on the above, just an idea if implementation is simple enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Returning generated SQL

2 participants