--- /dev/null
+.. change::
+ :tags: schema
+ :tickets: 10247
+
+ Deprecate Oracle only parameters :paramref:`_schema.Sequence.order`,
+ :paramref:`_schema.Identity.order` and :paramref:`_schema.Identity.on_null`.
+ They should be configured using the dialect kwargs ``oracle_order`` and
+ ``oracle_on_null``.
or the association of a SEQUENCE with the column.
Specifying GENERATED AS IDENTITY (Oracle 12 and above)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Starting from version 12 Oracle can make use of identity columns using
the :class:`_sql.Identity` to specify the autoincrementing behavior::
incrementing value, etc.
In addition to the standard options, Oracle supports setting
:paramref:`_schema.Identity.always` to ``None`` to use the default
-generated mode, rendering GENERATED AS IDENTITY in the DDL. It also supports
-setting :paramref:`_schema.Identity.on_null` to ``True`` to specify ON NULL
-in conjunction with a 'BY DEFAULT' identity column.
+generated mode, rendering GENERATED AS IDENTITY in the DDL.
+Oracle also supports two custom options specified using dialect kwargs:
+
+* ``oracle_on_null``: when set to ``True`` renders ``ON NULL`` in conjunction
+ with a 'BY DEFAULT' identity column.
+* ``oracle_order``: when ``True``, renders the ORDER keyword, indicating the
+ identity is definitively ordered. May be necessary to provide deterministic
+ ordering using Oracle RAC.
Using a SEQUENCE (all Oracle versions)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
autoload_with=engine
)
+In addition to the standard options, Oracle supports the following custom
+option specified using dialect kwargs:
+
+* ``oracle_order``: when ``True``, renders the ORDER keyword, indicating the
+ sequence is definitively ordered. May be necessary to provide deterministic
+ ordering using Oracle RAC.
+
.. versionchanged:: 1.4 Added :class:`_schema.Identity` construct
in a :class:`_schema.Column` to specify the option of an autoincrementing
column.
text = text.replace("NO MINVALUE", "NOMINVALUE")
text = text.replace("NO MAXVALUE", "NOMAXVALUE")
text = text.replace("NO CYCLE", "NOCYCLE")
- if identity_options.order is not None:
- text += " ORDER" if identity_options.order else " NOORDER"
+ options = identity_options.dialect_options["oracle"]
+ if options.get("order") is not None:
+ text += " ORDER" if options["order"] else " NOORDER"
return text.strip()
def visit_computed_column(self, generated, **kw):
else:
kind = "ALWAYS" if identity.always else "BY DEFAULT"
text = "GENERATED %s" % kind
- if identity.on_null:
+ if identity.dialect_options["oracle"].get("on_null"):
text += " ON NULL"
text += " AS IDENTITY"
options = self.get_identity_options(identity)
{"resolve_synonyms": False, "on_commit": None, "compress": False},
),
(sa_schema.Index, {"bitmap": False, "compress": False}),
+ (sa_schema.Sequence, {"order": None}),
+ (sa_schema.Identity, {"order": None, "on_null": None}),
]
@util.deprecated_params(
parts = [p.strip() for p in identity_options.split(",")]
identity = {
"always": parts[0] == "ALWAYS",
- "on_null": default_on_null == "YES",
+ "oracle_on_null": default_on_null == "YES",
}
for part in parts[1:]:
elif "CACHE_SIZE" in option:
identity["cache"] = int(value)
elif "ORDER_FLAG" in option:
- identity["order"] = value == "Y"
+ identity["oracle_order"] = value == "Y"
return identity
@reflection.cache
)
-class IdentityOptions:
+class IdentityOptions(DialectKWArgs):
"""Defines options for a named database sequence or an identity column.
.. versionadded:: 1.3.18
cycle: Optional[bool] = None,
cache: Optional[int] = None,
order: Optional[bool] = None,
+ **dialect_kw: Any,
) -> None:
"""Construct a :class:`.IdentityOptions` object.
:param order: optional boolean value; if ``True``, renders the
ORDER keyword.
+ .. deprecated:: 2.0.21 Use ``oracle_order`` instead.
+
"""
self.start = start
self.increment = increment
self.nomaxvalue = nomaxvalue
self.cycle = cycle
self.cache = cache
- self.order = order
+ if order is not None:
+ if "oracle_order" in dialect_kw:
+ raise exc.ArgumentError(
+ "Cannot specify both 'order' and 'oracle_order'. "
+ "Plese use only 'oracle_order'."
+ )
+ dialect_kw["oracle_order"] = order
+ self._validate_dialect_kwargs(dialect_kw)
@property
def _increment_is_negative(self) -> bool:
return self.increment is not None and self.increment < 0
+ @property
+ def order(self) -> Optional[bool]:
+ """Alias of the ``dialect_kwargs`` ``'oracle_order'``.
+
+ .. deprecated:: 2.0.21 The 'order' attribute is deprecated.
+ """
+ value: Optional[bool] = self.dialect_kwargs.get("oracle_order")
+ return value
+
+ def _as_dict(self) -> Dict[str, Any]:
+ return {
+ k: v
+ for k, v in {
+ "start": self.start,
+ "increment": self.increment,
+ "minvalue": self.minvalue,
+ "maxvalue": self.maxvalue,
+ "nominvalue": self.nominvalue,
+ "nomaxvalue": self.nomaxvalue,
+ "cycle": self.cycle,
+ "cache": self.cache,
+ }.items()
+ if v != None
+ }
+
class Sequence(HasSchemaAttr, IdentityOptions, DefaultGenerator):
"""Represents a named database sequence.
column: Optional[Column[Any]]
data_type: Optional[TypeEngine[int]]
+ @util.deprecated_params(
+ order=(
+ "2.1",
+ "This parameter is supported only by Oracle, "
+ "use ``oracle_order`` instead.",
+ )
+ )
def __init__(
self,
name: str,
metadata: Optional[MetaData] = None,
quote_schema: Optional[bool] = None,
for_update: bool = False,
+ **dialect_kw: Any,
) -> None:
"""Construct a :class:`.Sequence` object.
cycle=cycle,
cache=cache,
order=order,
+ **dialect_kw,
)
self.column = None
self.name = quoted_name(name, quote)
def _copy(self) -> Sequence:
return Sequence(
name=self.name,
- start=self.start,
- increment=self.increment,
- minvalue=self.minvalue,
- maxvalue=self.maxvalue,
- nominvalue=self.nominvalue,
- nomaxvalue=self.nomaxvalue,
- cycle=self.cycle,
schema=self.schema,
- cache=self.cache,
- order=self.order,
data_type=self.data_type,
optional=self.optional,
metadata=self.metadata,
for_update=self.for_update,
+ **self._as_dict(),
+ **self.dialect_kwargs,
)
def _set_table(self, column: Column[Any], table: Table) -> None:
is_identity = True
+ @util.deprecated_params(
+ order=(
+ "2.1",
+ "This parameter is supported only by Oracle, "
+ "use ``oracle_order`` instead.",
+ ),
+ on_null=(
+ "2.1",
+ "This parameter is supported only by Oracle, "
+ "use ``oracle_on_null`` instead.",
+ ),
+ )
def __init__(
self,
- always: bool = False,
+ always: Optional[bool] = False,
on_null: Optional[bool] = None,
start: Optional[int] = None,
increment: Optional[int] = None,
cycle: Optional[bool] = None,
cache: Optional[int] = None,
order: Optional[bool] = None,
+ **dialect_kw: Any,
) -> None:
"""Construct a GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY DDL
construct to accompany a :class:`_schema.Column`.
ORDER keyword.
"""
+ self.dialect_options
+ if on_null is not None:
+ if "oracle_on_null" in dialect_kw:
+ raise exc.ArgumentError(
+ "Cannot specify both 'on_null' and 'oracle_on_null'. "
+ "Plese use only 'oracle_on_null'."
+ )
+ dialect_kw["oracle_on_null"] = on_null
+
IdentityOptions.__init__(
self,
start=start,
cycle=cycle,
cache=cache,
order=order,
+ **dialect_kw,
)
self.always = always
- self.on_null = on_null
self.column = None
+ @property
+ def on_null(self) -> Optional[bool]:
+ """Alias of the ``dialect_kwargs`` ``'oracle_on_null'``.
+
+ .. deprecated:: 2.0.21 The 'on_null' attribute is deprecated.
+ """
+ value: Optional[bool] = self.dialect_kwargs.get("oracle_on_null")
+ return value
+
def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
assert isinstance(parent, Column)
if not isinstance(
return self._copy(**kw)
def _copy(self, **kw: Any) -> Identity:
- i = Identity(
- always=self.always,
- on_null=self.on_null,
- start=self.start,
- increment=self.increment,
- minvalue=self.minvalue,
- maxvalue=self.maxvalue,
- nominvalue=self.nominvalue,
- nomaxvalue=self.nomaxvalue,
- cycle=self.cycle,
- cache=self.cache,
- order=self.order,
- )
+ i = Identity(**self._as_dict(), **self.dialect_kwargs)
return self._schema_item_copy(i)
+
+ def _as_dict(self) -> Dict[str, Any]:
+ return {
+ # always=None means something different than always=False
+ "always": self.always,
+ **super()._as_dict(),
+ }
from sqlalchemy.testing import assert_raises_message
from sqlalchemy.testing import AssertsCompiledSQL
from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_deprecated
from sqlalchemy.testing import fixtures
from sqlalchemy.testing.assertions import eq_ignore_whitespace
from sqlalchemy.testing.schema import Column
nominvalue=True,
nomaxvalue=True,
cycle=False,
- order=False,
),
),
)
schema.CreateTable(t),
"CREATE TABLE t (y INTEGER GENERATED ALWAYS AS IDENTITY "
"(INCREMENT BY 7 START WITH 4 NOMINVALUE NOMAXVALUE "
- "NOCYCLE NOORDER))",
+ "NOCYCLE))",
)
+ def test_column_identity_dialect_args(self):
+ m = MetaData()
+ t = Table(
+ "t",
+ m,
+ Column("y", Integer, Identity(cycle=True, oracle_order=False)),
+ Column("x", Integer, Identity(nomaxvalue=True, oracle_order=True)),
+ )
+ self.assert_compile(
+ schema.CreateTable(t),
+ "CREATE TABLE t ("
+ "y INTEGER GENERATED BY DEFAULT AS IDENTITY (CYCLE NOORDER), "
+ "x INTEGER GENERATED BY DEFAULT AS IDENTITY (NOMAXVALUE ORDER)"
+ ")",
+ )
+
+ def test_deprecated_options(self):
+ with expect_deprecated(
+ ".+use ``oracle_on_null`` instead",
+ ".+use ``oracle_order`` instead",
+ ):
+ idx = Identity(order=False, on_null=True)
+ eq_(idx.dialect_options["oracle"]["order"], False)
+ eq_(idx.dialect_options["oracle"]["on_null"], True)
+
+ def test_deprecated_attrs(self):
+ idx = Identity(oracle_order=True, oracle_on_null=True)
+ eq_(idx.order, True)
+ eq_(idx.on_null, True)
+
def test_column_identity_no_generated(self):
m = MetaData()
t = Table("t", m, Column("y", Integer, Identity(always=None)))
def test_column_identity_on_null(self, always, on_null, text):
m = MetaData()
t = Table(
- "t", m, Column("y", Integer, Identity(always, on_null=on_null))
+ "t",
+ m,
+ Column("y", Integer, Identity(always, oracle_on_null=on_null)),
)
self.assert_compile(
schema.CreateTable(t),
dialect=oracle.OracleDialect(),
)
+ def test_compile_dialect_args(self):
+ self.assert_compile(
+ ddl.CreateSequence(Sequence("my_seq", oracle_order=False)),
+ "CREATE SEQUENCE my_seq NOORDER",
+ dialect=oracle.OracleDialect(),
+ )
+ self.assert_compile(
+ ddl.CreateSequence(
+ Sequence("my_seq", nominvalue=True, oracle_order=True)
+ ),
+ "CREATE SEQUENCE my_seq NOMINVALUE ORDER",
+ dialect=oracle.OracleDialect(),
+ )
+
+ def test_deprecated_options(self):
+ with expect_deprecated(".+use ``oracle_order`` instead"):
+ seq = Sequence("foo", order=False)
+ eq_(seq.dialect_options["oracle"]["order"], False)
+
class RegexpTest(fixtures.TestBase, testing.AssertsCompiledSQL):
__dialect__ = "oracle"
@classmethod
def define_tables(cls, metadata):
- Table("t1", metadata, Column("id1", Integer, Identity(on_null=True)))
- Table("t2", metadata, Column("id2", Integer, Identity(order=True)))
+ Table(
+ "t1",
+ metadata,
+ Column("id1", Integer, Identity(oracle_on_null=True)),
+ )
+ Table(
+ "t2", metadata, Column("id2", Integer, Identity(oracle_order=True))
+ )
def test_reflect_identity(self):
insp = inspect(testing.db)
"always": False,
"start": 1,
"increment": 1,
- "on_null": False,
+ "oracle_on_null": False,
"maxvalue": 10**28 - 1,
"minvalue": 1,
"cycle": False,
"cache": 20,
- "order": False,
+ "oracle_order": False,
}
for col in insp.get_columns("t1") + insp.get_columns("t2"):
if col["name"] == "id1":
is_true("identity" in col)
exp = common.copy()
- exp["on_null"] = True
+ exp["oracle_on_null"] = True
eq_(col["identity"], exp)
if col["name"] == "id2":
is_true("identity" in col)
exp = common.copy()
- exp["order"] = True
+ exp["oracle_order"] = True
eq_(col["identity"], exp)
# this uses the connection dialect
__requires__ = ("identity_columns_standard",)
- def test_on_null(self):
- t = Table(
- "foo_table",
- MetaData(),
- Column(
- "foo",
- Integer(),
- Identity(always=False, on_null=True, start=42, cycle=True),
- ),
- )
- text = " ON NULL" if testing.against("oracle") else ""
- self.assert_compile(
- CreateTable(t),
- (
- "CREATE TABLE foo_table (foo INTEGER GENERATED BY DEFAULT"
- + text
- + " AS IDENTITY (START WITH 42 CYCLE))"
- ),
- )
-
class DefaultDialectIdentityDDL(_IdentityDDLFixture, fixtures.TestBase):
# this uses the default dialect
t = Table(
"foo_table",
MetaData(),
- Column("foo", Integer(), Identity("always", start=3)),
+ Column("foo", Integer(), Identity(always=True, start=3)),
)
t_exp = Table(
"foo_table",
Column(
"foo",
Integer(),
- Identity("always", start=3),
+ Identity(always=True, start=3),
primary_key=True,
autoincrement=autoincrement,
),
assert_raises_message(ArgumentError, text, fn, server_onupdate="42")
def test_to_metadata(self):
- identity1 = Identity("by default", cycle=True, start=123)
+ identity1 = Identity(always=False, cycle=True, start=123)
m = MetaData()
t = Table(
"t", m, Column("x", Integer), Column("y", Integer, identity1)
Sequence("foo_seq", minvalue=42, increment=-2),
"INCREMENT BY -2 MINVALUE 42",
),
- (
- # remove this when the `order` parameter is removed
- # issue #10207 - ensure ORDER does not render
- Sequence("foo_seq", order=True),
- "",
- ),
(
Sequence("foo_seq", minvalue=-42, increment=-2),
"INCREMENT BY -2 MINVALUE -42",