mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-09-28 21:04:13 +02:00
db.table() only returns tables, added db.view(), refs #657
This commit is contained in:
parent
d892d2ae49
commit
290ad27d40
5 changed files with 100 additions and 74 deletions
|
|
@ -251,21 +251,36 @@ In this example ``next()`` is used to retrieve the first result in the iterator
|
||||||
Accessing tables
|
Accessing tables
|
||||||
================
|
================
|
||||||
|
|
||||||
Tables are accessed using the indexing operator, like so:
|
Tables are accessed using the ``db.table()`` method, like so:
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
table = db["my_table"]
|
|
||||||
|
|
||||||
If the table does not yet exist, it will be created the first time you attempt to insert or upsert data into it.
|
|
||||||
|
|
||||||
You can also access tables using the ``.table()`` method like so:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
table = db.table("my_table")
|
table = db.table("my_table")
|
||||||
|
|
||||||
Using this factory function allows you to set :ref:`python_api_table_configuration`.
|
Using this factory function allows you to set :ref:`python_api_table_configuration`. Additional keyword arguments to ``db.table()`` will be used if a further method call causes the table to be created.
|
||||||
|
|
||||||
|
The ``db.table()`` method will always return a :ref:`reference_db_table` instance, or raise a ``sqlite_utils.db.NoTable`` exception if the table name is actually a SQL view.
|
||||||
|
|
||||||
|
You can also access tables or views using dictionary-style syntax, like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
table = db["my_table"]
|
||||||
|
|
||||||
|
If a table accessed using either of these methods does not yet exist, it will be created the first time you attempt to insert or upsert data into it.
|
||||||
|
|
||||||
|
.. _python_api_view:
|
||||||
|
|
||||||
|
Accessing views
|
||||||
|
===============
|
||||||
|
|
||||||
|
SQL views can be accessed using the ``db.view()`` method, like so:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
view = db.view("my_view")
|
||||||
|
|
||||||
|
This will return a :ref:`reference_db_view` instance, or raise a ``sqlite_utils.db.NoView`` exception if the view does not exist.
|
||||||
|
|
||||||
.. _python_api_tables:
|
.. _python_api_tables:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,8 @@ def tables(
|
||||||
if schema:
|
if schema:
|
||||||
headers.append("schema")
|
headers.append("schema")
|
||||||
|
|
||||||
|
method = db.view if views else db.table
|
||||||
|
|
||||||
def _iter():
|
def _iter():
|
||||||
if views:
|
if views:
|
||||||
items = db.view_names()
|
items = db.view_names()
|
||||||
|
|
@ -194,15 +196,15 @@ def tables(
|
||||||
for name in items:
|
for name in items:
|
||||||
row = [name]
|
row = [name]
|
||||||
if counts:
|
if counts:
|
||||||
row.append(db[name].count)
|
row.append(method(name).count)
|
||||||
if columns:
|
if columns:
|
||||||
cols = [c.name for c in db[name].columns]
|
cols = [c.name for c in method(name).columns]
|
||||||
if csv:
|
if csv:
|
||||||
row.append("\n".join(cols))
|
row.append("\n".join(cols))
|
||||||
else:
|
else:
|
||||||
row.append(cols)
|
row.append(cols)
|
||||||
if schema:
|
if schema:
|
||||||
row.append(db[name].schema)
|
row.append(method(name).schema)
|
||||||
yield row
|
yield row
|
||||||
|
|
||||||
if table or fmt:
|
if table or fmt:
|
||||||
|
|
@ -1693,7 +1695,7 @@ def create_view(path, view, select, ignore, replace, load_extension):
|
||||||
if ignore:
|
if ignore:
|
||||||
return
|
return
|
||||||
elif replace:
|
elif replace:
|
||||||
db[view].drop()
|
db.view(view).drop()
|
||||||
else:
|
else:
|
||||||
raise click.ClickException(
|
raise click.ClickException(
|
||||||
'View "{}" already exists. Use --replace to delete and replace it.'.format(
|
'View "{}" already exists. Use --replace to delete and replace it.'.format(
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,10 @@ class NoTable(Exception):
|
||||||
"Specified table does not exist"
|
"Specified table does not exist"
|
||||||
|
|
||||||
|
|
||||||
|
class NoView(Exception):
|
||||||
|
"Specified view does not exist"
|
||||||
|
|
||||||
|
|
||||||
class BadPrimaryKey(Exception):
|
class BadPrimaryKey(Exception):
|
||||||
"Table does not have a single obvious primary key"
|
"Table does not have a single obvious primary key"
|
||||||
|
|
||||||
|
|
@ -419,6 +423,8 @@ class Database:
|
||||||
|
|
||||||
:param table_name: The name of the table
|
:param table_name: The name of the table
|
||||||
"""
|
"""
|
||||||
|
if table_name in self.view_names():
|
||||||
|
return self.view(table_name)
|
||||||
return self.table(table_name)
|
return self.table(table_name)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|
@ -541,7 +547,7 @@ class Database:
|
||||||
self._tracer(sql, None)
|
self._tracer(sql, None)
|
||||||
return self.conn.executescript(sql)
|
return self.conn.executescript(sql)
|
||||||
|
|
||||||
def table(self, table_name: str, **kwargs) -> Union["Table", "View"]:
|
def table(self, table_name: str, **kwargs) -> "Table":
|
||||||
"""
|
"""
|
||||||
Return a table object, optionally configured with default options.
|
Return a table object, optionally configured with default options.
|
||||||
|
|
||||||
|
|
@ -550,10 +556,19 @@ class Database:
|
||||||
:param table_name: Name of the table
|
:param table_name: Name of the table
|
||||||
"""
|
"""
|
||||||
if table_name in self.view_names():
|
if table_name in self.view_names():
|
||||||
return View(self, table_name, **kwargs)
|
raise NoTable("Table {} is actually a view".format(table_name))
|
||||||
else:
|
kwargs.setdefault("strict", self.strict)
|
||||||
kwargs.setdefault("strict", self.strict)
|
return Table(self, table_name, **kwargs)
|
||||||
return Table(self, table_name, **kwargs)
|
|
||||||
|
def view(self, view_name: str) -> "View":
|
||||||
|
"""
|
||||||
|
Return a view object.
|
||||||
|
|
||||||
|
:param view_name: Name of the view
|
||||||
|
"""
|
||||||
|
if view_name not in self.view_names():
|
||||||
|
raise NoView("View {} does not exist".format(view_name))
|
||||||
|
return View(self, view_name)
|
||||||
|
|
||||||
def quote(self, value: str) -> str:
|
def quote(self, value: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
@ -637,12 +652,12 @@ class Database:
|
||||||
@property
|
@property
|
||||||
def tables(self) -> List["Table"]:
|
def tables(self) -> List["Table"]:
|
||||||
"List of Table objects in this database."
|
"List of Table objects in this database."
|
||||||
return cast(List["Table"], [self[name] for name in self.table_names()])
|
return [self.table(name) for name in self.table_names()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def views(self) -> List["View"]:
|
def views(self) -> List["View"]:
|
||||||
"List of View objects in this database."
|
"List of View objects in this database."
|
||||||
return cast(List["View"], [self[name] for name in self.view_names()])
|
return [self.view(name) for name in self.view_names()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def triggers(self) -> List[Trigger]:
|
def triggers(self) -> List[Trigger]:
|
||||||
|
|
@ -808,7 +823,7 @@ class Database:
|
||||||
or a tuple of (column, other_table, other_column), or a tuple of
|
or a tuple of (column, other_table, other_column), or a tuple of
|
||||||
(table, column, other_table, other_column)
|
(table, column, other_table, other_column)
|
||||||
"""
|
"""
|
||||||
table = cast(Table, self[name])
|
table = self.table(name)
|
||||||
if all(isinstance(fk, ForeignKey) for fk in foreign_keys):
|
if all(isinstance(fk, ForeignKey) for fk in foreign_keys):
|
||||||
return cast(List[ForeignKey], foreign_keys)
|
return cast(List[ForeignKey], foreign_keys)
|
||||||
if all(isinstance(fk, str) for fk in foreign_keys):
|
if all(isinstance(fk, str) for fk in foreign_keys):
|
||||||
|
|
@ -1039,11 +1054,11 @@ class Database:
|
||||||
# Transform table to match the new definition if table already exists:
|
# Transform table to match the new definition if table already exists:
|
||||||
if self[name].exists():
|
if self[name].exists():
|
||||||
if ignore:
|
if ignore:
|
||||||
return cast(Table, self[name])
|
return self.table(name)
|
||||||
elif replace:
|
elif replace:
|
||||||
self[name].drop()
|
self[name].drop()
|
||||||
if transform and self[name].exists():
|
if transform and self[name].exists():
|
||||||
table = cast(Table, self[name])
|
table = self.table(name)
|
||||||
should_transform = False
|
should_transform = False
|
||||||
# First add missing columns and figure out columns to drop
|
# First add missing columns and figure out columns to drop
|
||||||
existing_columns = table.columns_dict
|
existing_columns = table.columns_dict
|
||||||
|
|
@ -1109,7 +1124,7 @@ class Database:
|
||||||
strict=strict,
|
strict=strict,
|
||||||
)
|
)
|
||||||
self.execute(sql)
|
self.execute(sql)
|
||||||
created_table = self.table(
|
return self.table(
|
||||||
name,
|
name,
|
||||||
pk=pk,
|
pk=pk,
|
||||||
foreign_keys=foreign_keys,
|
foreign_keys=foreign_keys,
|
||||||
|
|
@ -1119,7 +1134,6 @@ class Database:
|
||||||
hash_id=hash_id,
|
hash_id=hash_id,
|
||||||
hash_id_columns=hash_id_columns,
|
hash_id_columns=hash_id_columns,
|
||||||
)
|
)
|
||||||
return cast(Table, created_table)
|
|
||||||
|
|
||||||
def rename_table(self, name: str, new_name: str):
|
def rename_table(self, name: str, new_name: str):
|
||||||
"""
|
"""
|
||||||
|
|
@ -1196,12 +1210,9 @@ class Database:
|
||||||
|
|
||||||
# Verify that all tables and columns exist
|
# Verify that all tables and columns exist
|
||||||
for table, column, other_table, other_column in foreign_keys:
|
for table, column, other_table, other_column in foreign_keys:
|
||||||
if not self[table].exists():
|
if not self.table(table).exists():
|
||||||
raise AlterError("No such table: {}".format(table))
|
raise AlterError("No such table: {}".format(table))
|
||||||
table_obj = self[table]
|
table_obj = self.table(table)
|
||||||
if not isinstance(table_obj, Table):
|
|
||||||
raise AlterError("Must be a table, not a view: {}".format(table))
|
|
||||||
table_obj = cast(Table, table_obj)
|
|
||||||
if column not in table_obj.columns_dict:
|
if column not in table_obj.columns_dict:
|
||||||
raise AlterError("No such column: {} in {}".format(column, table))
|
raise AlterError("No such column: {} in {}".format(column, table))
|
||||||
if not self[other_table].exists():
|
if not self[other_table].exists():
|
||||||
|
|
@ -1231,7 +1242,7 @@ class Database:
|
||||||
by_table.setdefault(fk[0], []).append(fk)
|
by_table.setdefault(fk[0], []).append(fk)
|
||||||
|
|
||||||
for table, fks in by_table.items():
|
for table, fks in by_table.items():
|
||||||
cast(Table, self[table]).transform(add_foreign_keys=fks)
|
self.table(table).transform(add_foreign_keys=fks)
|
||||||
|
|
||||||
self.vacuum()
|
self.vacuum()
|
||||||
|
|
||||||
|
|
@ -3655,7 +3666,7 @@ class Table(Queryable):
|
||||||
already exists.
|
already exists.
|
||||||
"""
|
"""
|
||||||
if isinstance(other_table, str):
|
if isinstance(other_table, str):
|
||||||
other_table = cast(Table, self.db.table(other_table, pk=pk))
|
other_table = self.db.table(other_table, pk=pk)
|
||||||
our_id = self.last_pk
|
our_id = self.last_pk
|
||||||
if lookup is not None:
|
if lookup is not None:
|
||||||
assert record_or_iterable is None, "Provide lookup= or record, not both"
|
assert record_or_iterable is None, "Provide lookup= or record, not both"
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ def test_uses_counts_after_enable_counts(counts_db_path):
|
||||||
db = Database(counts_db_path)
|
db = Database(counts_db_path)
|
||||||
logged = []
|
logged = []
|
||||||
with db.tracer(lambda sql, parameters: logged.append((sql, parameters))):
|
with db.tracer(lambda sql, parameters: logged.append((sql, parameters))):
|
||||||
assert db["foo"].count == 1
|
assert db.table("foo").count == 1
|
||||||
assert logged == [
|
assert logged == [
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
("select count(*) from [foo]", []),
|
("select count(*) from [foo]", []),
|
||||||
|
|
@ -138,7 +138,7 @@ def test_uses_counts_after_enable_counts(counts_db_path):
|
||||||
assert not db.use_counts_table
|
assert not db.use_counts_table
|
||||||
db.enable_counts()
|
db.enable_counts()
|
||||||
assert db.use_counts_table
|
assert db.use_counts_table
|
||||||
assert db["foo"].count == 1
|
assert db.table("foo").count == 1
|
||||||
assert logged == [
|
assert logged == [
|
||||||
(
|
(
|
||||||
"CREATE TABLE IF NOT EXISTS [_counts](\n [table] TEXT PRIMARY KEY,\n count INTEGER DEFAULT 0\n);",
|
"CREATE TABLE IF NOT EXISTS [_counts](\n [table] TEXT PRIMARY KEY,\n count INTEGER DEFAULT 0\n);",
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,21 @@ def test_tracer():
|
||||||
db = Database(
|
db = Database(
|
||||||
memory=True, tracer=lambda sql, params: collected.append((sql, params))
|
memory=True, tracer=lambda sql, params: collected.append((sql, params))
|
||||||
)
|
)
|
||||||
db["dogs"].insert({"name": "Cleopaws"})
|
dogs = db.table("dogs")
|
||||||
db["dogs"].enable_fts(["name"])
|
dogs.insert({"name": "Cleopaws"})
|
||||||
db["dogs"].search("Cleopaws")
|
dogs.enable_fts(["name"])
|
||||||
|
dogs.search("Cleopaws")
|
||||||
assert collected == [
|
assert collected == [
|
||||||
("PRAGMA recursive_triggers=on;", None),
|
("PRAGMA recursive_triggers=on;", None),
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
("select name from sqlite_master where type = 'table'", None),
|
("select name from sqlite_master where type = 'table'", None),
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
("select name from sqlite_master where type = 'table'", None),
|
("select name from sqlite_master where type = 'table'", None),
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
("CREATE TABLE [dogs] (\n [name] TEXT\n);\n ", None),
|
("CREATE TABLE [dogs] (\n [name] TEXT\n);\n ", None),
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
("INSERT INTO [dogs] ([name]) VALUES (?)", ["Cleopaws"]),
|
("INSERT INTO [dogs] ([name]) VALUES (?)", ["Cleopaws"]),
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
|
||||||
(
|
(
|
||||||
"CREATE VIRTUAL TABLE [dogs_fts] USING FTS5 (\n [name],\n content=[dogs]\n)",
|
"CREATE VIRTUAL TABLE [dogs_fts] USING FTS5 (\n [name],\n content=[dogs]\n)",
|
||||||
None,
|
None,
|
||||||
|
|
@ -28,7 +29,6 @@ def test_tracer():
|
||||||
"INSERT INTO [dogs_fts] (rowid, [name])\n SELECT rowid, [name] FROM [dogs];",
|
"INSERT INTO [dogs_fts] (rowid, [name])\n SELECT rowid, [name] FROM [dogs];",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -40,60 +40,58 @@ def test_with_tracer():
|
||||||
|
|
||||||
db = Database(memory=True)
|
db = Database(memory=True)
|
||||||
|
|
||||||
db["dogs"].insert({"name": "Cleopaws"})
|
dogs = db.table("dogs")
|
||||||
db["dogs"].enable_fts(["name"])
|
|
||||||
|
dogs.insert({"name": "Cleopaws"})
|
||||||
|
dogs.enable_fts(["name"])
|
||||||
|
|
||||||
assert len(collected) == 0
|
assert len(collected) == 0
|
||||||
|
|
||||||
with db.tracer(tracer):
|
with db.tracer(tracer):
|
||||||
list(db["dogs"].search("Cleopaws"))
|
list(dogs.search("Cleopaws"))
|
||||||
|
|
||||||
assert len(collected) == 5
|
assert len(collected) == 5
|
||||||
assert collected == [
|
assert collected == [
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
|
||||||
(
|
(
|
||||||
(
|
"SELECT name FROM sqlite_master\n"
|
||||||
"SELECT name FROM sqlite_master\n"
|
" WHERE rootpage = 0\n"
|
||||||
" WHERE rootpage = 0\n"
|
" AND (\n"
|
||||||
" AND (\n"
|
" sql LIKE :like\n"
|
||||||
" sql LIKE :like\n"
|
" OR sql LIKE :like2\n"
|
||||||
" OR sql LIKE :like2\n"
|
" OR (\n"
|
||||||
" OR (\n"
|
" tbl_name = :table\n"
|
||||||
" tbl_name = :table\n"
|
" AND sql LIKE '%VIRTUAL TABLE%USING FTS%'\n"
|
||||||
" AND sql LIKE '%VIRTUAL TABLE%USING FTS%'\n"
|
" )\n"
|
||||||
" )\n"
|
" )",
|
||||||
" )",
|
{
|
||||||
{
|
"like": "%VIRTUAL TABLE%USING FTS%content=[dogs]%",
|
||||||
"like": "%VIRTUAL TABLE%USING FTS%content=[dogs]%",
|
"like2": '%VIRTUAL TABLE%USING FTS%content="dogs"%',
|
||||||
"like2": '%VIRTUAL TABLE%USING FTS%content="dogs"%',
|
"table": "dogs",
|
||||||
"table": "dogs",
|
},
|
||||||
},
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
("select sql from sqlite_master where name = ?", ("dogs_fts",)),
|
("select sql from sqlite_master where name = ?", ("dogs_fts",)),
|
||||||
(
|
(
|
||||||
(
|
"with original as (\n"
|
||||||
"with original as (\n"
|
" select\n"
|
||||||
" select\n"
|
" rowid,\n"
|
||||||
" rowid,\n"
|
" *\n"
|
||||||
" *\n"
|
" from [dogs]\n"
|
||||||
" from [dogs]\n"
|
")\n"
|
||||||
")\n"
|
"select\n"
|
||||||
"select\n"
|
" [original].*\n"
|
||||||
" [original].*\n"
|
"from\n"
|
||||||
"from\n"
|
" [original]\n"
|
||||||
" [original]\n"
|
" join [dogs_fts] on [original].rowid = [dogs_fts].rowid\n"
|
||||||
" join [dogs_fts] on [original].rowid = [dogs_fts].rowid\n"
|
"where\n"
|
||||||
"where\n"
|
" [dogs_fts] match :query\n"
|
||||||
" [dogs_fts] match :query\n"
|
"order by\n"
|
||||||
"order by\n"
|
" [dogs_fts].rank",
|
||||||
" [dogs_fts].rank"
|
|
||||||
),
|
|
||||||
{"query": "Cleopaws"},
|
{"query": "Cleopaws"},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Outside the with block collected should not be appended to
|
# Outside the with block collected should not be appended to
|
||||||
db["dogs"].insert({"name": "Cleopaws"})
|
dogs.insert({"name": "Cleopaws"})
|
||||||
assert len(collected) == 5
|
assert len(collected) == 5
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue