Skip to content

feat(postgres): add named query/procedure CLI, extension support, and ConcurrencyAware - #32

Merged
vistart merged 46 commits into
release/v1.0.0.dev12from
feature/named-query
Apr 27, 2026
Merged

vistart merged 46 commits into
release/v1.0.0.dev12from
feature/named-query

Conversation

@vistart

@vistart vistart commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Description

Add PostgreSQL backend support for named query/procedure, named connection, ConcurrencyAware protocol, extensive extension support (29 extensions), and align with core package architectural changes. Update CI to install rhosocial-activerecord from git branch release/v1.0.0.dev25 and add postgres-exts service container for extension testing.

This branch contains 43 commits covering:

New Features

  • Named Query/Procedure CLI: backend execute named-query and named-procedure subcommands for PostgreSQL with custom epilog
  • Named Connection: Unified handle_info config resolution, PostgreSQL CLI integration, and named connection examples
  • ConcurrencyAware Protocol: PostgreSQL implementation of ConcurrencyAware protocol
  • PostgreSQL Extensions: Comprehensive extension support with 29 extensions including Protocol/Mixin definitions, format_* methods, integration tests for 8 extensions, and usage examples
    • Extensions: postgis, pg_trgm, ltree, hstore, citext, fuzzystrmatch, uuid_ossp, pgcrypto, postgis_raster, pgrouting, and more
    • CreateExtensionExpression and DropExtensionExpression DDL classes
    • Extension availability detection from pg_available_extensions
  • CI postgres-exts Service: Added vistart/postgres-exts service container for extension testing in CI

Bug Fixes

  • Fix ? → %s placeholder conversion for psycopg in _prepare_sql_and_params
  • Fix ForUpdateClause handling without strength attribute
  • Fix @runtime_checkable missing on PostgresAdvisoryLockSupport and PostgresMultirangeSupport
  • Fix UUID v1 regex pattern to match standard 8-4-4-4-12 format
  • Fix RawSQLExpression usage in extension examples (replaced with expression API)
  • Fix AsyncIntrospectionSupport exclusion in tests
  • Fix async marker for pytest-asyncio 1.3.0 compatibility

Refactoring

  • Align expression system with three architectural principles
  • Modularize CLI from monolithic __main__.py to cli/ subpackage
  • Move threadsafety to PostgresBackendMixin
  • Use in_transaction property instead of direct _transaction_manager access
  • Remove _convert_qmark_to_format fallback (now uses proper dialect placeholder conversion)
  • Replace raw SQL with expression API in extension examples

CI Changes

  • Install rhosocial-activerecord from git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev25
  • Add postgres-exts service container for extension testing

Type of change

  • feat: A new feature
  • docs: Documentation only changes (if related to the new feature)
  • test: Adding missing tests or correcting existing tests (for the new feature)
  • chore: Changes to the build process or auxiliary tools and libraries such as documentation generation (if related to the new feature)
  • ci: Changes to our CI configuration files and scripts (if related to the new feature)

Breaking Change

  • Yes, for end-users (backward-incompatible API changes)
  • Yes, for backend developers (internal architectural changes that may affect custom implementations)
  • No breaking changes

Impact on End-Users:
No backward-incompatible changes are expected for typical end-user applications. All new features are additive.

Impact on Backend Developers (Custom Implementations):

  • The PostgresBackendMixin now includes threadsafety logic (moved from PostgreSQLBackend)
  • The expression system alignment may affect custom expression implementations
  • The _convert_qmark_to_format fallback has been removed; proper placeholder conversion via dialect is required
  • The CLI module structure changed from __main__.py to cli/ subpackage

Related Repositories/Packages

Testing

  • I have added new tests to cover my changes.
  • I have updated existing tests to reflect my changes.
  • All existing tests pass.
  • This change has been tested on PostgreSQL 9-18 with Python 3.8-3.14.

Test Plan:

  • Extension tests: tests/rhosocial/activerecord_postgres_test/feature/extensions/
  • Named connection tests: tests/rhosocial/activerecord_postgres_test/feature/connection/
  • Protocol conformance tests: tests/rhosocial/activerecord_postgres_test/feature/protocol/
  • CLI tests: tests/rhosocial/activerecord_postgres_test/feature/cli/
  • CI will run full matrix: Python 3.8-3.14 x PostgreSQL 9-18

Checklist

  • My code follows the project's code style guidelines.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation (Docstrings, README.md, etc.).
  • My changes generate no new warnings.
  • I have added a Changelog fragment (changelog.d/<issue_number>.<type>.md).
  • I have verified that my changes do not introduce SQL injection vulnerabilities.
  • I have checked for potential performance regressions.

vistart and others added 30 commits April 18, 2026 08:18
Adds support for executing named queries via CLI using the shared
named_query module from core library.

Features:
- --describe: show function signature
- --dry-run: preview SQL without execution
- --list: list queries in a module
- --param: pass parameters
- --force: allow DML/DDL execution
- Register named-procedure parser and handler
- Add async support for named-query and named-procedure
This adds test coverage for PostgreSQL backend CLI:
- TestCLINamedQueryArgs: 6 tests for named-query args parsing
- TestCLINamedProcedureArgs: 9 tests for named-procedure args parsing

These tests mirror the MySQL CLI tests for consistency.
Provide PostgreSQL-specific connection parameter examples in help text
instead of SQLite's --db-file examples.
- Add PostgresConcurrencyMixin in base.py
- Uses connection pool size as concurrency limit
- Both sync and async backends implement ConcurrencyAware protocol
- Replace pytest_asyncio.mark.asyncio with pytest.mark.asyncio
- This fixes test collection error in pytest-asyncio 1.3.0
- Add CreateExtensionExpression for CREATE EXTENSION SQL generation
- Add DropExtensionExpression for DROP EXTENSION SQL generation
- Update PostgresExtensionSupport protocol with format methods
- Add format_create_extension and format_drop_extension to mixin
- Add comprehensive tests for extension DDL expressions
- Auto-quote extension names containing hyphens or underscores
- Add Dockerfile for PostgreSQL 14-18 with postgis, pgvector, earthdistance
- Add docker-compose.yml for easy deployment
- Add build.sh for multi-version builds
- Add init-extensions.sh for automatic extension creation
- Add comprehensive README documentation

Supports: amd64, arm64, ppc64le, s390x
- Add test_postgres_protocol_conformance.py with tests for:
  - PostgresDialect implements all declared protocols
  - All protocols have at least one member
  - No overlap between protocols
- Skip non-runtime_checkable protocols in isinstance tests
AsyncIntrospectionSupport was removed from python-activerecord,
so the test exclusion is no longer needed.
PostgresBackendMixin now provides threadsafety property (returns 2).
Removed duplicate implementation from sync.py and async_backend.py.
…ests

Refactor handle_info() to use _resolve_postgres_config() instead of
inline named connection logic, ensuring consistency across all
subcommands. Add test suite with PostgreSQL 18 example connections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pytest does not allow __init__.py in test directories.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ility status

- Add 'available' field to PostgresExtensionInfo to track installable extensions
- Add is_extension_available() method to protocol and mixin
- Expand KNOWN_EXTENSIONS from 11 to 35+ extensions
- Query pg_available_extensions for complete extension status
- Add 'extensions' introspection command for CLI
- Update info command to display installed vs available extensions
…ions

Previously _detect_extensions() only queried pg_extension for installed
extensions and used hardcoded KNOWN_EXTENSIONS as fallback. This missed
pre-installed extensions that are available but not enabled.

Now the method:
1. Queries pg_extension for installed extensions (installed=True, available=True)
2. Queries pg_available_extensions for all available extensions
3. Marks installed extensions as available
4. Marks not-installed-but-available extensions as available=True
- Sort extensions alphabetically by name
…ions

Implemented support for the following extensions:
- pg_cron: job scheduling
- citext: case-insensitive text
- bloom: bloom filter index
- pg_partman: partition management
- orafce: Oracle compatibility
- pglogical: logical replication
- pgaudit: audit logging
- pg_repack: online rebuild
- hypopg: hypothetical indexes
- address_standardizer: address parsing
- pg_surgery: data repair
- pg_walinspect: WAL inspection
- fuzzystrmatch: fuzzy string matching
- cube: multidimensional cube
- pgcrypto: cryptographic functions
- btree_gin: GIN index for btree types
- btree_gist: GiST index for btree types
- uuid-ossp: UUID generation
- Follow the examples README structure (Setup/Business Logic/Execution/Teardown)
- Use expressions (CreateTableExpression, InsertExpression, QueryExpression)
- Check extension availability before execution
- Auto-create extension if needed
- Use CreateExtensionExpression for extension creation
- Follow examples README structure (Setup/Business Logic/Execution/Teardown)
- Check extension availability before execution
- Use CreateExtensionExpression for extension creation
- Re-detect extensions after creation with introspect_and_adapt()
- Follow examples README structure
- Remove backend_cls parameter requirement from named connection factories
- Update CLI config resolution to use new API
- Add CLI parameter priority tests (5 tests pass)
- Add named-connection subcommand for managing named connections
- Support --list, --show, --describe options
- Allows users to list and inspect named connection configs
Add examples for development and production PostgreSQL connections:
- local_dev(): local development database
- local_dev_no_password(): local database without password
- test_db(): test database
- prod_db(): production database with SSL
- prod_db_ssl(): production with full SSL verification
- prod_replica(): read replica for read-heavy workloads
- Remove erroneous reset_pg_stat_statements() from pg_stat_statements Protocol
  (violates Dialect layer "generate SQL, don't execute" principle)
- Add supports_pg_cron_run capability to pg_cron Protocol/Mixin
- Add format_auto_partition_config to pg_partman Mixin/Protocol
  (aligns with existing supports_pg_partman_auto_partition declaration)
- Add format_pglogical_show_subscription_status and
  format_pglogical_alter_subscription_synchronize to pglogical Mixin/Protocol
  (aligns with existing supports_pglogical_replication declaration)
- Update KNOWN_EXTENSIONS: add 'cancel' and 'run' features for pg_cron
… for 29 extensions

Add missing format_* methods to Mixins where supports_* declared a capability
but no SQL generation method existed:
- hstore: format_hstore_delete_key, format_hstore_each, format_hstore_keys/values
- ltree: format_ltree_concat, format_ltree_lca
- intarray: format_intarray_index_statement
- citext: format_citext_literal
- cube: format_cube_contains, format_cube_distance, format_cube_size
- pgcrypto: format_digest, format_pgp_encrypt, format_pgp_decrypt
- postgis: format_geography_literal, format_st_area/transform/as_geojson/buffer
- pgvector: format_create_hnsw_index_statement, format_vector_l2_distance/inner_product
- fuzzystrmatch: format_difference, format_metaphone
- btree_gin/gist: format_btree_gin/gist_operator_class
- hypopg: format_hypopg_estimate_size
- orafce: format_instr, format_substr

Add format_* method signatures to all 29 Protocol files (previously only
supports_* was declared). Register new Mixins in PostgresDialect inheritance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sions

Add requires_extension() and requires_extension_available() decorators
to conftest.py for skipping integration tests when extensions are not
installed on the test database.

Add unit tests for all 20 extensions that previously lacked test coverage:
citext, pgcrypto, fuzzystrmatch, cube, uuid-ossp, bloom, btree_gin,
btree_gist, tablefunc, pg_stat_statements, pg_cron, orafce, pg_partman,
hypopg, pglogical, pgaudit, pg_repack, address_standardizer, pg_surgery,
pg_walinspect.

All tests are pure in-memory (no database connection required).
Integration tests that connect to real PostgreSQL database:
- hstore: CRUD, operators (@>/?/||), functions (each/akeys/avals)
- pg_trgm: similarity, % operator, GIN index, show_trgm
- uuid-ossp: uuid_generate_v1/v4, UUID column default
- citext: case-insensitive comparison, unique constraint, LIKE, JOIN
- ltree: path operators (@>/<@/~), ancestor/descendant, GiST index
- pgcrypto: crypt+gen_salt, digest, hmac, pgp_sym_encrypt/decrypt
- intarray: @>/&&/<@ operators, idx() function, GIN index
- fuzzystrmatch: levenshtein, soundex, difference, metaphone/dmetaphone

All tests use requires_extension() guard to skip when not installed.
- postgis: spatial data (geometry), ST_Distance/Contains, GIST index
- pgvector: vector column, cosine similarity search, HNSW index
- ltree: hierarchical paths, ancestor/descendant queries, GiST index
- pgcrypto: crypt+gen_salt password hashing, digest, hmac
- pg_trgm: similarity fuzzy search, % operator, GIN index
- intarray: @>/&& operators, idx/sort/uniq functions
- earthdistance: earth_distance calculation, range queries
- cube: @> contains, <-> distance, nearest-neighbor search

All examples follow Setup → Business Logic → Execution → Teardown
structure with CreateExtensionExpression and introspect_and_adapt().
…date KNOWN_EXTENSIONS

- Add PostgresPostgisRasterSupport protocol and PostgresPostgisRasterMixin
  for PostGIS raster type operations (ST_AsRaster, ST_RastFromText, etc.)
- Add PostgresPgRoutingSupport protocol and PostgresPgRoutingMixin
  for pgRouting path finding operations (pgr_dijkstra, pgr_astar, etc.)
- Update KNOWN_EXTENSIONS: add raster_type feature for postgis_raster,
  dijkstra feature for pgrouting, cancel/run features for pg_cron
- Mark 8 extensions as requires_dialect: False that don't need
  Protocol/Mixin adaptation (postgis_topology, postgis_sfcgal,
  postgis_tiger_geocoder, amcheck, pageinspect, pgtap,
  pg_logicalinspect, address_standardizer_data_us)
vistart and others added 15 commits April 24, 2026 20:18
…examples

Replace raw SQL strings with expression-based API calls across all 12
extension example files. Key changes:

- Use DropTableExpression instead of raw "DROP TABLE IF EXISTS" SQL
- Use FunctionCall for all SQL function invocations (similarity, crypt,
  digest, ST_Distance, levenshtein, etc.)
- Use BinaryExpression for PostgreSQL-specific operators (@>, <@, <->,
  <=>, ?, ||, ~, %, &&)
- Use Subquery wrapping for BinaryExpression aliases (BinaryExpression
  lacks AliasableMixin)
- Use QueryExpression for all SELECT queries instead of raw SQL
- Simplify RawSQLPredicate workaround: pass BinaryExpression directly
  as WHERE condition (format_where_clause calls to_sql() generically)

Remaining RawSQLExpression usage is for extension-specific literals
that have no expression support: hstore key=>value syntax, pgvector
[x,y,z] syntax, cube (x,y,z) syntax, intarray {1,3} syntax, and
PostgreSQL ::type cast notation.
…x expression system gaps

- Override PostgresDialect.format_binary_operator to escape % as %% for
  psycopg compatibility (pg_trgm similarity operator)
- Add PostgresIndexMixin.format_create_index_statement for CREATE INDEX
  with operator classes via dialect_options["opclasses"]
- Move PostgresIndexMixin before IndexMixin in PostgresDialect MRO to
  ensure override takes effect
- Remove blind sql.replace("?", "%s") in PostgresBackendMixin that broke
  hstore/jsonb ? operators
- Add uuid_ns_dns/url/oid/x500 helper functions returning FunctionCall
- Replace all RawSQLExpression usage in 12 extension examples with
  proper expression API (Literal.cast(), FunctionCall, Subquery, etc.)
- Fix cube range data format from invalid (a,b),(c,d),(e,f) to correct
  (min_point),(max_point)
- Fix result data access from integer index to dict key access
- Fix uuid_generate_v5 namespace parameter to use uuid_ns_dns() instead
  of string literal
- Remove uuid_max() call (not available in current PG versions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…/ subpackage

Split the 1779-line __main__.py into a thin entry point and 9 focused
modules under cli/, following the same pattern as SQLite and MySQL:
- __init__.py: subcommand registration and dynamic dispatch
- output.py: provider factory and RICH_AVAILABLE
- connection.py: connection args, config resolution, backend creation
- info.py: info subcommand with protocol display (26 Postgres Protocols)
- query.py: query subcommand (sync + async)
- introspect.py: introspect subcommand with 'extensions' type
- status.py: status subcommand with WAL/Replication/Archive/Security sections
- named_query.py: named-query adapter using closures
- named_procedure.py: named-procedure adapter using closures
- named_connection.py: named-connection adapter with empty parent parser

Key changes:
- Remove global _default_backend variable, use closure-based approach
- Move -v/--verbose from main parser to info/status subcommands only
- Each subcommand independently decides its own arguments
- Rename test_cli.py to test_named_connection_cli.py for clarity
- Update mock paths from __main__ to cli.connection module
…YTHONPATH, and enable autocommit

1. Remove `tests.` prefix from module paths in integration tests.
   The `tests.rhosocial...` import paths only worked with `python -m
   pytest` (CWD in sys.path makes `tests` an implicit namespace package).
   Direct `pytest` does not add CWD to sys.path, causing ModuleNotFoundError.
   Fix by using `rhosocial.xxx` paths that work via PYTHONPATH entry.

2. Add `tests` to PYTHONPATH in CI to ensure `tests/rhosocial/` is
   searched as part of the namespace package path.

3. Enable autocommit on psycopg connections to prevent psycopg 3 from
   auto-managing transactions (which conflicts with manual BEGIN/COMMIT
   statements in the ActiveRecord layer).
…rinciples

- Derive 5 PG protocols from generic counterparts:
  PostgresTableSupport(TableSupport),
  PostgresIndexSupport(IndexSupport),
  PostgresLockingSupport(LockingSupport),
  PostgresTriggerSupport(TriggerSupport),
  PostgresConstraintSupport(ConstraintSupport)
- Remove 4 duplicate generic protocols from PostgresDialect base class list
- Rename 22 PG expression classes with Postgres prefix:
  AdvisoryLockExpression → PostgresAdvisoryLockExpression,
  RefreshMaterializedViewPgExpression → PostgresRefreshMaterializedViewExpression,
  CreateEnumTypeExpression → PostgresCreateEnumTypeExpression, etc.
- Fix Expression-Dialect separation for 3 expression classes:
  PostgresAlterEnumTypeAddValueExpression,
  PostgresAlterEnumTypeRenameValueExpression,
  PostgresCreateRangeTypeExpression
- Make PostgresRefreshMaterializedViewExpression derive from
  RefreshMaterializedViewExpression with schema/name/concurrently aliases
- Rename format_postgres_for_update_clause → format_for_update_clause
  in PostgresLockingMixin and PostgresLockingSupport protocol
- Add TestPostgresProtocolDerivation and TestPostgresExpressionDialectSeparation
  test classes for protocol coverage validation
- Update test_postgres_protocol_conformance.py excluded_overlaps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…2 format

The third segment of UUID v1 was incorrectly matched with {3} hex chars
instead of {4}, causing test_uuid_generate_v1 to always fail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PostgresIndexSupport, PostgresTableSupport, and PostgresConstraintSupport
now derive from their generic counterparts, so method overlap is expected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and PostgresMultirangeSupport

These two protocols were missing the decorator, causing them to be
skipped in protocol conformance isinstance() checks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l_and_params

ActiveQuery layer uses ? as parameter placeholders (e.g. where("name = ?",
params)), but psycopg v3 requires %s format. Previously the conversion
was not performed, causing "the query has N placeholders but M parameters
were passed" errors for any query using RawSQLPredicate/RawSQLExpression.

The new _convert_qmark_to_format() method handles the conversion while
preserving PostgreSQL's hstore/jsonb operators (?, ?|, ?&) by:
- Keeping ? followed by | or & as-is (operator context)
- Keeping ? preceded by alphanumeric/underscore as-is (identifier context)
- Replacing all other ? with %s (parameter placeholder context)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…greSQL backend

Placeholder conversion is now handled by the ActiveRecord layer via
convert_qmark_placeholder(). The backend no longer needs to do any
? → %s conversion in _prepare_sql_and_params.

Remove _convert_qmark_to_format static method and _prepare_sql_and_params
override, restoring base class pass-through behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…in format_for_update_clause

PostgreSQL dialect's format_for_update_clause assumed clause.strength
always exists, but the base ForUpdateClause class (used by
Query.for_update()) does not have this attribute — only
PostgresForUpdateClause does. Use getattr() with a default of
LockStrength.UPDATE for compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from PyPI package to git+https source for
rhosocial-activerecord-testsuite to track the new dev12 branch.
…nsaction_manager access

Replace direct `_transaction_manager.is_active` checks with the
`in_transaction` property in both sync and async backends. The property
already handles the None case for `_transaction_manager`, making the
code simpler and consistent with the project's abstraction conventions.
Add a second PostgreSQL service using vistart/postgres-exts image
alongside the official postgres container, allowing tests against
databases with additional extensions installed.

- postgres service: port 5432 (official image)
- postgres-exts service: port 5433 (vistart/postgres-exts image)
- Both use same version tag from matrix, same credentials
- Added postgres_exts scenario to CI config
- Wait step now checks both services are ready

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vistart vistart self-assigned this Apr 27, 2026
@codecov

codecov Bot commented Apr 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 44.29383% with 1274 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.31%. Comparing base (79ec719) to head (1bb15ca).
⚠️ Report is 46 commits behind head on release/v1.0.0.dev12.

Files with missing lines Patch % Lines
...l/activerecord/backend/impl/postgres/cli/status.py 7.47% 334 Missing ⚠️
...tiverecord/backend/impl/postgres/cli/introspect.py 12.56% 181 Missing ⚠️
...ial/activerecord/backend/impl/postgres/cli/info.py 16.91% 167 Missing ⚠️
...al/activerecord/backend/impl/postgres/cli/query.py 20.00% 84 Missing ⚠️
...verecord/backend/impl/postgres/mixins/ddl/index.py 5.66% 50 Missing ⚠️
...ecord/backend/impl/postgres/cli/named_procedure.py 23.25% 33 Missing ⚠️
...iverecord/backend/impl/postgres/cli/named_query.py 21.42% 33 Missing ⚠️
.../backend/impl/postgres/mixins/extensions/orafce.py 52.63% 17 Missing and 1 partial ⚠️
...l/activerecord/backend/impl/postgres/cli/output.py 34.78% 15 Missing ⚠️
...verecord/backend/impl/postgres/mixins/extension.py 68.18% 13 Missing and 1 partial ⚠️
... and 77 more

❗ There is a different number of reports uploaded between BASE (79ec719) and HEAD (1bb15ca). Click for more details.

HEAD has 3 uploads less than BASE
Flag BASE (79ec719) HEAD (1bb15ca)
unittests 2 1
python-3.14 2 1
postgres-18 2 1
Additional details and impacted files
@@                   Coverage Diff                    @@
##           release/v1.0.0.dev12      #32      +/-   ##
========================================================
- Coverage                 73.12%   66.31%   -6.82%     
========================================================
  Files                       136      188      +52     
  Lines                      7365     9474    +2109     
  Branches                   1218     1468     +250     
========================================================
+ Hits                       5386     6283     +897     
- Misses                     1546     2750    +1204     
- Partials                    433      441       +8     
Flag Coverage Δ
postgres-18 66.31% <44.29%> (-6.82%) ⬇️
python-3.14 66.31% <44.29%> (-6.82%) ⬇️
unittests 66.31% <44.29%> (-6.82%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vistart
vistart marked this pull request as ready for review April 27, 2026 03:59
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@vistart
vistart merged commit 195b185 into release/v1.0.0.dev12 Apr 27, 2026
12 of 14 checks passed
@vistart vistart mentioned this pull request May 1, 2026
11 of 14 tasks
vistart added a commit that referenced this pull request May 1, 2026
* feat(postgres): add named-query CLI subcommand

Adds support for executing named queries via CLI using the shared
named_query module from core library.

Features:
- --describe: show function signature
- --dry-run: preview SQL without execution
- --list: list queries in a module
- --param: pass parameters
- --force: allow DML/DDL execution

* feat(backend): add named-procedure CLI subcommand

- Register named-procedure parser and handler
- Add async support for named-query and named-procedure

* Add CLI tests for named-query and named-procedure subcommands

This adds test coverage for PostgreSQL backend CLI:
- TestCLINamedQueryArgs: 6 tests for named-query args parsing
- TestCLINamedProcedureArgs: 9 tests for named-procedure args parsing

These tests mirror the MySQL CLI tests for consistency.

* feat(postgres): use custom epilog for named-query/procedure CLI

Provide PostgreSQL-specific connection parameter examples in help text
instead of SQLite's --db-file examples.

* feat(backend): add ConcurrencyAware for PostgreSQL

- Add PostgresConcurrencyMixin in base.py
- Uses connection pool size as concurrency limit
- Both sync and async backends implement ConcurrencyAware protocol

* test(backend): add ConcurrencyAware protocol tests for PostgreSQL

* fix: update async marker for pytest-asyncio 1.3.0 compatibility

- Replace pytest_asyncio.mark.asyncio with pytest.mark.asyncio
- This fixes test collection error in pytest-asyncio 1.3.0

* Add CreateExtensionExpression and DropExtensionExpression DDL classes

- Add CreateExtensionExpression for CREATE EXTENSION SQL generation
- Add DropExtensionExpression for DROP EXTENSION SQL generation
- Update PostgresExtensionSupport protocol with format methods
- Add format_create_extension and format_drop_extension to mixin
- Add comprehensive tests for extension DDL expressions
- Auto-quote extension names containing hyphens or underscores

* Add multi-arch PostgreSQL image with all extensions

- Add Dockerfile for PostgreSQL 14-18 with postgis, pgvector, earthdistance
- Add docker-compose.yml for easy deployment
- Add build.sh for multi-version builds
- Add init-extensions.sh for automatic extension creation
- Add comprehensive README documentation

Supports: amd64, arm64, ppc64le, s390x

* test(postgres): add protocol conformance tests

- Add test_postgres_protocol_conformance.py with tests for:
  - PostgresDialect implements all declared protocols
  - All protocols have at least one member
  - No overlap between protocols
- Skip non-runtime_checkable protocols in isinstance tests

* fix(tests): remove AsyncIntrospectionSupport exclusion

AsyncIntrospectionSupport was removed from python-activerecord,
so the test exclusion is no longer needed.

* refactor(backend): move threadsafety to PostgresBackendMixin

PostgresBackendMixin now provides threadsafety property (returns 2).
Removed duplicate implementation from sync.py and async_backend.py.

* feat(named-connection): unify handle_info config resolution and add tests

Refactor handle_info() to use _resolve_postgres_config() instead of
inline named connection logic, ensuring consistency across all
subcommands. Add test suite with PostgreSQL 18 example connections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tests): remove __init__.py from named_connection test directory

pytest does not allow __init__.py in test directories.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(extensions): enhance PostgreSQL extension detection with availability status

- Add 'available' field to PostgresExtensionInfo to track installable extensions
- Add is_extension_available() method to protocol and mixin
- Expand KNOWN_EXTENSIONS from 11 to 35+ extensions
- Query pg_available_extensions for complete extension status
- Add 'extensions' introspection command for CLI
- Update info command to display installed vs available extensions

* fix(extensions): detect available extensions from pg_available_extensions

Previously _detect_extensions() only queried pg_extension for installed
extensions and used hardcoded KNOWN_EXTENSIONS as fallback. This missed
pre-installed extensions that are available but not enabled.

Now the method:
1. Queries pg_extension for installed extensions (installed=True, available=True)
2. Queries pg_available_extensions for all available extensions
3. Marks installed extensions as available
4. Marks not-installed-but-available extensions as available=True

* feat(introspect): sort extensions by name

- Sort extensions alphabetically by name

* feat(extensions): add Protocol and Mixin for 17 new PostgreSQL extensions

Implemented support for the following extensions:
- pg_cron: job scheduling
- citext: case-insensitive text
- bloom: bloom filter index
- pg_partman: partition management
- orafce: Oracle compatibility
- pglogical: logical replication
- pgaudit: audit logging
- pg_repack: online rebuild
- hypopg: hypothetical indexes
- address_standardizer: address parsing
- pg_surgery: data repair
- pg_walinspect: WAL inspection
- fuzzystrmatch: fuzzy string matching
- cube: multidimensional cube
- pgcrypto: cryptographic functions
- btree_gin: GIN index for btree types
- btree_gist: GiST index for btree types
- uuid-ossp: UUID generation

* feat(examples): add citext extension example

- Follow the examples README structure (Setup/Business Logic/Execution/Teardown)
- Use expressions (CreateTableExpression, InsertExpression, QueryExpression)
- Check extension availability before execution
- Auto-create extension if needed

* feat(examples): add fuzzystrmatch and citext extension examples

- Use CreateExtensionExpression for extension creation
- Follow examples README structure (Setup/Business Logic/Execution/Teardown)
- Check extension availability before execution

* feat(examples): add uuid_ossp and hstore extension examples

- Use CreateExtensionExpression for extension creation
- Re-detect extensions after creation with introspect_and_adapt()
- Follow examples README structure

* feat: simplify named connection API and add PostgreSQL CLI tests

- Remove backend_cls parameter requirement from named connection factories
- Update CLI config resolution to use new API
- Add CLI parameter priority tests (5 tests pass)

* feat: integrate named-connection subcommand into PostgreSQL CLI

- Add named-connection subcommand for managing named connections
- Support --list, --show, --describe options
- Allows users to list and inspect named connection configs

* feat: add PostgreSQL named connection examples

Add examples for development and production PostgreSQL connections:
- local_dev(): local development database
- local_dev_no_password(): local database without password
- test_db(): test database
- prod_db(): production database with SSL
- prod_db_ssl(): production with full SSL verification
- prod_replica(): read replica for read-heavy workloads

* fix(extensions): align Protocol and Mixin interfaces for 4 extensions

- Remove erroneous reset_pg_stat_statements() from pg_stat_statements Protocol
  (violates Dialect layer "generate SQL, don't execute" principle)
- Add supports_pg_cron_run capability to pg_cron Protocol/Mixin
- Add format_auto_partition_config to pg_partman Mixin/Protocol
  (aligns with existing supports_pg_partman_auto_partition declaration)
- Add format_pglogical_show_subscription_status and
  format_pglogical_alter_subscription_synchronize to pglogical Mixin/Protocol
  (aligns with existing supports_pglogical_replication declaration)
- Update KNOWN_EXTENSIONS: add 'cancel' and 'run' features for pg_cron

* feat(extensions): supplement format_* methods and Protocol signatures for 29 extensions

Add missing format_* methods to Mixins where supports_* declared a capability
but no SQL generation method existed:
- hstore: format_hstore_delete_key, format_hstore_each, format_hstore_keys/values
- ltree: format_ltree_concat, format_ltree_lca
- intarray: format_intarray_index_statement
- citext: format_citext_literal
- cube: format_cube_contains, format_cube_distance, format_cube_size
- pgcrypto: format_digest, format_pgp_encrypt, format_pgp_decrypt
- postgis: format_geography_literal, format_st_area/transform/as_geojson/buffer
- pgvector: format_create_hnsw_index_statement, format_vector_l2_distance/inner_product
- fuzzystrmatch: format_difference, format_metaphone
- btree_gin/gist: format_btree_gin/gist_operator_class
- hypopg: format_hypopg_estimate_size
- orafce: format_instr, format_substr

Add format_* method signatures to all 29 Protocol files (previously only
supports_* was declared). Register new Mixins in PostgresDialect inheritance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(extensions): add test skip mechanism and unit tests for 20 extensions

Add requires_extension() and requires_extension_available() decorators
to conftest.py for skipping integration tests when extensions are not
installed on the test database.

Add unit tests for all 20 extensions that previously lacked test coverage:
citext, pgcrypto, fuzzystrmatch, cube, uuid-ossp, bloom, btree_gin,
btree_gist, tablefunc, pg_stat_statements, pg_cron, orafce, pg_partman,
hypopg, pglogical, pgaudit, pg_repack, address_standardizer, pg_surgery,
pg_walinspect.

All tests are pure in-memory (no database connection required).

* test(extensions): add integration tests for 8 extensions

Integration tests that connect to real PostgreSQL database:
- hstore: CRUD, operators (@>/?/||), functions (each/akeys/avals)
- pg_trgm: similarity, % operator, GIN index, show_trgm
- uuid-ossp: uuid_generate_v1/v4, UUID column default
- citext: case-insensitive comparison, unique constraint, LIKE, JOIN
- ltree: path operators (@>/<@/~), ancestor/descendant, GiST index
- pgcrypto: crypt+gen_salt, digest, hmac, pgp_sym_encrypt/decrypt
- intarray: @>/&&/<@ operators, idx() function, GIN index
- fuzzystrmatch: levenshtein, soundex, difference, metaphone/dmetaphone

All tests use requires_extension() guard to skip when not installed.

* feat(examples): add 8 extension usage examples

- postgis: spatial data (geometry), ST_Distance/Contains, GIST index
- pgvector: vector column, cosine similarity search, HNSW index
- ltree: hierarchical paths, ancestor/descendant queries, GiST index
- pgcrypto: crypt+gen_salt password hashing, digest, hmac
- pg_trgm: similarity fuzzy search, % operator, GIN index
- intarray: @>/&& operators, idx/sort/uniq functions
- earthdistance: earth_distance calculation, range queries
- cube: @> contains, <-> distance, nearest-neighbor search

All examples follow Setup → Business Logic → Execution → Teardown
structure with CreateExtensionExpression and introspect_and_adapt().

* feat(extensions): add postgis_raster and pgrouting Protocol/Mixin, update KNOWN_EXTENSIONS

- Add PostgresPostgisRasterSupport protocol and PostgresPostgisRasterMixin
  for PostGIS raster type operations (ST_AsRaster, ST_RastFromText, etc.)
- Add PostgresPgRoutingSupport protocol and PostgresPgRoutingMixin
  for pgRouting path finding operations (pgr_dijkstra, pgr_astar, etc.)
- Update KNOWN_EXTENSIONS: add raster_type feature for postgis_raster,
  dijkstra feature for pgrouting, cancel/run features for pg_cron
- Mark 8 extensions as requires_dialect: False that don't need
  Protocol/Mixin adaptation (postgis_topology, postgis_sfcgal,
  postgis_tiger_geocoder, amcheck, pageinspect, pgtap,
  pg_logicalinspect, address_standardizer_data_us)

* refactor(examples): replace raw SQL with expression API in extension examples

Replace raw SQL strings with expression-based API calls across all 12
extension example files. Key changes:

- Use DropTableExpression instead of raw "DROP TABLE IF EXISTS" SQL
- Use FunctionCall for all SQL function invocations (similarity, crypt,
  digest, ST_Distance, levenshtein, etc.)
- Use BinaryExpression for PostgreSQL-specific operators (@>, <@, <->,
  <=>, ?, ||, ~, %, &&)
- Use Subquery wrapping for BinaryExpression aliases (BinaryExpression
  lacks AliasableMixin)
- Use QueryExpression for all SELECT queries instead of raw SQL
- Simplify RawSQLPredicate workaround: pass BinaryExpression directly
  as WHERE condition (format_where_clause calls to_sql() generically)

Remaining RawSQLExpression usage is for extension-specific literals
that have no expression support: hstore key=>value syntax, pgvector
[x,y,z] syntax, cube (x,y,z) syntax, intarray {1,3} syntax, and
PostgreSQL ::type cast notation.

* fix(postgres): remove RawSQLExpression from extension examples and fix expression system gaps

- Override PostgresDialect.format_binary_operator to escape % as %% for
  psycopg compatibility (pg_trgm similarity operator)
- Add PostgresIndexMixin.format_create_index_statement for CREATE INDEX
  with operator classes via dialect_options["opclasses"]
- Move PostgresIndexMixin before IndexMixin in PostgresDialect MRO to
  ensure override takes effect
- Remove blind sql.replace("?", "%s") in PostgresBackendMixin that broke
  hstore/jsonb ? operators
- Add uuid_ns_dns/url/oid/x500 helper functions returning FunctionCall
- Replace all RawSQLExpression usage in 12 extension examples with
  proper expression API (Literal.cast(), FunctionCall, Subquery, etc.)
- Fix cube range data format from invalid (a,b),(c,d),(e,f) to correct
  (min_point),(max_point)
- Fix result data access from integer index to dict key access
- Fix uuid_generate_v5 namespace parameter to use uuid_ns_dns() instead
  of string literal
- Remove uuid_max() call (not available in current PG versions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(postgres): modularize CLI from monolithic __main__.py to cli/ subpackage

Split the 1779-line __main__.py into a thin entry point and 9 focused
modules under cli/, following the same pattern as SQLite and MySQL:
- __init__.py: subcommand registration and dynamic dispatch
- output.py: provider factory and RICH_AVAILABLE
- connection.py: connection args, config resolution, backend creation
- info.py: info subcommand with protocol display (26 Postgres Protocols)
- query.py: query subcommand (sync + async)
- introspect.py: introspect subcommand with 'extensions' type
- status.py: status subcommand with WAL/Replication/Archive/Security sections
- named_query.py: named-query adapter using closures
- named_procedure.py: named-procedure adapter using closures
- named_connection.py: named-connection adapter with empty parent parser

Key changes:
- Remove global _default_backend variable, use closure-based approach
- Move -v/--verbose from main parser to info/status subcommands only
- Each subcommand independently decides its own arguments
- Rename test_cli.py to test_named_connection_cli.py for clarity
- Update mock paths from __main__ to cli.connection module

* fix(postgres): remove tests. prefix from module paths, add tests to PYTHONPATH, and enable autocommit

1. Remove `tests.` prefix from module paths in integration tests.
   The `tests.rhosocial...` import paths only worked with `python -m
   pytest` (CWD in sys.path makes `tests` an implicit namespace package).
   Direct `pytest` does not add CWD to sys.path, causing ModuleNotFoundError.
   Fix by using `rhosocial.xxx` paths that work via PYTHONPATH entry.

2. Add `tests` to PYTHONPATH in CI to ensure `tests/rhosocial/` is
   searched as part of the namespace package path.

3. Enable autocommit on psycopg connections to prevent psycopg 3 from
   auto-managing transactions (which conflicts with manual BEGIN/COMMIT
   statements in the ActiveRecord layer).

* refactor(backend): align expression system with three architectural principles

- Derive 5 PG protocols from generic counterparts:
  PostgresTableSupport(TableSupport),
  PostgresIndexSupport(IndexSupport),
  PostgresLockingSupport(LockingSupport),
  PostgresTriggerSupport(TriggerSupport),
  PostgresConstraintSupport(ConstraintSupport)
- Remove 4 duplicate generic protocols from PostgresDialect base class list
- Rename 22 PG expression classes with Postgres prefix:
  AdvisoryLockExpression → PostgresAdvisoryLockExpression,
  RefreshMaterializedViewPgExpression → PostgresRefreshMaterializedViewExpression,
  CreateEnumTypeExpression → PostgresCreateEnumTypeExpression, etc.
- Fix Expression-Dialect separation for 3 expression classes:
  PostgresAlterEnumTypeAddValueExpression,
  PostgresAlterEnumTypeRenameValueExpression,
  PostgresCreateRangeTypeExpression
- Make PostgresRefreshMaterializedViewExpression derive from
  RefreshMaterializedViewExpression with schema/name/concurrently aliases
- Rename format_postgres_for_update_clause → format_for_update_clause
  in PostgresLockingMixin and PostgresLockingSupport protocol
- Add TestPostgresProtocolDerivation and TestPostgresExpressionDialectSeparation
  test classes for protocol coverage validation
- Update test_postgres_protocol_conformance.py excluded_overlaps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tests): correct UUID v1 regex pattern to match standard 8-4-4-4-12 format

The third segment of UUID v1 was incorrectly matched with {3} hex chars
instead of {4}, causing test_uuid_generate_v1 to always fail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tests): add derivation pairs to protocol non-overlap exclusions

PostgresIndexSupport, PostgresTableSupport, and PostgresConstraintSupport
now derive from their generic counterparts, so method overlap is expected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(protocols): add @runtime_checkable to PostgresAdvisoryLockSupport and PostgresMultirangeSupport

These two protocols were missing the decorator, causing them to be
skipped in protocol conformance isinstance() checks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(backend): convert ? placeholders to %s for psycopg in _prepare_sql_and_params

ActiveQuery layer uses ? as parameter placeholders (e.g. where("name = ?",
params)), but psycopg v3 requires %s format. Previously the conversion
was not performed, causing "the query has N placeholders but M parameters
were passed" errors for any query using RawSQLPredicate/RawSQLExpression.

The new _convert_qmark_to_format() method handles the conversion while
preserving PostgreSQL's hstore/jsonb operators (?, ?|, ?&) by:
- Keeping ? followed by | or & as-is (operator context)
- Keeping ? preceded by alphanumeric/underscore as-is (identifier context)
- Replacing all other ? with %s (parameter placeholder context)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(backend): remove _convert_qmark_to_format fallback from PostgreSQL backend

Placeholder conversion is now handled by the ActiveRecord layer via
convert_qmark_placeholder(). The backend no longer needs to do any
? → %s conversion in _prepare_sql_and_params.

Remove _convert_qmark_to_format static method and _prepare_sql_and_params
override, restoring base class pass-through behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(dialect): handle base ForUpdateClause without strength attribute in format_for_update_clause

PostgreSQL dialect's format_for_update_clause assumed clause.strength
always exists, but the base ForUpdateClause class (used by
Query.for_update()) does not have this attribute — only
PostgresForUpdateClause does. Use getattr() with a default of
LockStrength.UPDATE for compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: install testsuite from git branch release/v1.0.0.dev12

Switch from PyPI package to git+https source for
rhosocial-activerecord-testsuite to track the new dev12 branch.

* refactor(backend): use in_transaction property instead of direct _transaction_manager access

Replace direct `_transaction_manager.is_active` checks with the
`in_transaction` property in both sync and async backends. The property
already handles the None case for `_transaction_manager`, making the
code simpler and consistent with the project's abstraction conventions.

* ci: install rhosocial-activerecord from git branch release/v1.0.0.dev25

* ci: add postgres-exts service container for extension testing

Add a second PostgreSQL service using vistart/postgres-exts image
alongside the official postgres container, allowing tests against
databases with additional extensions installed.

- postgres service: port 5432 (official image)
- postgres-exts service: port 5433 (vistart/postgres-exts image)
- Both use same version tag from matrix, same credentials
- Added postgres_exts scenario to CI config
- Wait step now checks both services are ready

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add changelog fragment for PR #32

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(backend): extract shared non-I/O methods into mixins for sync/async parity

