Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: apply all function settings as transaction-scoped settings #3142

Merged
merged 1 commit into from
Feb 9, 2024

Conversation

taimoorzaeem
Copy link
Collaborator

@taimoorzaeem taimoorzaeem commented Jan 6, 2024

Fixes #3061.

@taimoorzaeem taimoorzaeem marked this pull request as draft January 6, 2024 18:46
@taimoorzaeem
Copy link
Collaborator Author

queryFuncSettings :: PgVersion -> Bool -> Session FunctionSettings
queryFuncSettings pgVer prepared =
  let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in
  transaction SQL.ReadCommitted SQL.Read $ SQL.statement mempty $ SQL.Statement sql HE.noParams (processRows <$> rows) prepared
 where
    sql = [q|
      WITH
      func_setting AS (
        SELECT current_user AS rolname, unnest(p.proconfig) as setting
        FROM pg_proc p
      ),
      kv_settings AS (
        SELECT
          rolname,
          substr(setting, 1, strpos(setting, '=') - 1) AS key,
          lower(substr(setting, strpos(setting, '=') + 1)) AS value
        FROM func_setting
      ),
      SELECT
        kv.rolname,
        array_agg(row(kv.key, kv.value)) AS func_setting
        FROM kv_settings kv
      JOIN pg_settings ps on ps.name = kv.key |] <>
      (if pgVer >= pgVersion150
        then "and (ps.context = 'user' or has_parameter_privilege(current_user::regrole::oid, ps.name, 'set')) "
        else "and ps.context = 'user' ") <> [q|
      GROUP BY kv.rolname;

@steve-chavez With the function settings defined as:

type FunctionSettings = (HM.HashMap ByteString (HM.HashMap ByteString ByteString))

The first ByteString here is role name. How are we going to obtain this? Unlike Impersonated Role Settings which have a rolename in pg_roles, we don't have a role name in pg_proc. Is the query alright?

@steve-chavez
Copy link
Member

The first ByteString here is role name. How are we going to obtain this? Unlike Impersonated Role Settings which have a rolename in pg_roles, we don't have a role name in pg_proc. Is the query alright?

@taimoorzaeem We don't need a role here. Since the settings are defined in the function, we only need the settings from proconfig.

@taimoorzaeem taimoorzaeem marked this pull request as ready for review January 24, 2024 15:44
@taimoorzaeem
Copy link
Collaborator Author

@steve-chavez The type of FuncSettings is:

type FuncSettings = [(ByteString,ByteString,ByteString)]

I think a hashmap would be better performance-wise but I'm not sure.

@steve-chavez
Copy link
Member

steve-chavez commented Jan 29, 2024

@taimoorzaeem How about having the function settings as part of:

data Routine = Function
{ pdSchema :: Schema
, pdName :: Text
, pdDescription :: Maybe Text
, pdParams :: [RoutineParam]
, pdReturnType :: RetType
, pdVolatility :: FuncVolatility
, pdHasVariadic :: Bool
, pdIsoLvl :: Maybe SQL.IsolationLevel
}
deriving (Eq, Show, Generic)

I think a hashmap would be better performance-wise but I'm not sure.

Then you wouldn't need to worry about search because the Function is already searched on Plan.callReadPlan, you would just need to get the funcSettings attribute.


Edit: Btw don't worry about the tests failures on older pg versions. We can figure that out later.

@taimoorzaeem taimoorzaeem force-pushed the all_function_setting branch 2 times, most recently from a138f39 to f354c88 Compare February 5, 2024 18:21
Comment on lines 442 to 445
-- the proconfig is returned as text[], which returns something like
-- e.g "\NUL\NUL\DB4statement_timeout=1s", this is solved by using
-- selecting first element
(p.proconfig)[1] AS func_setting
Copy link
Member

@steve-chavez steve-chavez Feb 8, 2024

Choose a reason for hiding this comment

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

@taimoorzaeem This won't work if the function has two settings like:

create or replace function four_sec_timeout() returns void as $$
  select pg_sleep(3);
$$ language sql
set statement_timeout = '4s'
set log_min_duration_sample = '5s';

Copy link
Member

@steve-chavez steve-chavez Feb 8, 2024

Choose a reason for hiding this comment

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

Just tested this and the query on this part should be like:

     coalesce(func_settings.kvs, '{}') as kvs

And below..

@@ -442,7 +452,6 @@ funcsSqlQuery pgVer = [q|
LEFT JOIN pg_class comp ON comp.oid = t.typrelid
LEFT JOIN pg_description as d ON d.objoid = p.oid
LEFT JOIN LATERAL unnest(proconfig) iso_config ON iso_config like 'default_transaction_isolation%'
LEFT JOIN LATERAL unnest(proconfig) timeout_config ON timeout_config like 'statement_timeout%'
Copy link
Member

@steve-chavez steve-chavez Feb 8, 2024

Choose a reason for hiding this comment

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

Use this query here:

  LEFT JOIN LATERAL (
    SELECT
      array_agg(row(
        substr(setting, 1, strpos(setting, '=') - 1),
        lower(substr(setting, strpos(setting, '=') + 1))
      )) as kvs
    FROM unnest(proconfig) setting
    WHERE setting not like 'default_transaction_isolation%'
  ) func_settings ON TRUE

This will output the settings like:

-[ RECORD 14 ]--------------+--------------------------------------------------------------
proc_schema                 | public
proc_name                   | four_sec_timeout
kvs                         | {"(statement_timeout,4s)","(log_min_duration_sample,5s)"}

Comment on lines 321 to 326
parseFuncSetting :: Text -> FuncSetting
parseFuncSetting txt = toTuple $ T.splitOn "=" txt
where
toTuple [x,y] = (x,y)
toTuple _ = ("error","parsing")

Copy link
Member

Choose a reason for hiding this comment

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

The decoding would then need to use compositeArrayColumn, like done here:

rows :: HD.Result [(Text, Maybe Text, [(Text, Text)])]
rows = HD.rowList $ (,,) <$> column HD.text <*> nullableColumn HD.text <*> compositeArrayColumn ((,) <$> compositeField HD.text <*> compositeField HD.text)

This won't need text splitting on the Haskell code.

@@ -49,6 +50,8 @@ data FuncVolatility
| Immutable
deriving (Eq, Show, Ord, Generic, JSON.ToJSON)

type FuncSetting = (Text,Text)
Copy link
Member

@steve-chavez steve-chavez Feb 8, 2024

Choose a reason for hiding this comment

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

With the above changes then this type should be:

Suggested change
type FuncSetting = (Text,Text)
type FuncSettings = [(Text,Text)]

@@ -58,12 +61,12 @@ data Routine = Function
, pdVolatility :: FuncVolatility
, pdHasVariadic :: Bool
, pdIsoLvl :: Maybe SQL.IsolationLevel
, pdTimeout :: Maybe Text
, pdFuncSetting :: Maybe FuncSetting
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
, pdFuncSetting :: Maybe FuncSetting
, pdFuncSettings :: FuncSettings

Comment on lines 201 to 206

GRANT SET ON PARAMETER log_min_duration_sample TO postgrest_test_anonymous;

create or replace function log_min_duration_test() returns text as $$
select current_setting('log_min_duration_sample',false);
$$ language sql set log_min_duration_sample = '5s';
Copy link
Member

Choose a reason for hiding this comment

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

Since this causes an error on older pg versions, maybe you can use work_mem instead.

IIRC it won't require a GRANT.

Suggested change
GRANT SET ON PARAMETER log_min_duration_sample TO postgrest_test_anonymous;
create or replace function log_min_duration_test() returns text as $$
select current_setting('log_min_duration_sample',false);
$$ language sql set log_min_duration_sample = '5s';
create or replace function work_mem_test() returns text as $$
select current_setting('work_mem',false);
$$ language sql set work_mem = '6000';

@steve-chavez steve-chavez merged commit f9ee1f7 into PostgREST:main Feb 9, 2024
30 of 32 checks passed
@steve-chavez
Copy link
Member

Hm, the Build Linux static (Nix) CI job is failing with error: writing to file: No space left on device. I thought this would clear on main but didn't.

@wolfgangwalther
Copy link
Member

I pushed one of the commits out of #3211 to main. Hopefully this fixes it for the moment. I am working on more things that should improve this, though.

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.

Apply all function settings as transaction-scoped settings
3 participants