fix: handle PostgreSQL identifier quoting in partial index predicate comparison (#5788)
## Description
Closes #5787
## Problem
After #5780, partial indexes with object literal `where` clauses are
still recreated on every migration when predicates contain multiple
conditions. PostgreSQL's `pg_get_expr()` returns unquoted identifiers
for lowercase column names (`postcode`), while Prisma generates quoted
identifiers (`"postcode"`). The AST comparison treats these as
different, causing needless drop+recreate cycles.
```sql
-- Prisma generates:
("deletedAt" IS NULL AND "postcode" IS NOT NULL)
-- pg_get_expr() returns:
((("deletedAt" IS NULL) AND (postcode IS NOT NULL)))
```
## Solution
Extend `exprs_semantically_eq` to treat quoted and unquoted identifiers
as equivalent when the unquoted form is a valid PostgreSQL identifier.
Uses `sqlparser` with `PostgreSqlDialect` to verify that the identifier
value can be safely used without quotes (i.e., it's not a reserved
keyword and follows identifier rules).
## Changes
- **PG `schema_differ.rs`** — Added identifier quoting comparison in
`exprs_semantically_eq`; checks if `"foo"` and `foo` refer to the same
column by re-parsing with `sqlparser`
- **`partial.rs`** — Regression test for object literal predicates with
`null` and `not: null` conditions
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved PostgreSQL schema comparison to more robustly treat
identifiers as equivalent across quote styles and within expressions.
* **Tests**
* Added an idempotency test for Postgres partial indexes using
object-literal filters with null and not-null conditions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->