Skip to content

Commit 5ef439f

Browse files
authored
ENG-950 Refactor the concept query function with new security-scoped views to avoid RLS slowdown (#482)
* Make queries through security-aware views to bypass RLS. Optimize the RLS functions. * Also avoid policy overlap. * Use foreign key, not computed relation, where possible in queries.ts
1 parent 2232736 commit 5ef439f

10 files changed

Lines changed: 1284 additions & 108 deletions

File tree

packages/database/src/dbTypes.ts

Lines changed: 582 additions & 27 deletions
Large diffs are not rendered by default.

packages/database/src/lib/contextFunctions.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,17 @@ export const fetchOrCreateSpaceDirect = async (
102102
});
103103

104104
if (result2.data === null) {
105-
return asPostgrestFailure(
106-
JSON.stringify(result2.error),
107-
"Failed to create space",
108-
);
105+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
106+
let error: string = (result2.error?.message as string | undefined) || "";
107+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
108+
if (result2.error?.context?.body)
109+
try {
110+
// eslint-disable-next-line
111+
error += await new Response(result2.error.context.body).text();
112+
} catch (err) {
113+
// could not parse, not important
114+
}
115+
return asPostgrestFailure(error, "Failed to create space");
109116
}
110117
return {
111118
data: result2.data,

packages/database/src/lib/queries.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,14 @@ const composeConceptQuery = ({
114114
if (contentFields.length > 0) {
115115
const args: string[] = contentFields.slice();
116116
if (documentFields.length > 0) {
117-
args.push("Document (\n" + documentFields.join(",\n") + ")");
117+
args.push(
118+
`Document:my_documents!document_id${innerContent ? "!inner" : ""} (\n${documentFields.join(",\n")})`,
119+
);
118120
}
119-
q += `,\nContent${innerContent ? "!inner" : ""} (\n${args.join(",\n")})`;
121+
q += `,\nContent:my_contents!represented_by_id${innerContent ? "!inner" : ""} (\n${args.join(",\n")})`;
120122
}
121123
if (nodeAuthor !== undefined) {
122-
q += ", author:author_id!inner(account_local_id)";
124+
q += ", author:my_accounts!author_id!inner(account_local_id)";
123125
}
124126
if (
125127
inRelsOfType !== undefined ||
@@ -139,16 +141,18 @@ const composeConceptQuery = ({
139141
if (inRelsToNodesOfType !== undefined && !args2.includes("schema_id"))
140142
args2.push("schema_id");
141143
if (inRelsToNodeLocalIds !== undefined)
142-
args2.push("Content!inner(source_local_id)");
144+
args2.push(
145+
"Content:my_contents!represented_by_id!inner(source_local_id)",
146+
);
143147
if (inRelsToNodesOfAuthor !== undefined) {
144148
if (!args2.includes("author_id")) args2.push("author_id");
145-
args2.push("author:author_id!inner(account_local_id)");
149+
args2.push("author:my_accounts!author_id!inner(account_local_id)");
146150
}
147151
args.push(`subnodes:concepts_of_relation!inner(${args2.join(",\n")})`);
148152
}
149153
q += `, relations:concept_in_relations!inner(${args.join(",\n")})`;
150154
}
151-
let query = supabase.from("Concept").select(q);
155+
let query = supabase.from("my_concepts").select(q);
152156
if (fetchNodes === true) {
153157
query = query.eq("arity", 0);
154158
} else if (fetchNodes === false) {
@@ -172,7 +176,7 @@ const composeConceptQuery = ({
172176
else throw new Error("schemaDbIds should be a number or number[]");
173177
}
174178
if (baseNodeLocalIds.length > 0)
175-
query = query.in("content.source_local_id", baseNodeLocalIds);
179+
query = query.in("Content.source_local_id", baseNodeLocalIds);
176180
if (inRelsOfType !== undefined && inRelsOfType.length > 0)
177181
query = query.in("relations.schema_id", inRelsOfType);
178182
if (inRelsToNodesOfType !== undefined && inRelsToNodesOfType.length > 0)

packages/database/supabase/functions/create-space/index.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ const processAndGetOrCreateSpace = async (
114114
return asPostgrestFailure(error.message, "authentication_error");
115115
}
116116
anonymousUser = data.user;
117+
await supabase.auth.signOut({ scope: "local" });
117118
}
118119
if (anonymousUser === null) {
119120
const resultCreateAnonymousUser = await supabase.auth.admin.createUser({
@@ -185,28 +186,28 @@ const processAndGetOrCreateSpace = async (
185186
const allowedOrigins = ["https://roamresearch.com", "http://localhost:3000"];
186187

187188
const isVercelPreviewUrl = (origin: string): boolean =>
188-
/^https:\/\/.*-discourse-graph-[a-z0-9]+\.vercel\.app$/.test(origin)
189+
/^https:\/\/.*-discourse-graph-[a-z0-9]+\.vercel\.app$/.test(origin);
189190

190191
const isAllowedOrigin = (origin: string): boolean =>
191192
allowedOrigins.some((allowed) => origin.startsWith(allowed)) ||
192193
isVercelPreviewUrl(origin);
193194

194195
// @ts-ignore Deno is not visible to the IDE
195196
Deno.serve(async (req) => {
196-
const origin = req.headers.get("origin");
197-
const originIsAllowed = origin && isAllowedOrigin(origin);
198-
if (req.method === "OPTIONS") {
199-
return new Response(null, {
200-
status: 204,
201-
headers: {
202-
...(originIsAllowed ? { "Access-Control-Allow-Origin": origin } : {}),
203-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
204-
"Access-Control-Allow-Headers":
205-
"Content-Type, Authorization, x-vercel-protection-bypass, x-client-info, apikey",
206-
"Access-Control-Max-Age": "86400",
207-
},
208-
});
209-
}
197+
const origin = req.headers.get("origin");
198+
const originIsAllowed = origin && isAllowedOrigin(origin);
199+
if (req.method === "OPTIONS") {
200+
return new Response(null, {
201+
status: 204,
202+
headers: {
203+
...(originIsAllowed ? { "Access-Control-Allow-Origin": origin } : {}),
204+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
205+
"Access-Control-Allow-Headers":
206+
"Content-Type, Authorization, x-vercel-protection-bypass, x-client-info, apikey",
207+
"Access-Control-Max-Age": "86400",
208+
},
209+
});
210+
}
210211

211212
const input = await req.json();
212213
// @ts-ignore Deno is not visible to the IDE

0 commit comments

Comments
 (0)