I'm trying to create a SQL expression for an INSERT policy for a resource_authors table (a JOIN table linking resources and users):
CREATE POLICY insert_resources_authors ON public.resource_authors
FOR INSERT TO public_user
WITH CHECK (*some expression*)
What I'd like to say is "the user must be logged in and there must not be an existing author record for that resource yet". I can express the first part using a custom current_user_id function I have:
current_user_id() IS NOT NULL
And I can get the second with:
SELECT count(user_id) > 0
FROM resource_authors
WHERE resource_id = resource_id
... but I can't figure out how to combine them. Can anyone clue me in to how I can select both, as a single boolean?
Literally place an AND between them (and wrap them in parenthesis to make clear they're subqueries):
(current_user_id() IS NOT NULL)
AND
NOT (SELECT count(user_id) > 0
FROM resource_authors
WHERE resource_id = resource_id)
I'd write
CREATE POLICY insert_resources_authors ON public.resource_authors
FOR INSERT TO public_user
WITH CHECK (
current_user_id() IS NOT NULL
AND
NOT EXISTS (SELECT *
FROM resource_authors ra
WHERE ra.resource_id = resource_authors.resource_id
)
);
However I find it questionable to allow anyone to make themselves author of any authorless resource. I'd rather record the author via a trigger when they create the resource.
Related
I have a RLS policy violation on a Postgres function. I believe it's because the policy relies on rows created in the function. A SELECT command is run in the function. New rows are not available because they are still in a transaction.
Here is the function:
CREATE FUNCTION public.create_message(organization_id int, content text, tags Int[])
RETURNS setof public.message
AS $$
-- insert message, return PK
WITH moved_rows AS (
INSERT INTO public.message (organization_id, content)
VALUES($1, $2)
RETURNING *
),
-- many to many relation
moved_tags AS (
INSERT INTO public.message_tag (message_id, tag_id)
SELECT moved_rows.id, tagInput.tag_id
FROM moved_rows, UNNEST($3) as tagInput(tag_id)
RETURNING *
)
SELECT moved_rows.* FROM moved_rows LEFT JOIN moved_tags ON moved_rows.id = moved_tags.message_id
$$ LANGUAGE sql VOLATILE STRICT;
Here is the policy:
CREATE POLICY select_if_organization
on message_tag
for select
USING ( message_id IN (
SELECT message.id
FROM message
INNER JOIN organization_user ON (organization_user.organization_id = message.organization_id)
INNER JOIN sessions ON (sessions.user_id = organization_user.user_id)
WHERE sessions.session_token = current_user_id()));
Ideas:
Add a field to the joining table to simplify the policy, but it violates normal form.
Return user input instead of running the SELECT, but input may be escaped and I should be able to run a SELECT command
Split into two functions. Create the message row, then add the message_tag. I'm running postgraphile, so two mutations. I have foreign key relations setup between the two. I don't know if graphile will do that automatically.
Error message:
ERROR: new row violates row-level security policy for table "message_tag"
CONTEXT: SQL function "create_message" statement 1
I receive the error when I run the function. I want the function to run successfully, insert one row in the message table, and turning the input array into rows for the message_tag table with message_tag.message_id=message.id, the last inserted id. I need a policy in place so users from that join relation only see their own organization's message_tag rows.
Here is another policy on the INSERT command. It allows INSERT if a user is logged in:
create policy insert_message_tag_if_author
on message_tag
for insert
with check (EXISTS (SELECT * FROM sessions WHERE sessions.session_token = current_user_id()));
According to the error message, this part of your SQL statement causes the error:
INSERT INTO public.message_tag (message_id, tag_id)
SELECT moved_rows.id, tagInput.tag_id
FROM moved_rows, UNNEST($3) as tagInput(tag_id)
RETURNING *
You need to add another policy FOR INSERT with an appropriate WITH CHECK clause.
I ended up adding a field to the joining table, and creating a policy with that. That way, RLS validation does not require a row which would be created in a middle of a function.
I have this postgres table:
CREATE TABLE News (
tags text[](10),
contract varchar(40),
...others
);
I need to get all the distinct tags for a given contract. I found the postgresql request which works well:
SELECT array_agg(acc)
FROM (
SELECT DISTINCT unnest(tags::text[])
FROM my_schema.news
WHERE contract = 'acontract'
) AS dt(acc);
As I am using spring data jpa, I try to convert this request into an HSQL request but I can't make it work. Any idea on what the conversion could be?
In addition to SET DATABASE SQL SYNTAX PGS TRUE you need to reference the arrays according to the SQL standard.
CREATE TABLE News (tags text array[10], contract varchar(40))
Then
select array_agg(acc) from (
select distinct acc from news, unnest(tags) un(acc)
WHERE contract = 'acontract'
)
I recently, approx 2 months ago, needed to check for any field in a table that has NULL value.
I am now onto another task but this time i need to check if any field in a table has an Empty String value.
Starting Query:
;With xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
SELECT *
FROM [Schema].[Table] act
where (
select act.*
for xml path('row'), elements xsinil, type
).exist('//*/#ns:nil') = 1
I know i need to change #ns:nil but as i am uneducated on TSql's XQuery implementation, i need someone to help me with this initial query. As well, where i should go outside of MSDN to get read up on usage and functionality.
Update #1:
;With xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
Select *
from Schema.Table act
where (
select act.*
for xml path('row'), elements xsinil, type
).value('(//row/*)[1]', 'varchar(max)') = ''
Tried this but evidently one of the fields contains character 0x001C and so requires a conversion to binary, varbinary, or image and then use BINARY BASE64 directive.
Build the XML and check for node values that are empty. Simpler than checking for null and as stated in comment, only (n)varchar produces an empty string as a node value.
select *
from T
where (
select T.*
for xml path(''), type
).exist('*/text()[. = ""]') = 1
Not sure whether this is possible and what's the best way to do it, but I have a list of values (say it is 'ACBC', 'ADFC', 'AGGD' etc.) which grows over time. I want to use these values in pgADmin as a sort of variable/parameter for SQL statements; e.g:
Codes = {'ACBC', 'ADFC', 'AGGD'}
SQL: Statement => SELECT * FROM xxx WHERE SUBSTRING IN (Codes)
Is that somehow realizable, either with a variable, parameter, function or anyhing else?
I can think of the following options:
1) Create separate table
create table qry_params(prm_value varchar);
insert into qry_params values
('xxx'),
('yyy'),
('zzz');
select * from xxx where substring in (select prm_value from qry_params)
Whenever you have new parameter, you just need to add it to the table.
2) CTE at the top of query
Use query like:
with params (prm_value) as (select values ('xxx'), ('yyy'), ('zzz'))
select * from xxx where substring in (select prm_value from qry_params)
Whenever you have new parameter, you just need to add it to the CTE.
I wrote this very simple SP in SQL 2008:
Create procedure dbo.GetNextID
(
#TableName nvarchar(50),
#FieldName nvarchar(50)
)
AS
BEGIN
exec('select isnull(max('+#FieldName+'),0)+1 as NewGeneratedID from '+ #TableName);
END
When I execute this procedure in Visual Studio SQL Express and pass a table name and a field name, it works fine. But when I try to add this SP as a query in a QueryTableAdapter in my ADO DataSet, I receive this error before clicking on Finish button:
the max function requires 1 argument(s)
can anyone help me with this?
I guess that VS tries to determine a field list by executing the SP. But as it does not know what to pass to the SP, it uses empty parameters. Now, of course, your select statement fails.
You could try adding the following to your SP:
IF ISNULL(#TableName,'') = '' SET #TableName = '<Name of a test table>';
IF ISNULL(#FieldName,'') = '' SET #FieldName = '<Name of some field>';
Use the names of some field and table that do exist here (for example names that you'd use from your application, too).
Alternatively you could add the following above the exec:
IF (ISNULL(#TableName, '') = '') OR (ISNULL(#FieldName, '') = '')
BEGIN
SELECT -1 AS NewGeneratedId
RETURN 0
END
EDIT
On a side note, I'd like to warn you about concurrency issues that I see coming up from what your code does. If this code is supposed to return a unique ID for a new record in some table, I'd redesign this as follows:
Create a table NumberSeries where each row contains a unique name, a possible range for IDs and the current ID value.
Create a stored procedure that uses UPDATE ... OUTPUT to update the current ID for a number series and retrieve it in one step.
That way you can make sure that creating a new ID is a single operation that does not cause concurrency problems.