Replace append_consultation_question so the 200,000 quota sums only user-visible text, and add a 1,000,000-byte whole-JSON physical cap. Both still return session_full. Advisory lock, request_id idempotency, and the 200-message cap are unchanged.
136 lines
4.3 KiB
PL/PgSQL
136 lines
4.3 KiB
PL/PgSQL
-- BUG-732: split append_consultation_question into two caps.
|
||
-- Conversation quota (200,000) counts only user-visible `text`.
|
||
-- thinkingText / thinkingSections stay stored but must not enter that sum.
|
||
-- Physical cap counts the whole message JSON, including receipts.
|
||
-- Arithmetic: 50 rounds × (~4,000 body + ~4,000 thinkingText + ~3,000
|
||
-- thinkingSections + ~3,000 receipts) ≈ 700,000. Headroom → 1,000,000.
|
||
-- Signature, return columns, error_code values, advisory lock, request_id
|
||
-- idempotency, 200-message cap, and 16,000-char question check are unchanged
|
||
-- (BUG-464). CREATE OR REPLACE is backward compatible with deployed callers.
|
||
|
||
begin;
|
||
|
||
do $migration$
|
||
begin
|
||
if current_user <> 'schema_owner' then
|
||
raise exception 'consultation_session_capacity_requires_schema_owner'
|
||
using errcode = '42501';
|
||
end if;
|
||
end
|
||
$migration$;
|
||
|
||
create or replace function public.append_consultation_question(
|
||
p_user_id uuid,
|
||
p_request_id text,
|
||
p_session_id uuid,
|
||
p_question_message jsonb
|
||
)
|
||
returns table(success boolean, error_code text)
|
||
language plpgsql
|
||
security definer
|
||
set search_path = ''
|
||
as $$
|
||
declare
|
||
v_session public.chat_sessions%rowtype;
|
||
v_message jsonb;
|
||
v_text text;
|
||
v_request_id text;
|
||
v_count integer;
|
||
v_chars integer;
|
||
v_new_chars integer;
|
||
v_physical_chars bigint;
|
||
v_new_physical bigint;
|
||
begin
|
||
v_request_id := btrim(coalesce(p_request_id, ''));
|
||
if p_user_id is null or p_session_id is null or v_request_id = '' then
|
||
return query select false, 'invalid_request'::text;
|
||
return;
|
||
end if;
|
||
if jsonb_typeof(p_question_message) <> 'object'
|
||
or p_question_message->>'role' <> 'user' then
|
||
return query select false, 'invalid_question_message'::text;
|
||
return;
|
||
end if;
|
||
v_text := btrim(coalesce(p_question_message->>'text', ''));
|
||
if v_text = '' or char_length(v_text) > 16000 then
|
||
return query select false, 'invalid_question_message'::text;
|
||
return;
|
||
end if;
|
||
|
||
perform pg_advisory_xact_lock(hashtextextended(p_user_id::text || ':' || v_request_id, 0));
|
||
|
||
select session.* into v_session
|
||
from public.chat_sessions as session
|
||
where session.id = p_session_id
|
||
and session.user_id = p_user_id
|
||
and session.session_type = 'consultation'
|
||
for update;
|
||
|
||
if not found then
|
||
return query select false, 'session_missing'::text;
|
||
return;
|
||
end if;
|
||
|
||
if exists (
|
||
select 1
|
||
from jsonb_array_elements(coalesce(v_session.messages, '[]'::jsonb)) as elem
|
||
where elem->>'requestId' = v_request_id
|
||
) then
|
||
return query select true, null::text;
|
||
return;
|
||
end if;
|
||
|
||
v_message := p_question_message || jsonb_build_object('requestId', v_request_id);
|
||
|
||
v_count := jsonb_array_length(coalesce(v_session.messages, '[]'::jsonb));
|
||
select
|
||
coalesce(sum(length(coalesce(elem->>'text', ''))), 0),
|
||
coalesce(sum(length(elem::text)), 0)
|
||
into v_chars, v_physical_chars
|
||
from jsonb_array_elements(coalesce(v_session.messages, '[]'::jsonb)) as elem;
|
||
|
||
v_new_chars := length(v_text);
|
||
v_new_physical := length(v_message::text);
|
||
if v_count >= 200
|
||
or (v_chars + v_new_chars) > 200000
|
||
or (v_physical_chars + v_new_physical) > 1000000 then
|
||
return query select false, 'session_full'::text;
|
||
return;
|
||
end if;
|
||
|
||
update public.chat_sessions as session
|
||
set messages = coalesce(session.messages, '[]'::jsonb) || jsonb_build_array(v_message),
|
||
title = case
|
||
when coalesce(btrim(session.title), '') in ('', '新对话') then
|
||
case
|
||
when char_length(v_text) > 14 then left(v_text, 14) || '…'
|
||
else v_text
|
||
end
|
||
else session.title
|
||
end,
|
||
updated_at = clock_timestamp()
|
||
where session.id = p_session_id
|
||
and session.user_id = p_user_id
|
||
and session.session_type = 'consultation';
|
||
if not found then
|
||
return query select false, 'session_missing'::text;
|
||
return;
|
||
end if;
|
||
|
||
return query select true, null::text;
|
||
end;
|
||
$$;
|
||
|
||
revoke all on function public.append_consultation_question(uuid, text, uuid, jsonb)
|
||
from public, anon, authenticated;
|
||
grant execute on function public.append_consultation_question(uuid, text, uuid, jsonb)
|
||
to service_role;
|
||
do $$ begin
|
||
if exists(select 1 from pg_roles where rolname = 'admin_runtime') then
|
||
grant execute on function public.append_consultation_question(uuid, text, uuid, jsonb)
|
||
to admin_runtime;
|
||
end if;
|
||
end $$;
|
||
|
||
commit;
|