Merge pull request 'migraciones para permitir full text search sobre asignaturas' (#84) from issue/83-nueva-asignatura-clonada-desde-el-sistema into main
Reviewed-on: AlexRG/genesis-2#84
This commit was merged in pull request #84.
This commit is contained in:
@@ -1 +1 @@
|
||||
v2.78.1
|
||||
v2.84.2
|
||||
@@ -1,4 +1,4 @@
|
||||
export type Json =
|
||||
export type Json =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
@@ -7,10 +7,30 @@ export type Json =
|
||||
| Json[]
|
||||
|
||||
export type Database = {
|
||||
// Allows to automatically instantiate createClient with right options
|
||||
// instead of createClient<Database, { PostgrestVersion: 'XX' }>(URL, KEY)
|
||||
__InternalSupabase: {
|
||||
PostgrestVersion: "14.1"
|
||||
graphql_public: {
|
||||
Tables: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Views: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Functions: {
|
||||
graphql: {
|
||||
Args: {
|
||||
extensions?: Json
|
||||
operationName?: string
|
||||
query?: string
|
||||
variables?: Json
|
||||
}
|
||||
Returns: Json
|
||||
}
|
||||
}
|
||||
Enums: {
|
||||
[_ in never]: never
|
||||
}
|
||||
CompositeTypes: {
|
||||
[_ in never]: never
|
||||
}
|
||||
}
|
||||
public: {
|
||||
Tables: {
|
||||
@@ -135,6 +155,7 @@ export type Database = {
|
||||
orden_celda: number | null
|
||||
plan_estudio_id: string
|
||||
prerrequisito_asignatura_id: string | null
|
||||
search_vector: unknown
|
||||
tipo: Database["public"]["Enums"]["tipo_asignatura"]
|
||||
tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null
|
||||
}
|
||||
@@ -161,6 +182,7 @@ export type Database = {
|
||||
orden_celda?: number | null
|
||||
plan_estudio_id: string
|
||||
prerrequisito_asignatura_id?: string | null
|
||||
search_vector?: unknown
|
||||
tipo?: Database["public"]["Enums"]["tipo_asignatura"]
|
||||
tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null
|
||||
}
|
||||
@@ -187,6 +209,7 @@ export type Database = {
|
||||
orden_celda?: number | null
|
||||
plan_estudio_id?: string
|
||||
prerrequisito_asignatura_id?: string | null
|
||||
search_vector?: unknown
|
||||
tipo?: Database["public"]["Enums"]["tipo_asignatura"]
|
||||
tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null
|
||||
}
|
||||
@@ -1373,6 +1396,35 @@ export type Database = {
|
||||
Args: { p_append: Json; p_id: string }
|
||||
Returns: undefined
|
||||
}
|
||||
build_asignaturas_prefix_tsquery: {
|
||||
Args: { p_search: string }
|
||||
Returns: unknown
|
||||
}
|
||||
recalcular_vectores_asignaturas: { Args: never; Returns: undefined }
|
||||
search_asignaturas: {
|
||||
Args: {
|
||||
p_carrera_id?: string
|
||||
p_facultad_id?: string
|
||||
p_limit?: number
|
||||
p_offset?: number
|
||||
p_plan_estudio_id?: string
|
||||
p_search?: string
|
||||
}
|
||||
Returns: {
|
||||
codigo: string
|
||||
contenido_tematico: Json
|
||||
creditos: number
|
||||
datos: Json
|
||||
estado: Database["public"]["Enums"]["estado_asignatura"]
|
||||
id: string
|
||||
nombre: string
|
||||
numero_ciclo: number
|
||||
plan_estudio_id: string
|
||||
rank: number
|
||||
tipo: Database["public"]["Enums"]["tipo_asignatura"]
|
||||
total_count: number
|
||||
}[]
|
||||
}
|
||||
suma_porcentajes: { Args: { "": Json }; Returns: number }
|
||||
unaccent: { Args: { "": string }; Returns: string }
|
||||
unaccent_immutable: { Args: { "": string }; Returns: string }
|
||||
@@ -1548,6 +1600,9 @@ export type CompositeTypes<
|
||||
: never
|
||||
|
||||
export const Constants = {
|
||||
graphql_public: {
|
||||
Enums: {},
|
||||
},
|
||||
public: {
|
||||
Enums: {
|
||||
estado_asignatura: ["borrador", "revisada", "aprobada", "generando"],
|
||||
@@ -1607,3 +1662,4 @@ export const Constants = {
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
-- Paso 1
|
||||
drop text search configuration if exists public.es_simple_unaccent cascade;
|
||||
|
||||
create text search configuration public.es_simple_unaccent (copy = pg_catalog.simple);
|
||||
|
||||
alter text search configuration public.es_simple_unaccent
|
||||
alter mapping for hword, hword_part, word
|
||||
with unaccent, simple;
|
||||
|
||||
-- Paso 2
|
||||
alter table public.asignaturas
|
||||
add column if not exists search_vector tsvector;
|
||||
|
||||
-- Paso 3
|
||||
create or replace function public.fn_asignaturas_update_search_vector()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
new.search_vector :=
|
||||
setweight(
|
||||
to_tsvector('public.es_simple_unaccent', coalesce(new.nombre, '')),
|
||||
'A'
|
||||
)
|
||||
||
|
||||
setweight(
|
||||
to_tsvector('public.es_simple_unaccent', coalesce(new.codigo, '')),
|
||||
'A'
|
||||
)
|
||||
||
|
||||
setweight(
|
||||
to_tsvector('public.es_simple_unaccent', coalesce(new.datos, '{}'::jsonb)::text),
|
||||
'B'
|
||||
)
|
||||
||
|
||||
setweight(
|
||||
to_tsvector('public.es_simple_unaccent', coalesce(new.contenido_tematico, '[]'::jsonb)::text),
|
||||
'B'
|
||||
);
|
||||
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Paso 4
|
||||
drop trigger if exists trg_asignaturas_search_vector on public.asignaturas;
|
||||
|
||||
create trigger trg_asignaturas_search_vector
|
||||
before insert or update of nombre, codigo, datos, contenido_tematico
|
||||
on public.asignaturas
|
||||
for each row
|
||||
execute function public.fn_asignaturas_update_search_vector();
|
||||
|
||||
-- Paso 5
|
||||
create or replace function public.recalcular_vectores_asignaturas()
|
||||
returns void
|
||||
language sql
|
||||
as $$
|
||||
UPDATE public.asignaturas
|
||||
SET search_vector =
|
||||
setweight(to_tsvector('public.es_simple_unaccent', coalesce(nombre, '')), 'A') ||
|
||||
setweight(to_tsvector('public.es_simple_unaccent', coalesce(codigo, '')), 'A') ||
|
||||
setweight(to_tsvector('public.es_simple_unaccent', coalesce(datos, '{}'::jsonb)::text), 'B') ||
|
||||
setweight(to_tsvector('public.es_simple_unaccent', coalesce(contenido_tematico, '[]'::jsonb)::text), 'B')
|
||||
WHERE id IS NOT NULL;
|
||||
$$;
|
||||
|
||||
-- Paso 6
|
||||
create index if not exists asignaturas_search_vector_gin_idx
|
||||
on public.asignaturas
|
||||
using gin (search_vector);
|
||||
|
||||
-- Paso 7
|
||||
create or replace function public.build_asignaturas_prefix_tsquery(p_search text)
|
||||
returns tsquery
|
||||
language plpgsql
|
||||
stable
|
||||
as $$
|
||||
declare
|
||||
cleaned text;
|
||||
tokens text[];
|
||||
query_text text;
|
||||
begin
|
||||
cleaned := trim(coalesce(p_search, ''));
|
||||
|
||||
if cleaned = '' then
|
||||
return null;
|
||||
end if;
|
||||
|
||||
cleaned := lower(public.unaccent(cleaned));
|
||||
cleaned := regexp_replace(cleaned, '[^[:alnum:]\s]+', ' ', 'g');
|
||||
cleaned := regexp_replace(cleaned, '\s+', ' ', 'g');
|
||||
|
||||
tokens := regexp_split_to_array(cleaned, '\s+');
|
||||
|
||||
select string_agg(token || ':*', ' & ')
|
||||
into query_text
|
||||
from unnest(tokens) as token
|
||||
where token <> '';
|
||||
|
||||
if query_text is null or query_text = '' then
|
||||
return null;
|
||||
end if;
|
||||
|
||||
return to_tsquery('public.es_simple_unaccent', query_text);
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Paso 8
|
||||
create or replace function public.search_asignaturas(
|
||||
p_search text default '',
|
||||
p_facultad_id uuid default null,
|
||||
p_carrera_id uuid default null,
|
||||
p_plan_estudio_id uuid default null,
|
||||
p_limit integer default 20,
|
||||
p_offset integer default 0
|
||||
)
|
||||
returns table (
|
||||
id uuid,
|
||||
plan_estudio_id uuid,
|
||||
codigo text,
|
||||
nombre text,
|
||||
tipo public.tipo_asignatura,
|
||||
creditos numeric,
|
||||
numero_ciclo integer,
|
||||
datos jsonb,
|
||||
contenido_tematico jsonb,
|
||||
estado public.estado_asignatura,
|
||||
rank real,
|
||||
total_count bigint -- 👈 Total para la paginación del frontend
|
||||
)
|
||||
language plpgsql
|
||||
stable
|
||||
as $$
|
||||
declare
|
||||
v_tsq tsquery;
|
||||
begin
|
||||
-- 1. Construimos el query solo si hay texto
|
||||
v_tsq := public.build_asignaturas_prefix_tsquery(p_search);
|
||||
|
||||
return query
|
||||
select
|
||||
a.id,
|
||||
a.plan_estudio_id,
|
||||
a.codigo,
|
||||
a.nombre,
|
||||
a.tipo,
|
||||
a.creditos,
|
||||
a.numero_ciclo,
|
||||
a.datos,
|
||||
a.contenido_tematico,
|
||||
a.estado,
|
||||
coalesce(ts_rank(a.search_vector, v_tsq), 0)::real as rank,
|
||||
count(*) OVER() as total_count -- 👈 Cuenta total ignorando el LIMIT
|
||||
from public.asignaturas a
|
||||
-- 2. JOINS para poder filtrar por la jerarquía superior
|
||||
left join public.planes_estudio p on a.plan_estudio_id = p.id
|
||||
left join public.carreras c on p.carrera_id = c.id
|
||||
where
|
||||
-- 3. Si no hay búsqueda, trae todo. Si hay búsqueda, usa el FTS
|
||||
(v_tsq is null or a.search_vector @@ v_tsq)
|
||||
-- 4. Filtros jerárquicos dinámicos
|
||||
and (p_plan_estudio_id is null or a.plan_estudio_id = p_plan_estudio_id)
|
||||
and (p_carrera_id is null or p.carrera_id = p_carrera_id)
|
||||
and (p_facultad_id is null or c.facultad_id = p_facultad_id)
|
||||
order by
|
||||
(case when v_tsq is not null then ts_rank(a.search_vector, v_tsq) else 0 end) desc,
|
||||
a.nombre asc
|
||||
limit p_limit
|
||||
offset p_offset;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Corrección de bug con triggers
|
||||
-- 1. Borramos la llave foránea estricta actual
|
||||
ALTER TABLE public.cambios_asignatura
|
||||
DROP CONSTRAINT cambios_asignatura_asignatura_id_fkey;
|
||||
|
||||
-- 2. La volvemos a crear, pero le decimos que espere al final de la transacción para validar
|
||||
ALTER TABLE public.cambios_asignatura
|
||||
ADD CONSTRAINT cambios_asignatura_asignatura_id_fkey
|
||||
FOREIGN KEY (asignatura_id) REFERENCES public.asignaturas(id)
|
||||
DEFERRABLE INITIALLY DEFERRED;
|
||||
Reference in New Issue
Block a user