mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Move columns_to_select to TableView class, add lots of comments, refs #1518
This commit is contained in:
parent
0d4145d0f4
commit
2c07327d23
1 changed files with 63 additions and 54 deletions
|
|
@ -64,41 +64,6 @@ class Row:
|
||||||
|
|
||||||
|
|
||||||
class RowTableShared(DataView):
|
class RowTableShared(DataView):
|
||||||
async def columns_to_select(self, db, table, request):
|
|
||||||
table_columns = await db.table_columns(table)
|
|
||||||
pks = await db.primary_keys(table)
|
|
||||||
columns = list(table_columns)
|
|
||||||
if "_col" in request.args:
|
|
||||||
columns = list(pks)
|
|
||||||
_cols = request.args.getlist("_col")
|
|
||||||
bad_columns = [column for column in _cols if column not in table_columns]
|
|
||||||
if bad_columns:
|
|
||||||
raise DatasetteError(
|
|
||||||
"_col={} - invalid columns".format(", ".join(bad_columns)),
|
|
||||||
status=400,
|
|
||||||
)
|
|
||||||
# De-duplicate maintaining order:
|
|
||||||
columns.extend(dict.fromkeys(_cols))
|
|
||||||
if "_nocol" in request.args:
|
|
||||||
# Return all columns EXCEPT these
|
|
||||||
bad_columns = [
|
|
||||||
column
|
|
||||||
for column in request.args.getlist("_nocol")
|
|
||||||
if (column not in table_columns) or (column in pks)
|
|
||||||
]
|
|
||||||
if bad_columns:
|
|
||||||
raise DatasetteError(
|
|
||||||
"_nocol={} - invalid columns".format(", ".join(bad_columns)),
|
|
||||||
status=400,
|
|
||||||
)
|
|
||||||
tmp_columns = [
|
|
||||||
column
|
|
||||||
for column in columns
|
|
||||||
if column not in request.args.getlist("_nocol")
|
|
||||||
]
|
|
||||||
columns = tmp_columns
|
|
||||||
return columns
|
|
||||||
|
|
||||||
async def sortable_columns_for_table(self, database, table, use_rowid):
|
async def sortable_columns_for_table(self, database, table, use_rowid):
|
||||||
db = self.ds.databases[database]
|
db = self.ds.databases[database]
|
||||||
table_metadata = self.ds.table_metadata(database, table)
|
table_metadata = self.ds.table_metadata(database, table)
|
||||||
|
|
@ -321,6 +286,39 @@ class TableView(RowTableShared):
|
||||||
write=bool(canned_query.get("write")),
|
write=bool(canned_query.get("write")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def columns_to_select(self, table_columns, pks, request):
|
||||||
|
columns = list(table_columns)
|
||||||
|
if "_col" in request.args:
|
||||||
|
columns = list(pks)
|
||||||
|
_cols = request.args.getlist("_col")
|
||||||
|
bad_columns = [column for column in _cols if column not in table_columns]
|
||||||
|
if bad_columns:
|
||||||
|
raise DatasetteError(
|
||||||
|
"_col={} - invalid columns".format(", ".join(bad_columns)),
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
# De-duplicate maintaining order:
|
||||||
|
columns.extend(dict.fromkeys(_cols))
|
||||||
|
if "_nocol" in request.args:
|
||||||
|
# Return all columns EXCEPT these
|
||||||
|
bad_columns = [
|
||||||
|
column
|
||||||
|
for column in request.args.getlist("_nocol")
|
||||||
|
if (column not in table_columns) or (column in pks)
|
||||||
|
]
|
||||||
|
if bad_columns:
|
||||||
|
raise DatasetteError(
|
||||||
|
"_nocol={} - invalid columns".format(", ".join(bad_columns)),
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
tmp_columns = [
|
||||||
|
column
|
||||||
|
for column in columns
|
||||||
|
if column not in request.args.getlist("_nocol")
|
||||||
|
]
|
||||||
|
columns = tmp_columns
|
||||||
|
return columns
|
||||||
|
|
||||||
async def data(
|
async def data(
|
||||||
self,
|
self,
|
||||||
request,
|
request,
|
||||||
|
|
@ -331,6 +329,7 @@ class TableView(RowTableShared):
|
||||||
_next=None,
|
_next=None,
|
||||||
_size=None,
|
_size=None,
|
||||||
):
|
):
|
||||||
|
# If this is a canned query, not a table, then dispatch to QueryView instead
|
||||||
canned_query = await self.ds.get_canned_query(database, table, request.actor)
|
canned_query = await self.ds.get_canned_query(database, table, request.actor)
|
||||||
if canned_query:
|
if canned_query:
|
||||||
return await QueryView(self.ds).data(
|
return await QueryView(self.ds).data(
|
||||||
|
|
@ -348,9 +347,12 @@ class TableView(RowTableShared):
|
||||||
db = self.ds.databases[database]
|
db = self.ds.databases[database]
|
||||||
is_view = bool(await db.get_view_definition(table))
|
is_view = bool(await db.get_view_definition(table))
|
||||||
table_exists = bool(await db.table_exists(table))
|
table_exists = bool(await db.table_exists(table))
|
||||||
|
|
||||||
|
# If table or view not found, return 404
|
||||||
if not is_view and not table_exists:
|
if not is_view and not table_exists:
|
||||||
raise NotFound(f"Table not found: {table}")
|
raise NotFound(f"Table not found: {table}")
|
||||||
|
|
||||||
|
# Ensure user has permission to view this table
|
||||||
await self.check_permissions(
|
await self.check_permissions(
|
||||||
request,
|
request,
|
||||||
[
|
[
|
||||||
|
|
@ -364,15 +366,18 @@ class TableView(RowTableShared):
|
||||||
None, "view-table", (database, table), default=True
|
None, "view-table", (database, table), default=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Introspect columns and primary keys for table
|
||||||
pks = await db.primary_keys(table)
|
pks = await db.primary_keys(table)
|
||||||
table_columns = await db.table_columns(table)
|
table_columns = await db.table_columns(table)
|
||||||
|
|
||||||
specified_columns = await self.columns_to_select(db, table, request)
|
# Take ?_col= and ?_nocol= into account
|
||||||
|
specified_columns = await self.columns_to_select(table_columns, pks, request)
|
||||||
select_specified_columns = ", ".join(
|
select_specified_columns = ", ".join(
|
||||||
escape_sqlite(t) for t in specified_columns
|
escape_sqlite(t) for t in specified_columns
|
||||||
)
|
)
|
||||||
select_all_columns = ", ".join(escape_sqlite(t) for t in table_columns)
|
select_all_columns = ", ".join(escape_sqlite(t) for t in table_columns)
|
||||||
|
|
||||||
|
# rowid tables (no specified primary key) need a different SELECT
|
||||||
use_rowid = not pks and not is_view
|
use_rowid = not pks and not is_view
|
||||||
if use_rowid:
|
if use_rowid:
|
||||||
select_specified_columns = f"rowid, {select_specified_columns}"
|
select_specified_columns = f"rowid, {select_specified_columns}"
|
||||||
|
|
@ -487,7 +492,7 @@ class TableView(RowTableShared):
|
||||||
f'{through_table}.{other_column} = "{value}"'
|
f'{through_table}.{other_column} = "{value}"'
|
||||||
)
|
)
|
||||||
|
|
||||||
# _search support:
|
# _search= support:
|
||||||
fts_table = special_args.get("_fts_table")
|
fts_table = special_args.get("_fts_table")
|
||||||
fts_table = fts_table or table_metadata.get("fts_table")
|
fts_table = fts_table or table_metadata.get("fts_table")
|
||||||
fts_table = fts_table or await db.fts_table(table)
|
fts_table = fts_table or await db.fts_table(table)
|
||||||
|
|
@ -541,8 +546,6 @@ class TableView(RowTableShared):
|
||||||
)
|
)
|
||||||
params[f"search_{i}"] = search_text
|
params[f"search_{i}"] = search_text
|
||||||
|
|
||||||
sortable_columns = set()
|
|
||||||
|
|
||||||
sortable_columns = await self.sortable_columns_for_table(
|
sortable_columns = await self.sortable_columns_for_table(
|
||||||
database, table, use_rowid
|
database, table, use_rowid
|
||||||
)
|
)
|
||||||
|
|
@ -581,6 +584,7 @@ class TableView(RowTableShared):
|
||||||
|
|
||||||
count_sql = f"select count(*) {from_sql}"
|
count_sql = f"select count(*) {from_sql}"
|
||||||
|
|
||||||
|
# Handl pagination driven by ?_next=
|
||||||
_next = _next or special_args.get("_next")
|
_next = _next or special_args.get("_next")
|
||||||
offset = ""
|
offset = ""
|
||||||
if _next:
|
if _next:
|
||||||
|
|
@ -679,6 +683,7 @@ class TableView(RowTableShared):
|
||||||
else:
|
else:
|
||||||
page_size = self.ds.page_size
|
page_size = self.ds.page_size
|
||||||
|
|
||||||
|
# Facets are calculated against SQL without order by or limit
|
||||||
sql_no_order_no_limit = (
|
sql_no_order_no_limit = (
|
||||||
"select {select_all_columns} from {table_name} {where}".format(
|
"select {select_all_columns} from {table_name} {where}".format(
|
||||||
select_all_columns=select_all_columns,
|
select_all_columns=select_all_columns,
|
||||||
|
|
@ -686,6 +691,8 @@ class TableView(RowTableShared):
|
||||||
where=where_clause,
|
where=where_clause,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# This is the SQL that populates the main table on the page
|
||||||
sql = "select {select_specified_columns} from {table_name} {where}{order_by} limit {page_size}{offset}".format(
|
sql = "select {select_specified_columns} from {table_name} {where}{order_by} limit {page_size}{offset}".format(
|
||||||
select_specified_columns=select_specified_columns,
|
select_specified_columns=select_specified_columns,
|
||||||
table_name=escape_sqlite(table),
|
table_name=escape_sqlite(table),
|
||||||
|
|
@ -698,15 +705,17 @@ class TableView(RowTableShared):
|
||||||
if request.args.get("_timelimit"):
|
if request.args.get("_timelimit"):
|
||||||
extra_args["custom_time_limit"] = int(request.args.get("_timelimit"))
|
extra_args["custom_time_limit"] = int(request.args.get("_timelimit"))
|
||||||
|
|
||||||
|
# Execute the main query!
|
||||||
results = await db.execute(sql, params, truncate=True, **extra_args)
|
results = await db.execute(sql, params, truncate=True, **extra_args)
|
||||||
|
|
||||||
# Number of filtered rows in whole set:
|
# Calculate the total count for this query
|
||||||
filtered_table_rows_count = None
|
filtered_table_rows_count = None
|
||||||
if (
|
if (
|
||||||
not db.is_mutable
|
not db.is_mutable
|
||||||
and self.ds.inspect_data
|
and self.ds.inspect_data
|
||||||
and count_sql == f"select count(*) from {table} "
|
and count_sql == f"select count(*) from {table} "
|
||||||
):
|
):
|
||||||
|
# We can use a previously cached table row count
|
||||||
try:
|
try:
|
||||||
filtered_table_rows_count = self.ds.inspect_data[database]["tables"][
|
filtered_table_rows_count = self.ds.inspect_data[database]["tables"][
|
||||||
table
|
table
|
||||||
|
|
@ -714,6 +723,7 @@ class TableView(RowTableShared):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Otherwise run a select count(*) ...
|
||||||
if count_sql and filtered_table_rows_count is None and not nocount:
|
if count_sql and filtered_table_rows_count is None and not nocount:
|
||||||
try:
|
try:
|
||||||
count_rows = list(await db.execute(count_sql, from_sql_params))
|
count_rows = list(await db.execute(count_sql, from_sql_params))
|
||||||
|
|
@ -721,7 +731,7 @@ class TableView(RowTableShared):
|
||||||
except QueryInterrupted:
|
except QueryInterrupted:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# facets support
|
# Faceting
|
||||||
if not self.ds.setting("allow_facet") and any(
|
if not self.ds.setting("allow_facet") and any(
|
||||||
arg.startswith("_facet") for arg in request.args
|
arg.startswith("_facet") for arg in request.args
|
||||||
):
|
):
|
||||||
|
|
@ -764,6 +774,18 @@ class TableView(RowTableShared):
|
||||||
facet_results[key] = facet_info
|
facet_results[key] = facet_info
|
||||||
facets_timed_out.extend(instance_facets_timed_out)
|
facets_timed_out.extend(instance_facets_timed_out)
|
||||||
|
|
||||||
|
# Calculate suggested facets
|
||||||
|
suggested_facets = []
|
||||||
|
if (
|
||||||
|
self.ds.setting("suggest_facets")
|
||||||
|
and self.ds.setting("allow_facet")
|
||||||
|
and not _next
|
||||||
|
and not nofacet
|
||||||
|
and not nosuggest
|
||||||
|
):
|
||||||
|
for facet in facet_instances:
|
||||||
|
suggested_facets.extend(await facet.suggest())
|
||||||
|
|
||||||
# Figure out columns and rows for the query
|
# Figure out columns and rows for the query
|
||||||
columns = [r[0] for r in results.description]
|
columns = [r[0] for r in results.description]
|
||||||
rows = list(results.rows)
|
rows = list(results.rows)
|
||||||
|
|
@ -846,19 +868,6 @@ class TableView(RowTableShared):
|
||||||
)
|
)
|
||||||
rows = rows[:page_size]
|
rows = rows[:page_size]
|
||||||
|
|
||||||
# Detect suggested facets
|
|
||||||
suggested_facets = []
|
|
||||||
|
|
||||||
if (
|
|
||||||
self.ds.setting("suggest_facets")
|
|
||||||
and self.ds.setting("allow_facet")
|
|
||||||
and not _next
|
|
||||||
and not nofacet
|
|
||||||
and not nosuggest
|
|
||||||
):
|
|
||||||
for facet in facet_instances:
|
|
||||||
suggested_facets.extend(await facet.suggest())
|
|
||||||
|
|
||||||
# human_description_en combines filters AND search, if provided
|
# human_description_en combines filters AND search, if provided
|
||||||
human_description_en = filters.human_description_en(
|
human_description_en = filters.human_description_en(
|
||||||
extra=extra_human_descriptions
|
extra=extra_human_descriptions
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue