添加注册登录功能

This commit is contained in:
2025-08-29 00:34:40 +08:00
parent 09065f2ce7
commit 2fe3474d9e
3060 changed files with 29217 additions and 87137 deletions

View File

@@ -266,7 +266,7 @@ will remain consistent with the state of the transaction::
from sqlalchemy import event
postgresql_engine = create_engine(
"postgresql+pyscopg2://scott:tiger@hostname/dbname",
"postgresql+psycopg2://scott:tiger@hostname/dbname",
# disable default reset-on-return scheme
pool_reset_on_return=None,
)
@@ -978,6 +978,8 @@ PostgreSQL-Specific Index Options
Several extensions to the :class:`.Index` construct are available, specific
to the PostgreSQL dialect.
.. _postgresql_covering_indexes:
Covering Indexes
^^^^^^^^^^^^^^^^
@@ -990,6 +992,10 @@ would render the index as ``CREATE INDEX my_index ON table (x) INCLUDE (y)``
Note that this feature requires PostgreSQL 11 or later.
.. seealso::
:ref:`postgresql_constraint_options`
.. versionadded:: 1.4
.. _postgresql_partial_indexes:
@@ -1264,6 +1270,65 @@ with selected constraint constructs:
<https://www.postgresql.org/docs/current/static/sql-altertable.html>`_ -
in the PostgreSQL documentation.
* ``INCLUDE``: This option adds one or more columns as a "payload" to the
unique index created automatically by PostgreSQL for the constraint.
For example, the following table definition::
Table(
"mytable",
metadata,
Column("id", Integer, nullable=False),
Column("value", Integer, nullable=False),
UniqueConstraint("id", postgresql_include=["value"]),
)
would produce the DDL statement
.. sourcecode:: sql
CREATE TABLE mytable (
id INTEGER NOT NULL,
value INTEGER NOT NULL,
UNIQUE (id) INCLUDE (value)
)
Note that this feature requires PostgreSQL 11 or later.
.. versionadded:: 2.0.41
.. seealso::
:ref:`postgresql_covering_indexes`
.. seealso::
`PostgreSQL CREATE TABLE options
<https://www.postgresql.org/docs/current/static/sql-createtable.html>`_ -
in the PostgreSQL documentation.
* Column list with foreign key ``ON DELETE SET`` actions: This applies to
:class:`.ForeignKey` and :class:`.ForeignKeyConstraint`, the :paramref:`.ForeignKey.ondelete`
parameter will accept on the PostgreSQL backend only a string list of column
names inside parenthesis, following the ``SET NULL`` or ``SET DEFAULT``
phrases, which will limit the set of columns that are subject to the
action::
fktable = Table(
"fktable",
metadata,
Column("tid", Integer),
Column("id", Integer),
Column("fk_id_del_set_null", Integer),
ForeignKeyConstraint(
columns=["tid", "fk_id_del_set_null"],
refcolumns=[pktable.c.tid, pktable.c.id],
ondelete="SET NULL (fk_id_del_set_null)",
),
)
.. versionadded:: 2.0.40
.. _postgresql_table_valued_overview:
Table values, Table and Column valued functions, Row and Tuple objects
@@ -1482,6 +1547,7 @@ from functools import lru_cache
import re
from typing import Any
from typing import cast
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
@@ -1672,6 +1738,7 @@ RESERVED_WORDS = {
"verbose",
}
colspecs = {
sqltypes.ARRAY: _array.ARRAY,
sqltypes.Interval: INTERVAL,
@@ -1788,6 +1855,8 @@ class PGCompiler(compiler.SQLCompiler):
}"""
def visit_array(self, element, **kw):
if not element.clauses and not element.type.item_type._isnull:
return "ARRAY[]::%s" % element.type.compile(self.dialect)
return "ARRAY[%s]" % self.visit_clauselist(element, **kw)
def visit_slice(self, element, **kw):
@@ -1811,9 +1880,23 @@ class PGCompiler(compiler.SQLCompiler):
kw["eager_grouping"] = True
return self._generate_generic_binary(
binary, " -> " if not _cast_applied else " ->> ", **kw
)
if (
not _cast_applied
and isinstance(binary.left.type, _json.JSONB)
and self.dialect._supports_jsonb_subscripting
):
# for pg14+JSONB use subscript notation: col['key'] instead
# of col -> 'key'
return "%s[%s]" % (
self.process(binary.left, **kw),
self.process(binary.right, **kw),
)
else:
# Fall back to arrow notation for older versions or when cast
# is applied
return self._generate_generic_binary(
binary, " -> " if not _cast_applied else " ->> ", **kw
)
def visit_json_path_getitem_op_binary(
self, binary, operator, _cast_applied=False, **kw
@@ -2001,9 +2084,10 @@ class PGCompiler(compiler.SQLCompiler):
for c in select._for_update_arg.of:
tables.update(sql_util.surface_selectables_only(c))
of_kw = dict(kw)
of_kw.update(ashint=True, use_schema=False)
tmp += " OF " + ", ".join(
self.process(table, ashint=True, use_schema=False, **kw)
for table in tables
self.process(table, **of_kw) for table in tables
)
if select._for_update_arg.nowait:
@@ -2232,6 +2316,18 @@ class PGDDLCompiler(compiler.DDLCompiler):
not_valid = constraint.dialect_options["postgresql"]["not_valid"]
return " NOT VALID" if not_valid else ""
def _define_include(self, obj):
includeclause = obj.dialect_options["postgresql"]["include"]
if not includeclause:
return ""
inclusions = [
obj.table.c[col] if isinstance(col, str) else col
for col in includeclause
]
return " INCLUDE (%s)" % ", ".join(
[self.preparer.quote(c.name) for c in inclusions]
)
def visit_check_constraint(self, constraint, **kw):
if constraint._type_bound:
typ = list(constraint.columns)[0].type
@@ -2255,6 +2351,29 @@ class PGDDLCompiler(compiler.DDLCompiler):
text += self._define_constraint_validity(constraint)
return text
def visit_primary_key_constraint(self, constraint, **kw):
text = super().visit_primary_key_constraint(constraint)
text += self._define_include(constraint)
return text
def visit_unique_constraint(self, constraint, **kw):
text = super().visit_unique_constraint(constraint)
text += self._define_include(constraint)
return text
@util.memoized_property
def _fk_ondelete_pattern(self):
return re.compile(
r"^(?:RESTRICT|CASCADE|SET (?:NULL|DEFAULT)(?:\s*\(.+\))?"
r"|NO ACTION)$",
re.I,
)
def define_constraint_ondelete_cascade(self, constraint):
return " ON DELETE %s" % self.preparer.validate_sql_phrase(
constraint.ondelete, self._fk_ondelete_pattern
)
def visit_create_enum_type(self, create, **kw):
type_ = create.element
@@ -2356,15 +2475,7 @@ class PGDDLCompiler(compiler.DDLCompiler):
)
)
includeclause = index.dialect_options["postgresql"]["include"]
if includeclause:
inclusions = [
index.table.c[col] if isinstance(col, str) else col
for col in includeclause
]
text += " INCLUDE (%s)" % ", ".join(
[preparer.quote(c.name) for c in inclusions]
)
text += self._define_include(index)
nulls_not_distinct = index.dialect_options["postgresql"][
"nulls_not_distinct"
@@ -3112,9 +3223,16 @@ class PGDialect(default.DefaultDialect):
"not_valid": False,
},
),
(
schema.PrimaryKeyConstraint,
{"include": None},
),
(
schema.UniqueConstraint,
{"nulls_not_distinct": None},
{
"include": None,
"nulls_not_distinct": None,
},
),
]
@@ -3123,6 +3241,7 @@ class PGDialect(default.DefaultDialect):
_backslash_escapes = True
_supports_create_index_concurrently = True
_supports_drop_index_concurrently = True
_supports_jsonb_subscripting = True
def __init__(
self,
@@ -3151,6 +3270,8 @@ class PGDialect(default.DefaultDialect):
)
self.supports_identity_columns = self.server_version_info >= (10,)
self._supports_jsonb_subscripting = self.server_version_info >= (14,)
def get_isolation_level_values(self, dbapi_conn):
# note the generic dialect doesn't have AUTOCOMMIT, however
# all postgresql dialects should include AUTOCOMMIT.
@@ -3601,6 +3722,7 @@ class PGDialect(default.DefaultDialect):
pg_catalog.pg_sequence.c.seqcache,
"cycle",
pg_catalog.pg_sequence.c.seqcycle,
type_=sqltypes.JSON(),
)
)
.select_from(pg_catalog.pg_sequence)
@@ -3742,8 +3864,8 @@ class PGDialect(default.DefaultDialect):
def _reflect_type(
self,
format_type: Optional[str],
domains: dict[str, ReflectedDomain],
enums: dict[str, ReflectedEnum],
domains: Dict[str, ReflectedDomain],
enums: Dict[str, ReflectedEnum],
type_description: str,
) -> sqltypes.TypeEngine[Any]:
"""
@@ -3813,7 +3935,8 @@ class PGDialect(default.DefaultDialect):
charlen = int(attype_args[0])
args = (charlen,)
elif attype.startswith("interval"):
# a domain or enum can start with interval, so be mindful of that.
elif attype == "interval" or attype.startswith("interval "):
schema_type = INTERVAL
field_match = re.match(r"interval (.+)", attype)
@@ -3830,7 +3953,6 @@ class PGDialect(default.DefaultDialect):
schema_type = ENUM
enum = enums[enum_or_domain_key]
args = tuple(enum["labels"])
kwargs["name"] = enum["name"]
if not enum["visible"]:
@@ -3995,21 +4117,35 @@ class PGDialect(default.DefaultDialect):
result = connection.execute(oid_q, params)
return result.all()
@lru_cache()
def _constraint_query(self, is_unique):
@util.memoized_property
def _constraint_query(self):
if self.server_version_info >= (11, 0):
indnkeyatts = pg_catalog.pg_index.c.indnkeyatts
else:
indnkeyatts = pg_catalog.pg_index.c.indnatts.label("indnkeyatts")
if self.server_version_info >= (15,):
indnullsnotdistinct = pg_catalog.pg_index.c.indnullsnotdistinct
else:
indnullsnotdistinct = sql.false().label("indnullsnotdistinct")
con_sq = (
select(
pg_catalog.pg_constraint.c.conrelid,
pg_catalog.pg_constraint.c.conname,
pg_catalog.pg_constraint.c.conindid,
sql.func.unnest(pg_catalog.pg_constraint.c.conkey).label(
"attnum"
),
sql.func.unnest(pg_catalog.pg_index.c.indkey).label("attnum"),
sql.func.generate_subscripts(
pg_catalog.pg_constraint.c.conkey, 1
pg_catalog.pg_index.c.indkey, 1
).label("ord"),
indnkeyatts,
indnullsnotdistinct,
pg_catalog.pg_description.c.description,
)
.join(
pg_catalog.pg_index,
pg_catalog.pg_constraint.c.conindid
== pg_catalog.pg_index.c.indexrelid,
)
.outerjoin(
pg_catalog.pg_description,
pg_catalog.pg_description.c.objoid
@@ -4018,6 +4154,9 @@ class PGDialect(default.DefaultDialect):
.where(
pg_catalog.pg_constraint.c.contype == bindparam("contype"),
pg_catalog.pg_constraint.c.conrelid.in_(bindparam("oids")),
# NOTE: filtering also on pg_index.indrelid for oids does
# not seem to have a performance effect, but it may be an
# option if perf problems are reported
)
.subquery("con")
)
@@ -4026,9 +4165,10 @@ class PGDialect(default.DefaultDialect):
select(
con_sq.c.conrelid,
con_sq.c.conname,
con_sq.c.conindid,
con_sq.c.description,
con_sq.c.ord,
con_sq.c.indnkeyatts,
con_sq.c.indnullsnotdistinct,
pg_catalog.pg_attribute.c.attname,
)
.select_from(pg_catalog.pg_attribute)
@@ -4051,7 +4191,7 @@ class PGDialect(default.DefaultDialect):
.subquery("attr")
)
constraint_query = (
return (
select(
attr_sq.c.conrelid,
sql.func.array_agg(
@@ -4063,31 +4203,15 @@ class PGDialect(default.DefaultDialect):
).label("cols"),
attr_sq.c.conname,
sql.func.min(attr_sq.c.description).label("description"),
sql.func.min(attr_sq.c.indnkeyatts).label("indnkeyatts"),
sql.func.bool_and(attr_sq.c.indnullsnotdistinct).label(
"indnullsnotdistinct"
),
)
.group_by(attr_sq.c.conrelid, attr_sq.c.conname)
.order_by(attr_sq.c.conrelid, attr_sq.c.conname)
)
if is_unique:
if self.server_version_info >= (15,):
constraint_query = constraint_query.join(
pg_catalog.pg_index,
attr_sq.c.conindid == pg_catalog.pg_index.c.indexrelid,
).add_columns(
sql.func.bool_and(
pg_catalog.pg_index.c.indnullsnotdistinct
).label("indnullsnotdistinct")
)
else:
constraint_query = constraint_query.add_columns(
sql.false().label("indnullsnotdistinct")
)
else:
constraint_query = constraint_query.add_columns(
sql.null().label("extra")
)
return constraint_query
def _reflect_constraint(
self, connection, contype, schema, filter_names, scope, kind, **kw
):
@@ -4103,26 +4227,42 @@ class PGDialect(default.DefaultDialect):
batches[0:3000] = []
result = connection.execute(
self._constraint_query(is_unique),
self._constraint_query,
{"oids": [r[0] for r in batch], "contype": contype},
)
).mappings()
result_by_oid = defaultdict(list)
for oid, cols, constraint_name, comment, extra in result:
result_by_oid[oid].append(
(cols, constraint_name, comment, extra)
)
for row_dict in result:
result_by_oid[row_dict["conrelid"]].append(row_dict)
for oid, tablename in batch:
for_oid = result_by_oid.get(oid, ())
if for_oid:
for cols, constraint, comment, extra in for_oid:
if is_unique:
yield tablename, cols, constraint, comment, {
"nullsnotdistinct": extra
}
for row in for_oid:
# See note in get_multi_indexes
all_cols = row["cols"]
indnkeyatts = row["indnkeyatts"]
if len(all_cols) > indnkeyatts:
inc_cols = all_cols[indnkeyatts:]
cst_cols = all_cols[:indnkeyatts]
else:
yield tablename, cols, constraint, comment, None
inc_cols = []
cst_cols = all_cols
opts = {}
if self.server_version_info >= (11,):
opts["postgresql_include"] = inc_cols
if is_unique:
opts["postgresql_nulls_not_distinct"] = row[
"indnullsnotdistinct"
]
yield (
tablename,
cst_cols,
row["conname"],
row["description"],
opts,
)
else:
yield tablename, None, None, None, None
@@ -4148,20 +4288,27 @@ class PGDialect(default.DefaultDialect):
# only a single pk can be present for each table. Return an entry
# even if a table has no primary key
default = ReflectionDefaults.pk_constraint
def pk_constraint(pk_name, cols, comment, opts):
info = {
"constrained_columns": cols,
"name": pk_name,
"comment": comment,
}
if opts:
info["dialect_options"] = opts
return info
return (
(
(schema, table_name),
(
{
"constrained_columns": [] if cols is None else cols,
"name": pk_name,
"comment": comment,
}
pk_constraint(pk_name, cols, comment, opts)
if pk_name is not None
else default()
),
)
for table_name, cols, pk_name, comment, _ in result
for table_name, cols, pk_name, comment, opts in result
)
@reflection.cache
@@ -4255,7 +4402,8 @@ class PGDialect(default.DefaultDialect):
r"[\s]?(ON UPDATE "
r"(CASCADE|RESTRICT|NO ACTION|SET NULL|SET DEFAULT)+)?"
r"[\s]?(ON DELETE "
r"(CASCADE|RESTRICT|NO ACTION|SET NULL|SET DEFAULT)+)?"
r"(CASCADE|RESTRICT|NO ACTION|"
r"SET (?:NULL|DEFAULT)(?:\s\(.+\))?)+)?"
r"[\s]?(DEFERRABLE|NOT DEFERRABLE)?"
r"[\s]?(INITIALLY (DEFERRED|IMMEDIATE)+)?"
)
@@ -4371,7 +4519,10 @@ class PGDialect(default.DefaultDialect):
@util.memoized_property
def _index_query(self):
pg_class_index = pg_catalog.pg_class.alias("cls_idx")
# NOTE: pg_index is used as from two times to improve performance,
# since extraing all the index information from `idx_sq` to avoid
# the second pg_index use leads to a worse performing query in
# particular when querying for a single table (as of pg 17)
# NOTE: repeating oids clause improve query performance
# subquery to get the columns
@@ -4380,6 +4531,9 @@ class PGDialect(default.DefaultDialect):
pg_catalog.pg_index.c.indexrelid,
pg_catalog.pg_index.c.indrelid,
sql.func.unnest(pg_catalog.pg_index.c.indkey).label("attnum"),
sql.func.unnest(pg_catalog.pg_index.c.indclass).label(
"att_opclass"
),
sql.func.generate_subscripts(
pg_catalog.pg_index.c.indkey, 1
).label("ord"),
@@ -4411,6 +4565,8 @@ class PGDialect(default.DefaultDialect):
else_=pg_catalog.pg_attribute.c.attname.cast(TEXT),
).label("element"),
(idx_sq.c.attnum == 0).label("is_expr"),
pg_catalog.pg_opclass.c.opcname,
pg_catalog.pg_opclass.c.opcdefault,
)
.select_from(idx_sq)
.outerjoin(
@@ -4421,6 +4577,10 @@ class PGDialect(default.DefaultDialect):
pg_catalog.pg_attribute.c.attrelid == idx_sq.c.indrelid,
),
)
.outerjoin(
pg_catalog.pg_opclass,
pg_catalog.pg_opclass.c.oid == idx_sq.c.att_opclass,
)
.where(idx_sq.c.indrelid.in_(bindparam("oids")))
.subquery("idx_attr")
)
@@ -4435,6 +4595,12 @@ class PGDialect(default.DefaultDialect):
sql.func.array_agg(
aggregate_order_by(attr_sq.c.is_expr, attr_sq.c.ord)
).label("elements_is_expr"),
sql.func.array_agg(
aggregate_order_by(attr_sq.c.opcname, attr_sq.c.ord)
).label("elements_opclass"),
sql.func.array_agg(
aggregate_order_by(attr_sq.c.opcdefault, attr_sq.c.ord)
).label("elements_opdefault"),
)
.group_by(attr_sq.c.indexrelid)
.subquery("idx_cols")
@@ -4443,7 +4609,7 @@ class PGDialect(default.DefaultDialect):
if self.server_version_info >= (11, 0):
indnkeyatts = pg_catalog.pg_index.c.indnkeyatts
else:
indnkeyatts = sql.null().label("indnkeyatts")
indnkeyatts = pg_catalog.pg_index.c.indnatts.label("indnkeyatts")
if self.server_version_info >= (15,):
nulls_not_distinct = pg_catalog.pg_index.c.indnullsnotdistinct
@@ -4453,13 +4619,13 @@ class PGDialect(default.DefaultDialect):
return (
select(
pg_catalog.pg_index.c.indrelid,
pg_class_index.c.relname.label("relname_index"),
pg_catalog.pg_class.c.relname,
pg_catalog.pg_index.c.indisunique,
pg_catalog.pg_constraint.c.conrelid.is_not(None).label(
"has_constraint"
),
pg_catalog.pg_index.c.indoption,
pg_class_index.c.reloptions,
pg_catalog.pg_class.c.reloptions,
pg_catalog.pg_am.c.amname,
# NOTE: pg_get_expr is very fast so this case has almost no
# performance impact
@@ -4477,6 +4643,8 @@ class PGDialect(default.DefaultDialect):
nulls_not_distinct,
cols_sq.c.elements,
cols_sq.c.elements_is_expr,
cols_sq.c.elements_opclass,
cols_sq.c.elements_opdefault,
)
.select_from(pg_catalog.pg_index)
.where(
@@ -4484,12 +4652,12 @@ class PGDialect(default.DefaultDialect):
~pg_catalog.pg_index.c.indisprimary,
)
.join(
pg_class_index,
pg_catalog.pg_index.c.indexrelid == pg_class_index.c.oid,
pg_catalog.pg_class,
pg_catalog.pg_index.c.indexrelid == pg_catalog.pg_class.c.oid,
)
.join(
pg_catalog.pg_am,
pg_class_index.c.relam == pg_catalog.pg_am.c.oid,
pg_catalog.pg_class.c.relam == pg_catalog.pg_am.c.oid,
)
.outerjoin(
cols_sq,
@@ -4506,7 +4674,9 @@ class PGDialect(default.DefaultDialect):
== sql.any_(_array.array(("p", "u", "x"))),
),
)
.order_by(pg_catalog.pg_index.c.indrelid, pg_class_index.c.relname)
.order_by(
pg_catalog.pg_index.c.indrelid, pg_catalog.pg_class.c.relname
)
)
def get_multi_indexes(
@@ -4541,17 +4711,19 @@ class PGDialect(default.DefaultDialect):
continue
for row in result_by_oid[oid]:
index_name = row["relname_index"]
index_name = row["relname"]
table_indexes = indexes[(schema, table_name)]
all_elements = row["elements"]
all_elements_is_expr = row["elements_is_expr"]
all_elements_opclass = row["elements_opclass"]
all_elements_opdefault = row["elements_opdefault"]
indnkeyatts = row["indnkeyatts"]
# "The number of key columns in the index, not counting any
# included columns, which are merely stored and do not
# participate in the index semantics"
if indnkeyatts and len(all_elements) > indnkeyatts:
if len(all_elements) > indnkeyatts:
# this is a "covering index" which has INCLUDE columns
# as well as regular index columns
inc_cols = all_elements[indnkeyatts:]
@@ -4566,10 +4738,18 @@ class PGDialect(default.DefaultDialect):
not is_expr
for is_expr in all_elements_is_expr[indnkeyatts:]
)
idx_elements_opclass = all_elements_opclass[
:indnkeyatts
]
idx_elements_opdefault = all_elements_opdefault[
:indnkeyatts
]
else:
idx_elements = all_elements
idx_elements_is_expr = all_elements_is_expr
inc_cols = []
idx_elements_opclass = all_elements_opclass
idx_elements_opdefault = all_elements_opdefault
index = {"name": index_name, "unique": row["indisunique"]}
if any(idx_elements_is_expr):
@@ -4583,6 +4763,19 @@ class PGDialect(default.DefaultDialect):
else:
index["column_names"] = idx_elements
dialect_options = {}
if not all(idx_elements_opdefault):
dialect_options["postgresql_ops"] = {
name: opclass
for name, opclass, is_default in zip(
idx_elements,
idx_elements_opclass,
idx_elements_opdefault,
)
if not is_default
}
sorting = {}
for col_index, col_flags in enumerate(row["indoption"]):
col_sorting = ()
@@ -4602,7 +4795,6 @@ class PGDialect(default.DefaultDialect):
if row["has_constraint"]:
index["duplicates_constraint"] = index_name
dialect_options = {}
if row["reloptions"]:
dialect_options["postgresql_with"] = dict(
[
@@ -4681,12 +4873,7 @@ class PGDialect(default.DefaultDialect):
"comment": comment,
}
if options:
if options["nullsnotdistinct"]:
uc_dict["dialect_options"] = {
"postgresql_nulls_not_distinct": options[
"nullsnotdistinct"
]
}
uc_dict["dialect_options"] = options
uniques[(schema, table_name)].append(uc_dict)
return uniques.items()
@@ -5010,11 +5197,12 @@ class PGDialect(default.DefaultDialect):
key=lambda t: t[0],
)
for name, def_ in sorted_constraints:
# constraint is in the form "CHECK (expression)".
# constraint is in the form "CHECK (expression)"
# or "NOT NULL". Ignore the "NOT NULL" and
# remove "CHECK (" and the tailing ")".
check = def_[7:-1]
constraints.append({"name": name, "check": check})
if def_.casefold().startswith("check"):
check = def_[7:-1]
constraints.append({"name": name, "check": check})
domain_rec: ReflectedDomain = {
"name": domain["name"],
"schema": domain["schema"],