Skip to content

Commit

Permalink
feat: check for duplicate indexes
Browse files Browse the repository at this point in the history
Add rule to check for duplicate indexes. Two indexes are duplicates if
they have different relation OID and are indexes for the same relation
and has *exactly* the same list of keys in the same order.

Note that we are not checking if an index is redundant (if one index
has a set of key columns that is a prefix of the other).

Resolves #42
  • Loading branch information
mkindahl committed Feb 5, 2024
1 parent 76e10ba commit b5aa149
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/doctor/rules/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,22 @@ class UnusedIndex(doctor.Rule):
detail: str = "Index {indexrelname} is not used and occupied {index_size}."
hint: str = ("Since the index '{indexrelname}' on table '{relation}' is not used,"
" you can remove it.")

DUPLICATE_INDEX_QUERY = """
SELECT indrelid::regclass as relation,
a.indexrelid::regclass as index1,
b.indexrelid::regclass as index2
FROM pg_index a JOIN pg_index b USING (indrelid, indkey)
WHERE a.indexrelid != b.indexrelid;
"""

@doctor.register
@dataclass
class DuplicateIndex(doctor.Rule):
"""Find duplicate indexes."""

query: str = DUPLICATE_INDEX_QUERY
message: str = "index '{index1}' and '{index2}' seems to be duplicates"
detail: str = ("Index '{index1}' and '{index2}' are on the same relation "
"'{relation}' and has the same keys.")
hint: str = "You might want to remove one of the indexes to save space."
49 changes: 49 additions & 0 deletions src/doctor/rules/index_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2023 Timescale, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Unit tests for index checking rules."""

from timescaledb import Table

from doctor.unittest import PostgreSQLTestCase
from doctor.rules.index import DuplicateIndex

class TestIndexRules(PostgreSQLTestCase):
"""Test index checking rules."""

def setUp(self):
"""Set up unit tests for index checking rules."""
table = Table("with_duplicate_index", {
"one": "int",
"two": "int",
})
table.create(self.connection)

with self.connection.cursor() as cursor:
cursor.execute("CREATE INDEX index_one ON with_duplicate_index(one,two)")
cursor.execute("CREATE INDEX index_two ON with_duplicate_index(one,two)")
self.connection.commit()

def tearDown(self):
"""Tear down unit tests for index checking rules."""
with self.connection.cursor() as cursor:
cursor.execute("DROP TABLE with_duplicate_index")
self.connection.commit()

def test_duplicate(self):
"""Test rule for detecting duplicate index."""
messages = []
messages.extend(self.run_rule(DuplicateIndex()))
self.assertIn(DuplicateIndex.message.format(index1="index_one", index2="index_two"),
messages)

0 comments on commit b5aa149

Please sign in to comment.