home / github

Menu
  • Search all tables
  • GraphQL API

issue_comments

Table actions
  • GraphQL API for issue_comments

45 rows where issue = 1058072543 sorted by updated_at descending

✖
✖

✎ View and edit SQL

This data as json, CSV (advanced)

Suggested facets: created_at (date), updated_at (date)

user 1

  • simonw 45

issue 1

  • Complete refactor of TableView and table.html template · 45 ✖

author_association 1

  • OWNER 45
id html_url issue_url node_id user created_at updated_at ▲ author_association body reactions issue performed_via_github_app
999870993 https://github.com/simonw/datasette/issues/1518#issuecomment-999870993 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47mNIR simonw 9599 2021-12-22T20:47:18Z 2021-12-22T20:50:24Z OWNER

The reason they aren't showing up in the traces is that traces are stored just for the currently executing asyncio task ID: https://github.com/simonw/datasette/blob/ace86566b28280091b3844cf5fbecd20158e9004/datasette/tracer.py#L13-L25

This is so traces for other incoming requests don't end up mixed together. But there's no current mechanism to track async tasks that are effectively "child tasks" of the current request, and hence should be tracked the same.

https://stackoverflow.com/a/69349501/6083 suggests that you pass the task ID as an argument to the child tasks that are executed using asyncio.gather() to work around this kind of problem.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
999870282 https://github.com/simonw/datasette/issues/1518#issuecomment-999870282 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47mM9K simonw 9599 2021-12-22T20:45:56Z 2021-12-22T20:46:08Z OWNER

New short-term goal: get facets and suggested facets to execute in parallel with the main query. Generate a trace graph that proves that is happening using datasette-pretty-traces.

I wrote code to execute those in parallel using asyncio.gather() - which seems to work but causes the SQL run inside the parallel async def functions not to show up in the trace graph at all.

```diff diff --git a/datasette/views/table.py b/datasette/views/table.py index 9808fd2..ec9db64 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -1,3 +1,4 @@ +import asyncio import urllib import itertools import json @@ -615,44 +616,37 @@ class TableView(RowTableShared): if 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)

  • Calculate the total count for this query

  • filtered_table_rows_count = None
  • if (
  • not db.is_mutable
  • and self.ds.inspect_data
  • and count_sql == f"select count(*) from {table} "
  • ):
  • We can use a previously cached table row count

  • try:
  • filtered_table_rows_count = self.ds.inspect_data[database]["tables"][
  • table
  • ]["count"]
  • except KeyError:
  • pass

  • Otherwise run a select count(*) ...

  • if count_sql and filtered_table_rows_count is None and not nocount:
  • try:
  • count_rows = list(await db.execute(count_sql, from_sql_params))
  • filtered_table_rows_count = count_rows[0][0]
  • except QueryInterrupted:
  • pass

  • Faceting

  • if not self.ds.setting("allow_facet") and any(
  • arg.startswith("_facet") for arg in request.args
  • ):
  • raise BadRequest("_facet= is not allowed")
  • async def execute_count():
  • Calculate the total count for this query

  • filtered_table_rows_count = None
  • if (
  • not db.is_mutable
  • and self.ds.inspect_data
  • and count_sql == f"select count(*) from {table} "
  • ):
  • We can use a previously cached table row count

  • try:
  • filtered_table_rows_count = self.ds.inspect_data[database][
  • "tables"
  • ][table]["count"]
  • except KeyError:
  • pass +
  • if count_sql and filtered_table_rows_count is None and not nocount:
  • try:
  • count_rows = list(await db.execute(count_sql, from_sql_params))
  • filtered_table_rows_count = count_rows[0][0]
  • except QueryInterrupted:
  • pass +
  • return filtered_table_rows_count +
  • filtered_table_rows_count = await execute_count()

     # pylint: disable=no-member
     facet_classes = list(
         itertools.chain.from_iterable(pm.hook.register_facet_classes())
     )
    
    • facet_results = {}
    • facets_timed_out = [] facet_instances = [] for klass in facet_classes: facet_instances.append( @@ -668,33 +662,58 @@ class TableView(RowTableShared): ) )
  • if not nofacet:

  • for facet in facet_instances:
  • (
  • instance_facet_results,
  • instance_facets_timed_out,
  • ) = await facet.facet_results()
  • for facet_info in instance_facet_results:
  • base_key = facet_info["name"]
  • key = base_key
  • i = 1
  • while key in facet_results:
  • i += 1
  • key = f"{base_key}_{i}"
  • facet_results[key] = facet_info
  • 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())
  • async def execute_suggested_facets():
  • 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())
  • return suggested_facets +
  • async def execute_facets():
  • facet_results = {}
  • facets_timed_out = []
  • if not self.ds.setting("allow_facet") and any(
  • arg.startswith("_facet") for arg in request.args
  • ):
  • raise BadRequest("_facet= is not allowed") +
  • if not nofacet:
  • for facet in facet_instances:
  • (
  • instance_facet_results,
  • instance_facets_timed_out,
  • ) = await facet.facet_results()
  • for facet_info in instance_facet_results:
  • base_key = facet_info["name"]
  • key = base_key
  • i = 1
  • while key in facet_results:
  • i += 1
  • key = f"{base_key}_{i}"
  • facet_results[key] = facet_info
  • facets_timed_out.extend(instance_facets_timed_out) +
  • return facet_results, facets_timed_out +
  • Execute the main query, facets and facet suggestions in parallel:

  • (
  • results,
  • suggested_facets,
  • (facet_results, facets_timed_out),
  • ) = await asyncio.gather(
  • db.execute(sql, params, truncate=True, **extra_args),
  • execute_suggested_facets(),
  • execute_facets(),
  • ) +
  • results = await db.execute(sql, params, truncate=True, **extra_args)
     # Figure out columns and rows for the query
     columns = [r[0] for r in results.description]
    

    `` Here's the trace forhttp://127.0.0.1:4422/fixtures/compound_three_primary_keys?_trace=1&_facet=pk1&_facet=pk2` with the missing facet and facet suggestion queries:

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
999863269 https://github.com/simonw/datasette/issues/1518#issuecomment-999863269 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47mLPl simonw 9599 2021-12-22T20:35:41Z 2021-12-22T20:37:13Z OWNER

