some issues may be moved to later milestones in order to allow
for a timely release.
- Document last updated: January 14, 2016
+ Document last updated: January 19, 2016
Introduction
============
:ticket:`3601`
+.. _change_3081:
+
+Stringify of Query will consult the Session for the correct dialect
+-------------------------------------------------------------------
+
+Calling ``str()`` on a :class:`.Query` object will consult the :class:`.Session`
+for the correct "bind" to use, in order to render the SQL that would be
+passed to the database. In particular this allows a :class:`.Query` that
+refers to dialect-specific SQL constructs to be renderable, assuming the
+:class:`.Query` is associated with an appropriate :class:`.Session`.
+Previously, this behavior would only take effect if the :class:`.MetaData`
+to which the mappings were associated were itself bound to the target
+:class:`.Engine`.
+
+If neither the underlying :class:`.MetaData` nor the :class:`.Session` are
+associated with any bound :class:`.Engine`, then the fallback to the
+"default" dialect is used to generate the SQL string.
+
+:ticket:`3081`
+
New Features and Improvements - Core
====================================
self.session._autoflush()
return self._execute_and_instances(context)
+ def __str__(self):
+ context = self._compile_context()
+ try:
+ bind = self._get_bind_args(
+ context, self.session.get_bind) if self.session else None
+ except sa_exc.UnboundExecutionError:
+ bind = None
+ return str(context.statement.compile(bind))
+
def _connection_from_session(self, **kw):
- conn = self.session.connection(
- **kw)
+ conn = self.session.connection(**kw)
if self._execution_options:
conn = conn.execution_options(**self._execution_options)
return conn
def _execute_and_instances(self, querycontext):
- conn = self._connection_from_session(
- mapper=self._bind_mapper(),
- clause=querycontext.statement,
+ conn = self._get_bind_args(
+ querycontext,
+ self._connection_from_session,
close_with_result=True)
result = conn.execute(querycontext.statement, self._params)
return loading.instances(querycontext.query, result, querycontext)
+ def _get_bind_args(self, querycontext, fn, **kw):
+ return fn(
+ mapper=self._bind_mapper(),
+ clause=querycontext.statement,
+ **kw
+ )
+
@property
def column_descriptions(self):
"""Return metadata about the columns which would be
sql.True_._ifnone(context.whereclause),
single_crit)
- def __str__(self):
- return str(self._compile_context().statement)
from ..sql.selectable import ForUpdateArg
from sqlalchemy import (
testing, null, exists, text, union, literal, literal_column, func, between,
Unicode, desc, and_, bindparam, select, distinct, or_, collate, insert,
- Integer, String, Boolean, exc as sa_exc, util, cast)
+ Integer, String, Boolean, exc as sa_exc, util, cast, MetaData)
from sqlalchemy.sql import operators, expression
from sqlalchemy import column, table
from sqlalchemy.engine import default
from sqlalchemy.testing.schema import Table, Column
import sqlalchemy as sa
from sqlalchemy.testing.assertions import (
- eq_, assert_raises, assert_raises_message, expect_warnings)
+ eq_, assert_raises, assert_raises_message, expect_warnings,
+ eq_ignore_whitespace)
from sqlalchemy.testing import fixtures, AssertsCompiledSQL, assert_warnings
from test.orm import _fixtures
from sqlalchemy.orm.util import join, with_parent
)
+class BindSensitiveStringifyTest(fixtures.TestBase):
+ def _fixture(self, bind_to=None):
+ # building a totally separate metadata /mapping here
+ # because we need to control if the MetaData is bound or not
+
+ class User(object):
+ pass
+
+ m = MetaData(bind=bind_to)
+ user_table = Table(
+ 'users', m,
+ Column('id', Integer, primary_key=True),
+ Column('name', String(50)))
+
+ mapper(User, user_table)
+ return User
+
+ def _dialect_fixture(self):
+ class MyDialect(default.DefaultDialect):
+ default_paramstyle = 'qmark'
+
+ from sqlalchemy.engine import base
+ return base.Engine(mock.Mock(), MyDialect(), mock.Mock())
+
+ def _test(
+ self, bound_metadata, bound_session,
+ session_present, expect_bound):
+ if bound_metadata or bound_session:
+ eng = self._dialect_fixture()
+ else:
+ eng = None
+
+ User = self._fixture(bind_to=eng if bound_metadata else None)
+
+ s = Session(eng if bound_session else None)
+ q = s.query(User).filter(User.id == 7)
+ if not session_present:
+ q = q.with_session(None)
+
+ eq_ignore_whitespace(
+ str(q),
+ "SELECT users.id AS users_id, users.name AS users_name "
+ "FROM users WHERE users.id = ?" if expect_bound else
+ "SELECT users.id AS users_id, users.name AS users_name "
+ "FROM users WHERE users.id = :id_1"
+ )
+
+ def test_query_unbound_metadata_bound_session(self):
+ self._test(False, True, True, True)
+
+ def test_query_bound_metadata_unbound_session(self):
+ self._test(True, False, True, True)
+
+ def test_query_unbound_metadata_no_session(self):
+ self._test(False, False, False, False)
+
+ def test_query_unbound_metadata_unbound_session(self):
+ self._test(False, False, True, False)
+
+ def test_query_bound_metadata_bound_session(self):
+ self._test(True, True, True, True)
+
+
class RawSelectTest(QueryTest, AssertsCompiledSQL):
__dialect__ = 'default'