-
Notifications
You must be signed in to change notification settings - Fork 173
fix(schema): support complex Drizzle method chaining patterns #3614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/github-folder-url-support
Are you sure you want to change the base?
fix(schema): support complex Drizzle method chaining patterns #3614
Conversation
Enhance the Drizzle PostgreSQL parser to correctly handle complex method chaining patterns like `.enableRLS().$comment()`. Previously, only simple chaining patterns were supported. Key changes: - Add extractPgTableFromChain() function to recursively traverse method chains - Enhance parsePgTableWithComment() to handle nested method calls - Refactor mainParser for better separation of concerns - Add comprehensive tests for .enableRLS() and .$comment() method chaining - Add test for complex chaining combining multiple methods - Make isPgTableCall private as it's only used internally This fixes GitHub folder URL parsing for Drizzle schemas that use multiple method chaining patterns, ensuring all tables are correctly parsed and displayed in ERD visualizations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
|
Caution Review failedFailed to post review comments WalkthroughRefactors ERD page to delegate URL-based content fetching to new GitHub folder handler and a single-file path, consolidates error handling and format detection, and updates parsing flow. Adds a new GitHub URL handler module with tests. Enhances Drizzle Postgres parser to support chained pgTable patterns and comment/schema/enums handling with new AST utilities and tests. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant P as ERD Page (page.tsx)
participant H as URL Handlers
participant F as Format Detector
participant S as Schema Parser
participant V as ERDViewer
U->>P: Request /erd/p/[...slug]
P->>H: Resolve content (GitHub folder or single file)
alt Content fetched
H-->>P: { input, detectedFormatFromFiles }
P->>F: determineSchemaFormat(params, detectedFormatFromFiles, content)
F-->>P: format
P->>S: parse(input, format)
S-->>P: schema
P->>V: Render with schema/layout
else Error or missing input
H-->>P: { error }
P->>V: renderErrorView(error or NetworkError)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Updates to Preview Branch (feature/github-drizzle-method-chaining-support) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
🤖 Agent Deep Modeling ExecutionProcessing time: 7m 49s Command Output
RUN v3.2.4 /home/runner/work/liam/liam/frontend/internal-packages/agent (node:7523) ExperimentalWarning: WASI is an experimental feature and might change at any time ✅ [INFO] 2025-09-26T11:24:17.472Z ⏺ (pm): Purpose: Save finalized BRD for the simple user management system; Inputs: 1–2 sentence businessRequirement + categorized functionalRequirements object. 🔧 saveRequirementsToArtifactTool(businessRequirement: "Deliver a simple ...", functionalRequirements: {11 keys}) ⎿ unknown: Requirements saved successfully to artifact 🔧 schemaDesignTool(operations: [11 items]) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) ⏺ (qa): { 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) ⏺ (qa): { 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) ⏺ (qa): { 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) ⏺ (qa): { 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys}) ⏺ (qa): {"testcaseWithDml":{"requirementType":"functional","requirementCategory":"Integration & API Access","requirement":"Programmatic responses shall include pagination metadata (total, page, and page_size) and shall enforce Admin-only access.","title":"Admin-only paginated user listing with total metadata; admin role gate enforced","description":"This test seeds a set of users (including admins, regular users, and a soft-deleted user) using UUIDs generated by gen_random_uuid(). It then exercises admin-only pagination with total metadata across two pages and validates that a non-admin context yields zero admin-visible rows while still returning the pagination metadata. Soft-deleted records are excluded from results. The test uses distinct created_at timestamps to ensure deterministic ordering for pagination.","dmlOperation":{"operation_type":"INSERT","sql":"-- Seed data: admins, regular users and a soft-deleted user (respect all not-null constraints)\nINSERT INTO users (\n id, role, email, phone, status, timezone, last_name, anonymized, avatar_url, created_at, deleted_at, deleted_by, first_name, note_admin, updated_at, mfa_enabled, display_name, profile_json, last_login_at, lockout_until, password_hash, mfa_secret_enc, email_normalized, email_verified_at, failed_login_attempts, require_password_change, last_verification_sent_at, verification_resend_count_24h\n) VALUES\n (gen_random_uuid(), 'admin'::role_enum, 'admin1@example.com', NULL, 'active'::status_enum, 'UTC', 'AdminLast1', false, NULL, now() - INTERVAL '60 seconds', NULL, NULL, 'Admin', NULL, now() - INTERVAL '60 seconds', true, 'Admin One', NULL, NULL, NULL, NULL, NULL, 'admin1@example.com', NULL, 0, false, NULL, 0),\n (gen_random_uuid(), 'admin'::role_enum, 'admin2@example.com', NULL, 'active'::status_enum, 'UTC', 'AdminLast2', false, NULL, now() - INTERVAL '50 seconds', NULL, NULL, 'Admin', NULL, now() - INTERVAL '50 seconds', true, 'Admin Two', NULL, NULL, NULL, NULL, NULL, 'admin2@example.com', NULL, 0, false, NULL, 0),\n (gen_random_uuid(), 'user'::role_enum, 'user1@example.com', NULL, 'active'::status_enum, 'UTC', 'UserLast1', false, NULL, now() - INTERVAL '40 seconds', NULL, NULL, 'User', NULL, now() - INTERVAL '40 seconds', false, 'User One', NULL, NULL, NULL, NULL, NULL, 'user1@example.com', NULL, 0, false, NULL, 0),\n (gen_random_uuid(), 'user'::role_enum, 'user2@example.com', NULL, 'active'::status_enum, 'UTC', 'UserLast2', false, NULL, now() - INTERVAL '30 seconds', NULL, NULL, 'User', NULL, now() - INTERVAL '30 seconds', false, 'User Two', NULL, NULL, NULL, NULL, NULL, 'user2@example.com', NULL, 0, false, NULL, 0),\n (gen_random_uuid(), 'user'::role_enum, 'user_deleted@example.com', NULL, 'active'::status_enum, 'UTC', 'DeletedUser', false, NULL, now() - INTERVAL '20 seconds', now() - INTERVAL '1 day', NULL, 'Deleted', NULL, now() - INTERVAL '20 seconds', false, 'Deleted User', NULL, NULL, NULL, NULL, NULL, 'user_deleted@example.com', NULL, 0, false, NULL, 0);\n\n-- 2) Admin context: page 1 (page=1, page_size=2) with pagination metadata\nWITH current_role AS (SELECT 'admin' AS role)\nSELECT 1 AS page, 2 AS page_size, total.total AS total, u.id, u.email, u.display_name, u.created_at\nFROM (\n SELECT count() AS total\n FROM users\n WHERE deleted_at IS NULL AND (SELECT role FROM current_role) = 'admin' AND role = 'admin'\n) AS total\nLEFT JOIN (\n SELECT id, email, display_name, created_at\n FROM users\n WHERE deleted_at IS NULL AND (SELECT role FROM current_role) = 'admin' AND role = 'admin'\n ORDER BY created_at\n LIMIT 2\n OFFSET 0\n) AS u ON TRUE;\n\n-- 3) Admin context: page 2 (page=2, page_size=2)\nWITH current_role AS (SELECT 'admin' AS role)\nSELECT 2 AS page, 2 AS page_size, total.total AS total, u.id, u.email, u.display_name, u.created_at\nFROM (\n SELECT count() AS total\n FROM users\n WHERE deleted_at IS NULL AND (SELECT role FROM current_role) = 'admin' AND role = 'admin'\n) AS total\nLEFT JOIN (\n SELECT id, email, display_name, created_at\n FROM users\n WHERE deleted_at IS NULL AND (SELECT role FROM current_role) = 'admin' AND role = 'admin'\n ORDER BY created_at\n LIMIT 2\n OFFSET 2\n) AS u ON TRUE;\n\n-- 4) Non-admin context: page 1 should yield total reflecting 0 admin-visible rows; data rows may be NULL for user fields\nWITH current_role AS (SELECT 'user' AS role)\nSELECT 1 AS page, 2 AS page_size, total.total AS total, u.id, u.email, u.display_name, u.created_at\nFROM (\n SELECT count(*) AS total\n FROM users\n WHERE deleted_at IS NULL AND (SELECT role FROM current_role) = 'admin' AND role = 'admin'\n) AS total\nLEFT JOIN (\n SELECT id, email, display_name, created_at\n FROM users\n WHERE deleted_at IS NULL AND (SELECT role FROM current_role) = 'admin' AND role = 'admin'\n ORDER BY created_at\n LIMIT 2\n OFFSET 0\n) AS u ON TRUE;","description":"Seed data includes two admin users and several regular users, plus a soft-deleted user. The admin context queries return pagination metadata (total, page, page_size) and up to 2 user rows for page 1 and page 2. The non-admin context demonstrates access enforcement by returning total=0 with no admin-visible rows, while still returning metadata."}}} 🔧 saveTestcase(testcaseWithDml: {6 keys}) 🔧 saveTestcase(testcaseWithDml: {6 keys})stderr | src/createGraph.integration.test.ts > createGraph Integration > should execute complete workflow Context: trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=f8e2f2d8-1664-43ff-9693-b8a089e96b1c; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=23428490-dea0-4b14-a2f4-512e4c15c578; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=0ab954fd-85cb-4cae-8825-7220c99a512d; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=31345c54-418a-4156-898f-dc427e5d2b7c; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=d05444c0-3917-4760-8a1c-f9f45ba2a448; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=62b0fb1b-987c-4e67-9234-153e9a738791; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=66e49701-d9ee-4eff-b41b-a4566c2dd7bd; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=44944a7e-2613-4daa-8a5c-e9e8aac04004; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=5fed826a-c2be-4083-bd14-23fd6d4c08f0; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=97e745e9-8b4d-4d17-b80c-c5139019766b; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=5ff369f1-bba5-4379-87ce-4cc180f3bf7e; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=d70824e1-f3b1-4dd0-aad0-ecb9306b72e6; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=283a7d48-fbe0-4ece-a1d7-dcd8f7cd926f; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=da81b46e-a86f-48ee-9e64-9b3beb96d3b8; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=54699032-299c-4eb8-94b1-55f1cf6de7f2; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=466275e5-f7cd-4e57-a530-533862d22440; trace=8f142a58-50ec-4089-8403-be670a2d7a80,id=ccda46db-d48e-4c47-beb2-45cf32eb0372 ⎿ unknown: 1/31 test cases passed, 30 failed ❌ Test Case: Email Uniqueness and Case-Insensitive Normalization on Save for Users1. Error:
|
Summary
Enhance the Drizzle PostgreSQL parser to correctly handle complex method chaining patterns like
.enableRLS().$comment()
. This fixes GitHub folder URL parsing for Drizzle schemas that use multiple method chaining patterns.Issues Fixed
.enableRLS().$comment()
were not being parsed correctlyChanges Made
Core Parser Enhancements
extractPgTableFromChain()
function: Recursively traverses method chains to find the basepgTable
call, handling any depth of method chainingparsePgTableWithComment()
: Modified to useextractPgTableFromChain()
instead of expecting directpgTable
callsmainParser
: Better separation of concerns with dedicated handler functions for different call typesisPgTableCall
private: No longer exported as it's only used internallyTest Coverage
.enableRLS()
method chaining: Ensures simple method chaining works correctly.$comment()
method chaining: Tests both simple and complex patterns.enableRLS().$comment()
pattern that was failingTechnical Details
AST Structure Analysis
The complex chaining pattern
.enableRLS().$comment()
creates this AST structure:$comment(...)
enableRLS()
callpgTable(...)
call (the base)Solution Implementation
The fix involves recursively traversing the method chain to find the underlying
pgTable
call, regardless of how many intermediate method calls are chained.Test Plan
.enableRLS()
method chaining pass.$comment()
method chaining pass.enableRLS().$comment()
) passesImpact
This change ensures that ERD visualizations from GitHub folder URLs correctly display all tables from Drizzle schemas, regardless of the method chaining patterns used. Users can now use complex patterns like
.enableRLS().$comment()
without losing tables in their ERD diagrams.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Refactor
Tests