It looks like the count has to be executed before facets can be, because the facet_class constructor needs that total count figure: https://github.com/simonw/datasette/blob/6b1384b2f529134998fb507e63307609a5b7f5c0/datasette/views/table.py#L660-L671

It's used in facet suggestion logic here: https://github.com/simonw/datasette/blob/ace86566b28280091b3844cf5fbecd20158e9004/datasette/facets.py#L172-L178

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
999850191 https://github.com/simonw/datasette/issues/1518#issuecomment-999850191 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47mIDP simonw 9599 2021-12-22T20:29:38Z 2021-12-22T20:29:38Z OWNER

New short-term goal: get facets and suggested facets to execute in parallel with the main query. Generate a trace graph that proves that is happening using datasette-pretty-traces.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
999837569 https://github.com/simonw/datasette/issues/1518#issuecomment-999837569 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47mE-B simonw 9599 2021-12-22T20:15:45Z 2021-12-22T20:15:45Z OWNER

Also the whole special_args v.s. request.args thing is pretty confusing, I think that might be an older code pattern back from when I was using Sanic.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
999837220 https://github.com/simonw/datasette/issues/1518#issuecomment-999837220 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47mE4k simonw 9599 2021-12-22T20:15:04Z 2021-12-22T20:15:04Z OWNER

I think I can move this much higher up in the method, it's a bit confusing having it half way through: https://github.com/simonw/datasette/blob/6b1384b2f529134998fb507e63307609a5b7f5c0/datasette/views/table.py#L414-L436

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
999831967 https://github.com/simonw/datasette/issues/1518#issuecomment-999831967 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47mDmf simonw 9599 2021-12-22T20:04:47Z 2021-12-22T20:10:11Z OWNER

I think I might be able to clean up a lot of the stuff in here using the render_cell plugin hook: https://github.com/simonw/datasette/blob/6b1384b2f529134998fb507e63307609a5b7f5c0/datasette/views/table.py#L87-L89

The catch with that hook - https://docs.datasette.io/en/stable/plugin_hooks.html#render-cell-value-column-table-database-datasette - is that it gets called for every single cell. I don't want the overhead of looking up the foreign key relationships etc once for every value in a specific column.

But maybe I could extend the hook to include a shared cache that gets used for all of the cells in a specific table? Something like this: python render_cell(value, column, table, database, datasette, cache) cache is a dictionary - and the same dictionary is passed to every call to that hook while rendering a specific page.

It's a bit of a gross hack though, and would it ever be useful for plugins outside of the default plugin in Datasette which does the foreign key stuff?

If I can think of one other potential application for this cache then I might implement it.

No, this optimization doesn't make sense: the most complex cell enrichment logic is the stuff that does a select * from categories where id in (2, 5, 6) query, using just the distinct set of IDs that are rendered on the current page. That's not going to fit in the render_cell hook no matter how hard I try to warp it into the right shape, because it needs full visibility of all of the results that are being rendered in order to collect those unique ID values.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
997472214 https://github.com/simonw/datasette/issues/1518#issuecomment-997472214 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47dDfW simonw 9599 2021-12-19T22:22:08Z 2021-12-19T22:22:08Z OWNER

I sketched out a chained SQL builder pattern that might be useful for further tidying up this code - though with the new plugin hook I'm less excited about it than I was:

```python class TableQuery: def init(self, table, columns, pks, is_view=False, prev=None): self.table = table self.columns = columns self.pks = pks self.is_view = is_view self.prev = prev

    # These can be changed for different instances in the chain:
    self._where_clauses = None
    self._order_by = None
    self._page_size = None
    self._offset = None
    self._select_columns = None

    self.select_all_columns = '*'
    self.select_specified_columns = '*'

@property
def where_clauses(self):
    wheres = []
    current = self
    while current:
        if current._where_clauses is not None:
            wheres.extend(current._where_clauses)
        current = current.prev
    return list(reversed(wheres))

def where(self, where):
    new_cls = TableQuery(self.table, self.columns, self.pks, self.is_view, self)
    new_cls._where_clauses = [where]
    return new_cls

@classmethod
async def introspect(cls, db, table):
    return cls(
        table,
        columns = await db.table_columns(table),
        pks = await db.primary_keys(table),
        is_view = bool(await db.get_view_definition(table))
    )

@property
def sql_from(self):
    return f"from {self.table}{self.sql_where}"

@property
def sql_where(self):
    if not self.where_clauses:
        return ""
    else:
        return f" where {' and '.join(self.where_clauses)}"

@property
def sql_no_order_no_limit(self):
    return f"select {self.select_all_columns} from {self.table}{self.sql_where}"

@property
def sql(self):
    return f"select {self.select_specified_columns} from {self.table} {self.sql_where}{self._order_by} limit {self._page_size}{self._offset}"

@property
def sql_count(self):
    return f"select count(*) {self.sql_from}"


def __repr__(self):
    return f"<TableQuery sql={self.sql}>"

Usage:python from datasette.app import Datasette ds = Datasette(memory=True, files=["/Users/simon/Dropbox/Development/datasette/fixtures.db"]) db = ds.get_database("fixtures") query = await TableQuery.introspect(db, "facetable") print(query.where("foo = bar").where("baz = 1").sql_count)

'select count(*) from facetable where foo = bar and baz = 1'

```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
981153060 https://github.com/simonw/datasette/issues/1518#issuecomment-981153060 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46ezUk simonw 9599 2021-11-28T21:13:09Z 2021-12-17T23:37:08Z OWNER

Two new requirements inspired by work on the datasette-table (and datasette-notebook) projects:

  • 1533

  • 1534

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
997082845 https://github.com/simonw/datasette/issues/1518#issuecomment-997082845 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47bkbd simonw 9599 2021-12-17T23:10:09Z 2021-12-17T23:10:17Z OWNER

These changes so far are now in the 0.60a0 alpha: https://github.com/simonw/datasette/releases/tag/0.60a0

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996286104 https://github.com/simonw/datasette/issues/1518#issuecomment-996286104 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47Yh6Y simonw 9599 2021-12-17T00:00:07Z 2021-12-17T00:00:07Z OWNER

Documentation of the new hook in the PR: https://github.com/simonw/datasette/blob/54e9b3972f277431a001e685f78e5dd6403a6d8d/docs/plugin_hooks.rst#filters_from_requestrequest-database-table-datasette

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996272906 https://github.com/simonw/datasette/issues/1518#issuecomment-996272906 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47YesK simonw 9599 2021-12-16T23:27:42Z 2021-12-16T23:27:42Z OWNER

Got a TIL out of this: https://til.simonwillison.net/pluggy/multiple-hooks-same-file

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996264617 https://github.com/simonw/datasette/issues/1518#issuecomment-996264617 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47Ycqp simonw 9599 2021-12-16T23:11:12Z 2021-12-16T23:11:12Z OWNER

I managed to extract both _search= and _where= out using a prototype of that hook. I wonder if it could extract the complex code for ?_next too?

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996250585 https://github.com/simonw/datasette/issues/1518#issuecomment-996250585 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47YZPZ simonw 9599 2021-12-16T22:43:37Z 2021-12-16T22:45:07Z OWNER

Ran into a problem prototyping that hook up for handling ?_where= - that feature also adds a little bit of extra template context in order to show the interface for removing wheres - the extra_wheres_for_ui variable: https://github.com/simonw/datasette/blob/0663d5525cc41e9260ac7d1f6386d3a6eb5ad2a9/datasette/views/table.py#L457-L463

Maybe change to this?

python class FilterArguments(NamedTuple): where_clauses: List[str] params: Dict[str, Union[str, int, float]] human_descriptions: List[str] extra_context: Dict[str, Any] That might be necessary for _search too.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996248713 https://github.com/simonw/datasette/issues/1518#issuecomment-996248713 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47YYyJ simonw 9599 2021-12-16T22:39:47Z 2021-12-16T22:39:47Z OWNER

The hook could return a named tuple like this one: ```python from typing import NamedTuple, List, Optional, Union, Dict

class FilterArguments(NamedTuple): where_clauses: List[str] params: Dict[str, Union[str, int, float]] human_descriptions: List[str] ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996240802 https://github.com/simonw/datasette/issues/1518#issuecomment-996240802 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47YW2i simonw 9599 2021-12-16T22:25:00Z 2021-12-16T22:36:04Z OWNER

I think that plugin hook would get given the request object (and datasette and the name of the database and table) and returns a list of SQL fragments, a dictionary of lookup arguments and a list of human-description fragments - or an awaitable.

filters_from_request(request, database, table, datasette) perhaps? (Similar in name to actor_from_request).

python @hookspec def filters_from_request(request, database, table, datasette): """Return (where_clauses, params_dict, human_descriptions) based on the request"""

Turns out that's pretty much exactly what I implemented in 5116c4ec8aed5091e1f75415424b80f613518dc6 for #473:

```python

@hookspec def table_filter(): "Custom filtering of the current table based on the request" python TableFilter = namedtuple("TableFilter", ( "human_description_extras", "where_clauses", "params") ) python # filter_arguments plugin hook support for awaitable_fn in pm.hook.table_filter(): extras = await awaitable_fn( view=self, name=name, table=table, request=request ) human_description_extras.extend(extras.human_description_extras) where_clauses.extend(extras.where_clauses) params.update(extras.params) ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996227713 https://github.com/simonw/datasette/issues/1518#issuecomment-996227713 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47YTqB simonw 9599 2021-12-16T22:02:35Z 2021-12-16T22:03:55Z OWNER

Is there an opportunity to refactor things using a new plugin hook here? Maybe the register_filters hook from #473, where the hook becomes responsible for building where clauses (and human descriptions of them) based on the incoming query string.

That version dealt with Filter classes, but those might be a bit too low-level for this.

?_spatial_within=GEOJSON was an interesting idea attached to that issue.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996225889 https://github.com/simonw/datasette/issues/1518#issuecomment-996225889 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47YTNh simonw 9599 2021-12-16T21:59:32Z 2021-12-16T22:00:42Z OWNER

I added a ton of comments to the data() method which really helps get a better feel for how this all works: https://github.com/simonw/datasette/blob/0663d5525cc41e9260ac7d1f6386d3a6eb5ad2a9/datasette/views/table.py#L322

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996225235 https://github.com/simonw/datasette/issues/1518#issuecomment-996225235 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47YTDT simonw 9599 2021-12-16T21:58:24Z 2021-12-16T21:58:41Z OWNER

A fundamental operation of this view is to construct the SQL query and accompanying human description based on the incoming query string parameters.

The human description is the bit at the top of https://latest.datasette.io/fixtures/searchable?_search=dog&_sort=pk&_facet=text2&text2=sara+weasel that says:

1 row where search matches "dog" and text2 = "sara weasel" sorted by pk

(Also used in the page <title>).

The code actually gathers three things:

  • Fragments of the where clause, for example "text2" = :p0
  • Parameters, e.g. {"p0": "sara weasel"}
  • Human description components, e.g. text2 = "sara weasel"