Move identical non-I/O methods from sync and async implementations into
shared mixin classes to enforce proper sync/async parity:

- PostgresBackendMixin: log, _register_postgres_adapters,
  _is_connection_error, _is_connection_error_message,
  _parse_explain_result, CONNECTION_ERROR_SQLSTATES
- PostgresTransactionMixin (new): _ISOLATION_LEVELS, _build_begin_sql,
  set_deferrable, get_deferred_constraints, get_active_savepoint

Also fix AsyncPostgresTransactionManager.set_deferrable being incorrectly
marked async (no I/O in method body), and fix member order inconsistency
in async_backend.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(pool): add PostgreSQL connection pool stress test examples

* fix: MRO ordering for ExplainBackendMixin and Windows event loop compatibility

Changes:
- Fix MRO ordering: PostgresBackendMixin must come before ExplainBackendMixin
  to ensure _parse_explain_result returns PostgresExplainResult instead of
  BaseExplainResult
- Add WindowsSelectorEventLoopPolicy setup in tests/conftest.py for Windows
  compatibility with async tests
- Fix test: set_deferrable() is sync, not async (PostgresTransactionManager)

* refactor(named_procedure): update CLI to use simplified runner.run() API

- Updated PostgreSQL CLI to match new runner.run(backend) signature
- Removed execute_query, get_dialect parameters from handle_named_procedure
- Updated docs examples to use new API

* chore: resolve ruff warnings and add F401 fixes

- Add F401 fixes to __all__ in named_connections/__init__.py, mixins/__init__.py, protocols/__init__.py
- Fix unused imports in various files (from ruff --fix)
- Update extension examples to use 4-section format consistently

* feat(postgres): add schema namespace support for PostgreSQL backend (#33)

* test(schema): add schema namespace tests for PostgresBackend

Add test_schema_namespace.py covering:
- Dialect layer: schema capability declarations, Column/TableExpression/
  WildcardExpression with schema_name, CreateSchemaExpression/
  DropSchemaExpression SQL generation (including IF NOT EXISTS/IF EXISTS
  CASCADE)
- Backend layer: get_default_schema(), CREATE/DROP schema, CRUD in
  custom schema, information_schema introspection with schema
- Async Backend layer: async equivalents of backend operations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(schema): add get_default_schema() and schema config to PostgresBackend

- PostgresBackendMixin.get_default_schema(): returns configured
  default_schema or 'public' as PostgreSQL standard default
- PostgresConnectionConfig: add search_path and default_schema fields
- Pass search_path to psycopg connection params

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(postgres): add UUID type adapter and gen_random_uuid function factory

Add PostgresUUIDAdapter for bidirectional conversion between Python
UUID objects and database values. Add gen_random_uuid() and
uuid-ossp extension function factories that return Expression objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(postgres): add pgvector type, adapter, and function factories

Add PostgresVector dataclass, PostgresVectorAdapter, and pgvector
function factories (l2_distance, cosine_distance, inner_product,
cosine_similarity, literal) that return Expression objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(postgres): add PostGIS type, adapter, and function factories

Add PostgresGeometry dataclass, PostgresGeometryAdapter, and PostGIS
function factories (ST_MakePoint, ST_Contains, ST_Distance, etc.)
that return Expression objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(postgres): rewrite hstore module with Expression/Dialect architecture

Replace raw string returns with Expression objects (FunctionCall,
BinaryExpression) in hstore function factories. Add PostgresHstore
dataclass, PostgresHstoreAdapter, and simplify hstore extension mixin
to only keep supports_*() methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(postgres): add extension-aware function version system

Replace flat _POSTGRES_FUNCTION_VERSIONS dict with categorized
FunctionVersionRequirement definitions in function_versions.py.
Functions from extensions (hstore, uuid-ossp, pgvector, PostGIS) now
require extension installation check via introspect_and_adapt().

Key changes:
- FunctionVersionRequirement: structured version req with extension awareness
- FunctionSupportInfo: detailed support status with reason for unavailability
- supports_functions() returns Dict[str, FunctionSupportInfo]
- Extension functions return False when _extensions not probed (no guessing)
- _POSTGRES_FUNCTION_VERSIONS moved to function_versions.py, 15 categories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(tests): move transaction tests into transaction/ subdirectory

Reorganize transaction-related test files into a dedicated transaction/
subdirectory under feature/backend/ for better test structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(postgres): align Expression-Dialect architecture across all function modules

Implement all 6 phases of the expression-dialect alignment plan:

Phase 1: Rewrite core native type function factories (geometric, bit_string,
enum, array, json, text_search, xml, network) to return Expression objects
instead of raw SQL strings.

Phase 2-3: Create new function factory modules for all extension types
(pgcrypto, ltree, pg_trgm, fuzzystrmatch, earthdistance, intarray, cube,
citext, pg_stat_statements, postgis_raster, pglogical, pgrouting, tablefunc,
pg_cron, pgaudit, hypopg, pg_surgery, pg_walinspect, address_standardizer,
pg_partman, pg_repack, orafce) and add data_type function factories
(xid8_literal, array_literal).

Phase 4: Migrate multirange/data_type format_* methods from mixins to
function factories, retaining only supports_* and DDL methods in mixins.

Phase 5: Update all tests to use Expression-based assertions (to_sql(),
isinstance checks) instead of raw string comparison.

Phase 6: Add 4 new protocol definitions (PostgresFullTextSearchSupport,
PostgresRangeTypeSupport, PostgresJSONBEnhancedSupport,
PostgresArrayEnhancedSupport) with corresponding supports_* methods in
PostgresDialect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(backend): add protocol implementation completeness tests for PostgreSQL

Add three test classes to verify bidirectional consistency between
Protocol declarations and Mixin/Dialect implementations:

- TestProtocolMethodSignatureConformance: verifies method signatures
  on PostgresDialect match Protocol declarations
- TestProtocolMixinForwardCoverage: verifies Protocol-declared methods
  exist in corresponding Mixins (42 Protocol-Mixin pairs)
- TestProtocolMixinReverseCoverage: verifies Mixin-implemented methods
  are declared in corresponding Protocols

Also fix protocol implementation gaps discovered by the tests:

New Mixins (5):
- PostgresFullTextSearchMixin: 4 methods migrated from Dialect
- PostgresRangeTypeMixin: 4 methods migrated from Dialect
- PostgresJSONBEnhancedMixin: 5 methods migrated + 1 new
  (supports_jsonb_subscript)
- PostgresArrayEnhancedMixin: 5 methods migrated + 1 new
  (supports_domain_arrays)
- PostgresParallelQueryMixin: 6 new methods (all previously missing)

Protocol declarations completed (8):
- PostgresExtensionSupport: +2 methods
- PostgresPartitionSupport: +1 method
- PostgresIndexSupport: +6 methods
- PostgresDataTypeSupport: +1 method
- PostgresMultirangeSupport: +2 methods
- PostgresEnumTypeSupport: +10 methods
- PostgresQueryOptimizationSupport: +6 methods
- PostgresSQLSyntaxSupport: +9 methods

Upgrade get_all_protocol_methods() to walk MRO for Python < 3.12,
and add get_own_protocol_methods() for forward coverage testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve all ruff lint errors across postgres backend

- Rename PostgresGeometryAdapter (postgis) to PostgresPostGISAdapter
  to avoid name collision with geometric adapter (F811)
- Rename orafce trunc/round to orafce_trunc/orafce_round to avoid
  collision with math_enhanced module (F811)
- Add TYPE_CHECKING imports for FunctionSupportInfo and
  FunctionVersionRequirement in dialect.py (F821)
- Remove unused imports: List, Optional, Dict, re, field (F401)
- Remove unused variable assignment for provider in cli/info.py (F841)
- Add `from None` to raise inside except in types/postgis.py (B904)
- Fix f-string without placeholders in examples/types/uuid.py (F541)
- Break long lines across adapters, cli, introspection, functions,
  mixins, and protocols modules (E501)
- Configure ruff per-file-ignores for examples/ (E402, E501) and
  migrate select/ignore to [tool.ruff.lint] section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore quoted type annotations and update test references

- Add quotes back around FunctionSupportInfo and
  FunctionVersionRequirement in dialect.py type annotations,
  since they are imported under TYPE_CHECKING and not available
  at runtime (Python 3.8+ evaluates annotations eagerly)
- Update test_postgis_types.py to use PostgresPostGISAdapter
  instead of the old PostgresGeometryAdapter name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tests): update function support tests for FunctionSupportInfo return type

