Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
2d10c50
fix(types): refuse a non-boolean unsigned when the type is rendered
vistart Oct 11, 2026
98f1339
refactor(sqlserver): read declared fields directly instead of probing…
vistart Oct 11, 2026
4bfcb42
refactor(sqlserver): follow the statement and expression renames
vistart Oct 14, 2026
017db9a
refactor(sqlserver): migrate constraints onto the two-level branch nodes
vistart Oct 14, 2026
e487241
test(sqlserver): pin the constraint switches to their measurements
vistart Oct 14, 2026
38387c7
refactor(sqlserver): split the wildcard by grammar position
vistart Oct 14, 2026
8a7613c
test(sqlserver): carry the wildcard-split follow-ups into the roundtr…
vistart Oct 14, 2026
f7d85c8
ci: point testsuite at the matching branch
vistart Oct 14, 2026
0c5f554
docs(sqlserver): carry the renames into the README and docs
vistart Oct 14, 2026
daa1bcb
refactor(expression): follow the core renames and the split registry
vistart Oct 16, 2026
da73e38
fix(sqlserver): register the type nodes with the kind registry
vistart Oct 17, 2026
8580524
refactor(sqlserver): read declared fields instead of probing with get…
vistart Oct 17, 2026
d7c4931
fix(sqlserver): register the type nodes with the read-path registry
vistart Oct 17, 2026
d9dee26
feat(sqlserver): adopt the typed value nodes with SQL Server literal …
vistart Oct 18, 2026
e302e9e
fix(sqlserver): follow the classed function entry points and the bare…
vistart Oct 18, 2026
0fb6fa3
refactor(sqlserver): follow the literal architecture the core settled
vistart Oct 19, 2026
dcdbf6e
refactor(sqlserver): read each node's own parameters and follow the r…
vistart Oct 20, 2026
40a0875
refactor(sqlserver): audit the kinds this backend overrides and owns
vistart Oct 20, 2026
23cecb0
test(sqlserver): reconcile the roundtrip matrix with the plain-JSON a…
vistart Oct 20, 2026
16836c8
test(sqlserver): move live-connection checks out of expression tests
vistart Oct 20, 2026
b186d62
test(sqlserver): load the round-trip harness by path, not by name
vistart Oct 20, 2026
dc2dfb2
test(sqlserver): keep the literal spellings file whole when it loses …
vistart Oct 20, 2026
fec0537
refactor(sqlserver): name a lock strength by the phrase SQL would say
vistart Oct 20, 2026
a2cafc0
test(sqlserver): read a UNIQUEIDENTIFIER back as the value it is
vistart Oct 20, 2026
53b8bab
refactor(sqlserver): drop the imports the matrix no longer names
vistart Oct 20, 2026
63330b7
fix(sqlserver): pass the LIKE operator as its enum, not the SQL keyword
vistart Oct 20, 2026
c3bce24
refactor(sqlserver): build introspection queries from nodes, not text
vistart Oct 20, 2026
0d62615
chore: ignore .worktree/, the branch toolchain directories
vistart Oct 21, 2026
c702fe9
fix(sqlserver): refuse the CTAS spelling SQL Server has no statement for
vistart Oct 21, 2026
88e02bc
test(sqlserver): drop the artifact-gap rows whose gap has closed
vistart Oct 21, 2026
8c18c11
fix(sqlserver): the dialect surface, and the type positions it decides
vistart Oct 22, 2026
1b7a3f9
refactor(expression)!: follow core's statement rename
vistart Oct 22, 2026
8fd04e4
refactor(ddl)!: the SQL Server create-table node is a statement
vistart Oct 22, 2026
a002514
refactor(expression)!: name statement nodes, kinds and formatters alike
vistart Oct 23, 2026
b806e29
refactor(backend)!: call the core helpers by their public names
vistart Oct 23, 2026
2a8e3d2
fix(introspection): take the cache from the manager, not the mixin
vistart Oct 23, 2026
e2f362b
fix(introspection): call the introspector by the names core defines
vistart Oct 23, 2026
b9ecb80
docs(cli): say what introspect can ask about
vistart Oct 24, 2026
b9d782b
feat(introspection): declare SQL Server's catalogue capabilities
vistart Oct 24, 2026
a553f32
feat(introspection): declare Firebird's generators and domains
vistart Oct 24, 2026
f06e2d9
revert(cli): let each backend name its own types, and drop --schema
vistart Oct 24, 2026
2d296a6
fix(introspection): restore two returns displaced by inserted switches
vistart Oct 24, 2026
2602a16
refactor(sqlserver)!: drop the underscores from the introspection con…
vistart Oct 25, 2026
0d7ba94
refactor(sqlserver): write the catalogue questions in the dialect
vistart Oct 25, 2026
b3f13b9
refactor(sqlserver): the backend-only catalogue questions get stateme…
vistart Oct 25, 2026
405bde6
fix(sqlserver): the extended-property statement defaults to a rendera…
vistart Oct 25, 2026
0fc5d4c
feat(cli)!: the introspect table aggregate is gone
vistart Oct 26, 2026
a98b66f
test(cli): the tests stop asking for the removed aggregate
vistart Oct 26, 2026
ffa1483
test(cli): restore the fixture the removal took with it
vistart Oct 26, 2026
26a0b2c
feat(introspection): a system table is not a base table
vistart Oct 27, 2026
7daabc3
test(transaction): the shim describes refusal, not a warning
vistart Oct 27, 2026
5a19e9f
fix(introspection): a view's definition is read where the catalogue k…
vistart Oct 30, 2026
3d65346
fix(introspection): a trigger says what fires it and whether it is on
vistart Oct 30, 2026
695126d
feat(introspection): the extended properties, asked by table name
vistart Oct 30, 2026
74b1350
feat(introspection): the table and column comments come from where th…
vistart Oct 30, 2026
a8ad8a4
feat(cli): the introspect types come with their capability switches
vistart Oct 30, 2026
21f9b39
docs(cli): twelve introspect types and what NAME means for each
vistart Oct 30, 2026
3470bef
test(expression): the extended-properties question is registered, not…
vistart Oct 30, 2026
d992c02
ci: install core from the release branch now that #127 has merged
vistart Oct 30, 2026
1118442
docs(changelog): the fragments for #9
vistart Oct 31, 2026
8b0c7bd
docs(changelog): the carried fragments belong to this pull request
vistart Oct 31, 2026
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
18 changes: 9 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

Check warning on line 63 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4h&open=AaGQaZ3sM0eP3oF9qj4h&pullRequest=9

- name: Create test database
run: |
Expand Down Expand Up @@ -150,7 +150,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

Check warning on line 153 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4j&open=AaGQaZ3sM0eP3oF9qj4j&pullRequest=9

Check warning on line 153 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4i&open=AaGQaZ3sM0eP3oF9qj4i&pullRequest=9

- name: Create test database
run: |
Expand Down Expand Up @@ -240,7 +240,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

Check warning on line 243 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4k&open=AaGQaZ3sM0eP3oF9qj4k&pullRequest=9

- name: Create test database
run: |
Expand Down Expand Up @@ -330,7 +330,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

Check warning on line 333 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4n&open=AaGQaZ3sM0eP3oF9qj4n&pullRequest=9

- name: Create test database
run: |
Expand Down Expand Up @@ -420,7 +420,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

Check warning on line 423 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4p&open=AaGQaZ3sM0eP3oF9qj4p&pullRequest=9

Check warning on line 423 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4o&open=AaGQaZ3sM0eP3oF9qj4o&pullRequest=9

- name: Create test database
run: |
Expand Down Expand Up @@ -510,7 +510,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

Check warning on line 513 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4r&open=AaGQaZ3sM0eP3oF9qj4r&pullRequest=9

Check warning on line 513 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4q&open=AaGQaZ3sM0eP3oF9qj4q&pullRequest=9

- name: Create test database
run: |
Expand Down Expand Up @@ -601,7 +601,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

- name: Create test database
run: |
Expand Down Expand Up @@ -691,7 +691,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

Check warning on line 694 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4u&open=AaGQaZ3sM0eP3oF9qj4u&pullRequest=9

- name: Create test database
run: |
Expand Down Expand Up @@ -790,7 +790,7 @@
- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@fix/column-constraint-gates"

Check warning on line 793 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4w&open=AaGQaZ3sM0eP3oF9qj4w&pullRequest=9

Check warning on line 793 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaGQaZ3sM0eP3oF9qj4x&open=AaGQaZ3sM0eP3oF9qj4x&pullRequest=9