Some operations such as ?_where= don't currently provide an extra human description component.

_where= also doesn't populate a parameter, but maybe it could? Would be neat if in the future ?_where=foo+=+:bar worked and added a bar input field to the screen, as seen with custom queries.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
996219117 https://github.com/simonw/datasette/issues/1518#issuecomment-996219117 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47YRjt simonw 9599 2021-12-16T21:47:51Z 2021-12-16T21:49:24Z OWNER

Should facets really not be displayed on pages past page one (where ?_next= is set)? That made sense to me at the time, but I'm now having second thoughts about it.

I guess it's a useful performance tweak for when crawlers keep hitting the ?_next= link.

Actually it looks like facets DO display on subsequent pages, e.g. on https://global-power-plants.datasettes.com/global-power-plants/global-power-plants?_next=200 - but facet suggestions do not, thanks to this code: https://github.com/simonw/datasette/blob/2c07327d23d9c5cf939ada9ba4091c1b8b2ba42d/datasette/views/table.py#L777-L785

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
994085710 https://github.com/simonw/datasette/issues/1518#issuecomment-994085710 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47QItO simonw 9599 2021-12-14T22:03:16Z 2021-12-14T22:04:28Z OWNER

There are actually four forms of SQL query used by the table page:

  • from_sql - just the from table_name where ...
  • sql_no_order_no_limit - used for faceting, "select {select_all_columns} from {table_name} {where}"
  • sql - the above but with order and limit clauses: "select {select_specified_columns} from {table_name} {where}{order_by} limit {page_size}{offset}"
  • count_sql used for the count, built out of from_sql: "select count(*) {from_sql}"

I'm tempted to encapsulate those in a Query class.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
994042389 https://github.com/simonw/datasette/issues/1518#issuecomment-994042389 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47P-IV simonw 9599 2021-12-14T21:35:53Z 2021-12-14T21:35:53Z OWNER

Maybe a better way to approach this would be to focus on the JSON side of things - try to get a basic JSON version with ?_extra= support working, then eventually build that up to the point where it can power the HTML version.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
993794247 https://github.com/simonw/datasette/issues/1518#issuecomment-993794247 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47PBjH simonw 9599 2021-12-14T17:09:40Z 2021-12-14T17:09:40Z OWNER
  • table_actions should be an extra.
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
993000787 https://github.com/simonw/datasette/issues/1518#issuecomment-993000787 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47L_1T simonw 9599 2021-12-13T23:19:20Z 2021-12-14T17:06:05Z OWNER

Useful old comment here: https://github.com/simonw/datasette/issues/617#issuecomment-552253893

As noted in #621 (comment) a common pattern in this method is blocks of code that append new items to the where_clauses, params and extra_human_descriptions arrays. This is a useful refactoring opportunity.

Code that fits this pattern:

  • The code that builds based on the filters: where_clauses, params = filters.build_where_clauses(table) and human_description_en = filters.human_description_en(extra=extra_human_descriptions)
  • Code that handles ?_where=: where_clauses.extend(request.args["_where"]) - though note that this also appends to a extra_wheres_for_ui array which nothing else uses
  • The _through= code, see Syntax for ?_through= that works as a form field #621 for details
  • The code that deals with ?_search= FTS

The keyset pagination code modifies where_clauses and params too, but I don't think it's quite going to work with the same abstraction that would cover the above examples.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
992833868 https://github.com/simonw/datasette/issues/1518#issuecomment-992833868 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47LXFM simonw 9599 2021-12-13T19:59:17Z 2021-12-13T19:59:17Z OWNER

Built a new plugin to help with this work by improving the display of ?_trace=1 output: https://datasette.io/plugins/datasette-pretty-traces

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
991978789 https://github.com/simonw/datasette/issues/1518#issuecomment-991978789 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47IGUl simonw 9599 2021-12-12T22:04:19Z 2021-12-12T22:04:19Z OWNER

Idea: in JSON output include a warnings block listing any _ parameters that were not recognized.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
991828014 https://github.com/simonw/datasette/issues/1518#issuecomment-991828014 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47Hhgu simonw 9599 2021-12-12T03:21:35Z 2021-12-12T03:21:35Z OWNER

No, removing that gave me the following test failure: ``` tests/test_table_api.py::test_table_filter_queries[/fixtures/simple_primary_key.json?content__exact=-expected_rows2] FAILED [100%]

=============================================================================== FAILURES ================================================================================ _____ testtable_filter_queries[/fixtures/simple_primary_key.json?contentexact=-expected_rows2] ________

app_client = <datasette.utils.testing.TestClient object at 0x10d45d2d0>, path = '/fixtures/simple_primary_key.json?content__exact=', expected_rows = [['3', '']]

@pytest.mark.parametrize(
    "path,expected_rows",
    [
        ("/fixtures/simple_primary_key.json?content=hello", [["1", "hello"]]),
        (
            "/fixtures/simple_primary_key.json?content__contains=o",
            [
                ["1", "hello"],
                ["2", "world"],
                ["4", "RENDER_CELL_DEMO"],
            ],
        ),
        ("/fixtures/simple_primary_key.json?content__exact=", [["3", ""]]),
        (
            "/fixtures/simple_primary_key.json?content__not=world",
            [
                ["1", "hello"],
                ["3", ""],
                ["4", "RENDER_CELL_DEMO"],
                ["5", "RENDER_CELL_ASYNC"],
            ],
        ),
    ],
)
def test_table_filter_queries(app_client, path, expected_rows):
    response = app_client.get(path)
  assert expected_rows == response.json["rows"]

E AssertionError: assert [['3', '']] == [['1', 'hello'],\n ['2', 'world'],\n ['3', ''],\n ['4', 'RENDER_CELL_DEMO'],\n ['5', 'RENDER_CELL_ASYNC']] E At index 0 diff: ['3', ''] != ['1', 'hello'] E Right contains 4 more items, first extra item: ['2', 'world'] E Full diff: E [ E - ['1', E - 'hello'], E - ['2', E - 'world'], E ['3', E ''], E - ['4', E - 'RENDER_CELL_DEMO'], E - ['5', E - 'RENDER_CELL_ASYNC'], E ]

/Users/simon/Dropbox/Development/datasette/tests/test_table_api.py:511: AssertionError ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
991827468 https://github.com/simonw/datasette/issues/1518#issuecomment-991827468 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47HhYM simonw 9599 2021-12-12T03:15:00Z 2021-12-12T03:15:00Z OWNER

I don't think this code is necessary any more: https://github.com/simonw/datasette/blob/492f9835aa7e90540dd0c6324282b109f73df71b/datasette/views/table.py#L396-L399

That dates back from when Datasette was built on top of Sanic and Sanic didn't preserve those query parameters the way I needed it to:

https://github.com/simonw/datasette/blob/1f69269fe93e4cd42e56890126cc0dbcf719c6cb/datasette/views/table.py#L202-L206

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
991823001 https://github.com/simonw/datasette/issues/1518#issuecomment-991823001 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47HgSZ simonw 9599 2021-12-12T02:25:32Z 2021-12-12T02:25:32Z OWNER

The tests for TableView are currently mixed in with everything else in tests/test_api.py and tests/html.py - might be good to split those out into test_table_html.py and test_table_api.py since they're such a key part of how Datasette works.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
991822853 https://github.com/simonw/datasette/issues/1518#issuecomment-991822853 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47HgQF simonw 9599 2021-12-12T02:24:00Z 2021-12-12T02:24:00Z OWNER

Rebuilding TableView from the ground up is proving not to be much fun. I'm going to explore starting the refactor of the existing code by separating out the bit that generates the SQL query from the rest of it.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
991819781 https://github.com/simonw/datasette/issues/1518#issuecomment-991819781 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47HfgF simonw 9599 2021-12-12T01:53:10Z 2021-12-12T01:53:10Z OWNER

I have a hunch that the conclusion of this experiment may end up being that the asyncinject trick is kinda neat but the code will be easier to maintain (while still executing in parallel) if it's written using asyncio.gather directly instead.

It's possible asyncinject will end up being neat enough that I'll want to keep it though.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
991285527 https://github.com/simonw/datasette/issues/1518#issuecomment-991285527 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c47FdEX simonw 9599 2021-12-10T20:52:00Z 2021-12-10T20:52:00Z OWNER

If I break this up into @inject methods, what methods could I have and what would they do?

  • resolve_path: Use request path to resolve the database and table. Could handle hash URLs too (if I don't manage to extract those to a plugin) - would be nice if this could raise a redirect, but I think that will instead have to be one of the things it returns
  • build_sql: Builds the SQL query based on the querystring (and some DB introspection)
  • execute_count: Execute the count(*)
  • execute_rows: Execute the limit 101 to fetch the rows
  • execute_facets: Execute all requested facets (could this do its own asyncio.gather() to run facets in parallel?)
  • suggest_facets: Execute facet suggestions

Are there any plugin hooks that would make sense to execute in parallel? Actually there might be: I don't think extra_template_vars, extra_css_urls, extra_js_urls, extra_body_script depend on each other so it might be possible to execute them in a parallel chunk (at least any of them that return awaitables).

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
981172801 https://github.com/simonw/datasette/issues/1518#issuecomment-981172801 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46e4JB simonw 9599 2021-11-28T23:23:51Z 2021-11-28T23:23:51Z OWNER

(I could experiment with merging the two tables by adding a temporary undocumented ?_sql= parameter to the in-progress table view that sets an alternative query instead of select cols from table - added bonus, this will force me to use introspection against the returned columns rather than mixing in the known columns for the specified table)

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
981172385 https://github.com/simonw/datasette/issues/1518#issuecomment-981172385 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46e4Ch simonw 9599 2021-11-28T23:21:26Z 2021-11-28T23:21:26Z OWNER

Aside: is there any reason this work can't complete the long-running goal of merging the TableView and QueryView, such that most of the features available for tables become available for arbitrary queries too?

I had already mentally committed to implementing facets for queries, but I just realized that filters could work too - using either a CTE or a nested query.

Pagination is the one holdout here, since table pagination uses keyset pagination over a known order. But maybe arbitrary queries can only be paginated off you order them first?

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
981153186 https://github.com/simonw/datasette/issues/1518#issuecomment-981153186 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46ezWi simonw 9599 2021-11-28T21:13:50Z 2021-11-28T21:13:50Z OWNER

I'm also going to use the new datasette-table Web Component to help guide the design of the new API, which relates directly to this issue too:

  • 1532

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
974300823 https://github.com/simonw/datasette/issues/1518#issuecomment-974300823 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46EqaX simonw 9599 2021-11-19T18:18:32Z 2021-11-19T18:18:32Z OWNER

This may be an argument for continuing to allow non-JSON-objects through to the HTML templates. Need to think about that a bit more.

I can definitely support this using pure-JSON - I could make two versions of the row available, one that's an array of cell objects and the other that's an object mapping column names to column raw values.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
974285803 https://github.com/simonw/datasette/issues/1518#issuecomment-974285803 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46Emvr simonw 9599 2021-11-19T17:56:48Z 2021-11-19T18:14:30Z OWNER

Very confused by this piece of code here: https://github.com/simonw/datasette/blob/1c13e1af0664a4dfb1e69714c56523279cae09e4/datasette/views/table.py#L37-L63

I added it in https://github.com/simonw/datasette/commit/754836eef043676e84626c4fd3cb993eed0d2976 - in the new world that should probably be replaced by pure JSON.

Aha - this comment explains it: https://github.com/simonw/datasette/issues/521#issuecomment-505279560

I think the trick is to redefine what a "cell_row" is. Each row is currently a list of cells:

https://github.com/simonw/datasette/blob/6341f8cbc7833022012804dea120b838ec1f6558/datasette/views/table.py#L159-L163

I can redefine the row (the cells variable in the above example) as a thing-that-iterates-cells (hence behaving like a list) but that also supports __getitem__ access for looking up cell values if you know the name of the column.

The goal was to support neater custom templates like this: ```html+jinja {% for row in display_rows %}

{{ row["First_Name"] }} {{ row["Last_Name"] }}

... ``` This may be an argument for continuing to allow non-JSON-objects through to the HTML templates. Need to think about that a bit more.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
974287570 https://github.com/simonw/datasette/issues/1518#issuecomment-974287570 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46EnLS simonw 9599 2021-11-19T17:59:33Z 2021-11-19T17:59:33Z OWNER

I'm going to try leaning into the asyncinject mechanism a bit here. One method can execute and return the raw rows. Another can turn that into the default minimal JSON representation. Then a third can take that (or take both) and use it to inflate out the JSON that the HTML template needs, with those extras and with the rendered cells from plugins.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
973700549 https://github.com/simonw/datasette/issues/1518#issuecomment-973700549 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46CX3F simonw 9599 2021-11-19T03:31:20Z 2021-11-19T03:31:26Z OWNER

... and while I'm doing all of this I can rewrite the templates to not use those cheating magical functions AND document the template context at the same time, refs: - #1510.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
973700322 https://github.com/simonw/datasette/issues/1518#issuecomment-973700322 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46CXzi simonw 9599 2021-11-19T03:30:30Z 2021-11-19T03:30:30Z OWNER

Right now the HTML version gets to cheat - it passes through objects that are not JSON serializable, including custom functions that can then be called by Jinja.

I'm interested in maybe removing this cheating - if the HTML version could only request JSON-serializable extras those could be exposed in the API as well.

It would also help cleanup the kind-of-nasty pattern I use in the current BaseView where everything returns both a bunch of JSON-serializable data AND an awaitable function that then gets to add extra things to the HTML context.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
973698917 https://github.com/simonw/datasette/issues/1518#issuecomment-973698917 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46CXdl simonw 9599 2021-11-19T03:26:18Z 2021-11-19T03:29:03Z OWNER

A (likely incomplete) list of features on the table page:

  • [ ] Display table/database/instance metadata
  • [ ] Show count of all results
  • [ ] Display table of results
  • [ ] Special table display treatment for URLs, numbers
  • [ ] Allow plugins to modify table cells
  • [ ] Respect ?_col= and ?_nocol=
  • [ ] Show interface for filtering by columns and operations
  • [ ] Show search box, support executing FTS searches
  • [ ] Sort table by specified column
  • [ ] Paginate table
  • [ ] Show facet results
  • [ ] Show suggested facets
  • [ ] Link to available exports
  • [ ] Display schema for table
  • [ ] Maybe it should show the SQL for the query too?
  • [ ] Handle various non-obvious querystring options, like ?_where= and ?_through=
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
973699424 https://github.com/simonw/datasette/issues/1518#issuecomment-973699424 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46CXlg simonw 9599 2021-11-19T03:27:49Z 2021-11-19T03:27:49Z OWNER

My goal is to break up a lot of this functionality into separate methods. These methods can be executed in parallel by asyncinject, but more importantly they can be used to build a much better JSON representation, where the default representation is lighter and ?_extra=x options can be used to execute more expensive portions and add them to the response.

So the HTML version itself needs to be re-written to use those JSON extras.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
973687978 https://github.com/simonw/datasette/issues/1518#issuecomment-973687978 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46CUyq simonw 9599 2021-11-19T03:07:47Z 2021-11-19T03:07:47Z OWNER

I was wrong about that, you CAN over-ride default routes already.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
973682389 https://github.com/simonw/datasette/issues/1518#issuecomment-973682389 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46CTbV simonw 9599 2021-11-19T02:57:39Z 2021-11-19T02:57:39Z OWNER

Ideally I'd like to execute the existing test suite against the new implementation - that would require me to solve this so I can replace the view with the plugin version though:

  • 1517

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  
973681970 https://github.com/simonw/datasette/issues/1518#issuecomment-973681970 https://api.github.com/repos/simonw/datasette/issues/1518 IC_kwDOBm6k_c46CTUy simonw 9599 2021-11-19T02:56:31Z 2021-11-19T02:56:53Z OWNER

Here's where I got to with my hacked-together initial plugin prototype - it managed to render the table page with some rows on it (and a bunch of missing functionality such as filters): https://gist.github.com/simonw/281eac9c73b062c3469607ad86470eb2

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Complete refactor of TableView and table.html template 1058072543  

Advanced export

JSON shape: default, array, newline-delimited, object

CSV options:

CREATE TABLE [issue_comments] (
   [html_url] TEXT,
   [issue_url] TEXT,
   [id] INTEGER PRIMARY KEY,
   [node_id] TEXT,
   [user] INTEGER REFERENCES [users]([id]),
   [created_at] TEXT,
   [updated_at] TEXT,
   [author_association] TEXT,
   [body] TEXT,
   [reactions] TEXT,
   [issue] INTEGER REFERENCES [issues]([id])
, [performed_via_github_app] TEXT);
CREATE INDEX [idx_issue_comments_issue]
                ON [issue_comments] ([issue]);
CREATE INDEX [idx_issue_comments_user]
                ON [issue_comments] ([user]);
Powered by Datasette · Queries took 25.014ms · About: github-to-sqlite
  • Sort ascending
  • Sort descending
  • Facet by this
  • Hide this column
  • Show all columns
  • Show not-blank rows