-
Notifications
You must be signed in to change notification settings - Fork 14.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[sql lab] a better approach at limiting queries #4947
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,16 +22,14 @@ | |
import sqlalchemy as sqla | ||
from sqlalchemy import ( | ||
Boolean, Column, create_engine, DateTime, ForeignKey, Integer, | ||
MetaData, select, String, Table, Text, | ||
MetaData, String, Table, Text, | ||
) | ||
from sqlalchemy.engine import url | ||
from sqlalchemy.engine.url import make_url | ||
from sqlalchemy.orm import relationship, subqueryload | ||
from sqlalchemy.orm.session import make_transient | ||
from sqlalchemy.pool import NullPool | ||
from sqlalchemy.schema import UniqueConstraint | ||
from sqlalchemy.sql import text | ||
from sqlalchemy.sql.expression import TextAsFrom | ||
from sqlalchemy_utils import EncryptedType | ||
|
||
from superset import app, db, db_engine_specs, security_manager, utils | ||
|
@@ -722,14 +720,7 @@ def select_star( | |
indent=indent, latest_partition=latest_partition, cols=cols) | ||
|
||
def wrap_sql_limit(self, sql, limit=1000): | ||
qry = ( | ||
select('*') | ||
.select_from( | ||
TextAsFrom(text(sql), ['*']) | ||
.alias('inner_qry'), | ||
).limit(limit) | ||
) | ||
return self.compile_sqla_query(qry) | ||
return self.db_engine_spec.apply_limit_to_sql(sql, limit, self) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better update the method name here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, rename There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. went with |
||
|
||
def safe_sqlalchemy_uri(self): | ||
return self.sqlalchemy_uri | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,12 +4,13 @@ | |
from __future__ import print_function | ||
from __future__ import unicode_literals | ||
|
||
import unittest | ||
from superset.db_engine_specs import ( | ||
HiveEngineSpec, MssqlEngineSpec, MySQLEngineSpec) | ||
from superset.models.core import Database | ||
from .base_tests import SupersetTestCase | ||
|
||
from superset.db_engine_specs import HiveEngineSpec | ||
|
||
|
||
class DbEngineSpecsTestCase(unittest.TestCase): | ||
class DbEngineSpecsTestCase(SupersetTestCase): | ||
def test_0_progress(self): | ||
log = """ | ||
17/02/07 18:26:27 INFO log.PerfLogger: <PERFLOG method=compile from=org.apache.hadoop.hive.ql.Driver> | ||
|
@@ -80,3 +81,45 @@ def test_job_2_launched_stage_2_stages_progress(self): | |
17/02/07 19:16:09 INFO exec.Task: 2017-02-07 19:16:09,173 Stage-1 map = 40%, reduce = 0% | ||
""".split('\n') # noqa ignore: E501 | ||
self.assertEquals(60, HiveEngineSpec.progress(log)) | ||
|
||
def test_wrapped_query(self): | ||
sql = 'SELECT * FROM a' | ||
db = Database(sqlalchemy_uri='mysql://localhost') | ||
limited = MssqlEngineSpec.apply_limit_to_sql(sql, 1000, db) | ||
expected = 'SELECT * \nFROM (SELECT * FROM a) AS inner_qry \n LIMIT 1000' | ||
self.assertEquals(expected, limited) | ||
|
||
def test_simple_limit_query(self): | ||
sql = 'SELECT * FROM a' | ||
db = Database(sqlalchemy_uri='mysql://localhost') | ||
limited = MySQLEngineSpec.apply_limit_to_sql(sql, 1000, db) | ||
expected = 'SELECT * FROM a LIMIT 1000' | ||
self.assertEquals(expected, limited) | ||
|
||
def test_modify_limit_query(self): | ||
sql = 'SELECT * FROM a LIMIT 9999' | ||
db = Database(sqlalchemy_uri='mysql://localhost') | ||
limited = MySQLEngineSpec.apply_limit_to_sql(sql, 1000, db) | ||
expected = 'SELECT * FROM a LIMIT 1000' | ||
self.assertEquals(expected, limited) | ||
|
||
def test_modify_newline_query(self): | ||
sql = 'SELECT * FROM a\nLIMIT 9999' | ||
db = Database(sqlalchemy_uri='mysql://localhost') | ||
limited = MySQLEngineSpec.apply_limit_to_sql(sql, 1000, db) | ||
expected = 'SELECT * FROM a LIMIT 1000' | ||
self.assertEquals(expected, limited) | ||
|
||
def test_modify_lcase_limit_query(self): | ||
sql = 'SELECT * FROM a\tlimit 9999' | ||
db = Database(sqlalchemy_uri='mysql://localhost') | ||
limited = MySQLEngineSpec.apply_limit_to_sql(sql, 1000, db) | ||
expected = 'SELECT * FROM a LIMIT 1000' | ||
self.assertEquals(expected, limited) | ||
|
||
def test_limit_query_with_limit_subquery(self): | ||
sql = 'SELECT * FROM (SELECT * FROM a LIMIT 10) LIMIT 9999' | ||
db = Database(sqlalchemy_uri='mysql://localhost') | ||
limited = MySQLEngineSpec.apply_limit_to_sql(sql, 1000, db) | ||
expected = 'SELECT * FROM (SELECT * FROM a LIMIT 10) LIMIT 1000' | ||
self.assertEquals(expected, limited) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm nervous about the regular expression, I'd add a few more unit tests covering some weird cases, eg: SELECT
'LIMIT 1000' AS a
, b
FROM
table
LIMIT
1000
<EOF> And maybe some stuff with extra space at the end: ...
LIMIT 1000 ;
<EOF> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scary! :-O
BTW, you can use backreferences here to replace the LIMIT number in one pass (untested):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I barely understand what I wrote here :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I just realized my example does not work when there's no LIMIT clause. :-/
You know about verbose regexes? Eg:
Though in this case I think it's pretty straightforward. You might wanna
rstrip
whitespace and semicolons from the end ofsql
, that would simplify the regex, no?