Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras

This commit is contained in:
Leonardo
2026-03-24 21:26:58 -03:00
parent a89d1f5560
commit 53a4980396
453 changed files with 121427 additions and 174407 deletions

View File

@@ -0,0 +1,877 @@
-- =============================================================================
-- AgenciaPsi — Functions — storage schema
-- =============================================================================
CREATE FUNCTION storage.can_insert_object(bucketid text, name text, owner uuid, metadata jsonb) RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO "storage"."objects" ("bucket_id", "name", "owner", "metadata") VALUES (bucketid, name, owner, metadata);
-- hack to rollback the successful insert
RAISE sqlstate 'PT200' using
message = 'ROLLBACK',
detail = 'rollback successful insert';
END
$$;
ALTER FUNCTION storage.can_insert_object(bucketid text, name text, owner uuid, metadata jsonb) OWNER TO supabase_storage_admin;
--
-- Name: enforce_bucket_name_length(); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.enforce_bucket_name_length() RETURNS trigger
LANGUAGE plpgsql
AS $$
begin
if length(new.name) > 100 then
raise exception 'bucket name "%" is too long (% characters). Max is 100.', new.name, length(new.name);
end if;
return new;
end;
$$;
ALTER FUNCTION storage.enforce_bucket_name_length() OWNER TO supabase_storage_admin;
--
-- Name: extension(text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.extension(name text) RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
_filename text;
BEGIN
select string_to_array(name, '/') into _parts;
select _parts[array_length(_parts,1)] into _filename;
-- @todo return the last part instead of 2
return reverse(split_part(reverse(_filename), '.', 1));
END
$$;
ALTER FUNCTION storage.extension(name text) OWNER TO supabase_storage_admin;
--
-- Name: filename(text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.filename(name text) RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[array_length(_parts,1)];
END
$$;
ALTER FUNCTION storage.filename(name text) OWNER TO supabase_storage_admin;
--
-- Name: foldername(text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.foldername(name text) RETURNS text[]
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[1:array_length(_parts,1)-1];
END
$$;
ALTER FUNCTION storage.foldername(name text) OWNER TO supabase_storage_admin;
--
-- Name: get_common_prefix(text, text, text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.get_common_prefix(p_key text, p_prefix text, p_delimiter text) RETURNS text
LANGUAGE sql IMMUTABLE
AS $$
SELECT CASE
WHEN position(p_delimiter IN substring(p_key FROM length(p_prefix) + 1)) > 0
THEN left(p_key, length(p_prefix) + position(p_delimiter IN substring(p_key FROM length(p_prefix) + 1)))
ELSE NULL
END;
$$;
ALTER FUNCTION storage.get_common_prefix(p_key text, p_prefix text, p_delimiter text) OWNER TO supabase_storage_admin;
--
-- Name: get_size_by_bucket(); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.get_size_by_bucket() RETURNS TABLE(size bigint, bucket_id text)
LANGUAGE plpgsql
AS $$
BEGIN
return query
select sum((metadata->>'size')::int) as size, obj.bucket_id
from "storage".objects as obj
group by obj.bucket_id;
END
$$;
ALTER FUNCTION storage.get_size_by_bucket() OWNER TO supabase_storage_admin;
--
-- Name: list_multipart_uploads_with_delimiter(text, text, text, integer, text, text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.list_multipart_uploads_with_delimiter(bucket_id text, prefix_param text, delimiter_param text, max_keys integer DEFAULT 100, next_key_token text DEFAULT ''::text, next_upload_token text DEFAULT ''::text) RETURNS TABLE(key text, id text, created_at timestamp with time zone)
LANGUAGE plpgsql
AS $_$
BEGIN
RETURN QUERY EXECUTE
'SELECT DISTINCT ON(key COLLATE "C") * from (
SELECT
CASE
WHEN position($2 IN substring(key from length($1) + 1)) > 0 THEN
substring(key from 1 for length($1) + position($2 IN substring(key from length($1) + 1)))
ELSE
key
END AS key, id, created_at
FROM
storage.s3_multipart_uploads
WHERE
bucket_id = $5 AND
key ILIKE $1 || ''%'' AND
CASE
WHEN $4 != '''' AND $6 = '''' THEN
CASE
WHEN position($2 IN substring(key from length($1) + 1)) > 0 THEN
substring(key from 1 for length($1) + position($2 IN substring(key from length($1) + 1))) COLLATE "C" > $4
ELSE
key COLLATE "C" > $4
END
ELSE
true
END AND
CASE
WHEN $6 != '''' THEN
id COLLATE "C" > $6
ELSE
true
END
ORDER BY
key COLLATE "C" ASC, created_at ASC) as e order by key COLLATE "C" LIMIT $3'
USING prefix_param, delimiter_param, max_keys, next_key_token, bucket_id, next_upload_token;
END;
$_$;
ALTER FUNCTION storage.list_multipart_uploads_with_delimiter(bucket_id text, prefix_param text, delimiter_param text, max_keys integer, next_key_token text, next_upload_token text) OWNER TO supabase_storage_admin;
--
-- Name: list_objects_with_delimiter(text, text, text, integer, text, text, text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.list_objects_with_delimiter(_bucket_id text, prefix_param text, delimiter_param text, max_keys integer DEFAULT 100, start_after text DEFAULT ''::text, next_token text DEFAULT ''::text, sort_order text DEFAULT 'asc'::text) RETURNS TABLE(name text, id uuid, metadata jsonb, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone)
LANGUAGE plpgsql STABLE
AS $_$
DECLARE
v_peek_name TEXT;
v_current RECORD;
v_common_prefix TEXT;
-- Configuration
v_is_asc BOOLEAN;
v_prefix TEXT;
v_start TEXT;
v_upper_bound TEXT;
v_file_batch_size INT;
-- Seek state
v_next_seek TEXT;
v_count INT := 0;
-- Dynamic SQL for batch query only
v_batch_query TEXT;
BEGIN
-- ========================================================================
-- INITIALIZATION
-- ========================================================================
v_is_asc := lower(coalesce(sort_order, 'asc')) = 'asc';
v_prefix := coalesce(prefix_param, '');
v_start := CASE WHEN coalesce(next_token, '') <> '' THEN next_token ELSE coalesce(start_after, '') END;
v_file_batch_size := LEAST(GREATEST(max_keys * 2, 100), 1000);
-- Calculate upper bound for prefix filtering (bytewise, using COLLATE "C")
IF v_prefix = '' THEN
v_upper_bound := NULL;
ELSIF right(v_prefix, 1) = delimiter_param THEN
v_upper_bound := left(v_prefix, -1) || chr(ascii(delimiter_param) + 1);
ELSE
v_upper_bound := left(v_prefix, -1) || chr(ascii(right(v_prefix, 1)) + 1);
END IF;
-- Build batch query (dynamic SQL - called infrequently, amortized over many rows)
IF v_is_asc THEN
IF v_upper_bound IS NOT NULL THEN
v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" >= $2 ' ||
'AND o.name COLLATE "C" < $3 ORDER BY o.name COLLATE "C" ASC LIMIT $4';
ELSE
v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" >= $2 ' ||
'ORDER BY o.name COLLATE "C" ASC LIMIT $4';
END IF;
ELSE
IF v_upper_bound IS NOT NULL THEN
v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" < $2 ' ||
'AND o.name COLLATE "C" >= $3 ORDER BY o.name COLLATE "C" DESC LIMIT $4';
ELSE
v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" < $2 ' ||
'ORDER BY o.name COLLATE "C" DESC LIMIT $4';
END IF;
END IF;
-- ========================================================================
-- SEEK INITIALIZATION: Determine starting position
-- ========================================================================
IF v_start = '' THEN
IF v_is_asc THEN
v_next_seek := v_prefix;
ELSE
-- DESC without cursor: find the last item in range
IF v_upper_bound IS NOT NULL THEN
SELECT o.name INTO v_next_seek FROM storage.objects o
WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_prefix AND o.name COLLATE "C" < v_upper_bound
ORDER BY o.name COLLATE "C" DESC LIMIT 1;
ELSIF v_prefix <> '' THEN
SELECT o.name INTO v_next_seek FROM storage.objects o
WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_prefix
ORDER BY o.name COLLATE "C" DESC LIMIT 1;
ELSE
SELECT o.name INTO v_next_seek FROM storage.objects o
WHERE o.bucket_id = _bucket_id
ORDER BY o.name COLLATE "C" DESC LIMIT 1;
END IF;
IF v_next_seek IS NOT NULL THEN
v_next_seek := v_next_seek || delimiter_param;
ELSE
RETURN;
END IF;
END IF;
ELSE
-- Cursor provided: determine if it refers to a folder or leaf
IF EXISTS (
SELECT 1 FROM storage.objects o
WHERE o.bucket_id = _bucket_id
AND o.name COLLATE "C" LIKE v_start || delimiter_param || '%'
LIMIT 1
) THEN
-- Cursor refers to a folder
IF v_is_asc THEN
v_next_seek := v_start || chr(ascii(delimiter_param) + 1);
ELSE
v_next_seek := v_start || delimiter_param;
END IF;
ELSE
-- Cursor refers to a leaf object
IF v_is_asc THEN
v_next_seek := v_start || delimiter_param;
ELSE
v_next_seek := v_start;
END IF;
END IF;
END IF;
-- ========================================================================
-- MAIN LOOP: Hybrid peek-then-batch algorithm
-- Uses STATIC SQL for peek (hot path) and DYNAMIC SQL for batch
-- ========================================================================
LOOP
EXIT WHEN v_count >= max_keys;
-- STEP 1: PEEK using STATIC SQL (plan cached, very fast)
IF v_is_asc THEN
IF v_upper_bound IS NOT NULL THEN
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_next_seek AND o.name COLLATE "C" < v_upper_bound
ORDER BY o.name COLLATE "C" ASC LIMIT 1;
ELSE
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_next_seek
ORDER BY o.name COLLATE "C" ASC LIMIT 1;
END IF;
ELSE
IF v_upper_bound IS NOT NULL THEN
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek AND o.name COLLATE "C" >= v_prefix
ORDER BY o.name COLLATE "C" DESC LIMIT 1;
ELSIF v_prefix <> '' THEN
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek AND o.name COLLATE "C" >= v_prefix
ORDER BY o.name COLLATE "C" DESC LIMIT 1;
ELSE
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek
ORDER BY o.name COLLATE "C" DESC LIMIT 1;
END IF;
END IF;
EXIT WHEN v_peek_name IS NULL;
-- STEP 2: Check if this is a FOLDER or FILE
v_common_prefix := storage.get_common_prefix(v_peek_name, v_prefix, delimiter_param);
IF v_common_prefix IS NOT NULL THEN
-- FOLDER: Emit and skip to next folder (no heap access needed)
name := rtrim(v_common_prefix, delimiter_param);
id := NULL;
updated_at := NULL;
created_at := NULL;
last_accessed_at := NULL;
metadata := NULL;
RETURN NEXT;
v_count := v_count + 1;
-- Advance seek past the folder range
IF v_is_asc THEN
v_next_seek := left(v_common_prefix, -1) || chr(ascii(delimiter_param) + 1);
ELSE
v_next_seek := v_common_prefix;
END IF;
ELSE
-- FILE: Batch fetch using DYNAMIC SQL (overhead amortized over many rows)
-- For ASC: upper_bound is the exclusive upper limit (< condition)
-- For DESC: prefix is the inclusive lower limit (>= condition)
FOR v_current IN EXECUTE v_batch_query USING _bucket_id, v_next_seek,
CASE WHEN v_is_asc THEN COALESCE(v_upper_bound, v_prefix) ELSE v_prefix END, v_file_batch_size
LOOP
v_common_prefix := storage.get_common_prefix(v_current.name, v_prefix, delimiter_param);
IF v_common_prefix IS NOT NULL THEN
-- Hit a folder: exit batch, let peek handle it
v_next_seek := v_current.name;
EXIT;
END IF;
-- Emit file
name := v_current.name;
id := v_current.id;
updated_at := v_current.updated_at;
created_at := v_current.created_at;
last_accessed_at := v_current.last_accessed_at;
metadata := v_current.metadata;
RETURN NEXT;
v_count := v_count + 1;
-- Advance seek past this file
IF v_is_asc THEN
v_next_seek := v_current.name || delimiter_param;
ELSE
v_next_seek := v_current.name;
END IF;
EXIT WHEN v_count >= max_keys;
END LOOP;
END IF;
END LOOP;
END;
$_$;
ALTER FUNCTION storage.list_objects_with_delimiter(_bucket_id text, prefix_param text, delimiter_param text, max_keys integer, start_after text, next_token text, sort_order text) OWNER TO supabase_storage_admin;
--
-- Name: operation(); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.operation() RETURNS text
LANGUAGE plpgsql STABLE
AS $$
BEGIN
RETURN current_setting('storage.operation', true);
END;
$$;
ALTER FUNCTION storage.operation() OWNER TO supabase_storage_admin;
--
-- Name: protect_delete(); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.protect_delete() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
-- Check if storage.allow_delete_query is set to 'true'
IF COALESCE(current_setting('storage.allow_delete_query', true), 'false') != 'true' THEN
RAISE EXCEPTION 'Direct deletion from storage tables is not allowed. Use the Storage API instead.'
USING HINT = 'This prevents accidental data loss from orphaned objects.',
ERRCODE = '42501';
END IF;
RETURN NULL;
END;
$$;
ALTER FUNCTION storage.protect_delete() OWNER TO supabase_storage_admin;
--
-- Name: search(text, text, integer, integer, integer, text, text, text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.search(prefix text, bucketname text, limits integer DEFAULT 100, levels integer DEFAULT 1, offsets integer DEFAULT 0, search text DEFAULT ''::text, sortcolumn text DEFAULT 'name'::text, sortorder text DEFAULT 'asc'::text) RETURNS TABLE(name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb)
LANGUAGE plpgsql STABLE
AS $_$
DECLARE
v_peek_name TEXT;
v_current RECORD;
v_common_prefix TEXT;
v_delimiter CONSTANT TEXT := '/';
-- Configuration
v_limit INT;
v_prefix TEXT;
v_prefix_lower TEXT;
v_is_asc BOOLEAN;
v_order_by TEXT;
v_sort_order TEXT;
v_upper_bound TEXT;
v_file_batch_size INT;
-- Dynamic SQL for batch query only
v_batch_query TEXT;
-- Seek state
v_next_seek TEXT;
v_count INT := 0;
v_skipped INT := 0;
BEGIN
-- ========================================================================
-- INITIALIZATION
-- ========================================================================
v_limit := LEAST(coalesce(limits, 100), 1500);
v_prefix := coalesce(prefix, '') || coalesce(search, '');
v_prefix_lower := lower(v_prefix);
v_is_asc := lower(coalesce(sortorder, 'asc')) = 'asc';
v_file_batch_size := LEAST(GREATEST(v_limit * 2, 100), 1000);
-- Validate sort column
CASE lower(coalesce(sortcolumn, 'name'))
WHEN 'name' THEN v_order_by := 'name';
WHEN 'updated_at' THEN v_order_by := 'updated_at';
WHEN 'created_at' THEN v_order_by := 'created_at';
WHEN 'last_accessed_at' THEN v_order_by := 'last_accessed_at';
ELSE v_order_by := 'name';
END CASE;
v_sort_order := CASE WHEN v_is_asc THEN 'asc' ELSE 'desc' END;
-- ========================================================================
-- NON-NAME SORTING: Use path_tokens approach (unchanged)
-- ========================================================================
IF v_order_by != 'name' THEN
RETURN QUERY EXECUTE format(
$sql$
WITH folders AS (
SELECT path_tokens[$1] AS folder
FROM storage.objects
WHERE objects.name ILIKE $2 || '%%'
AND bucket_id = $3
AND array_length(objects.path_tokens, 1) <> $1
GROUP BY folder
ORDER BY folder %s
)
(SELECT folder AS "name",
NULL::uuid AS id,
NULL::timestamptz AS updated_at,
NULL::timestamptz AS created_at,
NULL::timestamptz AS last_accessed_at,
NULL::jsonb AS metadata FROM folders)
UNION ALL
(SELECT path_tokens[$1] AS "name",
id, updated_at, created_at, last_accessed_at, metadata
FROM storage.objects
WHERE objects.name ILIKE $2 || '%%'
AND bucket_id = $3
AND array_length(objects.path_tokens, 1) = $1
ORDER BY %I %s)
LIMIT $4 OFFSET $5
$sql$, v_sort_order, v_order_by, v_sort_order
) USING levels, v_prefix, bucketname, v_limit, offsets;
RETURN;
END IF;
-- ========================================================================
-- NAME SORTING: Hybrid skip-scan with batch optimization
-- ========================================================================
-- Calculate upper bound for prefix filtering
IF v_prefix_lower = '' THEN
v_upper_bound := NULL;
ELSIF right(v_prefix_lower, 1) = v_delimiter THEN
v_upper_bound := left(v_prefix_lower, -1) || chr(ascii(v_delimiter) + 1);
ELSE
v_upper_bound := left(v_prefix_lower, -1) || chr(ascii(right(v_prefix_lower, 1)) + 1);
END IF;
-- Build batch query (dynamic SQL - called infrequently, amortized over many rows)
IF v_is_asc THEN
IF v_upper_bound IS NOT NULL THEN
v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" >= $2 ' ||
'AND lower(o.name) COLLATE "C" < $3 ORDER BY lower(o.name) COLLATE "C" ASC LIMIT $4';
ELSE
v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" >= $2 ' ||
'ORDER BY lower(o.name) COLLATE "C" ASC LIMIT $4';
END IF;
ELSE
IF v_upper_bound IS NOT NULL THEN
v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" < $2 ' ||
'AND lower(o.name) COLLATE "C" >= $3 ORDER BY lower(o.name) COLLATE "C" DESC LIMIT $4';
ELSE
v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" < $2 ' ||
'ORDER BY lower(o.name) COLLATE "C" DESC LIMIT $4';
END IF;
END IF;
-- Initialize seek position
IF v_is_asc THEN
v_next_seek := v_prefix_lower;
ELSE
-- DESC: find the last item in range first (static SQL)
IF v_upper_bound IS NOT NULL THEN
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_prefix_lower AND lower(o.name) COLLATE "C" < v_upper_bound
ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
ELSIF v_prefix_lower <> '' THEN
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_prefix_lower
ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
ELSE
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = bucketname
ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
END IF;
IF v_peek_name IS NOT NULL THEN
v_next_seek := lower(v_peek_name) || v_delimiter;
ELSE
RETURN;
END IF;
END IF;
-- ========================================================================
-- MAIN LOOP: Hybrid peek-then-batch algorithm
-- Uses STATIC SQL for peek (hot path) and DYNAMIC SQL for batch
-- ========================================================================
LOOP
EXIT WHEN v_count >= v_limit;
-- STEP 1: PEEK using STATIC SQL (plan cached, very fast)
IF v_is_asc THEN
IF v_upper_bound IS NOT NULL THEN
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_next_seek AND lower(o.name) COLLATE "C" < v_upper_bound
ORDER BY lower(o.name) COLLATE "C" ASC LIMIT 1;
ELSE
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_next_seek
ORDER BY lower(o.name) COLLATE "C" ASC LIMIT 1;
END IF;
ELSE
IF v_upper_bound IS NOT NULL THEN
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek AND lower(o.name) COLLATE "C" >= v_prefix_lower
ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
ELSIF v_prefix_lower <> '' THEN
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek AND lower(o.name) COLLATE "C" >= v_prefix_lower
ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
ELSE
SELECT o.name INTO v_peek_name FROM storage.objects o
WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek
ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
END IF;
END IF;
EXIT WHEN v_peek_name IS NULL;
-- STEP 2: Check if this is a FOLDER or FILE
v_common_prefix := storage.get_common_prefix(lower(v_peek_name), v_prefix_lower, v_delimiter);
IF v_common_prefix IS NOT NULL THEN
-- FOLDER: Handle offset, emit if needed, skip to next folder
IF v_skipped < offsets THEN
v_skipped := v_skipped + 1;
ELSE
name := split_part(rtrim(v_common_prefix, v_delimiter), v_delimiter, levels);
id := NULL;
updated_at := NULL;
created_at := NULL;
last_accessed_at := NULL;
metadata := NULL;
RETURN NEXT;
v_count := v_count + 1;
END IF;
-- Advance seek past the folder range
IF v_is_asc THEN
v_next_seek := lower(left(v_common_prefix, -1)) || chr(ascii(v_delimiter) + 1);
ELSE
v_next_seek := lower(v_common_prefix);
END IF;
ELSE
-- FILE: Batch fetch using DYNAMIC SQL (overhead amortized over many rows)
-- For ASC: upper_bound is the exclusive upper limit (< condition)
-- For DESC: prefix_lower is the inclusive lower limit (>= condition)
FOR v_current IN EXECUTE v_batch_query
USING bucketname, v_next_seek,
CASE WHEN v_is_asc THEN COALESCE(v_upper_bound, v_prefix_lower) ELSE v_prefix_lower END, v_file_batch_size
LOOP
v_common_prefix := storage.get_common_prefix(lower(v_current.name), v_prefix_lower, v_delimiter);
IF v_common_prefix IS NOT NULL THEN
-- Hit a folder: exit batch, let peek handle it
v_next_seek := lower(v_current.name);
EXIT;
END IF;
-- Handle offset skipping
IF v_skipped < offsets THEN
v_skipped := v_skipped + 1;
ELSE
-- Emit file
name := split_part(v_current.name, v_delimiter, levels);
id := v_current.id;
updated_at := v_current.updated_at;
created_at := v_current.created_at;
last_accessed_at := v_current.last_accessed_at;
metadata := v_current.metadata;
RETURN NEXT;
v_count := v_count + 1;
END IF;
-- Advance seek past this file
IF v_is_asc THEN
v_next_seek := lower(v_current.name) || v_delimiter;
ELSE
v_next_seek := lower(v_current.name);
END IF;
EXIT WHEN v_count >= v_limit;
END LOOP;
END IF;
END LOOP;
END;
$_$;
ALTER FUNCTION storage.search(prefix text, bucketname text, limits integer, levels integer, offsets integer, search text, sortcolumn text, sortorder text) OWNER TO supabase_storage_admin;
--
-- Name: search_by_timestamp(text, text, integer, integer, text, text, text, text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.search_by_timestamp(p_prefix text, p_bucket_id text, p_limit integer, p_level integer, p_start_after text, p_sort_order text, p_sort_column text, p_sort_column_after text) RETURNS TABLE(key text, name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb)
LANGUAGE plpgsql STABLE
AS $_$
DECLARE
v_cursor_op text;
v_query text;
v_prefix text;
BEGIN
v_prefix := coalesce(p_prefix, '');
IF p_sort_order = 'asc' THEN
v_cursor_op := '>';
ELSE
v_cursor_op := '<';
END IF;
v_query := format($sql$
WITH raw_objects AS (
SELECT
o.name AS obj_name,
o.id AS obj_id,
o.updated_at AS obj_updated_at,
o.created_at AS obj_created_at,
o.last_accessed_at AS obj_last_accessed_at,
o.metadata AS obj_metadata,
storage.get_common_prefix(o.name, $1, '/') AS common_prefix
FROM storage.objects o
WHERE o.bucket_id = $2
AND o.name COLLATE "C" LIKE $1 || '%%'
),
-- Aggregate common prefixes (folders)
-- Both created_at and updated_at use MIN(obj_created_at) to match the old prefixes table behavior
aggregated_prefixes AS (
SELECT
rtrim(common_prefix, '/') AS name,
NULL::uuid AS id,
MIN(obj_created_at) AS updated_at,
MIN(obj_created_at) AS created_at,
NULL::timestamptz AS last_accessed_at,
NULL::jsonb AS metadata,
TRUE AS is_prefix
FROM raw_objects
WHERE common_prefix IS NOT NULL
GROUP BY common_prefix
),
leaf_objects AS (
SELECT
obj_name AS name,
obj_id AS id,
obj_updated_at AS updated_at,
obj_created_at AS created_at,
obj_last_accessed_at AS last_accessed_at,
obj_metadata AS metadata,
FALSE AS is_prefix
FROM raw_objects
WHERE common_prefix IS NULL
),
combined AS (
SELECT * FROM aggregated_prefixes
UNION ALL
SELECT * FROM leaf_objects
),
filtered AS (
SELECT *
FROM combined
WHERE (
$5 = ''
OR ROW(
date_trunc('milliseconds', %I),
name COLLATE "C"
) %s ROW(
COALESCE(NULLIF($6, '')::timestamptz, 'epoch'::timestamptz),
$5
)
)
)
SELECT
split_part(name, '/', $3) AS key,
name,
id,
updated_at,
created_at,
last_accessed_at,
metadata
FROM filtered
ORDER BY
COALESCE(date_trunc('milliseconds', %I), 'epoch'::timestamptz) %s,
name COLLATE "C" %s
LIMIT $4
$sql$,
p_sort_column,
v_cursor_op,
p_sort_column,
p_sort_order,
p_sort_order
);
RETURN QUERY EXECUTE v_query
USING v_prefix, p_bucket_id, p_level, p_limit, p_start_after, p_sort_column_after;
END;
$_$;
ALTER FUNCTION storage.search_by_timestamp(p_prefix text, p_bucket_id text, p_limit integer, p_level integer, p_start_after text, p_sort_order text, p_sort_column text, p_sort_column_after text) OWNER TO supabase_storage_admin;
--
-- Name: search_v2(text, text, integer, integer, text, text, text, text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.search_v2(prefix text, bucket_name text, limits integer DEFAULT 100, levels integer DEFAULT 1, start_after text DEFAULT ''::text, sort_order text DEFAULT 'asc'::text, sort_column text DEFAULT 'name'::text, sort_column_after text DEFAULT ''::text) RETURNS TABLE(key text, name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb)
LANGUAGE plpgsql STABLE
AS $$
DECLARE
v_sort_col text;
v_sort_ord text;
v_limit int;
BEGIN
-- Cap limit to maximum of 1500 records
v_limit := LEAST(coalesce(limits, 100), 1500);
-- Validate and normalize sort_order
v_sort_ord := lower(coalesce(sort_order, 'asc'));
IF v_sort_ord NOT IN ('asc', 'desc') THEN
v_sort_ord := 'asc';
END IF;
-- Validate and normalize sort_column
v_sort_col := lower(coalesce(sort_column, 'name'));
IF v_sort_col NOT IN ('name', 'updated_at', 'created_at') THEN
v_sort_col := 'name';
END IF;
-- Route to appropriate implementation
IF v_sort_col = 'name' THEN
-- Use list_objects_with_delimiter for name sorting (most efficient: O(k * log n))
RETURN QUERY
SELECT
split_part(l.name, '/', levels) AS key,
l.name AS name,
l.id,
l.updated_at,
l.created_at,
l.last_accessed_at,
l.metadata
FROM storage.list_objects_with_delimiter(
bucket_name,
coalesce(prefix, ''),
'/',
v_limit,
start_after,
'',
v_sort_ord
) l;
ELSE
-- Use aggregation approach for timestamp sorting
-- Not efficient for large datasets but supports correct pagination
RETURN QUERY SELECT * FROM storage.search_by_timestamp(
prefix, bucket_name, v_limit, levels, start_after,
v_sort_ord, v_sort_col, sort_column_after
);
END IF;
END;
$$;
ALTER FUNCTION storage.search_v2(prefix text, bucket_name text, limits integer, levels integer, start_after text, sort_order text, sort_column text, sort_column_after text) OWNER TO supabase_storage_admin;
--
-- Name: update_updated_at_column(); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin
--
CREATE FUNCTION storage.update_updated_at_column() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$;
ALTER FUNCTION storage.update_updated_at_column() OWNER TO supabase_storage_admin;
--
-- Name: http_request(); Type: FUNCTION; Schema: supabase_functions; Owner: supabase_functions_admin
--