supports_functions() returns Dict[str, FunctionSupportInfo] (not bool),
so tests must check .supported attribute instead of using `is True`/`is False`.

- Replace `result[func] is True` with `result[func].supported`
- Replace `result[func] is False` with `not result[func].supported`
- Update hstore extension tests: without database connection,
  extension functions return extension_not_probed instead of True
- Add FunctionSupportInfo import and type check test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(postgres): override format_column() with alias-aware schema handling

PostgreSQL rejects three-segment column references (schema.table.column)
when the table has an alias in the FROM/JOIN clause. Override
format_column() to implement PostgreSQL-specific rules:
- When table has an alias, use alias.column (ignore schema_name)
- When table has no alias but has schema, use schema.table.column
- Otherwise use standard table.column or column form

This fixes the JOIN bug where ON conditions generated
"public"."users"."id" instead of "users"."id" when tables had aliases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(postgres): add async integration tests and expression-based fixtures for all 29 extensions

Add async integration tests matching existing sync tests for all 29
extension test files, including async fixtures using
async_postgres_backend_single. Convert raw SQL strings in fixtures and
test methods to expression objects (CreateTableExpression,
InsertExpression, QueryExpression, FunctionCall, etc.).

Key changes:
- Add async_ensure_extension_installed to test utils
- Add async_postgres_backend_single fixture to conftest
- Add 29 async test classes with _async table suffix convention
- Replace raw SQL with expression objects across all integration tests
- Use Literal.cast("type") for PostgreSQL type casts (cube, hstore,
  ltree, vector, uuid, integer[], geography) instead of RawSQLExpression
- Use Subquery wrapper for ComparisonPredicate aliasing
- Use specific exception types (IntegrityError, DatabaseError) instead
  of bare Exception in pytest.raises
- Fix earth_distance() assertion (returns meters, not miles)
- Fix hypopg_estimate_size assertion (maps to hypopg_relation_size)

Remaining RawSQLExpression usage (with TODO comments):
- COUNT(*) where Literal("*") gets parameterized
- PostgreSQL operators with aliases where ComparisonPredicate lacks .as_()
- Index opclass specifications (no structural expression support)

All 316 tests pass, 13 skipped (extensions not available/permissions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tests): skip pg_stat_statements and pg_walinspect tests on CI env constraints

- pg_stat_statements: skip when extension requires shared_preload_libraries
- pg_walinspect: skip when LSN is rejected as future start LSN (Postgres 15)

Both failures are environment configuration issues, not code bugs.

* chore: add changelog fragment for PR #33

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(postgres): add SQL injection security fixes for PostgreSQL dialect

Add comprehensive SQL injection protection for the PostgreSQL backend:

- Add validation for EXCLUDE constraint index methods (using allowlist)
- Add validation for EXCLUDE constraint operators (using allowlist)
- Add _escape_sql_string() for DEFAULT constraint values
- Add _validate_data_type() for column data types
- Add security unit tests for constraint validation
- Add integration tests with real PostgreSQL database execution

Security fixes prevent SQL injection through:
- Index method allowlist validation (gist, btree, spgist, etc.)
- Operator allowlist validation (&&, =, @>, etc.)
- String escaping for DEFAULT values
- Type validation for column definitions

Fixes: #sql-injection-audit-remediation

* ci: use PyPI source for testsuite dependency

* ci: use PyPI source for activerecord dependency

* fix(postgres): support array data types in _validate_data_type

PostgreSQL array types like TEXT[] and INTEGER[] were rejected because
the base dialect's allowlist pattern didn't include brackets [].

Override _validate_data_type in PostgresDialect to support array types,
with a note that this can be removed once rhosocial-activerecord base
dialect includes this change in a future release.

* docs: update CHANGELOG for v1.0.0.dev12

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@vistart
vistart deleted the feature/named-query branch May 30, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant