diff --git a/.gitignore b/.gitignore index d8e19d02..47418755 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,8 @@ scratchpad Pipfile Pipfile.lock -# SQLite databases -*.db -*.sqlite +fixtures.db +*test.db # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/datasette/app.py b/datasette/app.py index f6ee38f1..707f3b52 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -344,6 +344,28 @@ class Datasette: except ValueError: # Plugin already registered pass + # Run the sanity checks + asyncio.get_event_loop().run_until_complete(self.run_sanity_checks()) + + async def run_sanity_checks(self): + # Only one check right now, for Spatialite + for database_name, database in self.databases.items(): + # Run pragma_info on every table + for table in await database.table_names(): + try: + await self.execute( + database_name, + "PRAGMA table_info({});".format(escape_sqlite(table)), + ) + except sqlite3.OperationalError as e: + if e.args[0] == "no such module: VirtualSpatialIndex": + raise click.UsageError( + "It looks like you're trying to load a SpatiaLite" + " database without first loading the SpatiaLite module." + "\n\nRead more: https://datasette.readthedocs.io/en/latest/spatialite.html" + ) + else: + raise def config(self, key): return self._config.get(key, None) @@ -530,29 +552,17 @@ class Datasette: name = path.stem if name in self._inspect: raise Exception("Multiple files with same stem %s" % name) - try: - with sqlite3.connect( - "file:{}?mode=ro".format(path), uri=True - ) as conn: - self.prepare_connection(conn) - self._inspect[name] = { - "hash": inspect_hash(path), - "file": str(path), - "size": path.stat().st_size, - "views": inspect_views(conn), - "tables": inspect_tables( - conn, (self.metadata("databases") or {}).get(name, {}) - ), - } - except sqlite3.OperationalError as e: - if e.args[0] == "no such module: VirtualSpatialIndex": - raise click.UsageError( - "It looks like you're trying to load a SpatiaLite" - " database without first loading the SpatiaLite module." - "\n\nRead more: https://datasette.readthedocs.io/en/latest/spatialite.html" - ) - else: - raise + with sqlite3.connect("file:{}?mode=ro".format(path), uri=True) as conn: + self.prepare_connection(conn) + self._inspect[name] = { + "hash": inspect_hash(path), + "file": str(path), + "size": path.stat().st_size, + "views": inspect_views(conn), + "tables": inspect_tables( + conn, (self.metadata("databases") or {}).get(name, {}) + ), + } return self._inspect def register_custom_units(self): diff --git a/tests/build_small_spatialite_db.py b/tests/build_small_spatialite_db.py new file mode 100644 index 00000000..c6b0593b --- /dev/null +++ b/tests/build_small_spatialite_db.py @@ -0,0 +1,23 @@ +import sqlite3 + +# This script generates the spatialite.db file in our tests directory. + + +def generate_it(filename): + conn = sqlite3.connect(filename) + # Lead the spatialite extension: + conn.enable_load_extension(True) + conn.load_extension("/usr/local/lib/mod_spatialite.dylib") + conn.execute("select InitSpatialMetadata(1)") + conn.executescript("create table museums (name text)") + conn.execute("SELECT AddGeometryColumn('museums', 'point_geom', 4326, 'POINT', 2);") + # At this point it is around 5MB - we can shrink it dramatically by doing thisO + conn.execute("delete from spatial_ref_sys") + conn.execute("delete from spatial_ref_sys_aux") + conn.commit() + conn.execute("vacuum") + conn.close() + + +if __name__ == "__main__": + generate_it("spatialite.db") diff --git a/tests/spatialite.db b/tests/spatialite.db new file mode 100644 index 00000000..c88d5b1f Binary files /dev/null and b/tests/spatialite.db differ diff --git a/tests/test_inspect.py b/tests/test_cli.py similarity index 75% rename from tests/test_inspect.py rename to tests/test_cli.py index d039c162..cd85c4d4 100644 --- a/tests/test_inspect.py +++ b/tests/test_cli.py @@ -1,6 +1,7 @@ from .fixtures import app_client from datasette.cli import cli from click.testing import CliRunner +import pathlib import json @@ -28,3 +29,12 @@ def test_inspect_cli_writes_to_file(app_client): assert 0 == result.exit_code, result.output data = json.load(open("foo.json")) assert ["fixtures"] == list(data.keys()) + + +def test_spatialite_error_if_attempt_to_open_spatialite(): + runner = CliRunner() + result = runner.invoke( + cli, ["serve", str(pathlib.Path(__file__).parent / "spatialite.db")] + ) + assert result.exit_code != 0 + assert "trying to load a SpatiaLite database" in result.output