I am needing to update several rows at a time through the Rest API for Postgres. The only way I have figured out how to do this is through a function call, where I pass the JSON to the function and iterate over the JSON and perform the updates.
However, depending on what I do, I am having one of two problems.
-
If I create generic function that takes in a type json or jsonb, PostgREST tells me:
No function matches the given name and argument types. You might need to add explicit type casts.
-
But then, if I create an overloaded function where I accept a json or a jsonb object, I get the error:
{"code":"PGRST203","details":null,"hint":"Try renaming the parameters or the function itself in the database so function overloading can be resolved","message":"Could not choose the best candidate function..."
How do I get around this issue? PostgREST can’t find the function without the overloading, but when overloaded, it can find the function but doesn’t know which one to use.
SQL:
-- TABLE --
CREATE TABLE IF NOT EXISTS vehicle_database.test_table(
test_table_id BIGSERIAL PRIMARY KEY,
test_data INTEGER,
test_type VARCHAR(8)
);
-- Overloaded function using json --
CREATE OR REPLACE FUNCTION update_test_table(p_json json)
RETURNS void AS $$
DECLARE
json_item json;
BEGIN
FOR json_item IN SELECT json_array_elements(p_json) LOOP
PERFORM update_test_table_row(
(json_item->>'data')::integer,
(json_item->>'test_type')::text);
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- Overloaded function using jsonb --
CREATE OR REPLACE FUNCTION update_test_table(p_json jsonb)
RETURNS void AS $$
DECLARE
json_item json;
BEGIN
FOR json_item IN SELECT json_array_elements(p_json) LOOP
PERFORM update_test_table_row(
(json_item->>'data')::integer,
(json_item->>'test_type')::text);
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- Overloaded function using text --
CREATE OR REPLACE FUNCTION update_test_table(p_json text)
RETURNS void AS $$
DECLARE
json_item json;
BEGIN
json_data = p_json::JSONB;
PERFORM update_test_table(json_data);
END;
$$ LANGUAGE plpgsql;
-- Update the row
CREATE OR REPLACE FUNCTION update_test_table_row(p_data integer, p_type text)
RETURNS void AS $$
BEGIN
UPDATE test_table SET test_data = p_data WHERE test_type = p_type;
IF NOT FOUND THEN
RAISE NOTICE 'No rows updated for data: %, type: %', p_data, p_type;
END IF;
END;
$$ LANGUAGE plpgsql;
-- Permissions (same kind of users as in the tutorials)
GRANT EXECUTE ON FUNCTION update_test_table(p_json json) TO web_insert;
GRANT EXECUTE ON FUNCTION update_test_table(p_json jsonb) TO web_insert;
GRANT EXECUTE ON FUNCTION update_test_table(p_json text) TO web_insert;
GRANT EXECUTE ON FUNCTION update_test_table_row(p_data integer, p_type text) TO web_insert;
Here is how I call it:
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '[{"test_data":"4","test_type":"RED"}]' http://127.0.0.1:3000/rpc/update_test_table
$TOKEN
is my private token that is used with PostgREST in the jwt-secret field.
Unfortunately, PostgREST has no verbose logging, and the Postgres logs don’t really help with this. Postgres tells me the function does not exist, but the Postgres logs don’t actually print the JSON parameters (it prints all of the other curl parameters, but not what is included with the -d
).
2
Answers
Found the answer.
This is not located in the PostgREST documentation, and it should be included.
If you are attempting to call an RPC function from the PostgREST API, passing JSON data or JSONB data, you have to assign that data to a variable.
So, in the instance of my problem listed above, get rid of the overloaded function with the text argument, and then get rid of one of the other overloaded functions (either the json or jsonb arguments).
Then your curl function needs to assign the JSON to a variable name that matches the name in the function.
So my solution (after removing 2 overloaded functions is):
I don’t think the example you showed fully reflects your actual config, because it just doesn’t work:
text
variant defines ajson_item
variable but then tries to use a not-yet definedjson_data
. Test1 here.::jsonb
in thetext
variant looks like you tried to redirect to thejsonb
function, but since the result of that cast is saved to a variable defined asjson_item json
, it actually redirects to thejson
variant instead.jsonb
variant is broken because it tries to usejson_array_elements
on ajsonb
. Test2 here.test_table
invehicle_database
schema, but then referring to it from the function bodies astest_table
without updating thesearch_path
first or using the fully schema-qualified identifier. Test3 here.Fixing the 3 things above leads to a setup where that exact set of overloaded functions works fine: Test4 here.
If you call
update_test_table()
with a single-quoted argument, without casting it, you’ll get thetext
variant because that’s thepg_type.typispreferred
type. If you only set upjson
andjsonb
variants (or only those two are visible), neither of them is preferred in their type group, which is why you’d get the error(Test5 here)
…unless you used explicit type casting as suggested by @Adrian Klaver.
My bet is you need to make sure all of those functions are visible in your search path and/or you use full, schema-qualified function and table names in your code.
You might want to take a look at Chapter 10. Type Conversion and 5.9.3. The Schema Search Path.
On the PostgREST side, it seems that they want you to always name and wrap your arguments in a json:
Which explains why your idea works:
But only if you make sure the function is visible and if overloaded, Postgres knows a clear type conversion path to pick one.
If you don’t like having to wrap your parameters, you can ditch their naming both in the call as well as in the function signature: Test6
Quoting PostgREST doc again:
PostgREST also offers a bulk update, which technically does "update several rows at a time":
Which means that, following your example, you could
And all REDs would get a 4 in their
test_data
. I’m guessing that in reality, you’re after something likewhere you can set
test_data
to something else iftest_type
isRED
than when it’sGREEN
orBLUE
, in which case your function method is necessary.