From 5c1df4e3063cf47229a305fbe75757a8d412a8af Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sat, 2 May 2020 09:02:04 -0700 Subject: [PATCH] replace=True and ignore=True parameters for create_view(), closes #106 --- docs/python-api.rst | 10 +++++++++ sqlite_utils/db.py | 25 +++++++++++++++-------- tests/test_create.py | 7 ------- tests/test_create_view.py | 43 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 tests/test_create_view.py diff --git a/docs/python-api.rst b/docs/python-api.rst index e3c40e6..90c5396 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -879,6 +879,16 @@ The ``.create_view()`` method on the database class can be used to create a view select * from dogs where is_good_dog = 1 """) +This will raise a ``sqlite_utils.utils.OperationalError`` if a view with that name already exists. + +You can pass ``ignore=True`` to silently ignore an existing view and do nothing, or ``replace=True`` to replace an existing view with a new definition if your select statement differs from the current view: + +.. code-block:: python + + db.create_view("good_dogs", """ + select * from dogs where is_good_dog = 1 + """, replace=True) + Storing JSON ============ diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index dd49d5c..cb249b3 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -322,14 +322,23 @@ class Database: hash_id=hash_id, ) - def create_view(self, name, sql): - self.conn.execute( - """ - CREATE VIEW {name} AS {sql} - """.format( - name=name, sql=sql - ) - ) + def create_view(self, name, sql, ignore=False, replace=False): + assert not ( + ignore and replace + ), "Use one or the other of ignore/replace, not both" + create_sql = "CREATE VIEW {name} AS {sql}".format(name=name, sql=sql) + if ignore or replace: + # Does view exist already? + if name in self.view_names(): + if ignore: + return self + elif replace: + # If SQL is the same, do nothing + if create_sql == self[name].schema: + return self + self[name].drop() + self.conn.execute(create_sql) + return self def m2m_table_candidates(self, table, other_table): "Returns potential m2m tables for arguments, based on FKs" diff --git a/tests/test_create.py b/tests/test_create.py index 5290cd8..79123b8 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -716,13 +716,6 @@ def test_insert_hash_id(fresh_db): assert 1 == dogs.count -def test_create_view(fresh_db): - fresh_db["data"].insert({"foo": "foo", "bar": "bar"}) - fresh_db.create_view("bar", "select bar from data") - rows = fresh_db.conn.execute("select * from bar").fetchall() - assert [("bar",)] == rows - - def test_vacuum(fresh_db): fresh_db["data"].insert({"foo": "foo", "bar": "bar"}) fresh_db.vacuum() diff --git a/tests/test_create_view.py b/tests/test_create_view.py new file mode 100644 index 0000000..26cf420 --- /dev/null +++ b/tests/test_create_view.py @@ -0,0 +1,43 @@ +import pytest +from sqlite_utils.utils import OperationalError + + +def test_create_view(fresh_db): + fresh_db.create_view("bar", "select 1 + 1") + rows = fresh_db.conn.execute("select * from bar").fetchall() + assert [(2,)] == rows + + +def test_create_view_error(fresh_db): + fresh_db.create_view("bar", "select 1 + 1") + with pytest.raises(OperationalError): + fresh_db.create_view("bar", "select 1 + 2") + + +def test_create_view_only_arrow_one_param(fresh_db): + with pytest.raises(AssertionError): + fresh_db.create_view("bar", "select 1 + 2", ignore=True, replace=True) + + +def test_create_view_ignore(fresh_db): + fresh_db.create_view("bar", "select 1 + 1").create_view( + "bar", "select 1 + 2", ignore=True + ) + rows = fresh_db.conn.execute("select * from bar").fetchall() + assert [(2,)] == rows + + +def test_create_view_replace(fresh_db): + fresh_db.create_view("bar", "select 1 + 1").create_view( + "bar", "select 1 + 2", replace=True + ) + rows = fresh_db.conn.execute("select * from bar").fetchall() + assert [(3,)] == rows + + +def test_create_view_replace_with_same_does_nothing(fresh_db): + fresh_db.create_view("bar", "select 1 + 1") + initial_version = fresh_db.conn.execute("PRAGMA schema_version").fetchone()[0] + fresh_db.create_view("bar", "select 1 + 1", replace=True) + after_version = fresh_db.conn.execute("PRAGMA schema_version").fetchone()[0] + assert after_version == initial_version