CREATE OR REPLACE FUNCTION public.search_addresses(search_term text, result_limit integer DEFAULT 8) RETURNS TABLE( identifiant_unique_adresse text, adresse_formatee text, numero_municipal text, numero_unite text, code_postal text, odonyme_recompose_normal text, nom_municipalite text, longitude double precision, latitude double precision, fiber_available boolean, zone_tarifaire integer, max_speed integer, similarity_score real ) LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE clean_term text; num_part text; street_part text; street_words text[]; BEGIN clean_term := trim(search_term); num_part := (regexp_match(clean_term, '^\s*(\d+)'))[1]; street_part := trim(regexp_replace(clean_term, '^\s*\d+\s*', '')); IF street_part <> '' THEN street_words := string_to_array(lower(street_part), ' '); ELSE street_words := ARRAY[]::text[]; END IF; -- Phase 1: We have a civic number IF num_part IS NOT NULL AND length(num_part) >= 1 THEN -- 1a: Exact numero_municipal match RETURN QUERY SELECT a.identifiant_unique_adresse, a.adresse_formatee, a.numero_municipal, a.numero_unite, a.code_postal, a.odonyme_recompose_normal, a.nom_municipalite, a.longitude, a.latitude, (f.id IS NOT NULL) AS fiber_available, f.zone_tarifaire, f.max_speed, CASE WHEN street_part = '' THEN CASE WHEN a.numero_municipal = num_part THEN 1.0 ELSE 0.5 END::real ELSE GREATEST( similarity(lower(COALESCE(a.odonyme_recompose_normal, '')), lower(street_part)), similarity(lower(COALESCE(a.odonyme_recompose_court, '')), lower(street_part)), similarity(lower(COALESCE(a.odonyme_recompose_long, '')), lower(street_part)), similarity(lower(COALESCE(a.nom_municipalite, '')), lower(street_part)) ) END AS similarity_score FROM public.addresses a LEFT JOIN public.fiber_availability f ON f.uuidadresse = a.identifiant_unique_adresse WHERE a.numero_municipal = num_part AND ( street_part = '' OR ( NOT EXISTS ( SELECT 1 FROM unnest(street_words) w WHERE NOT ( lower(COALESCE(a.odonyme_recompose_normal, '')) LIKE '%' || w || '%' OR lower(COALESCE(a.odonyme_recompose_court, '')) LIKE '%' || w || '%' OR lower(COALESCE(a.odonyme_recompose_long, '')) LIKE '%' || w || '%' OR lower(COALESCE(a.nom_municipalite, '')) LIKE '%' || w || '%' OR lower(COALESCE(a.code_postal, '')) LIKE '%' || w || '%' ) ) ) ) ORDER BY CASE WHEN f.id IS NOT NULL THEN 0 ELSE 1 END, similarity_score DESC LIMIT result_limit; IF FOUND THEN RETURN; END IF; -- 1b: Prefix match on numero_municipal RETURN QUERY SELECT a.identifiant_unique_adresse, a.adresse_formatee, a.numero_municipal, a.numero_unite, a.code_postal, a.odonyme_recompose_normal, a.nom_municipalite, a.longitude, a.latitude, (f.id IS NOT NULL) AS fiber_available, f.zone_tarifaire, f.max_speed, CASE WHEN street_part = '' THEN 0.5::real ELSE GREATEST( similarity(lower(COALESCE(a.odonyme_recompose_normal, '')), lower(street_part)), similarity(lower(COALESCE(a.odonyme_recompose_court, '')), lower(street_part)), similarity(lower(COALESCE(a.odonyme_recompose_long, '')), lower(street_part)), similarity(lower(COALESCE(a.nom_municipalite, '')), lower(street_part)) ) END AS similarity_score FROM public.addresses a LEFT JOIN public.fiber_availability f ON f.uuidadresse = a.identifiant_unique_adresse WHERE a.numero_municipal LIKE (num_part || '%') AND ( street_part = '' OR ( NOT EXISTS ( SELECT 1 FROM unnest(street_words) w WHERE NOT ( lower(COALESCE(a.odonyme_recompose_normal, '')) LIKE '%' || w || '%' OR lower(COALESCE(a.odonyme_recompose_court, '')) LIKE '%' || w || '%' OR lower(COALESCE(a.odonyme_recompose_long, '')) LIKE '%' || w || '%' OR lower(COALESCE(a.nom_municipalite, '')) LIKE '%' || w || '%' OR lower(COALESCE(a.code_postal, '')) LIKE '%' || w || '%' ) ) ) ) ORDER BY CASE WHEN f.id IS NOT NULL THEN 0 ELSE 1 END, similarity_score DESC, a.numero_municipal LIMIT result_limit; IF FOUND THEN RETURN; END IF; END IF; -- Phase 2: Full trigram search RETURN QUERY SELECT a.identifiant_unique_adresse, a.adresse_formatee, a.numero_municipal, a.numero_unite, a.code_postal, a.odonyme_recompose_normal, a.nom_municipalite, a.longitude, a.latitude, (f.id IS NOT NULL) AS fiber_available, f.zone_tarifaire, f.max_speed, GREATEST( similarity(COALESCE(a.odonyme_recompose_normal, ''), clean_term), similarity(COALESCE(a.adresse_formatee, ''), clean_term) ) AS similarity_score FROM public.addresses a LEFT JOIN public.fiber_availability f ON f.uuidadresse = a.identifiant_unique_adresse WHERE COALESCE(a.odonyme_recompose_normal, '') % clean_term OR a.adresse_formatee % clean_term ORDER BY CASE WHEN f.id IS NOT NULL THEN 0 ELSE 1 END, similarity_score DESC LIMIT result_limit; END; $$;