Skip to content

Commit 6a052b3

Browse files
author
James Robinson
authored
Fix BigQuery \list, \describe, and \introspect (#92)
* Harden for BigQuery dialect foibles per ENG-5350
1 parent a5ca2ff commit 6a052b3

File tree

1 file changed

+50
-25
lines changed

1 file changed

+50
-25
lines changed

noteable_magics/sql/meta_commands.py

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from pandas import DataFrame
1616
from sqlalchemy import inspect
1717
from sqlalchemy.engine.reflection import Inspector
18+
from sqlalchemy.exc import NoSuchTableError
1819

1920
from noteable_magics.sql.connection import Connection
2021
from noteable_magics.sql.gate_messaging_types import (
@@ -399,21 +400,17 @@ def run(self, invoked_as: str, args: List[str]) -> Tuple[DataFrame, bool]:
399400

400401
inspector = self.get_inspector()
401402

402-
is_view = relation_name in inspector.get_view_names(schema)
403-
404-
if not is_view:
405-
# Ensure is a table
406-
if relation_name not in inspector.get_table_names(schema):
407-
if schema:
408-
msg = f'Relation {schema}.{relation_name} does not exist'
409-
else:
410-
msg = f'Relation {relation_name} does not exist'
411-
raise MetaCommandException(msg)
412-
rtype = 'Table'
413-
else:
414-
rtype = 'View'
403+
try:
404+
# In some dialects (BigQuery), this will raise NoSuchTableError if
405+
# the table doesn't exist. Yay, sane.
415406

416-
column_dicts = inspector.get_columns(relation_name, schema=schema)
407+
# On some dialects (sigh, CockroachDB, what are you doing??),
408+
# this call may succeed returning empty list even if
409+
# the named relation does not exist. But the call to get_pk_constraint()
410+
# down below will then raise NoSuchTableError.
411+
column_dicts = inspector.get_columns(relation_name, schema=schema)
412+
except NoSuchTableError:
413+
self._raise_from_no_such_table(schema, relation_name)
417414

418415
# 'Pivot' the dicts from get_columns()
419416
names = []
@@ -454,11 +451,19 @@ def run(self, invoked_as: str, args: List[str]) -> Tuple[DataFrame, bool]:
454451

455452
displayable_rname = displayable_relation_name(schema, relation_name)
456453

454+
if is_view := relation_name in inspector.get_view_names(schema):
455+
rtype = 'View'
456+
else:
457+
rtype = 'Table'
458+
457459
main_relation_df = set_dataframe_metadata(
458460
DataFrame(data=data), title=f'{rtype} "{displayable_rname}" Structure'
459461
)
460462

461-
display(main_relation_df)
463+
# Keep a list of things to call display() on. Only do so at the very
464+
# end if we don't hit any exceptions.
465+
466+
displayables = [main_relation_df]
462467

463468
if is_view:
464469
view_definition = inspector.get_view_definition(relation_name, schema)
@@ -472,7 +477,7 @@ def run(self, invoked_as: str, args: List[str]) -> Tuple[DataFrame, bool]:
472477
html_buf.append('<br />')
473478
html_buf.append(f'<pre>{view_definition}</pre>')
474479

475-
display(HTML('\n'.join(html_buf)))
480+
displayables.append(HTML('\n'.join(html_buf)))
476481
else:
477482
# Is a table. Let's go get indices, foreign keys, other table constraints.
478483
# If meaningful dataframe returned for any of these, transform to
@@ -484,7 +489,11 @@ def run(self, invoked_as: str, args: List[str]) -> Tuple[DataFrame, bool]:
484489
):
485490
secondary_df = secondary_function(inspector, relation_name, schema)
486491
if len(secondary_df):
487-
display(secondary_dataframe_to_html(secondary_df))
492+
displayables.append(secondary_dataframe_to_html(secondary_df))
493+
494+
# Make it this far? Display what we should display.
495+
for displayable in displayables:
496+
display(displayable)
488497

489498
return main_relation_df, False
490499

@@ -598,7 +607,7 @@ def all_table_and_views(self, inspector) -> List[Tuple[str, str, str]]:
598607
default_schema = inspector.default_schema_name
599608
all_schemas = set(inspector.get_schema_names())
600609
all_schemas.difference_update(self.AVOID_SCHEMAS)
601-
if default_schema not in all_schemas:
610+
if default_schema and default_schema not in all_schemas:
602611
all_schemas.add(default_schema)
603612

604613
for schema_name in sorted(all_schemas):
@@ -971,7 +980,10 @@ def index_dataframe(
971980
uniques: List[bool] = []
972981

973982
# Primary key index is ... treated special by SQLA for some reason. Sigh.
974-
primary_index_dict = inspector.get_pk_constraint(table_name, schema)
983+
try:
984+
primary_index_dict = inspector.get_pk_constraint(table_name, schema)
985+
except NoSuchTableError:
986+
_raise_from_no_such_table(schema, table_name)
975987

976988
# If it returned something truthy with nonempty constrained_columns, then
977989
# we assume it described a real primary key constraint here.
@@ -1164,7 +1176,8 @@ def __init__(self, underlying_inspector: Inspector):
11641176

11651177
# Direct passthrough attributes / methods
11661178
@property
1167-
def default_schema_name(self) -> str:
1179+
def default_schema_name(self) -> Optional[str]:
1180+
# BigQuery, Trino dialects may end up returning None.
11681181
return self.underlying_inspector.default_schema_name
11691182

11701183
def get_schema_names(self) -> List[str]:
@@ -1183,16 +1196,19 @@ def get_foreign_keys(self, table_name: str, schema: Optional[str] = None) -> Lis
11831196
return self.underlying_inspector.get_foreign_keys(table_name, schema=schema)
11841197

11851198
def get_check_constraints(self, table_name: str, schema: Optional[str] = None) -> List[dict]:
1186-
return self.underlying_inspector.get_check_constraints(table_name, schema=schema)
1199+
try:
1200+
return self.underlying_inspector.get_check_constraints(table_name, schema=schema)
1201+
except NotImplementedError:
1202+
return []
11871203

11881204
def get_indexes(self, table_name: str, schema: Optional[str] = None) -> List[dict]:
11891205
return self.underlying_inspector.get_indexes(table_name, schema=schema)
11901206

11911207
def get_unique_constraints(self, table_name: str, schema: Optional[str] = None) -> List[dict]:
1192-
return self.underlying_inspector.get_unique_constraints(table_name, schema=schema)
1193-
1194-
def get_check_constraints(self, table_name: str, schema: Optional[str] = None) -> List[dict]:
1195-
return self.underlying_inspector.get_check_constraints(table_name, schema=schema)
1208+
try:
1209+
return self.underlying_inspector.get_unique_constraints(table_name, schema=schema)
1210+
except NotImplementedError:
1211+
return []
11961212

11971213
# Now the value-adding filtering methods.
11981214
def get_table_names(self, schema: Optional[str] = None) -> List[str]:
@@ -1211,3 +1227,12 @@ def _strip_schema(self, names: List[str], schema: Optional[str] = None) -> List[
12111227
# Remove "schema." from the start of each name if starts with.
12121228
# (name[False:] is equiv to name[0:], 'cause python bools are subclasses of ints)
12131229
return [name[name.startswith(prefix) and len(prefix) :] for name in names]
1230+
1231+
1232+
def _raise_from_no_such_table(schema: str, relation_name: str):
1233+
"""Raise a MetaCommandException when eaten a NoSuchTableException"""
1234+
if schema:
1235+
msg = f'Relation {schema}.{relation_name} does not exist'
1236+
else:
1237+
msg = f'Relation {relation_name} does not exist'
1238+
raise MetaCommandException(msg)

0 commit comments

Comments
 (0)