Возможно, вы не знаете об этом, но вы можете создавать уникальные индексы над функцией. И даже сделать их частичными индексами.
Например:
create unique index some_name on users (lower(username));
сделает имя пользователя уникальным независимо от регистра.
Вы также можете продвинуться на 1 шаг дальше и (например, это может быть не очень хорошей идеей в вашей среде), чтобы уникальность применялась только для активных пользователей:
create unique index some_name on users (lower(username)) where is_active = true;
Также обратите внимание, что для поиска без учета регистра вы не должны использовать ILIKE. Проблема в том, что ILIKE не может (по некоторым причинам, которые я не совсем понимаю) использовать индексы.
Итак, пока можно использовать функциональный индекс для ускорения запросов:
select * from users where lower(username) = '...'
или
select * from users where lower(username) like '...'
(хотя бы для некоторых значений "...")
индекс не будет использоваться (насколько я знаю) в:
select * from users where username ilike '...'