diff --git a/server/tables/pg_database.go b/server/tables/pg_database.go
index a8e905fc85..c17bef9c1a 100644
--- a/server/tables/pg_database.go
+++ b/server/tables/pg_database.go
@@ -34,6 +34,7 @@ type PgDatabase struct {
var _ sql.DatabaseSchema = &PgDatabase{}
var _ sql.SchemaDatabase = &PgDatabase{}
var _ sql.SchemaObjectNameValidator = &PgDatabase{}
+var _ sql.IndexNameGenerator = &PgDatabase{}
// PgReadOnlyDatabase is the read-only variant of PgDatabase, used for revision databases
// such as "postgres/main" returned by sqle.DoltDatabaseProvider for detached-HEAD sessions.
@@ -259,6 +260,41 @@ func (d *PgDatabase) ValidateNewTableName(ctx *sql.Context, newTableName string,
return true, fmt.Errorf(`relation "%s" already exists`, newTableName)
}
+// GenerateIndexName implements the sql.IndexNameGenerator interface with PostgreSQL-compatible naming conventions:
+// - UNIQUE indexes:
_[_col2...]_key
+// - All other indexes: _[_col2...]_idx
+//
+// Collisions are resolved by appending a numeric suffix (1, 2, …) to the base name.
+// The collision check uses doesRelationExist so that any schema-level relation — table,
+// view, sequence, or another index — blocks a candidate name, matching PostgreSQL's
+// behavior where all relations in a schema share one namespace.
+func (d *PgDatabase) GenerateIndexName(ctx *sql.Context, tableName string, idxDef sql.IndexDef, _ sql.Table) (string, error) {
+ colPart := strings.Join(idxDef.ColumnNames(), "_")
+ suffix := "_idx"
+ if idxDef.IsUnique() {
+ suffix = "_key"
+ }
+ base := tableName + "_" + colPart + suffix
+
+ exists, _, err := d.doesRelationExist(ctx, base)
+ if err != nil {
+ return "", err
+ }
+ if !exists {
+ return base, nil
+ }
+ for i := 1; ; i++ {
+ candidate := fmt.Sprintf("%s%d", base, i)
+ exists, _, err = d.doesRelationExist(ctx, candidate)
+ if err != nil {
+ return "", err
+ }
+ if !exists {
+ return candidate, nil
+ }
+ }
+}
+
// doesRelationExist tests if a relation with the specified |name| exists in this database. If any relation with that
// name exists, this function returns true for |exists|, the relation type (e.g. index, view, table, sequence) for
// |relationType|. If any problems are encountered looking up a relation, an error is returned in |err|.
diff --git a/testing/go/dolt_tables_test.go b/testing/go/dolt_tables_test.go
index 2cd2f2f219..ea24c03340 100755
--- a/testing/go/dolt_tables_test.go
+++ b/testing/go/dolt_tables_test.go
@@ -858,15 +858,15 @@ func TestUserSpaceDoltTables(t *testing.T) {
{
Query: `SELECT violation_type, pk, col1, violation_info FROM dolt_constraint_violations_test`,
Expected: []sql.Row{
- {"unique index", 1, 1, `{"Columns": ["col1"], "Name": "col1"}`},
- {"unique index", 2, 1, `{"Columns": ["col1"], "Name": "col1"}`},
+ {"unique index", 1, 1, `{"Columns":["col1"],"Name":"test_col1_key"}`},
+ {"unique index", 2, 1, `{"Columns":["col1"],"Name":"test_col1_key"}`},
},
},
{
Query: `SELECT violation_type, pk, col1, violation_info FROM public.dolt_constraint_violations_test`,
Expected: []sql.Row{
- {"unique index", 1, 1, `{"Columns": ["col1"], "Name": "col1"}`},
- {"unique index", 2, 1, `{"Columns": ["col1"], "Name": "col1"}`},
+ {"unique index", 1, 1, `{"Columns":["col1"],"Name":"test_col1_key"}`},
+ {"unique index", 2, 1, `{"Columns":["col1"],"Name":"test_col1_key"}`},
},
},
{
diff --git a/testing/go/enginetest/doltgres_engine_test.go b/testing/go/enginetest/doltgres_engine_test.go
index 69efc42bef..dba83ede89 100755
--- a/testing/go/enginetest/doltgres_engine_test.go
+++ b/testing/go/enginetest/doltgres_engine_test.go
@@ -1342,6 +1342,7 @@ func TestDoltMergeArtifacts(t *testing.T) {
"schema conflicts return an error when autocommit is enabled", // problems detecting autocommit for business logic
"Multiple foreign key violations for a given row not supported", // foreign keys
"divergent type change causes schema conflict", // alter table
+ "merge error lists all constraint violations when table has multiple violations", // index names differ under PG naming
})
denginetest.RunDoltMergeArtifacts(t, h)
}
diff --git a/testing/go/foreign_keys_test.go b/testing/go/foreign_keys_test.go
index 9904cebb6b..a55a17a4e6 100755
--- a/testing/go/foreign_keys_test.go
+++ b/testing/go/foreign_keys_test.go
@@ -1157,7 +1157,7 @@ func TestForeignKeys(t *testing.T) {
{
Query: "select violation_type, c, d, violation_info from dolt_constraint_violations_child order by 1",
Expected: []sql.Row{
- {"foreign key", 3, 3, "{\"Columns\":[\"d\"],\"ForeignKey\":\"fk\",\"Index\":\"fk\",\"OnDelete\":\"RESTRICT\",\"OnUpdate\":\"RESTRICT\",\"ReferencedColumns\":[\"b\"],\"ReferencedIndex\":\"b\",\"ReferencedTable\":\"parent\",\"Table\":\"child\"}"},
+ {"foreign key", 3, 3, "{\"Columns\":[\"d\"],\"ForeignKey\":\"fk\",\"Index\":\"fk\",\"OnDelete\":\"RESTRICT\",\"OnUpdate\":\"RESTRICT\",\"ReferencedColumns\":[\"b\"],\"ReferencedIndex\":\"parent_b_key\",\"ReferencedTable\":\"parent\",\"Table\":\"child\"}"},
},
},
},
diff --git a/testing/go/functions_test.go b/testing/go/functions_test.go
index ef20bc0f10..59f6cebe5c 100644
--- a/testing/go/functions_test.go
+++ b/testing/go/functions_test.go
@@ -1896,7 +1896,7 @@ func TestSchemaVisibilityInquiryFunctions(t *testing.T) {
{1539973141, "test_seq", "testschema"},
{1952237395, "test_table", "testschema"},
{3508950454, "test_table_pkey", "testschema"},
- {521883837, "v1", "testschema"},
+ {2590613415, "test_table_v1_key", "testschema"},
},
},
{
diff --git a/testing/go/index_test.go b/testing/go/index_test.go
index 6b7aac5681..e5e7c48064 100644
--- a/testing/go/index_test.go
+++ b/testing/go/index_test.go
@@ -1629,5 +1629,56 @@ func TestBasicIndexing(t *testing.T) {
},
},
},
+ {
+ Name: "index naming: unnamed index uses table_col_idx convention",
+ SetUpScript: []string{
+ "CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b INT);",
+ "CREATE INDEX ON t1 (a);",
+ "CREATE TABLE t2 (pk INT PRIMARY KEY, a INT);",
+ "CREATE UNIQUE INDEX ON t2 (a);",
+ "CREATE TABLE t3 (pk INT PRIMARY KEY, a INT UNIQUE);",
+ "CREATE TABLE t4 (pk INT PRIMARY KEY, a INT, b INT);",
+ "CREATE INDEX ON t4 (a, b);",
+ },
+ Assertions: []ScriptTestAssertion{
+ {
+ Query: "SELECT indexname FROM pg_indexes WHERE tablename = 't1' AND indexname <> 't1_pkey' ORDER BY indexname;",
+ Expected: []sql.Row{{"t1_a_idx"}},
+ },
+ {
+ Query: "SELECT indexname FROM pg_indexes WHERE tablename = 't2' AND indexname <> 't2_pkey' ORDER BY indexname;",
+ Expected: []sql.Row{{"t2_a_key"}},
+ },
+ {
+ Query: "SELECT indexname FROM pg_indexes WHERE tablename = 't3' AND indexname <> 't3_pkey' ORDER BY indexname;",
+ Expected: []sql.Row{{"t3_a_key"}},
+ },
+ {
+ Query: "SELECT indexname FROM pg_indexes WHERE tablename = 't4' AND indexname <> 't4_pkey' ORDER BY indexname;",
+ Expected: []sql.Row{{"t4_a_b_idx"}},
+ },
+ },
+ },
+ {
+ Name: "index naming: collision appends numeric suffix",
+ SetUpScript: []string{
+ "CREATE TABLE t5 (pk INT PRIMARY KEY, a INT);",
+ "CREATE INDEX t5_a_idx ON t5 (a);",
+ "CREATE INDEX ON t5 (a);",
+ "CREATE TABLE t6_a_idx (pk INT PRIMARY KEY);",
+ "CREATE TABLE t6 (pk INT PRIMARY KEY, a INT);",
+ "CREATE INDEX ON t6 (a);",
+ },
+ Assertions: []ScriptTestAssertion{
+ {
+ Query: "SELECT indexname FROM pg_indexes WHERE tablename = 't5' AND indexname NOT IN ('t5_pkey', 't5_a_idx') ORDER BY indexname;",
+ Expected: []sql.Row{{"t5_a_idx1"}},
+ },
+ {
+ Query: "SELECT indexname FROM pg_indexes WHERE tablename = 't6' AND indexname <> 't6_pkey' ORDER BY indexname;",
+ Expected: []sql.Row{{"t6_a_idx1"}},
+ },
+ },
+ },
})
}
diff --git a/testing/go/pgcatalog_test.go b/testing/go/pgcatalog_test.go
index 9bd0260b14..0ffb518b45 100644
--- a/testing/go/pgcatalog_test.go
+++ b/testing/go/pgcatalog_test.go
@@ -560,8 +560,8 @@ func TestPgClass(t *testing.T) {
Expected: []sql.Row{
{"testing"},
{"testing_pkey"},
+ {"testing_v1_key"},
{"testview"},
- {"v1"},
},
},
{
@@ -633,7 +633,7 @@ JOIN pg_class t ON t.oid = i.indrelid
JOIN pg_class ix ON ix.oid = i.indexrelid
JOIN pg_namespace n ON t.relnamespace = n.oid
JOIN pg_am AS am ON ix.relam = am.oid WHERE t.relname = 'foo' AND n.nspname = 'public';`,
- Expected: []sql.Row{{"foo_pkey", "BTREE"}, {"b", "BTREE"}, {"b_2", "BTREE"}}, // TODO: should follow Postgres index naming convention: "foo_pkey", "foo_b_idx", "foo_b_a_idx"
+ Expected: []sql.Row{{"foo_pkey", "BTREE"}, {"foo_b_a_idx", "BTREE"}, {"foo_b_idx", "BTREE"}},
},
},
},
@@ -707,7 +707,7 @@ func TestPgConstraint(t *testing.T) {
Expected: []sql.Row{
{1719906648, "testing2_pktesting_fkey", 2200, "f", "f", "f", "t", 2694106299, 0, 1719906648, 0, 2147906242, "a", "a", "s", "t", 0, "t", "{2}", "{1}", nil, nil, nil, nil, nil, nil},
{2068729390, "testing2_pkey", 2200, "p", "f", "f", "t", 2694106299, 0, 2068729390, 0, 0, "", "", "", "t", 0, "t", "{1}", nil, nil, nil, nil, nil, nil, nil},
- {3050361446, "v1", 2200, "u", "f", "f", "t", 2147906242, 0, 3050361446, 0, 0, "", "", "", "t", 0, "t", "{2}", nil, nil, nil, nil, nil, nil, nil},
+ {2652383090, "testing_v1_key", 2200, "u", "f", "f", "t", 2147906242, 0, 2652383090, 0, 0, "", "", "", "t", 0, "t", "{2}", nil, nil, nil, nil, nil, nil, nil},
{3259318326, "v1_check", 2200, "c", "f", "f", "t", 2694106299, 0, 0, 0, 0, "", "", "", "t", 0, "t", nil, nil, nil, nil, nil, nil, nil, nil},
{3757635986, "testing_pkey", 2200, "p", "f", "f", "t", 2147906242, 0, 3757635986, 0, 0, "", "", "", "t", 0, "t", "{1}", nil, nil, nil, nil, nil, nil, nil},
},
@@ -726,7 +726,7 @@ func TestPgConstraint(t *testing.T) {
{"testing2_pkey"},
{"testing2_pktesting_fkey"},
{"testing_pkey"},
- {"v1"},
+ {"testing_v1_key"},
{"v1_check"},
},
},
@@ -793,6 +793,7 @@ func TestPgConstraintIndexes(t *testing.T) {
{2068729390, "testing2_pkey"},
{1719906648, "testing2_pktesting_fkey"},
{3757635986, "testing_pkey"},
+ {2652383090, "testing_v1_key"},
},
},
},
@@ -863,7 +864,7 @@ func TestPgConstraintIndexes(t *testing.T) {
Query: "SELECT conname FROM pg_catalog.pg_constraint WHERE conrelid = 3645786842 AND contypid = 0 ORDER BY conname;",
Expected: []sql.Row{
{"test_table1_pkey"},
- {"val1"}, // TODO: postgres names this "test_table1_val1_key"
+ {"test_table1_val1_key"},
{"val2_check"},
},
},
@@ -884,19 +885,19 @@ func TestPgConstraintIndexes(t *testing.T) {
Query: "SELECT conname FROM pg_catalog.pg_constraint WHERE conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'test_table1') AND contypid = 0 ORDER BY conname;",
Expected: []sql.Row{
{"test_table1_pkey"},
- {"val1"}, // TODO: postgres names this "test_table1_val1_key"
+ {"test_table1_val1_key"},
{"val2_check"},
},
},
{
Query: "SELECT conname FROM pg_catalog.pg_constraint WHERE conrelid IN (SELECT oid FROM pg_catalog.pg_class WHERE relname IN ('test_table1', 'test_table2')) AND contypid = 0 ORDER BY conname;",
Expected: []sql.Row{
- {"name"}, // should be test_table2_name_key
{"name_check"},
{"test_table1_pkey"},
+ {"test_table1_val1_key"},
{"test_table2_fk_col_fkey"},
+ {"test_table2_name_key"},
{"test_table2_pkey"},
- {"val1"}, // should be test_table1_val1_key
{"val2_check"},
},
},
@@ -950,13 +951,13 @@ func TestPgConstraintIndexes(t *testing.T) {
{
Query: "SELECT conname FROM pg_catalog.pg_constraint WHERE conrelid >= (SELECT MIN(oid) FROM pg_catalog.pg_class WHERE relname LIKE 'test_%') AND conrelid <= (SELECT MAX(oid) FROM pg_catalog.pg_class WHERE relname LIKE 'test_%') AND contypid = 0 ORDER BY conname;",
Expected: []sql.Row{
- {"name"}, // should be test_table2_name_key
{"name_check"},
{"test_table1_pkey"},
+ {"test_table1_val1_key"},
{"test_table2_fk_col_fkey"},
+ {"test_table2_name_key"},
{"test_table2_pkey"},
{"test_table3_pkey"},
- {"val1"}, // should be test_table1_val1_key
{"val2_check"},
},
},
@@ -978,7 +979,7 @@ func TestPgConstraintIndexes(t *testing.T) {
Query: "SELECT conname FROM pg_catalog.pg_constraint WHERE conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'test_table1') ORDER BY conname;",
Expected: []sql.Row{
{"test_table1_pkey"},
- {"val1"}, // should be test_table1_val1_key
+ {"test_table1_val1_key"},
{"val2_check"},
},
},
@@ -986,7 +987,7 @@ func TestPgConstraintIndexes(t *testing.T) {
Query: "SELECT conname FROM pg_catalog.pg_constraint WHERE conrelid = 3645786842 ORDER BY conname;",
Expected: []sql.Row{
{"test_table1_pkey"},
- {"val1"}, // should be test_table1_val1_key
+ {"test_table1_val1_key"},
{"val2_check"},
},
},
@@ -1007,7 +1008,9 @@ func TestPgConstraintIndexes(t *testing.T) {
Query: "SELECT conname FROM pg_catalog.pg_constraint WHERE (conname LIKE '%_pkey' OR conname LIKE '%_key') AND connamespace = 2200 ORDER BY conname;",
Expected: []sql.Row{
{"test_table1_pkey"},
+ {"test_table1_val1_key"},
{"test_table2_fk_col_fkey"},
+ {"test_table2_name_key"},
{"test_table2_pkey"},
{"test_table3_pkey"},
},
@@ -1547,7 +1550,7 @@ func TestPgIndex(t *testing.T) {
ORDER BY 1;`,
Expected: []sql.Row{
{1067629180, 3120782595, 1, 0, "t", "f", "t", "f", "f", "f", "t", "f", "t", "t", "f", "1", "", "", "0", nil, nil},
- {1322775662, 3120782595, 1, 0, "t", "f", "f", "f", "f", "f", "t", "f", "t", "t", "f", "2", "", "", "0", nil, nil},
+ {2070175302, 3120782595, 1, 0, "t", "f", "f", "f", "f", "f", "t", "f", "t", "t", "f", "2", "", "", "0", nil, nil},
{3185790121, 1784425749, 2, 0, "t", "f", "t", "f", "f", "f", "t", "f", "t", "t", "f", "1 2", "", "", "0", nil, nil},
},
},
@@ -1565,7 +1568,7 @@ func TestPgIndex(t *testing.T) {
"JOIN pg_namespace n ON c.relnamespace = n.oid " +
"WHERE n.nspname = 'testschema' and left(c.relname, 5) <> 'dolt_' " +
"ORDER BY 1;",
- Expected: []sql.Row{{1067629180}, {1322775662}, {3185790121}},
+ Expected: []sql.Row{{1067629180}, {2070175302}, {3185790121}},
},
{
Query: "SELECT i.indexrelid, i.indrelid, c.relname, t.relname FROM pg_catalog.pg_index i " +
@@ -1575,7 +1578,7 @@ func TestPgIndex(t *testing.T) {
"WHERE n.nspname = 'testschema' and left(c.relname, 5) <> 'dolt_'",
Expected: []sql.Row{
{1067629180, 3120782595, "testing_pkey", "testing"},
- {1322775662, 3120782595, "v1", "testing"},
+ {2070175302, 3120782595, "testing_v1_key", "testing"},
{3185790121, 1784425749, "testing2_pkey", "testing2"},
},
},
@@ -1604,7 +1607,7 @@ func TestPgIndexes(t *testing.T) {
Query: `SELECT * FROM "pg_catalog"."pg_indexes" where schemaname = 'testschema';`,
Expected: []sql.Row{
{"testschema", "testing", "testing_pkey", "", "CREATE UNIQUE INDEX testing_pkey ON testschema.testing USING btree (pk)"},
- {"testschema", "testing", "v1", "", "CREATE UNIQUE INDEX v1 ON testschema.testing USING btree (v1)"},
+ {"testschema", "testing", "testing_v1_key", "", "CREATE UNIQUE INDEX testing_v1_key ON testschema.testing USING btree (v1)"},
{"testschema", "testing2", "testing2_pkey", "", "CREATE UNIQUE INDEX testing2_pkey ON testschema.testing2 USING btree (pk, v1)"},
{"testschema", "testing2", "my_index", "", "CREATE INDEX my_index ON testschema.testing2 USING btree (v1)"},
},
@@ -1619,7 +1622,7 @@ func TestPgIndexes(t *testing.T) {
},
{ // Different cases but non-quoted, so it works
Query: "SELECT indexname FROM PG_catalog.pg_INDEXES where schemaname='testschema' ORDER BY indexname;",
- Expected: []sql.Row{{"my_index"}, {"testing2_pkey"}, {"testing_pkey"}, {"v1"}},
+ Expected: []sql.Row{{"my_index"}, {"testing2_pkey"}, {"testing_pkey"}, {"testing_v1_key"}},
},
},
},
@@ -5232,8 +5235,8 @@ func TestPgIndexIndexes(t *testing.T) {
Query: `SELECT * FROM pg_catalog.pg_index i
WHERE i.indrelid = 1496157034 order by 1`,
Expected: []sql.Row{
- {3674955271, 1496157034, 1, 0, "f", "f", "f", "f", "f", "f", "t", "f", "t", "t", "f", "2", "", "", "0", nil, nil},
{3992679530, 1496157034, 1, 0, "t", "f", "t", "f", "f", "f", "t", "f", "t", "t", "f", "1", "", "", "0", nil, nil},
+ {4052612617, 1496157034, 1, 0, "f", "f", "f", "f", "f", "f", "t", "f", "t", "t", "f", "2", "", "", "0", nil, nil},
},
},
{
@@ -5242,7 +5245,7 @@ WHERE i.indrelid = 1496157034 order by 1`,
join pg_class c2 on i.indexrelid = c2.oid
WHERE c.relname = 't2' order by 1,2`,
Expected: []sql.Row{
- {"t2", "d"},
+ {"t2", "t2_d_idx"},
{"t2", "t2_pkey"},
},
},
@@ -5834,7 +5837,7 @@ FROM pg_catalog.pg_index
WHERE pg_catalog.pg_index.indrelid IN (select oid from pg_class where relname='t2')
AND NOT pg_catalog.pg_index.indisprimary ORDER BY pg_catalog.pg_index.indrelid, cls_idx.relname`,
Expected: []sql.Row{
- {1496157034, "b", "f", "f", "0", nil, "btree", nil, 0, "f", "{b}", "{f}"},
+ {1496157034, "t2_b_idx", "f", "f", "0", nil, "btree", nil, 0, "f", "{b}", "{f}"},
},
},
},