LANGUAGE sql function returns 0 rows; identical inlined body and identical plpgsql function both return rows #47609
Replies: 3 comments 2 replies
-
|
The screenshots narrow this down pretty well: the second one shows the same session seeing This does not look like a PostgREST pooler or prepared-statement cache issue. Since it reproduces directly in the SQL editor in the same transaction after The thing I’d check hardest is SQL-function name binding, not pgvector. In Try recreating the SQL function using positional args only, fully qualified table aliases, and an explicit search path/operator schema if needed: create or replace function public.match_kb_chunks(
query_embedding extensions.vector,
match_count integer
)
returns table (
-- your return columns here
)
language sql
stable
security invoker
set search_path = public, extensions
as $$
select
-- columns here
from public.knowledge_base_documents kbd
where kbd.source_document_id is not null
and kbd.embedding is not null
-- use $1 and $2, not argument names
-- example:
-- and (kbd.embedding <=> $1) < some_threshold
order by kbd.embedding <=> $1
limit $2
$$;If that fixes it, the root cause was almost certainly identifier resolution inside the SQL function body. If it does not, the next useful comparison is: explain (analyze, verbose, settings)
select *
from public.match_kb_chunks(
(
select ('[' || string_agg('0.01', ',') || ']')::extensions.vector(1536)
from generate_series(1, 1536)
),
8
);and the same If my answer solved your problem, you can click answered the question. I'm really here to help, and along the way I'm also collecting Galaxy Brain badges haha 😆 |
Beta Was this translation helpful? Give feedback.
-
|
The behavior you are seeing is a known PostgreSQL quirk with STABLE sql-language functions under RLS. When PostgreSQL plans a STABLE LANGUAGE sql function it may inline the body into the calling query and re-evaluate security context at that point. With SECURITY INVOKER and RLS enabled, the planner can see a different row-level security check depending on whether the function body is inlined or executed as a standalone call. This is why the raw SELECT, the plpgsql wrapper, and the inlined form all return 3 rows while the sql function returns 0: the planner inlines the sql function body and the RLS policy evaluation changes in that context. The most reliable fix is to mark the function SECURITY DEFINER and add it to a non-public search_path: CREATE OR REPLACE FUNCTION public.match_kb_chunks(query_embedding vector, match_count integer) This prevents the planner from inlining across the security boundary and the function runs with the owner role, bypassing RLS for the match itself. If you want RLS to still apply, keep SECURITY INVOKER and switch to LANGUAGE plpgsql, which PostgreSQL never inlines. |
Beta Was this translation helpful? Give feedback.
-
|
The detail that it also reproduces in the SQL editor (not just PostgREST) is important — that rules out the pooler/prepared-statement/plan-cache angle from your question (1) and points at the function boundary itself. The usual culprit when a Two concrete things to try:
For diagnosis, If you can paste the function body and the WHERE/ORDER BY that uses the vector operator, I can point at the exact line. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Project ref: kcsbwuptmqjkscpprkmy
Summary: A plain LANGUAGE sql STABLE function (public.match_kb_chunks(vector, integer) — single overload, SECURITY INVOKER, owner postgres, proconfig NULL) returns 0 rows. In the same transaction, same impersonated role (authenticated) and same request.jwt.claims: (a) the function's WHERE clause executed directly returns 3 rows; (b) the function body inlined as a raw SELECT returns 3 rows; (c) a newly created LANGUAGE plpgsql function with identical semantics (kb_search_chunks) returns 3 rows. Dropping and recreating the sql function (fresh pg_proc OID) plus notify pgrst, 'reload schema' changed nothing. Reproduces both through PostgREST rpc and directly in the SQL editor.
Catalog state: single pg_proc row; prosecdef = false; owner postgres; proconfig NULL. Table RLS enabled, not forced, owner postgres. pgvector 0.8.2 in extensions schema.
Questions: (1) Any pooler/prepared-statement/plan-cache mechanism that could cause this? (2) Any known pgvector operator resolution issue inside LANGUAGE sql functions? (3) Can anyone check server-side plans/logs for these calls?






Beta Was this translation helpful? Give feedback.
All reactions