- name: Create test database
run: |
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,8 @@ __marimo__/
.claude/tmp/
tests/config/
test_connection.py
# Worktrees for feature/* and fix/* branches. Never tracked: a `git add -A`
# from the repo root would commit each branch's whole toolchain -- the venv, the
# build directory and the test artifacts -- and the directory disappears when the
# branch merges, so the history would keep a second copy of the source tree.
.worktree/
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ from typing import ClassVar, Optional
from rhosocial.activerecord.base import FieldProxy
from rhosocial.activerecord.backend.impl.sqlserver.backend import SQLServerBackend
from rhosocial.activerecord.backend.impl.sqlserver.config import SQLServerConnectionConfig
from rhosocial.activerecord.backend.expression import CreateTableExpression
from rhosocial.activerecord.backend.expression import CreateTableStatement
from rhosocial.activerecord.backend.expression.statements import (
ColumnConstraint,
ColumnConstraintType,
ColumnDefinition,
ColumnPrimaryKeyConstraint,
)
from rhosocial.activerecord.backend.expression.types import IntegerType, VarCharType
from rhosocial.activerecord.model import ActiveRecord
Expand Down Expand Up @@ -79,14 +79,16 @@ User.configure(
)

dialect = User.__backend__.dialect
User.__backend__.execute(*CreateTableExpression(
User.__backend__.execute(*CreateTableStatement(
dialect,
"users",
columns=[
ColumnDefinition(
dialect, "id", IntegerType(dialect),
constraints=[ColumnConstraint(dialect, ColumnConstraintType.PRIMARY_KEY,
is_auto_increment=True)],
constraints=[ColumnConstraint(
dialect,
branch=ColumnPrimaryKeyConstraint(dialect, is_auto_increment=True),
)],
),
ColumnDefinition(dialect, "name", VarCharType(dialect, 100)),
ColumnDefinition(dialect, "email", VarCharType(dialect, 255)),
Expand Down Expand Up @@ -145,7 +147,7 @@ from rhosocial.activerecord.backend.impl.sqlserver.expression import (
SQLServerCreateTableOptions,
)

CreateTableExpression(
CreateTableStatement(
dialect,
"sessions",
columns=[...],
Expand Down
3 changes: 3 additions & 0 deletions changelog.d/9.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The extended-property family, asked by table name, plus table and column comments read from `MS_Description`; and `views NAME` on the introspect CLI, whose twelve types derive from `_TYPE_CAPABILITIES`.

Add the SQL Server catalogue questions core has no shape for, on both introspector managers with identical signatures: ``list_schemas`` (``sys.schemas`` with the owning database principal resolved by ``principal_id``, not name), ``list_extended_properties`` (``sys.extended_properties`` -- ``MS_Description`` and friends -- for one catalogue object, optionally one column, with the ``sql_variant`` value cast off the type pyodbc cannot fetch), ``list_procedures`` and ``list_functions`` (``sys.objects`` types ``P``/``PC`` and ``FN``/``IF``/``TF``/``FS``/``FT``, one builder parameterized by the code set), and ``list_sequences`` (``sys.sequences`` with every ``sql_variant`` bound cast to text and parsed back into exact ``Decimal`` values rather than floats that would round a wide sequence's bounds). Each question is built from expression nodes and rendered by the dialect, carries its family name into its cache key so two families never answer each other, and reports a schema-free family as unknown rather than returning nothing.
1 change: 1 addition & 0 deletions changelog.d/9.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A system table is not a base table; the backend-only catalogue questions are statement classes; and a `NAME` a type cannot use is an error (exit 2) instead of an ignored argument.
3 changes: 3 additions & 0 deletions changelog.d/9.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
A view's definition is read from `INFORMATION_SCHEMA.VIEWS` rather than left null, and a trigger reports the events that fire it and whether it is disabled. Two returns displaced by inserted switches were restored, and the expression matrix registers the new statement instead of skipping it.

Fix the ``introspect`` subcommand calling introspector methods that exist nowhere in the backend. It asked ``get_tables``/``get_views``/``get_columns``/``get_indexes``/``get_foreign_keys``/``get_triggers``/``get_constraints``/``get_procedures``/``get_functions``/``get_sequences`` -- core spells those questions ``list_tables``/``list_views``/``list_columns`` and friends -- so every type failed against a live server while the old offline fakes answered the misspelled names happily. The type-to-method mapping now lives in one place (``_steps``) and is exercised twice: the offline dispatch test checks every advertised type against the real introspector classes with no fake in the way, and a live test runs the command itself against a SQL Server. ``table`` and ``database <name>`` now render through ``get_table_info``, whose single query already fills columns, indexes and foreign keys, replacing the three separate calls plus the iteration over every table that triggers used to need.
1 change: 1 addition & 0 deletions changelog.d/9.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The catalogue questions moved to statement classes, the CLI tests follow the removed aggregate, and CI installs core from `release/v1.0.0.dev30` now that core #127 has merged.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dev = [
"ruff>=0.1.0",
"pre-commit>=3.0.0",
"towncrier>=23.0.0",
"pytest-xdist>=3.0.0",
"rhosocial-activerecord-testsuite",
]
docs = [
Expand Down
13 changes: 11 additions & 2 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@ testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short -p rhosocial.activerecord.testsuite.conftest
; pythonpath is what makes ``-p plugins.expression_xdist`` loadable: pytest
; applies this ini value *before* it imports the -p plugins, and a plugin
; imported by name has to be findable by name. It also means a plain ``pytest``
; invocation works without PYTHONPATH=src:tests, which CI sets anyway.
pythonpath = tests
; plugins.expression_xdist is this repository's own: it makes the expression
; area (feature/backend/expression) parallel by default, which the org test
; rules ask for and a conftest cannot do because xdist reads the worker count
; before any conftest loads. See tests/plugins/expression_xdist.py.
addopts = -v --tb=short -p rhosocial.activerecord.testsuite.conftest -p plugins.expression_xdist
filterwarnings =
ignore::DeprecationWarning
markers =
requires_sqlserver: mark test as requiring SQL Server connection
slow: mark test as slow running
slow: mark test as slow running
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,33 @@ def supports_add_column_if_not_exists(self) -> bool:
return False

def supports_drop_column_if_exists(self) -> bool:
"""``DROP COLUMN IF EXISTS`` is supported since SQL Server 2016."""
"""``DROP COLUMN IF EXISTS`` is supported since SQL Server 2016.

The "since 2016" floor comes from the official ``ALTER TABLE``
grammar page; the instance measured here is 2019 only
(``tests/config/sqlserver_scenarios.yaml``: ``sqlserver_2019``,
RTM-CU32-GDR 15.0.4465.1, Developer Edition).
"""
return True

def supports_drop_constraint_if_exists(self) -> bool:
"""``DROP CONSTRAINT IF EXISTS`` is supported since SQL Server 2016."""
"""``DROP CONSTRAINT IF EXISTS`` is supported since SQL Server 2016.

Measured on 2019 (RTM-CU32-GDR, 15.0.4465.1)::

ALTER TABLE dbo.probe_t DROP CONSTRAINT IF EXISTS does_not_exist
-> accepted, silent no-op (no result set, no error)

so ``IF EXISTS`` is what makes dropping a constraint idempotent on
this backend — the plain form is *not* a no-op, it raises
``'does_not_exist' is not a constraint. (3728)`` followed by
``Could not drop constraint. See previous errors. (3727)``.
Catching that error pair instead of spelling ``IF EXISTS`` would also
break on MySQL, which has no ``DROP CONSTRAINT IF EXISTS`` at all.

The "since 2016" floor is from the official ``ALTER TABLE`` grammar
page; no pre-2019 instance was measured here.
"""
return True

def format_add_column_action(self, action: Any) -> Tuple[str, tuple]:
Expand Down Expand Up @@ -112,10 +134,13 @@ def format_alter_column_type_action(self, action: Any) -> Tuple[str, tuple]:
if data_type is None:
raise ValueError("data type is required for ALTER COLUMN")

from rhosocial.activerecord.backend.expression.types import DataType

type_sql, _ = DataType.as_type(self, data_type).to_sql()
parts = [
"ALTER COLUMN",
self.format_identifier(action.column_name),
str(data_type),
type_sql,
]

collate = getattr(action, "collate", None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
from ..async_transaction import AsyncSQLServerTransactionManager
from .backend import SQLServerUnicodeDialect
from ..mixins import SQLServerBackendMixin
from ..query_nodes import (
health_check_sql,
identity_columns_sql,
server_property_value_sql,
session_identity_sql,
)

try:
import aioodbc
Expand Down Expand Up @@ -253,8 +259,9 @@ async def _get_cursor(self):

async def _get_scope_identity_async(self) -> Optional[int]:
"""Get the identity value of the last INSERT via @@IDENTITY."""
sql, params = session_identity_sql(self.dialect)
async with await self._get_cursor() as cursor:
await cursor.execute("SELECT @@IDENTITY")
await cursor.execute(sql, params)
row = await cursor.fetchone()
if row and row[0] is not None:
return int(row[0])
Expand All @@ -273,13 +280,9 @@ async def _get_identity_columns_async(self, table_name: str) -> Set[str]:
return self._identity_columns_cache[cache_key]
columns: Set[str] = set()
try:
sql, params = identity_columns_sql(self.dialect, table_name)
async with await self._get_cursor() as cursor:
await cursor.execute(
"SELECT c.name FROM sys.identity_columns c "
"JOIN sys.tables t ON c.object_id = t.object_id "
f"WHERE t.name = {self.dialect.p()} AND SCHEMA_NAME(t.schema_id) = 'dbo'",
(table_name,),
)
await cursor.execute(sql, params)
for row in await cursor.fetchall():
columns.add(str(row[0]).lower())
except Exception:
Expand Down Expand Up @@ -413,8 +416,8 @@ async def _execute_statement(
data = []
for row in rows:
row_dict = dict(zip(column_names, row))
adapted_row = self._adapt_row_types(row_dict, adapters)
final_row = self._remap_row_columns(adapted_row, mapping)
adapted_row = self.adapt_row_types(row_dict, adapters)
final_row = self.remap_row_columns(adapted_row, mapping)
data.append(final_row)
elif cursor.description:
# Also process result when cursor has data (e.g. OUTPUT clause)
Expand All @@ -426,8 +429,8 @@ async def _execute_statement(
data = []
for row in rows:
row_dict = dict(zip(column_names, row))
adapted_row = self._adapt_row_types(row_dict, adapters)
final_row = self._remap_row_columns(adapted_row, mapping)
adapted_row = self.adapt_row_types(row_dict, adapters)
final_row = self.remap_row_columns(adapted_row, mapping)
data.append(final_row)
except Exception:
data = None
Expand Down Expand Up @@ -500,8 +503,11 @@ async def get_server_version(self) -> Tuple[int, int, int]:
await self.connect()

try:
probe, probe_params = server_property_value_sql(
self.dialect, "ProductVersion"
)
async with await self._get_cursor() as cursor:
await cursor.execute("SELECT SERVERPROPERTY('ProductVersion')")
await cursor.execute(probe, probe_params)
row = await cursor.fetchone()

if row:
Expand Down Expand Up @@ -536,8 +542,9 @@ async def ping(self, reconnect: bool = True) -> bool:
return True
return False

probe, probe_params = health_check_sql(self.dialect)
async with await self._get_cursor() as cursor:
await cursor.execute("SELECT 1")
await cursor.execute(probe, probe_params)
await cursor.fetchone()
return True

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from ..dialect import SQLServerDialect
from ..transaction import SQLServerTransactionManager
from ..mixins import SQLServerBackendMixin, SQLServerConcurrencyMixin
from ..query_nodes import health_check_sql, server_property_value_sql


class SQLServerUnicodeDialect(SQLServerDialect):
Expand Down Expand Up @@ -289,8 +290,9 @@ def _get_cursor(self):
self.connect()

try:
probe, probe_params = health_check_sql(self.dialect)
cursor = self._connection.cursor()
cursor.execute("SELECT 1")
cursor.execute(probe, probe_params)
cursor.fetchone()
cursor.close()
except (PyODBCError, InterfaceError) as e:
Expand Down Expand Up @@ -460,8 +462,11 @@ def get_server_version(self) -> Tuple[int, int, int]:

cursor = None
try:
probe, probe_params = server_property_value_sql(
self.dialect, "ProductVersion"
)
cursor = self._get_cursor()
cursor.execute("SELECT SERVERPROPERTY('ProductVersion')")
cursor.execute(probe, probe_params)
version_str = cursor.fetchone()[0]

parts = version_str.split('.')
Expand Down Expand Up @@ -497,8 +502,9 @@ def ping(self, reconnect: bool = True) -> bool:
return True
return False

probe, probe_params = health_check_sql(self.dialect)
cursor = self._get_cursor()
cursor.execute("SELECT 1")
cursor.execute(probe, probe_params)
cursor.fetchone()
cursor.close()
return True
Expand Down
Loading
Loading