plain.postgres 0.99.0__tar.gz → 0.99.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/PKG-INFO +1 -1
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/CHANGELOG.md +10 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/checks_structural.py +32 -17
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/pyproject.toml +1 -1
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_diagnose.py +168 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/.gitignore +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/CLAUDE.md +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/LICENSE +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/README.md +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/README.md +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/adapters.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/agents/.claude/rules/plain-postgres.md +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/agents/.claude/skills/plain-postgres-doctor/SKILL.md +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/aggregates.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/base.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/cli/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/cli/converge.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/cli/core.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/cli/decorators.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/cli/diagnose.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/cli/migrations.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/cli/schema.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/cli/sync.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/config.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/connection.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/constants.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/constraints.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/convergence/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/convergence/analysis.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/convergence/fixes.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/convergence/planning.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/database_url.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/db.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/ddl.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/default_settings.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/deletion.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/dialect.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/entrypoints.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/enums.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/exceptions.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/expressions.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/base.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/binary.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/boolean.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/duration.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/encrypted.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/json.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/mixins.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/network.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/numeric.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/primary_key.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/related.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/related_descriptors.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/related_lookups.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/related_managers.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/reverse_descriptors.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/reverse_related.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/temporal.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/text.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/timezones.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/uuid.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/forms.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/comparison.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/datetime.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/math.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/mixins.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/random.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/text.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/uuid.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/functions/window.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/indexes.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/checks_cumulative.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/checks_snapshot.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/context.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/helpers.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/ownership.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/runner.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/types.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/schema.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/lookups.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/meta.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/middleware.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/autodetector.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/exceptions.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/executor.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/graph.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/loader.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/migration.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/base.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/fields.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/models.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/special.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/optimizer.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/questioner.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/recorder.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/serializer.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/state.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/utils.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/writer.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/options.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/otel.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/preflight.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/query.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/query_utils.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/registry.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/schema.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/sources.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/sql/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/sql/compiler.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/sql/constants.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/sql/datastructures.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/sql/query.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/sql/where.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/test/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/test/database.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/test/pytest.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/transaction.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/types.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/types.pyi +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/utils.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/forms.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0001_initial.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0004_defaultquerysetmodel_mixintestmodel_and_more.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0005_feature_carfeature_car_features.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0006_secretstore.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0007_treenode_unconstrainedchild.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0008_setsentinelparent_diamondparenta_midparent_and_more.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0009_circb_circa_circb_partner.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0010_hideableitem.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0011_defaultsexample.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0012_iterationexample.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0013_indexexample_constraintexample_nullabilityexample.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0014_widget_rename_feature_tag_remove_carfeature_car_and_more.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0015_dbdefaultsexample.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0016_formsexample.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0017_random_string_token.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/__init__.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/constraints.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/defaults.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/delete.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/encrypted.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/forms.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/indexes.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/iteration.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/mixins.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/nullability.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/querysets.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/relationships.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/trees.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/models/unregistered.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/urls.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/views.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/settings.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/urls.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/conftest.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/conftest_convergence.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_autodetector_not_null_errors.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_autodetector_type_change.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_connection_isolation.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_connection_lifecycle.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_connection_pool.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_convergence.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_convergence_constraints.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_convergence_defaults.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_convergence_fk.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_convergence_indexes.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_convergence_nullability.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_convergence_timeouts.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_database_url.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_db_expression_defaults.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_delete_behaviors.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_encrypted_fields.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_exceptions.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_executor_connection_hook.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_field_defaults.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_functions_uuid.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_health.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_introspection.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_iterator.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_literal_default_persistence.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_m2m.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_management_connection.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_manager_assignment.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_migration_executor.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_mixins.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_modelform_roundtrip.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_no_callable_defaults.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_otel_metrics.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_random_string_field.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_raw_query.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_read_only_transactions.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_related.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_schema_normalize_type.py +0 -0
- {plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/test_schema_timeouts.py +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# plain-postgres changelog
|
|
2
2
|
|
|
3
|
+
## [0.99.1](https://github.com/dropseed/plain/releases/plain-postgres@0.99.1) (2026-04-26)
|
|
4
|
+
|
|
5
|
+
### What's changed
|
|
6
|
+
|
|
7
|
+
- **Duplicate-index check now catches expression-prefix duplicates.** Previously the check excluded any index containing expressions (it compared raw `indkey`/`indclass` arrays), so a redundant `(LOWER(email))` alongside `(LOWER(email), team_id)` was missed. The query now compares per-column `pg_get_indexdef(indexrelid, k, false)` text — canonical output that includes column name/expression, opclass, collation, and sort order — and checks `pg_am.amname` separately so a hash and btree on the same column don't false-match. ([4bd8a713649f](https://github.com/dropseed/plain/commit/4bd8a713649f))
|
|
8
|
+
|
|
9
|
+
### Upgrade instructions
|
|
10
|
+
|
|
11
|
+
- No changes required.
|
|
12
|
+
|
|
3
13
|
## [0.99.0](https://github.com/dropseed/plain/releases/plain-postgres@0.99.0) (2026-04-23)
|
|
4
14
|
|
|
5
15
|
### What's changed
|
|
@@ -66,50 +66,65 @@ def check_invalid_indexes(
|
|
|
66
66
|
def check_duplicate_indexes(
|
|
67
67
|
cursor: Any, table_owners: dict[str, TableOwner]
|
|
68
68
|
) -> CheckResult:
|
|
69
|
-
"""Indexes where one is a column-prefix of another on the same table.
|
|
69
|
+
"""Indexes where one is a column-prefix of another on the same table.
|
|
70
|
+
|
|
71
|
+
Each index column's canonical definition comes from
|
|
72
|
+
``pg_get_indexdef(indexrelid, k, false)`` — that text includes the
|
|
73
|
+
column name or expression, plus any non-default operator class,
|
|
74
|
+
collation, or sort order. Comparing per-column definitions means we
|
|
75
|
+
catch expression duplicates (e.g. two ``LOWER(email)`` columns) and
|
|
76
|
+
won't false-positive across different opclasses or collations.
|
|
77
|
+
Access method (``pg_am.amname``) is checked separately because the
|
|
78
|
+
per-column text doesn't include it — a hash and btree on the same
|
|
79
|
+
column have identical column text but support different operators.
|
|
80
|
+
"""
|
|
70
81
|
cursor.execute("""
|
|
71
82
|
SELECT
|
|
72
83
|
ct.relname AS table_name,
|
|
73
84
|
ci.relname AS index_name,
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
am.amname AS access_method,
|
|
86
|
+
array(
|
|
87
|
+
SELECT pg_get_indexdef(i.indexrelid, k, false)
|
|
88
|
+
FROM generate_series(1, i.indnatts) AS k
|
|
89
|
+
) AS column_defs,
|
|
76
90
|
i.indisunique,
|
|
77
|
-
pg_size_pretty(pg_relation_size(ci.oid)) AS index_size
|
|
78
|
-
pg_relation_size(ci.oid) AS index_size_bytes
|
|
91
|
+
pg_size_pretty(pg_relation_size(ci.oid)) AS index_size
|
|
79
92
|
FROM pg_catalog.pg_index i
|
|
80
93
|
JOIN pg_catalog.pg_class ci ON ci.oid = i.indexrelid
|
|
81
94
|
JOIN pg_catalog.pg_class ct ON ct.oid = i.indrelid
|
|
95
|
+
JOIN pg_catalog.pg_am am ON am.oid = ci.relam
|
|
82
96
|
JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace
|
|
83
97
|
WHERE n.nspname = 'public'
|
|
84
98
|
AND i.indisvalid
|
|
85
|
-
AND i.indexprs IS NULL
|
|
86
99
|
AND i.indpred IS NULL
|
|
87
100
|
ORDER BY ct.relname, ci.relname
|
|
88
101
|
""")
|
|
89
102
|
rows = cursor.fetchall()
|
|
90
103
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
for table_name, index_name, cols, opclasses, is_unique, size, size_bytes in rows:
|
|
104
|
+
by_table: dict[str, list[tuple[str, str, list[str], bool, str]]] = {}
|
|
105
|
+
for table_name, index_name, am_name, defs, is_unique, size in rows:
|
|
94
106
|
by_table.setdefault(table_name, []).append(
|
|
95
|
-
(index_name,
|
|
107
|
+
(index_name, am_name, defs, is_unique, size)
|
|
96
108
|
)
|
|
97
109
|
|
|
98
110
|
items: list[CheckItem] = []
|
|
99
|
-
flagged: set[str] = set()
|
|
111
|
+
flagged: set[str] = set()
|
|
100
112
|
for table_name, indexes in by_table.items():
|
|
101
113
|
for i, idx_a in enumerate(indexes):
|
|
102
114
|
for idx_b in indexes[i + 1 :]:
|
|
103
115
|
# Check both directions: is either a prefix of the other?
|
|
104
116
|
for shorter, longer in [(idx_a, idx_b), (idx_b, idx_a)]:
|
|
105
|
-
name_s,
|
|
106
|
-
name_l,
|
|
117
|
+
name_s, am_s, defs_s, unique_s, size_s = shorter
|
|
118
|
+
name_l, am_l, defs_l, _, _ = longer
|
|
119
|
+
# Different access methods serve different operators
|
|
120
|
+
# (e.g. hash supports `=` only, btree supports ordering),
|
|
121
|
+
# and unique indexes serve a constraint purpose.
|
|
107
122
|
if (
|
|
108
123
|
name_s not in flagged
|
|
109
|
-
and
|
|
110
|
-
and
|
|
111
|
-
and
|
|
112
|
-
and
|
|
124
|
+
and am_s == am_l
|
|
125
|
+
and not unique_s
|
|
126
|
+
and len(defs_s) < len(defs_l)
|
|
127
|
+
and defs_l[: len(defs_s)] == defs_s
|
|
113
128
|
):
|
|
114
129
|
source, package, model_class, model_file = _table_info(
|
|
115
130
|
table_name, table_owners
|
|
@@ -126,6 +126,174 @@ class TestStructuralScenarios:
|
|
|
126
126
|
assert flagged[0]["name"] == "_diag_dup_a_idx"
|
|
127
127
|
assert "_diag_dup_ab_idx" in flagged[0]["detail"]
|
|
128
128
|
|
|
129
|
+
def test_duplicate_indexes_detected_against_expression_index(self) -> None:
|
|
130
|
+
"""A plain-column index is redundant with a longer index whose
|
|
131
|
+
trailing columns are expressions (e.g. `(team, LOWER(email))`)."""
|
|
132
|
+
_execute(
|
|
133
|
+
'CREATE TABLE "_diag_dup_expr" ('
|
|
134
|
+
'"id" serial PRIMARY KEY, "team_id" int, "email" text)'
|
|
135
|
+
)
|
|
136
|
+
_execute(
|
|
137
|
+
'CREATE INDEX "_diag_dup_expr_team_idx" ON "_diag_dup_expr" ("team_id")'
|
|
138
|
+
)
|
|
139
|
+
_execute(
|
|
140
|
+
'CREATE UNIQUE INDEX "_diag_dup_expr_team_lower_email_uniq" '
|
|
141
|
+
'ON "_diag_dup_expr" ("team_id", LOWER("email"))'
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
conn = get_connection()
|
|
145
|
+
with conn.cursor() as cursor:
|
|
146
|
+
result = check_duplicate_indexes(cursor, {})
|
|
147
|
+
|
|
148
|
+
flagged = [i for i in result["items"] if i["table"] == "_diag_dup_expr"]
|
|
149
|
+
assert len(flagged) == 1, (
|
|
150
|
+
f"expected one duplicate on _diag_dup_expr, got {flagged}"
|
|
151
|
+
)
|
|
152
|
+
assert flagged[0]["name"] == "_diag_dup_expr_team_idx"
|
|
153
|
+
assert "_diag_dup_expr_team_lower_email_uniq" in flagged[0]["detail"]
|
|
154
|
+
|
|
155
|
+
def test_duplicate_indexes_detected_when_shorter_is_expression(self) -> None:
|
|
156
|
+
"""A `(LOWER(email))` index is redundant with `(LOWER(email), team_id)`
|
|
157
|
+
— the longer index's leading column satisfies any read on the shorter,
|
|
158
|
+
and `pg_get_indexdef` per-column comparison catches the match."""
|
|
159
|
+
_execute(
|
|
160
|
+
'CREATE TABLE "_diag_dup_expr_short" ('
|
|
161
|
+
'"id" serial PRIMARY KEY, "email" text, "team_id" int)'
|
|
162
|
+
)
|
|
163
|
+
_execute(
|
|
164
|
+
'CREATE INDEX "_diag_dup_expr_short_lower_idx" '
|
|
165
|
+
'ON "_diag_dup_expr_short" (LOWER("email"))'
|
|
166
|
+
)
|
|
167
|
+
_execute(
|
|
168
|
+
'CREATE INDEX "_diag_dup_expr_short_lower_team_idx" '
|
|
169
|
+
'ON "_diag_dup_expr_short" (LOWER("email"), "team_id")'
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
conn = get_connection()
|
|
173
|
+
with conn.cursor() as cursor:
|
|
174
|
+
result = check_duplicate_indexes(cursor, {})
|
|
175
|
+
|
|
176
|
+
flagged = [i for i in result["items"] if i["table"] == "_diag_dup_expr_short"]
|
|
177
|
+
assert len(flagged) == 1, (
|
|
178
|
+
f"expected one duplicate on _diag_dup_expr_short, got {flagged}"
|
|
179
|
+
)
|
|
180
|
+
assert flagged[0]["name"] == "_diag_dup_expr_short_lower_idx"
|
|
181
|
+
assert "_diag_dup_expr_short_lower_team_idx" in flagged[0]["detail"]
|
|
182
|
+
|
|
183
|
+
def test_duplicate_indexes_not_flagged_for_same_length_expression_indexes(
|
|
184
|
+
self,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Two single-column expression indexes with different expressions
|
|
187
|
+
(`LOWER(email)` vs `UPPER(email)`) — neither qualifies as shorter
|
|
188
|
+
under `len(defs_s) < len(defs_l)`, so no false positive."""
|
|
189
|
+
_execute(
|
|
190
|
+
'CREATE TABLE "_diag_dup_expr_eq" ("id" serial PRIMARY KEY, "email" text)'
|
|
191
|
+
)
|
|
192
|
+
_execute(
|
|
193
|
+
'CREATE INDEX "_diag_dup_expr_eq_lower_idx" '
|
|
194
|
+
'ON "_diag_dup_expr_eq" (LOWER("email"))'
|
|
195
|
+
)
|
|
196
|
+
_execute(
|
|
197
|
+
'CREATE INDEX "_diag_dup_expr_eq_upper_idx" '
|
|
198
|
+
'ON "_diag_dup_expr_eq" (UPPER("email"))'
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
conn = get_connection()
|
|
202
|
+
with conn.cursor() as cursor:
|
|
203
|
+
result = check_duplicate_indexes(cursor, {})
|
|
204
|
+
|
|
205
|
+
flagged = [i for i in result["items"] if i["table"] == "_diag_dup_expr_eq"]
|
|
206
|
+
assert flagged == [], (
|
|
207
|
+
f"expected no duplicates flagged for same-length expression indexes, got {flagged}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def test_duplicate_indexes_detected_when_longer_expression_index_is_not_unique(
|
|
211
|
+
self,
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Uniqueness only matters for the shorter side (a unique short index
|
|
214
|
+
serves a constraint purpose). The longer side's uniqueness is
|
|
215
|
+
irrelevant — a redundant non-unique short index should still be
|
|
216
|
+
flagged against a non-unique longer expression index."""
|
|
217
|
+
_execute(
|
|
218
|
+
'CREATE TABLE "_diag_dup_expr_nonuniq" ('
|
|
219
|
+
'"id" serial PRIMARY KEY, "team_id" int, "email" text)'
|
|
220
|
+
)
|
|
221
|
+
_execute(
|
|
222
|
+
'CREATE INDEX "_diag_dup_expr_nonuniq_team_idx" '
|
|
223
|
+
'ON "_diag_dup_expr_nonuniq" ("team_id")'
|
|
224
|
+
)
|
|
225
|
+
_execute(
|
|
226
|
+
'CREATE INDEX "_diag_dup_expr_nonuniq_team_lower_idx" '
|
|
227
|
+
'ON "_diag_dup_expr_nonuniq" ("team_id", LOWER("email"))'
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
conn = get_connection()
|
|
231
|
+
with conn.cursor() as cursor:
|
|
232
|
+
result = check_duplicate_indexes(cursor, {})
|
|
233
|
+
|
|
234
|
+
flagged = [i for i in result["items"] if i["table"] == "_diag_dup_expr_nonuniq"]
|
|
235
|
+
assert len(flagged) == 1, (
|
|
236
|
+
f"expected one duplicate on _diag_dup_expr_nonuniq, got {flagged}"
|
|
237
|
+
)
|
|
238
|
+
assert flagged[0]["name"] == "_diag_dup_expr_nonuniq_team_idx"
|
|
239
|
+
assert "_diag_dup_expr_nonuniq_team_lower_idx" in flagged[0]["detail"]
|
|
240
|
+
|
|
241
|
+
def test_duplicate_indexes_not_flagged_across_access_methods(self) -> None:
|
|
242
|
+
"""A hash index and a btree index on the same column support different
|
|
243
|
+
operators (hash: only `=`; btree: ordering, range, etc.). Per-column
|
|
244
|
+
text is identical, so we must check `pg_am.amname` to avoid telling
|
|
245
|
+
the user to drop a deliberately-chosen hash index."""
|
|
246
|
+
_execute(
|
|
247
|
+
'CREATE TABLE "_diag_dup_am" ('
|
|
248
|
+
'"id" serial PRIMARY KEY, "team_id" int, "email" text)'
|
|
249
|
+
)
|
|
250
|
+
_execute(
|
|
251
|
+
'CREATE INDEX "_diag_dup_am_team_hash_idx" '
|
|
252
|
+
'ON "_diag_dup_am" USING hash ("team_id")'
|
|
253
|
+
)
|
|
254
|
+
_execute(
|
|
255
|
+
'CREATE INDEX "_diag_dup_am_team_email_idx" '
|
|
256
|
+
'ON "_diag_dup_am" USING btree ("team_id", LOWER("email"))'
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
conn = get_connection()
|
|
260
|
+
with conn.cursor() as cursor:
|
|
261
|
+
result = check_duplicate_indexes(cursor, {})
|
|
262
|
+
|
|
263
|
+
flagged = [i for i in result["items"] if i["table"] == "_diag_dup_am"]
|
|
264
|
+
assert flagged == [], (
|
|
265
|
+
f"expected no duplicates flagged across access methods, got {flagged}"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def test_duplicate_indexes_not_flagged_when_longer_starts_with_expression(
|
|
269
|
+
self,
|
|
270
|
+
) -> None:
|
|
271
|
+
"""A column-only shorter index `(team_id)` is not a true prefix of a
|
|
272
|
+
longer index that leads with an expression `(LOWER(email), team_id)`
|
|
273
|
+
— Postgres can't satisfy `WHERE team_id = ?` from the longer index,
|
|
274
|
+
and per-column text comparison correctly skips it."""
|
|
275
|
+
_execute(
|
|
276
|
+
'CREATE TABLE "_diag_dup_expr_lead" ('
|
|
277
|
+
'"id" serial PRIMARY KEY, "email" text, "team_id" int)'
|
|
278
|
+
)
|
|
279
|
+
_execute(
|
|
280
|
+
'CREATE INDEX "_diag_dup_expr_lead_team_idx" '
|
|
281
|
+
'ON "_diag_dup_expr_lead" ("team_id")'
|
|
282
|
+
)
|
|
283
|
+
_execute(
|
|
284
|
+
'CREATE INDEX "_diag_dup_expr_lead_lower_team_idx" '
|
|
285
|
+
'ON "_diag_dup_expr_lead" (LOWER("email"), "team_id")'
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
conn = get_connection()
|
|
289
|
+
with conn.cursor() as cursor:
|
|
290
|
+
result = check_duplicate_indexes(cursor, {})
|
|
291
|
+
|
|
292
|
+
flagged = [i for i in result["items"] if i["table"] == "_diag_dup_expr_lead"]
|
|
293
|
+
assert flagged == [], (
|
|
294
|
+
f"expected no duplicates flagged when longer leads with expression, got {flagged}"
|
|
295
|
+
)
|
|
296
|
+
|
|
129
297
|
def test_missing_fk_index_detected(self) -> None:
|
|
130
298
|
_execute('CREATE TABLE "_diag_fk_parent" ("id" serial PRIMARY KEY)')
|
|
131
299
|
_execute(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/related_descriptors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/fields/reverse_descriptors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/context.py
RENAMED
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/helpers.py
RENAMED
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/ownership.py
RENAMED
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/runner.py
RENAMED
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/introspection/health/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/__init__.py
RENAMED
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/base.py
RENAMED
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/fields.py
RENAMED
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/models.py
RENAMED
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/plain/postgres/migrations/operations/special.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0006_secretstore.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0010_hideableitem.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.99.0 → plain_postgres-0.99.1}/tests/app/examples/migrations/0016_formsexample.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|