diff --git a/docs/cli.rst b/docs/cli.rst index b314b2d7d..e5ffcfcdc 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -1033,6 +1033,14 @@ Use the ``--unique`` option to create a unique index. Use ``--if-not-exists`` to avoid attempting to create the index if one with that name already exists. +To add an index on a column in descending order, prefix the column with a hyphen. Since this can be confused for a command-line option you need to construct that like this:: + + $ sqlite-utils create-index mydb.db mytable -- col1 -col2 col3 + +This will create an index on that table on ``(col1, col2 desc, col3)``. + +If your column names are already prefixed with a hyphen you'll need to manually execute a ``CREATE INDEX`` SQL statement to add indexes to them rather than using this tool. + .. _cli_fts: Configuring full-text search diff --git a/docs/python-api.rst b/docs/python-api.rst index ad7b394d1..418c58d5f 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -1866,6 +1866,17 @@ By default the index will be named ``idx_{table-name}_{columns}`` - if you want index_name="good_dogs_by_age" ) +To create an index in descending order for a column, wrap the column name in ``db.DescIndex()`` like this: + +.. code-block:: python + + from sqlite_utils.db import DescIndex + + db["dogs"].create_index( + ["is_good_dog", DescIndex("age")], + index_name="good_dogs_by_age" + ) + You can create a unique index by passing ``unique=True``: .. code-block:: python diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 960d34c44..52de64a0e 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -5,7 +5,7 @@ import hashlib import pathlib import sqlite_utils -from sqlite_utils.db import AlterError +from sqlite_utils.db import AlterError, DescIndex import textwrap import io import itertools @@ -450,11 +450,21 @@ def index_foreign_keys(path, load_extension): ) @load_extension_option def create_index(path, table, column, name, unique, if_not_exists, load_extension): - "Add an index to the specified table covering the specified columns" + """ + Add an index to the specified table covering the specified columns. + Use "sqlite-utils create-index mydb -- -column" to specify descending + order for a column. + """ db = sqlite_utils.Database(path) _load_extensions(db, load_extension) + # Treat -prefix as descending for columns + columns = [] + for col in column: + if col.startswith("-"): + col = DescIndex(col[1:]) + columns.append(col) db[table].create_index( - column, index_name=name, unique=unique, if_not_exists=if_not_exists + columns, index_name=name, unique=unique, if_not_exists=if_not_exists ) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 0e37b1203..fdfe360aa 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -144,6 +144,10 @@ class InvalidColumns(Exception): pass +class DescIndex(str): + pass + + _COUNTS_TABLE_CREATE_SQL = """ CREATE TABLE IF NOT EXISTS [{}]( [table] TEXT PRIMARY KEY, @@ -1156,6 +1160,13 @@ def create_index(self, columns, index_name=None, unique=False, if_not_exists=Fal index_name = "idx_{}_{}".format( self.name.replace(" ", "_"), "_".join(columns) ) + columns_sql = [] + for column in columns: + if isinstance(column, DescIndex): + fmt = "[{}] desc" + else: + fmt = "[{}]" + columns_sql.append(fmt.format(column)) sql = ( textwrap.dedent( """ @@ -1167,7 +1178,7 @@ def create_index(self, columns, index_name=None, unique=False, if_not_exists=Fal .format( index_name=index_name, table_name=self.name, - columns=", ".join("[{}]".format(c) for c in columns), + columns=", ".join(columns_sql), unique="UNIQUE " if unique else "", if_not_exists="IF NOT EXISTS " if if_not_exists else "", ) diff --git a/tests/test_cli.py b/tests/test_cli.py index 2e982dd25..17ce27d8f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -208,6 +208,17 @@ def test_create_index(db_path): ) +def test_create_index_desc(db_path): + db = Database(db_path) + assert [] == db["Gosh"].indexes + result = CliRunner().invoke(cli.cli, ["create-index", db_path, "Gosh", "--", "-c1"]) + assert result.exit_code == 0 + assert ( + db.execute("select sql from sqlite_master where type='index'").fetchone()[0] + == "CREATE INDEX [idx_Gosh_c1]\n ON [Gosh] ([c1] desc)" + ) + + @pytest.mark.parametrize( "col_name,col_type,expected_schema", ( diff --git a/tests/test_create.py b/tests/test_create.py index e08eb2514..6e925dcf9 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -1,6 +1,7 @@ from sqlite_utils.db import ( Index, Database, + DescIndex, ForeignKey, AlterError, NoObviousTable, @@ -739,6 +740,19 @@ def test_create_index_if_not_exists(fresh_db): dogs.create_index(["name"], if_not_exists=True) +def test_create_index_desc(fresh_db): + dogs = fresh_db["dogs"] + dogs.insert({"name": "Cleo", "twitter": "cleopaws", "age": 3, "is good dog": True}) + assert [] == dogs.indexes + dogs.create_index([DescIndex("age"), "name"]) + sql = fresh_db.execute( + "select sql from sqlite_master where name='idx_dogs_age_name'" + ).fetchone()[0] + assert sql == ( + "CREATE INDEX [idx_dogs_age_name]\n" " ON [dogs] ([age] desc, [name])" + ) + + @pytest.mark.parametrize( "data_structure", (