diff --git a/pkg/bench/rttanalysis/orm_queries_bench_test.go b/pkg/bench/rttanalysis/orm_queries_bench_test.go index 181554e2cc03..66079ed2aa63 100644 --- a/pkg/bench/rttanalysis/orm_queries_bench_test.go +++ b/pkg/bench/rttanalysis/orm_queries_bench_test.go @@ -213,6 +213,31 @@ WHERE Stmt: `SELECT nspname, pg_is_other_temp_schema(oid) FROM (SELECT * FROM pg_namespace LIMIT 5) n`, }, + + { + Name: "information_schema._pg_index_position", + Setup: `CREATE TABLE indexed ( + a INT PRIMARY KEY, + b INT, + c INT, + d INT, + INDEX (b, d), + INDEX (c, a) +); +CREATE VIEW indexes AS + SELECT i.relname, indkey::INT2[], indexrelid + FROM pg_catalog.pg_index + JOIN pg_catalog.pg_class AS t ON indrelid = t.oid + JOIN pg_catalog.pg_class AS i ON indexrelid = i.oid + WHERE t.relname = 'indexed' +ORDER BY i.relname`, + Stmt: `SELECT relname, + indkey, + generate_series(1, 4) input, + information_schema._pg_index_position(indexrelid, generate_series(1, 4)) +FROM indexes +ORDER BY relname DESC, input`, + }, } RunRoundTripBenchmark(b, tests) diff --git a/pkg/sql/logictest/testdata/logic_test/pg_builtins b/pkg/sql/logictest/testdata/logic_test/pg_builtins index 627d78d74e87..e9b46d03698c 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_builtins +++ b/pkg/sql/logictest/testdata/logic_test/pg_builtins @@ -484,3 +484,56 @@ varchar 64 bit 1 varbit 16 numeric NULL + +# information_schema._pg_index_position + +statement ok +CREATE TABLE indexed ( + a INT PRIMARY KEY, + b INT, + c INT, + d INT, + INDEX (b, d), + INDEX (c, a) +); + +# NOTE, we cast indkey to an INT2[], because an INT2VECTOR's formatting appears +# to be dependent on whether the result set spilled to disk or not. It was being +# formatted differently with the "local" test config (and others) than with the +# "fakedist-disk" test config. +statement ok +CREATE TEMPORARY VIEW indexes AS + SELECT i.relname, indkey::INT2[], indexrelid + FROM pg_catalog.pg_index + JOIN pg_catalog.pg_class AS t ON indrelid = t.oid + JOIN pg_catalog.pg_class AS i ON indexrelid = i.oid + WHERE t.relname = 'indexed' +ORDER BY i.relname + +query TT +SELECT relname, indkey FROM indexes ORDER BY relname DESC +---- +primary {1} +indexed_c_a_idx {3,1} +indexed_b_d_idx {2,4} + +query TTII +SELECT relname, + indkey, + generate_series(1, 4) input, + information_schema._pg_index_position(indexrelid, generate_series(1, 4)) +FROM indexes +ORDER BY relname DESC, input +---- +primary {1} 1 1 +primary {1} 2 NULL +primary {1} 3 NULL +primary {1} 4 NULL +indexed_c_a_idx {3,1} 1 2 +indexed_c_a_idx {3,1} 2 NULL +indexed_c_a_idx {3,1} 3 1 +indexed_c_a_idx {3,1} 4 NULL +indexed_b_d_idx {2,4} 1 NULL +indexed_b_d_idx {2,4} 2 1 +indexed_b_d_idx {2,4} 3 NULL +indexed_b_d_idx {2,4} 4 2 diff --git a/pkg/sql/sem/builtins/pg_builtins.go b/pkg/sql/sem/builtins/pg_builtins.go index 8a60b08b4a95..abd989903922 100644 --- a/pkg/sql/sem/builtins/pg_builtins.go +++ b/pkg/sql/sem/builtins/pg_builtins.go @@ -2082,6 +2082,52 @@ SELECT description Volatility: tree.VolatilityImmutable, }, ), + + // Given an index's OID and an underlying-table column number, + // _pg_index_position return the column's position in the index + // (or NULL if not there). + // + // NOTE: this could be defined as a user-defined function, like + // it is in Postgres: + // https://github.com/postgres/postgres/blob/master/src/backend/catalog/information_schema.sql + // + // CREATE FUNCTION _pg_index_position(oid, smallint) RETURNS int + // LANGUAGE sql STRICT STABLE + // BEGIN ATOMIC + // SELECT (ss.a).n FROM + // (SELECT information_schema._pg_expandarray(indkey) AS a + // FROM pg_catalog.pg_index WHERE indexrelid = $1) ss + // WHERE (ss.a).x = $2; + // END; + // + "information_schema._pg_index_position": makeBuiltin(defProps(), + tree.Overload{ + Types: tree.ArgTypes{ + {"oid", types.Oid}, + {"col", types.Int2}, + }, + ReturnType: tree.FixedReturnType(types.Int), + Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) { + r, err := ctx.InternalExecutor.QueryRow( + ctx.Ctx(), "information_schema._pg_index_position", + ctx.Txn, + `SELECT (ss.a).n FROM + (SELECT information_schema._pg_expandarray(indkey) AS a + FROM pg_catalog.pg_index WHERE indexrelid = $1) ss + WHERE (ss.a).x = $2`, + args[0], args[1]) + if err != nil { + return nil, err + } + if len(r) == 0 { + return tree.DNull, nil + } + return r[0], nil + }, + Info: notUsableInfo, + Volatility: tree.VolatilityStable, + }, + ), } func getSessionVar(ctx *tree.EvalContext, settingName string, missingOk bool) (tree.Datum, error) {