chore: move executable code to src/

src/ now contains all source code - in subdirectories, according to the
.cabal component they belong to. This will allow us to put vendored
libraries in the same place - and later split our own code into multiple
components/libraries as well.
This commit is contained in:
Wolfgang Walther
2026-07-14 06:58:15 +00:00
parent 44e15e4e9b
commit aa7d442d32
63 changed files with 21 additions and 21 deletions
+207
View File
@@ -0,0 +1,207 @@
{-# LANGUAGE QuasiQuotes #-}
module PostgREST.Config.Database
( pgVersionStatement
, queryDbSettings
, queryPgVersion
, queryRoleSettings
, RoleSettings
, RoleIsolationLvl
, TimezoneNames
, toIsolationLevel
) where
import Control.Arrow ((***))
import PostgREST.Config.PgVersion (PgVersion (..), pgVersion150)
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import qualified Hasql.Decoders as HD
import qualified Hasql.Encoders as HE
import Hasql.Session (Session, statement)
import qualified Hasql.Statement as SQL
import qualified Hasql.Transaction as SQL
import qualified Hasql.Transaction.Sessions as SQL
import NeatInterpolation (trimming)
import Protolude
type RoleSettings = (HM.HashMap ByteString (HM.HashMap ByteString ByteString))
type RoleIsolationLvl = HM.HashMap ByteString SQL.IsolationLevel
type TimezoneNames = Set Text -- cache timezone names for prefer timezone=
toIsolationLevel :: Text -> SQL.IsolationLevel
toIsolationLevel a = case T.toLower a of
"repeatable read" -> SQL.RepeatableRead
"serializable" -> SQL.Serializable
_ -> SQL.ReadCommitted
prefix :: Text
prefix = "pgrst."
-- | In-db settings names
dbSettingsNames :: [Text]
dbSettingsNames =
(prefix <>) <$>
["db_aggregates_enabled"
,"client_error_verbosity"
,"db_anon_role"
,"db_pre_config"
,"db_extra_search_path"
,"db_max_rows"
,"db_plan_enabled"
,"db_pre_request"
,"db_prepared_statements"
,"db_root_spec"
,"db_schemas"
,"db_timezone_enabled"
,"db_tx_end"
,"db_hoisted_tx_settings"
,"jwt_aud"
,"jwt_role_claim_key"
,"jwt_secret"
,"jwt_secret_is_base64"
,"jwt_cache_max_lifetime"
,"openapi_mode"
,"openapi_security_active"
,"openapi_server_proxy_uri"
,"server_cors_allowed_origins"
,"server_trace_header"
,"server_timing_enabled"
,"url_use_legacy_target_names"
]
queryPgVersion :: Session PgVersion
queryPgVersion = statement mempty $ pgVersionStatement False
pgVersionStatement :: Bool -> SQL.Statement () PgVersion
pgVersionStatement = SQL.Statement sql HE.noParams versionRow
where
sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version'), version()"
versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text <*> column HD.text
-- | Query the in-database configuration. The settings have the following priorities:
--
-- 1. Role + with database-specific settings:
-- ALTER ROLE authenticator IN DATABASE postgres SET <prefix>jwt_aud = 'val';
-- 2. Role + with settings:
-- ALTER ROLE authenticator SET <prefix>jwt_aud = 'overridden';
-- 3. pre-config function:
-- CREATE FUNCTION pre_config() .. PERFORM set_config(<prefix>jwt_aud, 'pre_config_aud'..)
--
-- The example above will result in <prefix>jwt_aud = 'val'
-- A setting on the database only will have no effect: ALTER DATABASE postgres SET <prefix>jwt_aud = 'xx'
queryDbSettings :: Maybe Text -> Session [(Text, Text)]
queryDbSettings preConfFunc =
SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ SQL.statement dbSettingsNames $ SQL.Statement sql (arrayParam HE.text) decodeSettings True
where
sql = encodeUtf8 [trimming|
WITH
role_setting AS (
SELECT setdatabase as database,
unnest(setconfig) as setting
FROM pg_catalog.pg_db_role_setting
WHERE setrole = quote_ident(CURRENT_USER)::regrole::oid
AND setdatabase IN (0, (SELECT oid FROM pg_catalog.pg_database WHERE datname = CURRENT_CATALOG))
),
kv_settings AS (
SELECT database,
substr(setting, 1, strpos(setting, '=') - 1) as k,
substr(setting, strpos(setting, '=') + 1) as v
FROM role_setting
${preConfigF}
)
SELECT DISTINCT ON (key)
replace(k, '${prefix}', '') AS key,
v AS value
FROM kv_settings
WHERE k = ANY($$1) AND v IS NOT NULL
ORDER BY key, database DESC NULLS LAST;
|]
preConfigF = case preConfFunc of
Nothing -> mempty
Just func -> [trimming|
UNION
SELECT
null as database,
x as k,
current_setting(x, true) as v
FROM unnest($$1) x
JOIN ${func}() _ ON TRUE
|]::Text
decodeSettings = HD.rowList $ (,) <$> column HD.text <*> column HD.text
queryRoleSettings :: PgVersion -> Session (RoleSettings, RoleIsolationLvl)
queryRoleSettings pgVer =
SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ SQL.statement mempty $ SQL.Statement sql HE.noParams (processRows <$> rows) True
where
sql = encodeUtf8 [trimming|
with
role_setting as (
select r.rolname, unnest(r.rolconfig) as setting
from pg_auth_members m
join pg_roles r on r.oid = m.roleid
where member = quote_ident(current_user)::regrole::oid
),
kv_settings AS (
SELECT
rolname,
substr(setting, 1, strpos(setting, '=') - 1) as key,
substr(setting, strpos(setting, '=') + 1) as value
FROM role_setting
),
iso_setting AS (
SELECT rolname, value
FROM kv_settings
WHERE key = 'default_transaction_isolation'
)
select
kv.rolname,
i.value as iso_lvl,
coalesce(array_agg(row(kv.key, kv.value)) filter (where key <> 'default_transaction_isolation'), '{}') as role_settings
from kv_settings kv
join pg_settings ps on ps.name = kv.key and (ps.context = 'user' ${hasParameterPrivilege})
left join iso_setting i on i.rolname = kv.rolname
group by kv.rolname, i.value;
|]
hasParameterPrivilege
| pgVer >= pgVersion150 = "or has_parameter_privilege(quote_ident(current_user)::regrole::oid, ps.name, 'set')"
| otherwise = ""
processRows :: [(Text, Maybe Text, [(Text, Text)])] -> (RoleSettings, RoleIsolationLvl)
processRows rs =
let
rowsWRoleSettings = [ (x, z) | (x, _, z) <- rs ]
rowsWIsolation = [ (x, y) | (x, Just y, _) <- rs ]
in
( HM.fromList $ bimap encodeUtf8 (HM.fromList . ((encodeUtf8 *** encodeUtf8) <$>)) <$> rowsWRoleSettings
, HM.fromList $ (encodeUtf8 *** toIsolationLevel) <$> rowsWIsolation
)
rows :: HD.Result [(Text, Maybe Text, [(Text, Text)])]
rows = HD.rowList $ (,,) <$> column HD.text <*> nullableColumn HD.text <*> compositeArrayColumn ((,) <$> compositeField HD.text <*> compositeField HD.text)
column :: HD.Value a -> HD.Row a
column = HD.column . HD.nonNullable
nullableColumn :: HD.Value a -> HD.Row (Maybe a)
nullableColumn = HD.column . HD.nullable
compositeField :: HD.Value a -> HD.Composite a
compositeField = HD.field . HD.nonNullable
compositeArrayColumn :: HD.Composite a -> HD.Row [a]
compositeArrayColumn = arrayColumn . HD.composite
arrayColumn :: HD.Value a -> HD.Row [a]
arrayColumn = column . HD.listArray . HD.nonNullable
param :: HE.Value a -> HE.Params a
param = HE.param . HE.nonNullable
arrayParam :: HE.Value a -> HE.Params [a]
arrayParam = param . HE.foldableArray . HE.nonNullable