I have a very strange problem that is driving we up the walls.
I am creating the following table
CREATE TABLE articles(
id BIGINT generated by default as IDENTITY PRIMARY KEY,
user_id uuid references auth.users NOT NULL,
title TEXT,
CONTENT TEXT,
user_email TEXT,
inserted_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::TEXT,NOW()) NOT NULL
);
ALTER TABLE articles ENABLE ROW LEVEL SECURITY;
CREATE policy "users can create articles" ON articles for
INSERT WITH CHECK(auth.uid() = user_id);
CREATE policy "users can update their own articles" ON articles for
UPDATE USING (auth.uid() = user_id);
CREATE policy "users can delete their own articles" ON articles for
DELETE USING (auth.uid() = user_id);
CREATE policy "users can read their own articles" ON articles for
SELECT USING (auth.uid() = user_id);
I can add data to it through my front-end with:
const { data, error } = await supabaseClient.from("articles").insert(
[{
title: title,
content: content,
user_email: user?.email?.toLowerCase(),
user_id: user?.id,
}]
).single()
and that works. I can verify on supabase that the row has indeed been added.
However, I can't access my data through the front-end since the expression:
auth.uid() = user_id seems to evaluate to false even though it is the same. If I disable Row level security or write TRUE instead everything works as intended.
Related
I'm building a multitenant app and running into an error after adding multiple relations that point to the same table:
Uncaught Error: More than one relationship was found for teams and users
When performing this query:
const data = await supabaseClient.from('organizations')
.select(`
*,
teams(
id,
org_id,
name,
members:users(
id,
full_name,
avatar_url
)
)
`);
I have the following table structures (leaving off some fields for brevity):
table users (
id uuid PK
full_name text
email text
)
table organizations (
id uuid PK
....
)
table organization_memberships (
id uuid PK
organization_id uuid FK
user_id uuid FK
role ENUM
)
table teams (
id uuid PK
name text PK
)
table team_memberships (
id uuid PK
team_id uuid FK
user_id uuid FK
role ENUM
)
table team_boards (
id uuid PK
team_id uuid FK
owner_id uuid FK
)
Under the hood, Supabase uses PostREST for queries. And I have deciphered from the error message that the query is ambigious and it's unsure which relationship(s) to fulfill. I'm not sure how to tell Supabase which relation to use in this particular query to avoid this error.
Here's the more verbose console error from postREST:
{
hint: "By following the 'details' key, disambiguate the request by changing the url to /origin?select=relationship(*) or /origin?select=target!relationship(*)",
message: 'More than one relationship was found for teams and users',
details: [
{
origin: 'public.teams',
relationship: 'public.team_memberships[team_memberships_team_id_fkey][team_memberships_user_id_fkey]',
cardinality: 'm2m',
target: 'public.users'
},
{
origin: 'public.teams',
relationship: 'public.team_boards[team_boards_team_id_fkey][team_boards_owner_id_fkey]',
cardinality: 'm2m',
target: 'public.users'
}
]
}
Digging a bit deeper into the PostgREST docs, it turns out what I was looking for is the disambiguation operator, !.
The working query looks like this (note that we are disambiguating which relation to use to satisfy the members query):
const data = await supabaseClient.from('organizations')
.select(`
*,
teams(
id,
org_id,
name,
members:users!team_memberships(
id,
full_name,
avatar_url
)
)
`);
I have the following nodejs code (simplified):
// tables
CREATE TABLE admins (
admin_id bigint DEFAULT nextval('admins_seq') NOT NULL,
username character varying(25) NOT NULL,
password character varying(150) NOT NULL
);
CREATE TABLE admin_notifications (
admin_notification_id bigint DEFAULT nextval('admin_notifications_seq') NOT NULL,
admin_id bigint NOT NULL,
type character varying(150) NOT NULL
);
ALTER TABLE admin_notifications ADD CONSTRAINT admin_notifications_to_admins_fk FOREIGN KEY (admin_id) REFERENCES admins(admin_id) ON DELETE CASCADE;
await client.query('BEGIN');
// create a new array in the database and return the newly created admin id
const query = 'INSERT INTO admins (username, password) VALUES($1,$2) RETURNING admin_id';
const values = [];
const result = await client.query(query, values);
// insert a welcome notification for the newly created admin
const query2 = 'INSERT INTO admin_notifications (admin_id, type) VALUES ($1,$2)';
const values2 = [result.rows[0].admin_id, 'welcome'];
const result2 = await client.query(query2, values2);
await client.query('COMMIT');
A little explanation: i create a new admin in the table admins. When a new admin is created, I want to insert a new welcome notification in the table admin_notifications. The column admin_id in the table admin_notifications is a foreign key to the table admins (admin_id).
I get the following error:
Error: insert or update on table "admin_notifications" violates foreign key constraint "admin_notifications_to_admins_fk"
When I leave out the transactions it works?!
Does it has something to do with deferring constraints? And how to handle this in my code?
I created a "favorite" functionality, which is similar to the common "Like" functionality in many websites.
There are 3 tables:
"User" with primary key UUID
"Photo" with pk UUID
"Favorite" with pk user.UUID and post.UUID
The corresponding SQL is:
CREATE TABLE public."user" (
id uuid DEFAULT public.gen_random_uuid() NOT NULL
);
CREATE TABLE public."photo" (
id uuid DEFAULT public.gen_random_uuid() NOT NULL
);
CREATE TABLE public."favorite" (
userId uuid NOT NULL
photoId uuid NOT NULL
);
Now, I would like to query photos with a computed field isFavorite as boolean where the value is set to true when the current user has favorited the photo.
So, I created this custom SQL function:
CREATE OR REPLACE FUNCTION public.isfavorite(photo photo, hasura_session json)
RETURNS boolean
LANGUAGE sql
STABLE
AS $function$
SELECT EXISTS (
SELECT *
FROM public.favorite
WHERE "userId" = (VALUES (hasura_session ->> 'x-hasura-role'))::uuid AND "photoId" = photo.uuid
)
$function$
I can create this function with SQL in Hasura, but when I set this function to a computed field in the photo table, Hasura display this error:
in table "photo": in computed field "isFavorite": function "isfavorite" is overloaded. Overloaded functions are not supported
Where I made a mistake? Can we build a custom function that return boolean? How do you build a favorite (or like) functionality?
Solved: There was two isFavorite functions in the database that cause overloading...
So now there is a isFavorite field in the photo schema, but I need te provide $args with hasura_session as argument.
How to provide hasura_session without the need to fill in arguments?
You will need to track your computed column passing the session variable.
https://hasura.io/docs/1.0/graphql/manual/api-reference/schema-metadata-api/computed-field.html
{
"type":"add_computed_field",
"args":{
"table":{
"name":"photo",
"schema":"public"
},
"name":"isfavorite",
"definition":{
"function":{
"name":"isfavorite",
"schema":"public"
},
"table_argument":"photo_row",
"session_argument": "hasura_session"
}
}
}
This was also added recently. Make sure your are on version v1.3 or later. I would also change the function to accept photo_row as the variable, instead of photo photo this might cause issues with PostgreSQL.
CREATE OR REPLACE FUNCTION public.isfavorite(photo_row photo, hasura_session json)
RETURNS boolean
LANGUAGE sql
STABLE
AS $function$
SELECT EXISTS (
SELECT *
FROM public.favorite
WHERE "userId" = (VALUES (hasura_session ->> 'x-hasura-role'))::uuid AND "photoId" = photo.uuid
)
$function$
I just started a Flask - SqlAlchemy project and am having some trouble with Foreign Keys.
I have the tables User and Portfolio. Portfolio has a foreign key to user, using username. I set up my model like this.
class User(db.Model):
__tablename__ = 'portfolio_users'
__table_args__ = {"schema":"keldan"}
username = Column(String(), primary_key=True)
date_added = Column(DateTime())
class Portfolio(db.Model):
__tablename__ = 'portfolios'
__table_args__ = {"schema":"keldan"}
id = Column('pid', Integer(), Sequence('portfolios_pid_seq'), primary_key=True)
date_added = Column(DateTime())
name = Column(String())
username = Column(String(), ForeignKey('portfolio_users.username'))
user = relationship('User', backref=backref('portfolios', cascade='save-update, merge, delete, delete-orphan'))
The error I get when I try to run a simple select all query is:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'portfolios.username' could not find table 'portfolio_users' with which to generate a foreign key to target column 'username'
The tables are created like this:
CREATE TABLE keldan.portfolio_users
(
username text NOT NULL,
date_added date NOT NULL,
CONSTRAINT users_pk PRIMARY KEY (username)
)
WITH (
OIDS=FALSE
);
CREATE TABLE keldan.portfolios
(
pid serial NOT NULL,
username text NOT NULL,
date_added date NOT NULL,
name text NOT NULL,
CONSTRAINT portfolios_pk PRIMARY KEY (pid),
CONSTRAINT portfolios_fk FOREIGN KEY (username)
REFERENCES keldan.portfolio_users (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
I have spent the better part of a day trying to figure this out or making workarounds using primaryjoin but nothing seems to work.
I finally found the answer I was looking for here
If you are not using the default schema (public) then it's not enough to specify the schema for each class, but I need to specify it in the foreign key as well.
username = Column(String(), ForeignKey('keldan.portfolio_users.username'))
Am working on bigger project where we have multiple schemas in one Postgres DB. We have created foreign keys between schemas. Here is an example >
We have company schema and user schema. Company schema has company_users table which have foreign key restriction on user.users table
CREATE TABLE company.company_user
(
id serial NOT NULL,
company_id integer NOT NULL,
user_id integer NOT NULL,
created_at timestamp(0) without time zone,
updated_at timestamp(0) without time zone,
deleted_at timestamp(0) without time zone,
CONSTRAINT company_user_pkey PRIMARY KEY (id),
CONSTRAINT company_user_company_id_foreign FOREIGN KEY (company_id)
REFERENCES company.companies (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT company_user_user_id_foreign FOREIGN KEY (user_id)
REFERENCES "user".users (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Following queries run in Postgres without issue
BEGIN;
insert into "db"."user"."users" (id,"surname", "firstname", "email", "position", "language_id", "is_super_admin", "updated_at", "created_at") values (156,'Mueller', 'Julianne', 'julianne.mueller1#example.org', 'Nuclear Power Reactor Operator', 41, false, '2017-01-13 12:35:10', '2017-01-13 12:35:10') returning "id";
insert into "db"."company"."company_user" ("company_id", "user_id", "updated_at", "created_at") values (4445, 156, '2017-01-13 12:35:10', '2017-01-13 12:35:10') returning "id";
COMMIT;
However if i perform same queries via Eloquent in Laravel
\DB::beginTransaction();
$user = new User(["surname" => 'Mueller',
"firstname" => 'Julianne',
"email" => 'julianne.mueller1#example.org',
"position" => 'Nuclear Power Reactor Operator',
"language_id" => 41,
"is_super_admin" => false]
);
if (!$user->save()) {
\DB::rollBack();
return false;
}
\Log::error($user->id);
$company_user = new CompanyUser([
"company_id" => 4445,
"user_id" => $user->id
]);
if (!$company_user->save()) {
\DB::rollBack();
return false;
}
\DB::commit();
is throwing folloing error (it seems that it cannot find id of user in the table)
PDOException: SQLSTATE[23503]: Foreign key violation: 7 ERROR: insert or update on table "company_user" violates foreign key constraint "company_user_user_id_foreign"
Would anyone can say why this is not working? \Log::error($user->id) is printing id of inserted user. I tried to print out queries from Laravel with DB listener, all queries are executed in correct order, but am still getting this error.
Ok so we found a solution. It seems that we need to start transaction for each of schemas separately + each foreign key that are referencing different schema than their own should be created as deferred.
Make sure CompanyUser has $fillable:
$fillable = ['user_id', 'company_id'];
Also, make sure there is a user with this ID is already in users table. Maybe you'll need to get rid of transaction.