Skip to content

Commit e8d9581

Browse files
committed
create_index(..., find_unique_name=True) option, refs #335
1 parent 92aa5c9 commit e8d9581

File tree

3 files changed

+62
-18
lines changed

3 files changed

+62
-18
lines changed

docs/python-api.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2141,7 +2141,9 @@ You can create an index on a table using the ``.create_index(columns)`` method.
21412141
21422142
db["dogs"].create_index(["is_good_dog"])
21432143
2144-
By default the index will be named ``idx_{table-name}_{columns}`` - if you want to customize the name of the created index you can pass the ``index_name`` parameter:
2144+
By default the index will be named ``idx_{table-name}_{columns}``. If you pass ``find_unique_name=True`` and the automatically derived name already exists, an available name will be found by incrementing a suffix number, for example ``idx_items_title_2``.
2145+
2146+
You can customize the name of the created index by passing the ``index_name`` parameter:
21452147
21462148
.. code-block:: python
21472149

sqlite_utils/db.py

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ def index_foreign_keys(self):
899899
}
900900
for fk in table.foreign_keys:
901901
if fk.column not in existing_indexes:
902-
table.create_index([fk.column])
902+
table.create_index([fk.column], find_unique_name=True)
903903

904904
def vacuum(self):
905905
"Run a SQLite ``VACUUM`` against the database."
@@ -1521,6 +1521,7 @@ def create_index(
15211521
index_name: Optional[str] = None,
15221522
unique: bool = False,
15231523
if_not_exists: bool = False,
1524+
find_unique_name: bool = False,
15241525
):
15251526
"""
15261527
Create an index on this table.
@@ -1530,6 +1531,8 @@ def create_index(
15301531
- ``index_name`` - the name to use for the new index. Defaults to the column names joined on ``_``.
15311532
- ``unique`` - should the index be marked as unique, forcing unique values?
15321533
- ``if_not_exists`` - only create the index if one with that name does not already exist.
1534+
- ``find_unique_name`` - if ``index_name`` is not provided and the automatically derived name
1535+
already exists, keep incrementing a suffix number to find an available name.
15331536
15341537
See :ref:`python_api_create_index`.
15351538
"""
@@ -1544,23 +1547,45 @@ def create_index(
15441547
else:
15451548
fmt = "[{}]"
15461549
columns_sql.append(fmt.format(column))
1547-
sql = (
1548-
textwrap.dedent(
1549-
"""
1550-
CREATE {unique}INDEX {if_not_exists}[{index_name}]
1551-
ON [{table_name}] ({columns});
1552-
"""
1553-
)
1554-
.strip()
1555-
.format(
1556-
index_name=index_name,
1557-
table_name=self.name,
1558-
columns=", ".join(columns_sql),
1559-
unique="UNIQUE " if unique else "",
1560-
if_not_exists="IF NOT EXISTS " if if_not_exists else "",
1550+
1551+
suffix = None
1552+
while True:
1553+
sql = (
1554+
textwrap.dedent(
1555+
"""
1556+
CREATE {unique}INDEX {if_not_exists}[{index_name}]
1557+
ON [{table_name}] ({columns});
1558+
"""
1559+
)
1560+
.strip()
1561+
.format(
1562+
index_name="{}_{}".format(index_name, suffix)
1563+
if suffix
1564+
else index_name,
1565+
table_name=self.name,
1566+
columns=", ".join(columns_sql),
1567+
unique="UNIQUE " if unique else "",
1568+
if_not_exists="IF NOT EXISTS " if if_not_exists else "",
1569+
)
15611570
)
1562-
)
1563-
self.db.execute(sql)
1571+
try:
1572+
self.db.execute(sql)
1573+
break
1574+
except OperationalError as e:
1575+
# find_unique_name=True - try again if 'index ... already exists'
1576+
arg = e.args[0]
1577+
if (
1578+
find_unique_name
1579+
and arg.startswith("index ")
1580+
and arg.endswith(" already exists")
1581+
):
1582+
if suffix is None:
1583+
suffix = 2
1584+
else:
1585+
suffix += 1
1586+
continue
1587+
else:
1588+
raise e
15641589
return self
15651590

15661591
def add_column(

tests/test_create.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
DescIndex,
55
AlterError,
66
NoObviousTable,
7+
OperationalError,
78
ForeignKey,
89
Table,
910
View,
@@ -752,6 +753,22 @@ def test_create_index_desc(fresh_db):
752753
)
753754

754755

756+
def test_create_index_find_unique_name():
757+
db = Database(memory=True)
758+
table = db["t"]
759+
table.insert({"id": 1})
760+
table.create_index(["id"])
761+
# Without find_unique_name should error
762+
with pytest.raises(OperationalError, match="index idx_t_id already exists"):
763+
table.create_index(["id"])
764+
# With find_unique_name=True it should work
765+
table.create_index(["id"], find_unique_name=True)
766+
table.create_index(["id"], find_unique_name=True)
767+
# Should have three now
768+
index_names = {idx.name for idx in table.indexes}
769+
assert index_names == {"idx_t_id", "idx_t_id_2", "idx_t_id_3"}
770+
771+
755772
@pytest.mark.parametrize(
756773
"data_structure",
757774
(

0 commit comments

Comments
 (0)