Supabase http_get triggered but failed on creating user - postgresql

Trigger Function below would trigger if there's a new record inserted into auth users table in supabase
declare
api_url text;
begin
SET search_path = extensions;
api_url := concat('https://cccccxzxxt?nme=',new.id);
SELECT * FROM http_get(api_url);
SET search_path = none;
return new;
end;
The http_get does get triggered all ok on this but I'm getting {"code":500,"msg":"Database error saving new user","error_id":"xxx"} when trying to create a new user and there's no user created in the auth users table.

Most likely, the problem is that you are setting the search_path to none at the end of your trigger. This means that any SQL statements that run AFTER your trigger will fail to execute UNLESS they use ONLY fully-qualified names (e.g. auth.users vs users).
Indeed, when I reproduce your trigger, I see the following error in the PostgreSQL logs:
{
"query": "UPDATE \"users\" SET \"role\" = $1, \"updated_at\" = $2 WHERE users.id = $3",
"event_message": relation "users" does not exist
}
This is most likely the update that Supabase's GoTrue server executes right after inserting the new user (i.e. right after your trigger). See: https://github.com/supabase/gotrue/blob/bfaa68ec2412abb44b76838dcfb817e68eb49aed/api/signup.go#L311).
To solve the issue, you can either:
Avoid manipulating the search_path altogether and instead use the fully qualified name of function you are calling, i.e. extensions.http_get
Use PostgreSQL's special syntax for changing the values of configuration parameters (e.g. search_path) ONLY for the duration of a function and then reverting them back. See: https://www.postgresql.org/docs/current/sql-createfunction.html, SET configuration_parameter { TO value | = value | FROM CURRENT })

Related

DB2oC (DB2 on Cloud) : Facing "attempted to modify data but was not defined as MODIFIES SQL DATA" error

I have created very simple function in DB2oC as below, which has one UPDATE sql statement and one SELECT sql statement along with MODIFIES SQL DATA. But still I get the below error, though I have specified MODIFIES SQL DATA. I did GRANT ALL on that TEST table to my user id and also did GRANT EXECUTE ON FUNCTION to my user id on safe side. Can you please help to explain on what could be the issue?
I have simply invoked the function using SELECT statement like below:
SELECT TESTSCHEMA.MAIN_FUNCTION() FROM TABLE(VALUES(1));
SQL Error [38002]: User defined routine "TESTSCHEMA.MAIN_FUNCTION"
(specific name "SQL201211013006981") attempted to modify data but was
not defined as MODIFIES SQL DATA.. SQLCODE=-577, SQLSTATE=38002,
DRIVER=4.27.25
CREATE OR REPLACE FUNCTION MAIN_FUNCTION()
RETURNS VARCHAR(20)
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE val VARCHAR(20);
UPDATE TEST t SET t.CONTENT_TEXT = 'test value' WHERE t.ID = 1;
select CONTENT_TEXT into val from TEST where ID = 1;
return val;
end;
Appreciate your help.
For the modifies SQL data clause , the usage of the function is restricted on Db2-LUW.
These restrictions do not apply for user defined functions that do not modify data.
For your specific example, that UDF will operate when used as the sole expression on the right hand side of an assignment statement in a compound-SQL compiled statemnent.
For example:
create or replace variable my_result varchar(20) default null;
begin
set my_result = main_function();
end#
Consider using stored procedures to modify table contents, instead of user defined functions.
You could avoid using a function, and just use a single "change data statement"
SELECT CONTENT_TEXT
FROM NEW TABLE(
UPDATE TEST t
SET t.CONTENT_TEXT = 'test value'
WHERE t.ID = 1
)

How to implement data authorization logic inside a Posgres db?

We are building a web app that sits on top of a postgres db. We would like to implement authorization logic inside the database so that it is opaque to the app. For example, suppose a server side controller requests all users from a view v_user. We would like for the db to handle the authorization of which users the currently logged in user can or cannot see. Obviously the server is going to need to send over the login_pkey (user_pkey of logged in user) on every request for this to work.
The issue we are having is with reads. We were able to do this for inserts, updates and deletes by putting the logic in the triggers behind those operations on all views. The issue we are having is how to do this for reads. How can we include variable logic (i.e. logic that depends on which login_pkey is passed) in a view (or some other place) and how can we pass this information for each query.
If it is important, the server we are using is Node and the ORM is Sequelize.
Thanks in advance.
Ideally you really want row security to do this well. It's available in the 9.5 version in beta now.
But you can do what you need without.
To pass a user identity you can use a custom variable, e.g.
SET myapp.appuser = 'fred';
then access it with current_setting e.g.
SELECT current_setting('myapp.appuser')
This will raise an ERROR if the setting does not exist, so you should set a default blank value in postgresql.conf, with ALTER DATABASE SET, etc. Or use PostgreSQL 9.5's current_setting('settingname', true) to return null on missing values.
To filter what users can see, use views that check the user identity setting your app sets at connect-time, per the above.
This is not safe if your users can run arbitrary SQL, because nothing stops them RESETing the setting or doing a SET myapp.appuser = 'the-admin'.
It's very easy to implement this using Pl/Python global dict GD. First, you need to write auth() function:
create or replace function auth(login text, pass text) as $$
-- Check auth login here
GD['user_id'] = get_user_id_by_login(login)
$$ language plpythonu;
Then you have to write get_current_user() function
create or replace function get_current_user() returns integer as $$
return GD['user_id']
$$ langugage plpythonu;
Now, you can get current user any time you want. For example:
-- inside stored procedure
vUserId := get_current_user()
-- in query
select * from some_table where owner_id = get_current_user()
Remember, that GD is stored per session, so, as you wrote, you need to login every time you connect to database. In my ORM I do like this:
class MyORM():
def login(self, user, password):
cursor = self.__conn.cursor()
result = cursor.execute('select core.login(%s, %s)', (user, password,))
data = cursor.fetchone()
cursor.close()
return data[0]
def auth(self, cursor):
cursor.execute('select core.auth(%s)', (g.user_id,))
def query(self, query):
cursor = self.__conn.cursor()
self.auth(cursor)
cursor.execute(query)
data = cursor.fetchall()
cursor.close()
return data

