Add support for all postgres explain options

This commit is contained in:
Konsta Vesterinen
2014-10-07 12:48:38 +03:00
parent 16ce626045
commit cafbb8aea1
8 changed files with 103 additions and 23 deletions

View File

@@ -1,26 +1,66 @@
import sqlalchemy as sa
from sqlalchemy.sql import expression
from sqlalchemy.sql.expression import Executable, ClauseElement, _literal_as_text
from sqlalchemy.sql.expression import (
Executable,
ClauseElement,
_literal_as_text
)
from sqlalchemy.ext.compiler import compiles
from sqlalchemy_utils.types import TSVectorType
class explain(Executable, ClauseElement):
def __init__(self, stmt, analyze=False):
"""
Define EXPLAIN element.
http://www.postgresql.org/docs/devel/static/sql-explain.html
"""
def __init__(
self,
stmt,
analyze=False,
verbose=False,
costs=True,
buffers=False,
timing=True,
format='text'
):
self.statement = _literal_as_text(stmt)
self.analyze = analyze
self.verbose = verbose
self.costs = costs
self.buffers = buffers
self.timing = timing
self.format = format
class explain_analyze(explain):
def __init__(self, stmt):
super(explain_analyze, self).__init__(stmt, analyze=True)
def __init__(self, stmt, **kwargs):
super(explain_analyze, self).__init__(
stmt,
analyze=True,
**kwargs
)
@compiles(explain, 'postgresql')
def pg_explain(element, compiler, **kw):
text = "EXPLAIN "
options = []
if element.analyze:
text += "ANALYZE "
options.append('ANALYZE true')
if not element.timing:
options.append('TIMING false')
if element.buffers:
options.append('BUFFERS true')
if element.format != 'text':
options.append('FORMAT %s' % element.format)
if element.verbose:
options.append('VERBOSE true')
if not element.costs:
options.append('COSTS false')
if options:
text += '(%s) ' % ', '.join(options)
text += compiler.process(element.statement)
return text

View File

@@ -9,7 +9,7 @@ try:
except:
pass
from sqlalchemy import types
from sqlalchemy_utils import ImproperlyConfigured
from sqlalchemy_utils.exceptions import ImproperlyConfigured
from .scalar_coercible import ScalarCoercible

View File

@@ -1,6 +1,6 @@
import six
from sqlalchemy import types
from sqlalchemy_utils import ImproperlyConfigured
from sqlalchemy_utils.exceptions import ImproperlyConfigured
from .scalar_coercible import ScalarCoercible
colour = None

View File

@@ -11,7 +11,7 @@ except ImportError:
from sqlalchemy import types
from sqlalchemy_utils import ImproperlyConfigured
from sqlalchemy_utils.exceptions import ImproperlyConfigured
from .scalar_coercible import ScalarCoercible

View File

@@ -1,6 +1,6 @@
import six
import weakref
from sqlalchemy_utils import ImproperlyConfigured
from sqlalchemy_utils.exceptions import ImproperlyConfigured
from sqlalchemy import types
from sqlalchemy.dialects import postgresql, oracle
from .scalar_coercible import ScalarCoercible

View File

@@ -1,6 +1,6 @@
import six
from sqlalchemy import types
from sqlalchemy_utils import ImproperlyConfigured
from sqlalchemy_utils.exceptions import ImproperlyConfigured
from .scalar_coercible import ScalarCoercible

View File

@@ -1,6 +1,6 @@
import six
from sqlalchemy import types
from sqlalchemy_utils import ImproperlyConfigured
from sqlalchemy_utils.exceptions import ImproperlyConfigured
from .scalar_coercible import ScalarCoercible

View File

@@ -27,22 +27,62 @@ class ExpressionTestCase(TestCase):
self.Article = Article
def assert_startswith(self, query, query_part):
assert str(
query.compile(dialect=postgresql.dialect())
).startswith(query_part)
# Check that query executes properly
self.session.execute(query)
class TestExplain(ExpressionTestCase):
def test_render_explain(self):
assert str(
explain(self.session.query(self.Article)).compile(
dialect=postgresql.dialect()
)
).startswith('EXPLAIN SELECT')
self.assert_startswith(
explain(self.session.query(self.Article)),
'EXPLAIN SELECT'
)
def test_render_explain_with_analyze(self):
assert str(
explain(self.session.query(self.Article), analyze=True)
.compile(
dialect=postgresql.dialect()
)
).startswith('EXPLAIN ANALYZE SELECT')
self.assert_startswith(
explain(self.session.query(self.Article), analyze=True),
'EXPLAIN (ANALYZE true) SELECT'
)
def test_with_string_as_stmt_param(self):
self.assert_startswith(
explain('SELECT 1 FROM article'),
'EXPLAIN SELECT'
)
def test_format(self):
self.assert_startswith(
explain('SELECT 1 FROM article', format='json'),
'EXPLAIN (FORMAT json) SELECT'
)
def test_timing(self):
self.assert_startswith(
explain('SELECT 1 FROM article', analyze=True, timing=False),
'EXPLAIN (ANALYZE true, TIMING false) SELECT'
)
def test_verbose(self):
self.assert_startswith(
explain('SELECT 1 FROM article', verbose=True),
'EXPLAIN (VERBOSE true) SELECT'
)
def test_buffers(self):
self.assert_startswith(
explain('SELECT 1 FROM article', analyze=True, buffers=True),
'EXPLAIN (ANALYZE true, BUFFERS true) SELECT'
)
def test_costs(self):
self.assert_startswith(
explain('SELECT 1 FROM article', costs=False),
'EXPLAIN (COSTS false) SELECT'
)
class TestExplainAnalyze(ExpressionTestCase):
@@ -52,7 +92,7 @@ class TestExplainAnalyze(ExpressionTestCase):
.compile(
dialect=postgresql.dialect()
)
).startswith('EXPLAIN ANALYZE SELECT')
).startswith('EXPLAIN (ANALYZE true) SELECT')
class TestMatchTSVector(ExpressionTestCase):