@@ -41,9 +41,10 @@ differs from a direct translation in several places:
4141 while ``emails[value ne "x"] `` holds when one email is not ``x ``.
4242- A value selection, such as ``emails[type eq "work" and primary eq true] ``, applies all its
4343 conditions to the same entry.
44- - A string compares in the form the comparison key of the policy gives it. By default, the key
45- normalizes the string to NFC, and lowers it unless the schema declares it ``caseExact ``. Each
46- database reproduces the key its own way, as the next section describes.
44+ - A string compares in the form the comparison key of the policy gives it. By default, the
45+ comparison key normalizes the string to NFC, and lowers it unless the schema declares it
46+ ``caseExact ``. Each database reproduces the comparison key its own way, as the next section
47+ describes.
4748
4849An attribute the resource type does not declare matches no resource
4950(:rfc: `RFC 7644 §3.4.2.1 <7644#section-3.4.2.1 >`). An attribute it declares but the mapping does
@@ -54,35 +55,35 @@ Strings compare as the policy says
5455----------------------------------
5556
5657The :attr: `comparison_key <scim2_models.ScimPolicy.comparison_key> ` of a
57- :class: `~scim2_models.ScimPolicy ` gives the form strings are compared in. scim2-models applies it
58- in Python, to filters, sorts and PATCH operations. For the reasons behind the key, read
58+ :class: `~scim2_models.ScimPolicy ` gives the form strings are compared in. scim2-models applies it in
59+ Python, to filters, sorts and PATCH operations. For the reasons behind the comparison key, read
5960:doc: `scim2_models:explanation/comparisons `. The storage follows the policy of the provider it
60- receives, or the default policy without a provider. It reproduces the key in SQL, as far as the
61- database allows:
62-
63- - SQLite runs Python functions. The storage gives each connection a SQL function that calls the
64- key, so filters, sorts and uniqueness checks compare exactly as in Python, whatever the key.
65- The function runs once for each row, and no index serves it. The ``co ``, ``sw `` and ``ew ``
66- filters use ``instr() `` and ``substr() ``, since the ``LIKE `` of SQLite ignores the case of ASCII
67- letters, even for a ``caseExact `` attribute.
61+ receives, or the default policy without a provider. It reproduces the comparison key in SQL, as far
62+ as the database allows:
63+
64+ - SQLite runs Python functions. The storage gives each connection a SQL function that calls the key,
65+ so filters, sorts and uniqueness checks compare exactly as in Python, whatever the comparison key.
66+ The function runs once for each row, and no index serves it. The ``co ``, ``sw `` and ``ew `` filters
67+ use ``instr() `` and ``substr() ``, since the ``LIKE `` of SQLite ignores the case of ASCII letters,
68+ even for a ``caseExact `` attribute.
6869- PostgreSQL normalizes both sides to NFC with ``normalize() ``, and lowers them with ``lower() ``.
69- This approaches the default key without reaching it. ``lower() `` follows the locale of the
70- database, and only lowers ASCII letters under the ``C `` locale. Even under a UTF-8 locale, a
71- few letters differ, such as ``İ `` and the final sigma. ``normalize() `` needs PostgreSQL 13 and
72- a database encoded in UTF-8. The comparison uses the ``C `` collation, which orders strings by
73- code point, as scim2-models does, whatever the collation of the column. An index that serves
74- these filters is an index on the same expression, with the same collation. With a key other
75- than the default one, the storage warns that it only approaches the key .
70+ This approaches the default comparison key without reaching it. ``lower() `` follows the locale of
71+ the database, and only lowers ASCII letters under the ``C `` locale. Even under a UTF-8 locale, a
72+ few letters differ, such as ``İ `` and the final sigma. ``normalize() `` needs PostgreSQL 13 and a
73+ database encoded in UTF-8. The comparison uses the ``C `` collation, which orders strings by code
74+ point, as scim2-models does, whatever the collation of the column. An index that serves these
75+ filters is an index on the same expression, with the same collation. With a comparison key other
76+ than the default one, the storage warns that it only approaches it .
7677- Other databases lower both sides with their own ``lower() ``. MySQL and MariaDB are not
7778 supported: their default collations ignore the case and the accents, even for a ``caseExact ``
7879 attribute.
7980
8081Where ``LIKE `` serves the ``co ``, ``sw `` and ``ew `` filters, the ``% `` and ``_ `` characters of the
8182value are escaped, so that they match themselves.
8283
83- A string the key refuses is equal to no other. A filter comparing an attribute with such a
84- string holds only for ``ne ``. On SQLite, a stored string the key refuses also matches `` ne ``
85- only, and sorts with the missing values.
84+ A string the comparison key refuses is equal to no other. A filter comparing an attribute with such
85+ a string holds only for ``ne ``. On SQLite, a stored string the comparison key refuses also matches
86+ `` ne `` only, and sorts with the missing values.
8687
8788Sorting follows RFC 7644
8889------------------------
@@ -132,13 +133,12 @@ refuses an entry without ``value``: storing nothing for it would lose the link.
132133Versions protect concurrent writes
133134----------------------------------
134135
135- The version of a resource comes from the
136- :ref: `version counter <sqlalchemy:mapper_version_counter >` of its SQLAlchemy model. SQLAlchemy
137- increments the column on every write, and adds the version it read to the ``WHERE `` clause of the
138- ``UPDATE ``. The storage first compares the stored version with the one the client sent in
139- ``If-Match ``, and answers 412 when they differ. A write by another transaction between the read
140- and the write matches no row: SQLAlchemy raises :exc: `~sqlalchemy.orm.exc.StaleDataError `, which the storage turns into
141- a 412 too.
136+ The version of a resource comes from the :ref: `version counter <sqlalchemy:mapper_version_counter >`
137+ of its SQLAlchemy model. SQLAlchemy increments the column on every write, and adds the version it
138+ read to the ``WHERE `` clause of the ``UPDATE ``. The storage first compares the stored version with
139+ the one the client sent in ``If-Match ``, and answers 412 when they differ. A write by another
140+ transaction between the read and the write matches no row: SQLAlchemy raises
141+ :exc: `~sqlalchemy.orm.exc.StaleDataError `, which the storage turns into a 412 too.
142142
143143Unique values are checked twice
144144-------------------------------
@@ -153,15 +153,16 @@ taken. Any other :exc:`~sqlalchemy.exc.IntegrityError` goes through unchanged.
153153The application owns the transaction
154154------------------------------------
155155
156- The storage :ref: `flushes <sqlalchemy:session_flushing >` each write, so that the database fills the identifiers and checks its
157- constraints, and never commits. The application, or its web framework, already decides when a
158- request ends and whether its changes are kept. Each operation runs in a :ref: `savepoint <sqlalchemy:session_begin_nested >`, so that a
159- failed operation leaves the session usable, and a bulk request goes on after a failure.
156+ The storage :ref: `flushes <sqlalchemy:session_flushing >` each write, so that the database fills the
157+ identifiers and checks its constraints, and never commits. The application, or its web framework,
158+ already decides when a request ends and whether its changes are kept. Each operation runs in a
159+ :ref: `savepoint <sqlalchemy:session_begin_nested >`, so that a failed operation leaves the session
160+ usable, and a bulk request goes on after a failure.
160161
161162The asynchronous storage loads every collection of a record with the record. An
162- :class: `~sqlalchemy.ext.asyncio.AsyncSession ` cannot load a relationship
163- :ref: ` lazily <sqlalchemy:asyncio_orm_avoid_lazyloads >`, on first access. Both storages load the same way, so
164- they send the same queries.
163+ :class: `~sqlalchemy.ext.asyncio.AsyncSession ` cannot load a relationship :ref: ` lazily
164+ <sqlalchemy:asyncio_orm_avoid_lazyloads>`, on first access. Both storages load the same way, so they
165+ send the same queries.
165166
166167Limits
167168------
0 commit comments