Reputation: 11
Hi stackoverflow community, this is my first ever question on stackoverflow, so I'd appreciate if you excuse my mistakes in presenting my question and really would appreciate any help.
I have written a plpgsql function that works as a middleware to check user permissions in my system, in which "function body" has an if-then-else block where "if" consists of a select statement that selects data from tables using joins and cast the response as 1 and 0 as integer if response = 1 then "then" block performs another select and return data as json if response = 0 then "else" block returns data as json.
plpgsql Function looks like as seen below:
create or replace function getUserData() returns json as
$body$
declare
v_belongs_to_group json;
v_has_permission_in_group json;
begin
if (
select case when exists(
SELECT u.email , utg.id, g.group_name, g.id
FROM users as u
inner join public.users_to_groups as utg on utg.user_id = u.id
inner join "groups" as g on g.id = utg.group_id
where u.email='[email protected]'
and g.group_name ='necessary group'
and g.status_id = 1
) then cast(1 as integer) else cast(0 as integer) end
) = 1 then
select
jsonb_build_object(
'user_id', u.id
)
from users as u
inner join users_to_groups utg on utg.user_id=u.id
inner join "groups" g on g.id = utg.group_id
inner join users_to_permissions utp on utp.user_group_id = utg.id
inner join permissions p on p.id =utp.permission_id
where u.email= '[email protected]'
and g.group_name = 'another Group'
and p."name" = 'some permission'
and g.status_id = 1
into v_has_permission_in_group;
return to_json(v_has_permission_in_group);
else
v_belongs_to_group = jsonb_build_object('v_belongs_to_group',false);
return to_json(v_belongs_to_group);
end if;
commit;
end;
$body$
language plpgsql;
select * from getUserData() as fruit;
This works absolutely fine when I execute 1 at a time however if I perform parallelism test and concurrency tests I randomly receive error "original: error: tuple concurrently updated"
Complete error description:
DatabaseError [SequelizeDatabaseError]: tuple concurrently updated
at Query.formatError (src\node_modules\sequelize\lib\dialects\postgres\query.js:366:16)
at src\node_modules\sequelize\lib\dialects\postgres\query.js:72:18
at tryCatcher (src\node_modules\bluebird\js\release\util.js:16:23)
at Promise._settlePromiseFromHandler (src\node_modules\bluebird\js\release\promise.js:547:31)
at Promise._settlePromise (src\node_modules\bluebird\js\release\promise.js:604:18)
at Promise._settlePromise0 (src\node_modules\bluebird\js\release\promise.js:649:10)
at Promise._settlePromises (src\node_modules\bluebird\js\release\promise.js:725:18)
at _drainQueueStep (src\node_modules\bluebird\js\release\async.js:93:12)
at _drainQueue (src\node_modules\bluebird\js\release\async.js:86:9)
at Async._drainQueues (src\node_modules\bluebird\js\release\async.js:102:5)
at Async.drainQueues (src\node_modules\bluebird\js\release\async.js:15:14)
at process.processImmediate (node:internal/timers:483:21) {
parent: error: tuple concurrently updated
at Parser.parseErrorMessage (src\node_modules\pg-protocol\dist\parser.js:287:98)
at Parser.handlePacket (src\node_modules\pg-protocol\dist\parser.js:126:29)
at Parser.parse (src\node_modules\pg-protocol\dist\parser.js:39:38)
at Socket.<anonymous> (src\node_modules\pg-protocol\dist\index.js:11:42)
at Socket.emit (node:events:520:28)
at addChunk (node:internal/streams/readable:559:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
at Readable.push (node:internal/streams/readable:390:5)
at TCP.onStreamRead (node:internal/stream_base_commons:191:23) {
length: 90,
severity: 'ERROR',
code: 'XX000',
detail: undefined,
hint: undefined,
position: undefined,
internalPosition: undefined,
internalQuery: undefined,
where: undefined,
schema: undefined,
table: undefined,
column: undefined,
dataType: undefined,
constraint: undefined,
file: 'heapam.c',
line: '4260',
routine: 'simple_heap_update',
sql: 'create or replace function getUserData() returns json as \n' +
' $body$\n' +
' declare \n' +
' belongs_to_default_group json;\n' +
' has_permission_in_group json;\n' +
' begin\n' +
' if (\n' +
' select case when exists(\n' +
' SELECT u.email , utg.id, g.group_name, g.id \n' +
' FROM users as u\n' +
' inner join public.users_to_groups as utg on utg.user_id = u.id\n' +
' inner join "groups" as g on g.id = utg.group_id \n' +
" where u.email='[email protected]' \n" +
" and g.group_name ='necessary group'\n" +
' and g.status_id = 1 for share\n' +
' ) then cast(1 as integer) else cast(0 as integer) end\n' +
' ) = 1 then\n' +
' select \n' +
' jsonb_build_object(\n' +
" 'user_id', u.id\n" +
' )\n' +
' from users as u \n' +
' inner join users_to_groups utg on utg.user_id=u.id \n' +
' inner join "groups" g on g.id = utg.group_id\n' +
' inner join users_to_permissions utp on utp.user_group_id = utg.id \n' +
' inner join permissions p on p.id =utp.permission_id \n' +
" where u.email= '[email protected]' \n" +
" and g.group_name ='another Group' \n" +
` and p."name" = 'some permission'\n` +
' and g.status_id = 1\n' +
' into has_permission_in_group for share;\n' +
' return to_json(has_permission_in_group);\n' +
' else\n' +
" belongs_to_default_group = jsonb_build_object('belongs_to_default_group',false);\n" +
' return to_json(belongs_to_default_group);\n' +
' end if;\n' +
' commit;\n' +
' end; \n' +
' $body$ \n' +
' language plpgsql;\n' +
' \n' +
' select * from getUserData() as fruit;',
parameters: undefined
},
original: error: tuple concurrently updated
at Parser.parseErrorMessage (src\node_modules\pg-protocol\dist\parser.js:287:98)
at Parser.handlePacket (src\node_modules\pg-protocol\dist\parser.js:126:29)
at Parser.parse (src\node_modules\pg-protocol\dist\parser.js:39:38)
at Socket.<anonymous> (src\node_modules\pg-protocol\dist\index.js:11:42)
at Socket.emit (node:events:520:28)
at addChunk (node:internal/streams/readable:559:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
at Readable.push (node:internal/streams/readable:390:5)
at TCP.onStreamRead (node:internal/stream_base_commons:191:23) {
length: 90,
severity: 'ERROR',
code: 'XX000',
detail: undefined,
hint: undefined,
position: undefined,
internalPosition: undefined,
internalQuery: undefined,
where: undefined,
schema: undefined,
table: undefined,
column: undefined,
dataType: undefined,
constraint: undefined,
file: 'heapam.c',
line: '4260',
routine: 'simple_heap_update',
sql: 'create or replace function getUserData() returns json as \n' +
' $body$\n' +
' declare \n' +
' belongs_to_default_group json;\n' +
' has_permission_in_group json;\n' +
' begin\n' +
' if (\n' +
' select case when exists(\n' +
' SELECT u.email , utg.id, g.group_name, g.id \n' +
' FROM users as u\n' +
' inner join public.users_to_groups as utg on utg.user_id = u.id\n' +
' inner join "groups" as g on g.id = utg.group_id \n' +
" where u.email='[email protected]' \n" +
" and g.group_name ='necessary group'\n" +
' and g.status_id = 1 for share\n' +
' ) then cast(1 as integer) else cast(0 as integer) end\n' +
' ) = 1 then\n' +
' select \n' +
' jsonb_build_object(\n' +
" 'user_id', u.id\n" +
' )\n' +
' from users as u \n' +
' inner join users_to_groups utg on utg.user_id=u.id \n' +
' inner join "groups" g on g.id = utg.group_id\n' +
' inner join users_to_permissions utp on utp.user_group_id = utg.id \n' +
' inner join permissions p on p.id =utp.permission_id \n' +
" where u.email= '[email protected]' \n" +
" and g.group_name ='another Group' \n" +
` and p."name" = 'some permission'\n` +
' and g.status_id = 1\n' +
' into has_permission_in_group for share;\n' +
' return to_json(has_permission_in_group);\n' +
' else\n' +
" belongs_to_default_group = jsonb_build_object('belongs_to_default_group',false);\n" +
' return to_json(belongs_to_default_group);\n' +
' end if;\n' +
' commit;\n' +
' end; \n' +
' $body$ \n' +
' language plpgsql;\n' +
' \n' +
' select * from getUserData() as fruit;',
parameters: undefined
},
sql: 'create or replace function getUserData() returns json as \n' +
' $body$\n' +
' declare \n' +
' belongs_to_default_group json;\n' +
' has_permission_in_group json;\n' +
' begin\n' +
' if (\n' +
' select case when exists(\n' +
' SELECT u.email , utg.id, g.group_name, g.id \n' +
' FROM users as u\n' +
' inner join public.users_to_groups as utg on utg.user_id = u.id\n' +
' inner join "groups" as g on g.id = utg.group_id \n' +
" where u.email='[email protected]' \n" +
" and g.group_name ='necessary group'\n" +
' and g.status_id = 1 for share\n' +
' ) then cast(1 as integer) else cast(0 as integer) end\n' +
' ) = 1 then\n' +
' select \n' +
' jsonb_build_object(\n' +
" 'user_id', u.id\n" +
' )\n' +
' from users as u \n' +
' inner join users_to_groups utg on utg.user_id=u.id \n' +
' inner join "groups" g on g.id = utg.group_id\n' +
' inner join users_to_permissions utp on utp.user_group_id = utg.id \n' +
' inner join permissions p on p.id =utp.permission_id \n' +
" where u.email= '[email protected]' \n" +
" and g.group_name ='another Group' \n" +
` and p."name" = 'some permission'\n` +
' and g.status_id = 1\n' +
' into has_permission_in_group for share;\n' +
' return to_json(has_permission_in_group);\n' +
' else\n' +
" belongs_to_default_group = jsonb_build_object('belongs_to_default_group',false);\n" +
' return to_json(belongs_to_default_group);\n' +
' end if;\n' +
' commit;\n' +
' end; \n' +
' $body$ \n' +
' language plpgsql;\n' +
' \n' +
' select * from getUserData() as fruit;',
parameters: undefined
}
I tried adding "commit" explicitly thinking that there maybe uncommitted-reads performed in background by this function even so, when pgsql adds "commit" by default in all of its function.
Parallelism/Concurrency tests description:
I am simulating 20vu(vistrual users) to call an API endpoint over 1 min that results in almost 350 to 400 requests.
Code and API-server description: Responding to these requests is a node server that is executing this plpgsql function within a async-await function.
const requestHandler = async(ctx) =>{
const result = await sequelize.query('plpgsql function as described above');
if(result) {ctx.body=result; ctx.status = 200}
else {ctx.status=409}
}
Upvotes: 1
Views: 124