Skip to content

Commit

Permalink
Add "validation_only" queries to the backend
Browse files Browse the repository at this point in the history
This is the first step toward supporting live as-you-type
SQL validation against databases that support doing it (for
starters just presto).

I'm trying to stick as much to the existing query machinery
as possible to avoid introducing too much new state for handling
this, so I plumbed it through as a db spec parameter and an
option you can set on an individual query.

As of this commit:

- [X] Plumb validation db_spec options
- [X] Add validation_only option and transform to the sql_json view
- [ ] Tests for the above on the backend
- [ ] Debounced requestValidationQuery in onSqlChange
- [ ] Check incoming results -- full query vs validation
- [ ] Tests for the above on the frontend
- [ ] Highlight error lines
- [ ] Print error message in alert-danger box above toolbar controls
  • Loading branch information
Alex Berghage committed May 1, 2019
1 parent 36a219d commit d076e0b
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 0 deletions.
33 changes: 33 additions & 0 deletions superset/db_engine_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class BaseEngineSpec(object):
force_column_alias_quotes = False
arraysize = None
max_column_name_length = None
supports_validation_queries = False

@classmethod
def get_time_expr(cls, expr, pdf, time_grain, grain):
Expand Down Expand Up @@ -479,6 +480,15 @@ def get_timestamp_column(expression, column_name):
can be overridden."""
return expression or column_name

@classmethod
def make_validation_query(cls, sql):
"""
If the underlying engine supports it, modify the query sql to request
that the database validate the query instead of running it.
"""
raise Exception(
f'Database engine {cls.engine} does not support validation queries')


class PostgresBaseEngineSpec(BaseEngineSpec):
""" Abstract class for Postgres 'like' databases """
Expand Down Expand Up @@ -785,6 +795,7 @@ def extract_error_message(cls, e):

class PrestoEngineSpec(BaseEngineSpec):
engine = 'presto'
supports_validation_queries = True

time_grain_functions = {
None: '{col}',
Expand Down Expand Up @@ -1081,13 +1092,25 @@ def latest_sub_partition(cls, table_name, schema, database, **kwargs):
return ''
return df.to_dict()[field_to_return][0]

@classmethod
def make_validation_query(cls, sql):
"""
Presto supports query-validation queries by prepending explain with a
type parameter.
For example, "SELECT 1 FROM default.mytable" becomes "EXPLAIN (TYPE
VALIDATE) SELECT 1 FROM default.mytable.
"""
return f'EXPLAIN (TYPE VALIDATE) {sql}'


class HiveEngineSpec(PrestoEngineSpec):

"""Reuses PrestoEngineSpec functionality."""

engine = 'hive'
max_column_name_length = 767
supports_validation_queries = False

# Scoping regex at class level to avoid recompiling
# 17/02/07 19:36:38 INFO ql.Driver: Total jobs = 5
Expand Down Expand Up @@ -1392,6 +1415,16 @@ def execute(cursor, query, async_=False):
cursor.execute(query, **kwargs)


@classmethod
def make_validation_query(cls, sql):
"""
Hive doesn't support query-validation queries, so we disable the handler
inherited from presto.
"""
raise Exception(
f'Database engine {cls.engine} does not support validation queries')


class MssqlEngineSpec(BaseEngineSpec):
engine = 'mssql'
epoch_to_dttm = "dateadd(S, {col}, '1970-01-01')"
Expand Down
9 changes: 9 additions & 0 deletions superset/views/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2512,6 +2512,7 @@ def sql_json(self):
sql = request.form.get('sql')
database_id = request.form.get('database_id')
schema = request.form.get('schema') or None
validate_only = request.form.get('validate_only') == 'true'
template_params = json.loads(
request.form.get('templateParams') or '{}')
limit = int(request.form.get('queryLimit', 0))
Expand Down Expand Up @@ -2580,6 +2581,14 @@ def sql_json(self):
limits = [mydb.db_engine_spec.get_limit_from_sql(rendered_query), limit]
query.limit = min(lim for lim in limits if lim is not None)

# apply validation transform last -- after template processing
if validate_only:
spec = mydb.db_engine_spec
if not spec.supports_validation_queries:
json_error_response('{} does not support validation queries'
.format(spec.engine))
rendered_query = spec.make_validation_query(rendered_query)

# Async request.
if async_:
logging.info('Running query on a Celery worker')
Expand Down

0 comments on commit d076e0b

Please sign in to comment.