Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Version 2.0.5
Unreleased

- Add SQLAlchemy 2.1 row security DDL and reflection support.

# Version 2.0.4
April 23, 2026
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Use `pip` to install the latest release of this dialect.
pip install sqlalchemy-cockroachdb
```

NOTE: This version of the dialect requires SQLAlchemy 2.0.x. To work with
NOTE: This version of the dialect requires SQLAlchemy 2.1.x. To work with
earlier versions of SQLAlchemy you'll need to install an earlier version of this
dialect.

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
},
packages=find_packages(include=["sqlalchemy_cockroachdb"]),
include_package_data=True,
install_requires=["SQLAlchemy>=2.0.47,<2.1"],
install_requires=["SQLAlchemy>=2.1.0b3,<2.2"],
zip_safe=False,
entry_points={
"sqlalchemy.dialects": [
Expand Down
5 changes: 5 additions & 0 deletions sqlalchemy_cockroachdb/ddl_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ def visit_computed_column(self, generated, **kw):
return "AS (%s) STORED" % self.sql_compiler.process(
generated.sqltext, include_table=False, literal_binds=True
)

def _format_policy_role(self, role):
if role == "CURRENT_ROLE":
raise exc.CompileError("CockroachDB row security does not support CURRENT_ROLE")
return super()._format_policy_role(role)
2 changes: 1 addition & 1 deletion test-requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ more-itertools
psycopg
psycopg2
pytest
sqlalchemy>=2.0.47,<2.1
sqlalchemy @ git+https://github.com/Pedrexus/sqlalchemy.git@1bbe0d64d6017b346a5e6f7bf784ba7a3294662f
4 changes: 1 addition & 3 deletions test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ exceptiongroup==1.3.1
# via pytest
futures==3.0.5
# via -r test-requirements.in
greenlet==3.5.0
# via sqlalchemy
iniconfig==2.3.0
# via pytest
mako==1.3.12
Expand All @@ -32,7 +30,7 @@ pygments==2.20.0
# via pytest
pytest==9.0.3
# via -r test-requirements.in
sqlalchemy==2.0.49
sqlalchemy @ git+https://github.com/Pedrexus/sqlalchemy.git@1bbe0d64d6017b346a5e6f7bf784ba7a3294662f
# via
# -r test-requirements.in
# alembic
Expand Down
77 changes: 77 additions & 0 deletions test/test_row_security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from sqlalchemy import Column
from sqlalchemy import exc
from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import Table
from sqlalchemy import testing
from sqlalchemy.dialects.postgresql import CreatePolicy
from sqlalchemy.dialects.postgresql import EnableRowLevelSecurity
from sqlalchemy.dialects.postgresql import ForceRowLevelSecurity
from sqlalchemy.dialects.postgresql import Policy
from sqlalchemy.testing import fixtures
from sqlalchemy.testing.assertions import AssertsCompiledSQL
from sqlalchemy.testing.assertions import eq_
from sqlalchemy.testing.assertions import expect_raises_message


class RowSecurityCompileTest(fixtures.TestBase, AssertsCompiledSQL):
__dialect__ = "cockroachdb"

def test_rejects_unsupported_current_role(self):
table = Table("item", MetaData(), Column("owner_id", Integer))
policy = Policy("read", table, roles=("CURRENT_ROLE",))

with expect_raises_message(
exc.CompileError,
"CockroachDB row security does not support CURRENT_ROLE",
):
self.assert_compile(CreatePolicy(policy), "")


class RowSecurityReflectionTest(fixtures.TestBase):
__only_on__ = "cockroachdb"
__requires__ = ("sync_driver",)

@testing.provide_metadata
def test_postgresql_compatible_reflection(self):
table = Table(
"row_security_reflection",
self.metadata,
Column("id", Integer, primary_key=True),
Column("owner_id", Integer),
)
with testing.db.begin() as connection:
table.create(connection)
connection.execute(EnableRowLevelSecurity(table))
connection.execute(ForceRowLevelSecurity(table))
connection.execute(
CreatePolicy(
Policy(
"read",
table,
command="SELECT",
using=table.c.owner_id == 7,
)
)
)

state = inspect(connection).get_row_security(table.name)

eq_(
state,
{
"enabled": True,
"forced": True,
"policies": [
{
"name": "read",
"command": "SELECT",
"roles": ["public"],
"using": "owner_id = 7:::INT8",
"check": None,
"permissive": True,
}
],
},
)