Suppressing "Notice: Relation exists" when using "CREATE ... IF NOT EXISTS"

I have a function that creates a temporary table to store and process data. Problem is I want to run this function on the order of 1M times within a single transaction, without having:
NOTICE: relation "foo" already exists, skipping
output ~1M times. Is there an efficient way to do so?
What is not efficient:
Dropping the table instead
DROP TABLE IF EXISTS
Leads to running out of shared memory
Catching the duplicate_table exception (less efficient than using IF NOT EXISTS?)
BEGIN
CREATE TEMPORARY TABLE foo () ON COMMIT DROP;
EXCEPTION
WHEN duplicate_table THEN --do nothing
END;
As others have pointed out, the client_min_messages setting is what you want. There are a number of ways to configure this.
SET client_min_messages = warning or SELECT set_config('client_min_messages', 'warning', false) will persist for the rest of the current session/connection.
SET LOCAL client_min_messages = warning or SELECT set_config('client_min_messages', 'warning', true) resets at the end of the current transaction.
The CREATE FUNCTION statement's SET clause will scope the setting only to this function; this sounds like the most suitable option in your case. For example:
CREATE FUNCTION f()
RETURNS void
SET client_min_messages = warning
LANGUAGE plpgsql
AS ...
Simply
SET client_min_messages = error;
before running the statement(s).
You can also set it on the psql command line for batch executions
PGOPTIONS="-c client_min_messages=error" psql -f somefile.sql dbname

Update field updated by trigger

I have a table with multiple fields and an additional outofsync field.
Created a trigger and trigger function to set outofsync field value to true before any update/insert.
Trigger:
CREATE TRIGGER goods_update_outofsync
BEFORE UPDATE
ON goods
FOR EACH ROW
EXECUTE PROCEDURE tg_update_goods_outofsync();
Trigger function:
CREATE OR REPLACE FUNCTION tg_update_goods_outofsync()
RETURNS trigger AS
$BODY$
BEGIN
NEW.outofsync=true;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION tg_update_goods_outofsync()
OWNER TO postgres;
And now comes to a "simple" question I am not able to find answer: how to update manually outofsync field to false, because after each attempt it is automatically changed to true by trigger.
EDIT:
This almost works:
IF (NEW.outofsync = OLD.outofsync) THEN
NEW.outofsync=true;
END IF;
Except when value of outofsync field is already false and I want to set it to false, because it became true then...
Thank you for your help in advance!
At least four options:
Set to in sync as another user and test current_user in the trigger;
Define a custom config variable (GUC) and SET LOCAL or set_config(...) it in the transaction before updating the in sync field; test that GUC in the trigger and change behaviour based on it;
Temporarily disable the trigger in the transaction before setting in sync;
Have the trigger check if all the other values are unchanged by the update and allow in sync to be set to true if no other values have changed. Use IS DISTINCT FROM for this test to handle nulls conveniently.
I'd probably use a custom GUC myself, with current_setting('my.guc') to fetch the value from within the trigger.
If you're on Pg 9.1 or older you must add my (or whatever you really call the prefix) to custom_variable_classes. In 9.2 and above any variable with a period (.) in it is assumed to be a custom variable.
See also passing user ID to triggers.

Execute triggers function of another schema on the actual chema

my problem is easy to explain with an example: I have a 'common' schema (the public one?) where I store common data between a clustered application.
For every instance of my application, I have a role (used as the application user).
And i have a common role, app_users, with read-only privileges on the common schema, and every application role is a member of app_users.
Now my problem is: how can i set a trigger on the app_a scheme that execute a function (procedure) in the common scheme, but affect the (and only the) app_a tables?
I mean:
// common_scheme, dummy function to emulate the mysql on update = now()
CREATE OR REPLACEFUNCTION update_etime() RETURNS TRIGGER AS $$
BEGIN
NEW.etime = date_part('epoch'::text, now())::int;
RETURN NEW;
END;
$$ language plpgsql;
// now, in the app_foo scheme, i have the table:
CREATE TABLE foo_table (fid serial not null primary key unique, label char(25));
// and the trigger:
CREATE TRIGGER foo_table_update_etime BEFORE UPDATE ON foo_talbe FOR EACH ROW EXECUTE PROCEDURE update_etime();
// ERROR: function update_etime() does not exist
CREATE TRIGGER foo_table_update_etime BEFORE UPDATE ON foo_talbe FOR EACH ROW EXECUTE PROCEDURE common_scheme.update_etime();
// ERROR: function common_scheme.update_etime() does not exist
The user that will access app_foo has the execute privilege on update_etime() function in common_schema.
Any idea?
I've googled around but the only solution I fount to call functions from other schemas is something like execute 'select * from ' || schema_name || '.table_name'; but i dont think this will do the trick in my case, becose the function must work with the 'local' scheme.
Your second set of syntax should work... the one with "EXECUTE PROCEDURE common_scheme.update_etime();"
If it isn't finding the function, I'd guess that you either have created it in a different schema than you think it is in, or you haven't created it at all (and note, your example create syntax has a bug, no space between "replace" and "function", which would cause an error when trying to create the function. Try doing a:
\df *.update_etime
As superuser to verify the function exists and is in the location you think it is in. HTH.