From 720310bbd7144e25493eb86f3ef1ca021924eef1 Mon Sep 17 00:00:00 2001 From: oniani1 Date: Tue, 18 Aug 2026 14:11:46 +0400 Subject: [PATCH] fix: correct index_attributes for indexes not on leading table columns --- src/lib/sql/indexes.sql.ts | 2 +- test/server/indexes.ts | 52 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/lib/sql/indexes.sql.ts b/src/lib/sql/indexes.sql.ts index f25edddc..ded23609 100644 --- a/src/lib/sql/indexes.sql.ts +++ b/src/lib/sql/indexes.sql.ts @@ -39,7 +39,7 @@ SELECT JOIN pg_class c ON c.oid = idx.indexrelid JOIN pg_namespace n ON c.relnamespace = n.oid JOIN pg_am am ON c.relam = am.oid - JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(idx.indkey) + JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum > 0 JOIN pg_indexes ix ON c.relname = ix.indexname WHERE ${props.schemaFilter ? `n.nspname ${props.schemaFilter}` : 'true'} diff --git a/test/server/indexes.ts b/test/server/indexes.ts index 1ad4d0a2..f2c2ee9b 100644 --- a/test/server/indexes.ts +++ b/test/server/indexes.ts @@ -56,6 +56,58 @@ test('list indexes', async () => { ) }) +test('index attributes for indexes not on the leading table columns', async () => { + let res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + drop table if exists public.index_attr_test cascade; + create table public.index_attr_test (id int, name text, email text); + create index idx_attr_single_email on public.index_attr_test (email); + create index idx_attr_composite on public.index_attr_test (email, name); + `, + }, + }) + if (res.json().error) { + throw new Error(res.payload) + } + + res = await app.inject({ method: 'GET', path: '/indexes' }) + const indexes = res.json() + + const single = indexes.find( + ({ index_definition }) => + index_definition === + 'CREATE INDEX idx_attr_single_email ON public.index_attr_test USING btree (email)' + )! + expect(single.index_attributes).toEqual([ + { attribute_name: 'email', attribute_number: 1, data_type: 'text' }, + ]) + + const composite = indexes.find( + ({ index_definition }) => + index_definition === + 'CREATE INDEX idx_attr_composite ON public.index_attr_test USING btree (email, name)' + )! + expect(composite.index_attributes).toEqual([ + { attribute_name: 'email', attribute_number: 1, data_type: 'text' }, + { attribute_name: 'name', attribute_number: 2, data_type: 'text' }, + ]) + + res = await app.inject({ method: 'GET', path: `/indexes/${single.id}` }) + expect(res.json().id).toBe(single.id) + + res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: `drop table public.index_attr_test;` }, + }) + if (res.json().error) { + throw new Error(res.payload) + } +}) + test('retrieve index', async () => { const res = await app.inject({ method: 'GET', path: '/indexes/16400' }) const index = res.json()