summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-01-13 14:33:33 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2015-01-13 14:33:33 -0500
commit92cc232726a01dd3beff762ebccd326a9659e8b9 (patch)
treea33faba2cdadd6f016feaff214fb8e2f5ecdbdb3 /lib/sqlalchemy
parentdc55ff6f99098450f20aa702a55ece30b7e5fc7c (diff)
downloadsqlalchemy-92cc232726a01dd3beff762ebccd326a9659e8b9.tar.gz
- The multi-values version of :meth:`.Insert.values` has been
repaired to work more usefully with tables that have Python- side default values and/or functions, as well as server-side defaults. The feature will now work with a dialect that uses "positional" parameters; a Python callable will also be invoked individually for each row just as is the case with an "executemany" style invocation; a server- side default column will no longer implicitly receive the value explicitly specified for the first row, instead refusing to invoke without an explicit value. fixes #3288
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/engine/default.py9
-rw-r--r--lib/sqlalchemy/sql/crud.py83
-rw-r--r--lib/sqlalchemy/sql/dml.py6
-rw-r--r--lib/sqlalchemy/testing/assertions.py3
4 files changed, 65 insertions, 36 deletions
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index a5af6ff19..c5b5deece 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -956,14 +956,17 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
def _process_executesingle_defaults(self):
key_getter = self.compiled._key_getters_for_crud_column[2]
-
prefetch = self.compiled.prefetch
self.current_parameters = compiled_parameters = \
self.compiled_parameters[0]
for c in prefetch:
if self.isinsert:
- val = self.get_insert_default(c)
+ if c.default and \
+ not c.default.is_sequence and c.default.is_scalar:
+ val = c.default.arg
+ else:
+ val = self.get_insert_default(c)
else:
val = self.get_update_default(c)
@@ -972,6 +975,4 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
del self.current_parameters
-
-
DefaultDialect.execution_ctx_cls = DefaultExecutionContext
diff --git a/lib/sqlalchemy/sql/crud.py b/lib/sqlalchemy/sql/crud.py
index 831d05be1..4bab69df0 100644
--- a/lib/sqlalchemy/sql/crud.py
+++ b/lib/sqlalchemy/sql/crud.py
@@ -116,11 +116,13 @@ def _get_crud_params(compiler, stmt, **kw):
def _create_bind_param(
- compiler, col, value, process=True, required=False, name=None):
+ compiler, col, value, process=True,
+ required=False, name=None, unique=False):
if name is None:
name = col.key
- bindparam = elements.BindParameter(name, value,
- type_=col.type, required=required)
+ bindparam = elements.BindParameter(
+ name, value,
+ type_=col.type, required=required, unique=unique)
bindparam._is_crud = True
if process:
bindparam = bindparam._compiler_dispatch(compiler)
@@ -299,14 +301,49 @@ def _append_param_insert_pk_returning(compiler, stmt, c, values, kw):
)
compiler.returning.append(c)
else:
- values.append(
- (c, _create_bind_param(compiler, c, None))
- )
- compiler.prefetch.append(c)
+ _create_prefetch_bind_param(compiler, c, values)
else:
compiler.returning.append(c)
+def _create_prefetch_bind_param(compiler, c, values, process=True, name=None):
+ values.append(
+ (c, _create_bind_param(compiler, c, None, process=process, name=name))
+ )
+ compiler.prefetch.append(c)
+
+
+class _multiparam_column(elements.ColumnElement):
+ def __init__(self, original, index):
+ self.key = "%s_%d" % (original.key, index + 1)
+ self.original = original
+ self.default = original.default
+
+ def __eq__(self, other):
+ return isinstance(other, _multiparam_column) and \
+ other.key == self.key and \
+ other.original == self.original
+
+
+def _process_multiparam_default_bind(
+ compiler, c, index, kw):
+
+ if not c.default:
+ raise exc.CompileError(
+ "INSERT value for column %s is explicitly rendered as a bound"
+ "parameter in the VALUES clause; "
+ "a Python-side value or SQL expression is required" % c)
+ elif c.default.is_clause_element:
+ return compiler.process(c.default.arg.self_group(), **kw)
+ else:
+ col = _multiparam_column(c, index)
+ bind = _create_bind_param(
+ compiler, col, None
+ )
+ compiler.prefetch.append(col)
+ return bind
+
+
def _append_param_insert_pk(compiler, stmt, c, values, kw):
if (
(c.default is not None and
@@ -317,11 +354,7 @@ def _append_param_insert_pk(compiler, stmt, c, values, kw):
compiler.dialect.
preexecute_autoincrement_sequences)
):
- values.append(
- (c, _create_bind_param(compiler, c, None))
- )
-
- compiler.prefetch.append(c)
+ _create_prefetch_bind_param(compiler, c, values)
def _append_param_insert_hasdefault(
@@ -349,10 +382,7 @@ def _append_param_insert_hasdefault(
# don't add primary key column to postfetch
compiler.postfetch.append(c)
else:
- values.append(
- (c, _create_bind_param(compiler, c, None))
- )
- compiler.prefetch.append(c)
+ _create_prefetch_bind_param(compiler, c, values)
def _append_param_insert_select_hasdefault(
@@ -368,10 +398,7 @@ def _append_param_insert_select_hasdefault(
proc = c.default.arg.self_group()
values.append((c, proc))
else:
- values.append(
- (c, _create_bind_param(compiler, c, None, process=False))
- )
- compiler.prefetch.append(c)
+ _create_prefetch_bind_param(compiler, c, values, process=False)
def _append_param_update(
@@ -389,10 +416,7 @@ def _append_param_update(
else:
compiler.postfetch.append(c)
else:
- values.append(
- (c, _create_bind_param(compiler, c, None))
- )
- compiler.prefetch.append(c)
+ _create_prefetch_bind_param(compiler, c, values)
elif c.server_onupdate is not None:
if implicit_return_defaults and \
c in implicit_return_defaults:
@@ -444,13 +468,7 @@ def _get_multitable_params(
)
compiler.postfetch.append(c)
else:
- values.append(
- (c, _create_bind_param(
- compiler, c, None, name=_col_bind_name(c)
- )
- )
- )
- compiler.prefetch.append(c)
+ _create_prefetch_bind_param(compiler, c, values, name=_col_bind_name(c))
elif c.server_onupdate is not None:
compiler.postfetch.append(c)
@@ -469,7 +487,8 @@ def _extend_values_for_multiparams(compiler, stmt, values, kw):
) if elements._is_literal(row[c.key])
else compiler.process(
row[c.key].self_group(), **kw))
- if c.key in row else param
+ if c.key in row else
+ _process_multiparam_default_bind(compiler, c, i, kw)
)
for (c, param) in values_0
]
diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py
index 62169319b..38b3b8c44 100644
--- a/lib/sqlalchemy/sql/dml.py
+++ b/lib/sqlalchemy/sql/dml.py
@@ -277,6 +277,12 @@ class ValuesBase(UpdateBase):
deals with an arbitrary number of rows, so the
:attr:`.ResultProxy.inserted_primary_key` accessor does not apply.
+ .. versionchanged:: 1.0.0 A multiple-VALUES INSERT now supports
+ columns with Python side default values and callables in the
+ same way as that of an "executemany" style of invocation; the
+ callable is invoked for each row. See :ref:`bug_3288`
+ for other details.
+
.. seealso::
:ref:`inserts_and_updates` - SQL Expression
diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py
index 66d1f3cb0..46fcd64b1 100644
--- a/lib/sqlalchemy/testing/assertions.py
+++ b/lib/sqlalchemy/testing/assertions.py
@@ -229,6 +229,7 @@ class AssertsCompiledSQL(object):
def assert_compile(self, clause, result, params=None,
checkparams=None, dialect=None,
checkpositional=None,
+ check_prefetch=None,
use_default_dialect=False,
allow_dialect_select=False,
literal_binds=False):
@@ -289,6 +290,8 @@ class AssertsCompiledSQL(object):
if checkpositional is not None:
p = c.construct_params(params)
eq_(tuple([p[x] for x in c.positiontup]), checkpositional)
+ if check_prefetch is not None:
+ eq_(c.prefetch, check_prefetch)
class ComparesTables(object):