@thierrynakoa/fire-flow 12.2.2 → 13.0.1
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.
- package/CREDITS.md +25 -0
- package/DOMINION-FLOW-OVERVIEW.md +182 -38
- package/README.md +399 -690
- package/TROUBLESHOOTING.md +264 -367
- package/agents/fire-debugger.md +54 -0
- package/agents/fire-executor.md +1610 -1033
- package/agents/fire-fact-checker.md +1 -1
- package/agents/fire-planner.md +85 -17
- package/agents/fire-project-researcher.md +1 -1
- package/agents/fire-researcher.md +4 -22
- package/agents/{fire-phoenix-analyst.md → fire-resurrection-analyst.md} +394 -394
- package/agents/fire-reviewer.md +552 -499
- package/agents/fire-verifier.md +114 -19
- package/bin/cli.js +18 -101
- package/commands/fire-0-orient.md +2 -2
- package/commands/fire-1a-new.md +50 -15
- package/commands/fire-1c-setup.md +33 -5
- package/commands/fire-1d-discuss.md +87 -1
- package/commands/fire-2-plan.md +556 -527
- package/commands/fire-3-execute.md +2046 -1356
- package/commands/fire-4-verify.md +975 -906
- package/commands/fire-5-handoff.md +46 -5
- package/commands/fire-6-resume.md +2 -31
- package/commands/fire-add-new-skill.md +138 -19
- package/commands/fire-autonomous.md +14 -2
- package/commands/fire-complete-milestone.md +1 -1
- package/commands/fire-cost.md +179 -183
- package/commands/fire-debug.md +1 -6
- package/commands/fire-loop-resume.md +2 -2
- package/commands/fire-loop-stop.md +1 -1
- package/commands/fire-loop.md +2 -15
- package/commands/fire-map-codebase.md +1 -1
- package/commands/fire-migrate-database.md +548 -0
- package/commands/fire-new-milestone.md +1 -1
- package/commands/fire-reflect.md +1 -2
- package/commands/fire-research.md +142 -21
- package/commands/{fire-phoenix.md → fire-resurrect.md} +859 -603
- package/commands/fire-scaffold.md +297 -0
- package/commands/fire-search.md +1 -2
- package/commands/fire-security-scan.md +483 -484
- package/commands/fire-setup.md +359 -0
- package/commands/fire-skill.md +770 -0
- package/commands/fire-skills-diff.md +506 -506
- package/commands/fire-skills-history.md +388 -388
- package/commands/fire-skills-rollback.md +7 -7
- package/commands/fire-skills-sync.md +470 -470
- package/commands/fire-test.md +5 -5
- package/commands/fire-todos.md +1 -1
- package/commands/fire-update.md +5 -5
- package/commands/fire-validate-skills.md +282 -0
- package/commands/fire-verify-uat.md +9 -177
- package/commands/fire-vuln-scan.md +492 -493
- package/hooks/run-hook.sh +8 -8
- package/hooks/run-session-end.sh +7 -7
- package/hooks/session-end.sh +90 -90
- package/hooks/session-start.sh +1 -1
- package/package.json +4 -25
- package/plugin.json +7 -7
- package/references/autonomy-levels.md +235 -0
- package/references/behavioral-directives.md +95 -3
- package/references/blocker-tracking.md +1 -1
- package/references/circuit-breaker.md +93 -2
- package/references/context-engineering.md +227 -9
- package/references/honesty-protocols.md +70 -1
- package/references/issue-to-pr-pipeline.md +149 -150
- package/references/metrics-and-trends.md +1 -2
- package/references/research-improvements.md +4 -108
- package/references/sdlc-mapping.md +73 -0
- package/references/state-machine.md +151 -0
- package/skills-library/AVAILABLE_TOOLS_REFERENCE.md +333 -0
- package/skills-library/SKILLS-INDEX.md +57 -558
- package/skills-library/SKILLS_LIBRARY_INDEX.md +532 -0
- package/skills-library/_general/api-patterns/api-field-name-mismatch.md +107 -0
- package/skills-library/_general/api-patterns/streaming-command-timeout.md +122 -0
- package/skills-library/_general/api-patterns/streaming-proxy-cors-bypass.md +102 -0
- package/skills-library/_general/automation/settings-gui-generator.md +172 -0
- package/skills-library/_general/database-solutions/data-type-mapping-reference.md +181 -0
- package/skills-library/_general/database-solutions/mysql-limit-offset-string-coercion.md +102 -0
- package/skills-library/_general/database-solutions/mysql-to-pg-migration.md +195 -0
- package/skills-library/_general/database-solutions/orm-schema-portability.md +193 -0
- package/skills-library/_general/database-solutions/persistent-analysis-storage.md +207 -0
- package/skills-library/_general/database-solutions/pg-to-mysql-schema-migration-methodology.md +190 -0
- package/skills-library/_general/database-solutions/sql-dialect-compatibility-matrix.md +306 -0
- package/skills-library/_general/database-solutions/sqlite-to-pg-migration.md +219 -0
- package/skills-library/_general/frontend/canvas-bubble-animation-grouping.md +270 -0
- package/skills-library/_general/frontend/color-token-migration.md +112 -0
- package/skills-library/_general/frontend/framer-motion-layoutid-grouping.md +150 -0
- package/skills-library/_general/frontend/pyqt6-settings-dialog.md +191 -0
- package/skills-library/_general/frontend/react-flow-animated-layout-switching.md +101 -0
- package/skills-library/_general/frontend/react-hooks-order-debugging.md +141 -0
- package/skills-library/_general/frontend/redux-localstorage-auth-desync.md +126 -0
- package/skills-library/_general/frontend/safari-csp-theme-color-debugging.md +124 -0
- package/skills-library/_general/frontend/safari-sw-cache-poisoning.md +138 -0
- package/skills-library/_general/frontend/svg-sparkline-no-charting-library.md +131 -0
- package/skills-library/_general/growth-marketing/oss-daily-growth-intelligence.md +224 -0
- package/skills-library/_general/integrations/claude-code-local-mcp-integration.md +250 -0
- package/skills-library/_general/integrations/mcp-composite-tool-orchestration.md +200 -0
- package/skills-library/_general/methodology/AGENT_SDK_STANDALONE_TOOLING.md +181 -0
- package/skills-library/_general/methodology/AGENT_TEAMS_GUIDE.md +169 -0
- package/skills-library/_general/methodology/ALAS_STATEFUL_EXECUTION.md +207 -0
- package/skills-library/_general/methodology/AUTO_REVIEWER_SUBAGENT.md +211 -0
- package/skills-library/_general/methodology/CONSISTENCY_CHECK_AMBIGUITY_GATE.md +96 -0
- package/skills-library/_general/methodology/DEAD_ENDS_SHELF.md +4 -4
- package/skills-library/_general/methodology/DISTILL_NOT_DUMP.md +108 -0
- package/skills-library/_general/methodology/EXECUTION_PROGRESS_MONITOR.md +157 -0
- package/skills-library/_general/methodology/HIERARCHICAL_REVIEW_MARS.md +122 -0
- package/skills-library/_general/methodology/MCP_INTER_AGENT_BRIDGE.md +207 -0
- package/skills-library/_general/methodology/MERMAID_WIZARD_DIAGRAMS.md +77 -0
- package/skills-library/_general/methodology/MISSING_DIMENSION_DETECTOR.md +89 -0
- package/skills-library/_general/methodology/MULTI_AGENT_COORDINATION.md +397 -0
- package/skills-library/_general/methodology/OBSERVATION_MASKING.md +100 -0
- package/skills-library/_general/methodology/PHOENIX_REBUILD_METHODOLOGY.md +82 -11
- package/skills-library/_general/methodology/REVIEW_BACKTRACK_PANEL.md +140 -0
- package/skills-library/_general/methodology/REVIEW_FIX_LOOP.md +117 -0
- package/skills-library/_general/methodology/VOTING_VERDICT_ARBITRATION.md +155 -0
- package/skills-library/_general/methodology/ZERO_FRICTION_CLI_SETUP.md +2 -2
- package/skills-library/_general/methodology/dead-code-activation.md +123 -0
- package/skills-library/_general/methodology/debug-swarm-researcher-escape-hatch.md +240 -240
- package/skills-library/_general/methodology/shell-autonomous-loop-fixplan.md +1 -1
- package/skills-library/_general/patterns-standards/GOF_DESIGN_PATTERNS_FOR_AI_AGENTS.md +5 -5
- package/skills-library/_general/patterns-standards/cascading-failure-diagnosis.md +119 -0
- package/skills-library/_general/patterns-standards/domain-specific-layout-algorithms.md +209 -0
- package/skills-library/_general/patterns-standards/python-desktop-app-architecture.md +399 -0
- package/skills-library/_general/patterns-standards/realtime-monitoring-dashboard.md +457 -0
- package/skills-library/_general/patterns-standards/togglable-processing-pipeline.md +169 -0
- package/skills-library/_general/performance/liveclock-extraction.md +112 -0
- package/skills-library/_general/performance/ref-based-canvas-animation.md +117 -0
- package/skills-library/_general/performance/use-visible-interval.md +131 -0
- package/skills-library/_general/testing/playwright-firefox-withcredentials-auth-issue.md +104 -0
- package/skills-library/_quarantine/README.md +30 -0
- package/skills-library/api-patterns/BROADCAST_SCHEDULER_SHARED_EXECUTE_FUNCTION.md +150 -0
- package/skills-library/api-patterns/ERROR_RESPONSE_STANDARDS.md +145 -0
- package/skills-library/api-patterns/EXPRESS_ROUTE_ORDERING_MIDDLEWARE_INTERCEPTION.md +326 -0
- package/skills-library/api-patterns/PAGINATION_PATTERNS.md +137 -0
- package/skills-library/api-patterns/PODCAST_PROGRESS_TRACKING_THREE_ROOT_CAUSES.md +277 -0
- package/skills-library/api-patterns/RATE_LIMITING_TOGGLE.md +155 -0
- package/skills-library/api-patterns/graphql-content-queries.md +708 -0
- package/skills-library/appointment-scheduler-design.md +423 -0
- package/skills-library/automation/AUTO_POPULATE_COMPLETE_GUIDE.md +631 -0
- package/skills-library/automation/CC_WORKFLOW_STUDIO.md +83 -0
- package/skills-library/automation/CLAUDE_CODE_SWARM_MODE.md +95 -0
- package/skills-library/automation/DAEMON_TRIGGER_FILE_IPC.md +195 -0
- package/skills-library/automation/scheduled-content-publishing.md +608 -0
- package/skills-library/awesome-workflows/Blogging-Platform-Instructions/view_commands.md +25 -0
- package/skills-library/awesome-workflows/CREDENTIAL-SECURITY-WORKFLOW.md +109 -0
- package/skills-library/awesome-workflows/DEBUGGING-WORKFLOW.md +124 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/README.md +31 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/design-principles-example.md +129 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-agent.md +107 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-claude-md-snippet.md +24 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-slash-command.md +38 -0
- package/skills-library/awesome-workflows/PARALLEL-RESEARCH-WORKFLOW.md +89 -0
- package/skills-library/awesome-workflows/PHASE-EXECUTION-WORKFLOW.md +97 -0
- package/skills-library/awesome-workflows/SESSION-HANDOFF-WORKFLOW.md +116 -0
- package/skills-library/cms-patterns/content-branch-preview.md +515 -0
- package/skills-library/cms-patterns/inline-visual-editing.md +666 -0
- package/skills-library/cms-patterns/mdx-component-content.md +649 -0
- package/skills-library/cms-patterns/media-manager-abstraction.md +827 -0
- package/skills-library/cms-patterns/schema-driven-form-generator.md +838 -0
- package/skills-library/complexity-metrics/complexity-divider.md +707 -0
- package/skills-library/complexity-metrics/work-with-complexity.md +193 -0
- package/skills-library/creative-multimedia/animation-stack-guide.md +577 -0
- package/skills-library/creative-multimedia/audio-enhancement-pipeline.md +625 -0
- package/skills-library/creative-multimedia/content-repurposing-pipeline.md +1146 -0
- package/skills-library/creative-multimedia/data-visualization-generator.md +862 -0
- package/skills-library/creative-multimedia/doc-to-podcast-pipeline.md +2184 -0
- package/skills-library/creative-multimedia/ffmpeg-command-generator.md +405 -0
- package/skills-library/creative-multimedia/image-optimization-pipeline.md +605 -0
- package/skills-library/creative-multimedia/multi-format-content-generator.md +1759 -0
- package/skills-library/creative-multimedia/og-image-generator.md +635 -0
- package/skills-library/creative-multimedia/podcast-audio-composition.md +1355 -0
- package/skills-library/creative-multimedia/podcast-quality-evaluation.md +1452 -0
- package/skills-library/creative-multimedia/podcast-script-generation.md +1841 -0
- package/skills-library/creative-multimedia/svg-generation.md +750 -0
- package/skills-library/creative-multimedia/text-to-speech-provider-selector.md +1414 -0
- package/skills-library/creative-multimedia/transcription-pipeline-selector.md +677 -0
- package/skills-library/creative-multimedia/video-streaming-setup.md +559 -0
- package/skills-library/database-solutions/AI_RESPONSE_DATABASE_CACHING.md +520 -0
- package/skills-library/database-solutions/CONDITIONAL_SQL_MIGRATION_PATTERN.md +119 -0
- package/skills-library/database-solutions/DATABASE_COLUMN_NAME_MISMATCH.md +393 -0
- package/skills-library/database-solutions/DATABASE_SCHEMA.md +394 -0
- package/skills-library/database-solutions/DATABASE_SCHEMA_VERIFICATION_GUIDE.md +348 -0
- package/skills-library/database-solutions/DATABASE_STRATEGY.md +71 -0
- package/skills-library/database-solutions/ES_MODULE_SEED_SCRIPT_PATTERN.md +52 -0
- package/skills-library/database-solutions/MIGRATION_GUIDE.md +3 -0
- package/skills-library/database-solutions/PLPGSQL_VARIABLE_CONFLICT_FIX.md +208 -0
- package/skills-library/database-solutions/POSTGRESQL_JSONB_DOUBLE_STRINGIFY_FIX.md +245 -0
- package/skills-library/database-solutions/POSTGRESQL_LICENSE_TABLE_DESIGN.md +393 -0
- package/skills-library/database-solutions/POSTGRESQL_UUID_DOCUMENT_RAG_DUAL_SCOPE.md +732 -0
- package/skills-library/database-solutions/POSTGRES_SQL_TEMPLATE_BINDING_ERROR.md +240 -0
- package/skills-library/database-solutions/PRISMA_DB_PUSH_DATA_LOSS_PREVENTION.md +141 -0
- package/skills-library/database-solutions/PRODUCTION_QUERY_OPTIMIZATION_RESTART_FIX.md +389 -0
- package/skills-library/database-solutions/RLS_SECURITY_GUIDE.md +107 -0
- package/skills-library/database-solutions/SCHEMA_ENHANCEMENTS_GUIDE.md +373 -0
- package/skills-library/database-solutions/SCHEMA_MIGRATION_GUIDE.md +368 -0
- package/skills-library/database-solutions/SCHEMA_VERIFICATION_QUICK_REFERENCE.md +104 -0
- package/skills-library/database-solutions/ai-erd-generator.md +1213 -0
- package/skills-library/database-solutions/content-publishing-states.md +631 -0
- package/skills-library/database-solutions/database-schema-designer.md +522 -0
- package/skills-library/database-solutions/er-diagram-components.md +569 -0
- package/skills-library/database-solutions/er-to-ddl-mapping.md +1405 -0
- package/skills-library/database-solutions/erd-creator-textbook-research.md +433 -0
- package/skills-library/database-solutions/erd-react-flow-architecture.md +1965 -0
- package/skills-library/database-solutions/mariadb-aggregate-function-replacement.md +145 -0
- package/skills-library/database-solutions/normalization-validator.md +778 -0
- package/skills-library/database-solutions/postgres-full-text-search-content.md +494 -0
- package/skills-library/database-solutions/postgresql-to-mysql-runtime-translation.md +286 -0
- package/skills-library/database-solutions/regex-alternation-ordering-sql-types.md +92 -0
- package/skills-library/database-solutions/reserved-word-context-aware-quoting.md +142 -0
- package/skills-library/database-solutions/sql-ddl-generator.md +756 -0
- package/skills-library/database-solutions/supabase-connection-pooler-fix.md +102 -0
- package/skills-library/deployment-security/CPANEL_NODE_DEPLOYMENT.md +166 -0
- package/skills-library/deployment-security/DEPLOYMENT.md +275 -0
- package/skills-library/deployment-security/DEPLOYMENT_CHECKLIST.md +363 -0
- package/skills-library/deployment-security/DEPLOYMENT_PLAN.md +669 -0
- package/skills-library/deployment-security/KNEX_DATABASE_ABSTRACTION.md +444 -0
- package/skills-library/deployment-security/LICENSE_KEY_SYSTEM.md +206 -0
- package/skills-library/deployment-security/NODE18_DEPENDENCY_COMPATIBILITY.md +284 -0
- package/skills-library/deployment-security/PHP_INSTALLER_WIZARD_GUIDE.md +315 -0
- package/skills-library/deployment-security/PM2_ENVIRONMENT_VARIABLE_CACHING.md +256 -0
- package/skills-library/deployment-security/PM2_MEMORY_EXHAUSTION_FIX.md +370 -0
- package/skills-library/deployment-security/PRODUCTION_DEPLOYMENT_GUIDE.md +592 -0
- package/skills-library/deployment-security/PRODUCTION_HARDENING_DOCUMENTATION.md +307 -0
- package/skills-library/deployment-security/PRODUCTION_RECOVERY_CHERRY_PICK_PATTERN.md +202 -0
- package/skills-library/deployment-security/PYINSTALLER_CUDA_WHISPER_BUNDLING.md +236 -0
- package/skills-library/deployment-security/SECURITY.md +41 -0
- package/skills-library/deployment-security/SMTP_SSL_HOSTNAME_MISMATCH_SHARED_HOSTING.md +220 -0
- package/skills-library/deployment-security/SPA_SEO_OPTIMIZATION_CPANEL.md +200 -0
- package/skills-library/deployment-security/SUPABASE_EDGE_FUNCTIONS.md +338 -0
- package/skills-library/deployment-security/VERCEL_GITHUB_DEPLOYMENT_GUIDE.md +858 -0
- package/skills-library/deployment-security/VPS_DEPLOYMENT_READINESS.md +356 -0
- package/skills-library/deployment-security/deployment-changes-not-applying.md +241 -0
- package/skills-library/deployment-security/env-file-management-production-local.md +203 -0
- package/skills-library/deployment-security/express-secure-file-downloads.md +413 -0
- package/skills-library/deployment-security/react-production-deployment-desktop-guide.md +2011 -0
- package/skills-library/deployment-security/self-hosted-supabase-coolify-guide.md +1684 -0
- package/skills-library/deployment-security/unique-features-ai-strategy-plaid-security.md +1613 -0
- package/skills-library/deployment-security/vps-deployment.md +135 -0
- package/skills-library/document-processing/WORD_EXPORT_MARKDOWN_FORMATTING.md +482 -0
- package/skills-library/document-processing/document-ai-landingai-integration.md +677 -0
- package/skills-library/document-processing/express-secure-file-downloads-mern.md +413 -0
- package/skills-library/document-processing/express-secure-file-downloads.md +413 -0
- package/skills-library/document-processing/md-to-word-converter.md +318 -0
- package/skills-library/document-processing/pdf-forms-integration/README.md +101 -0
- package/skills-library/document-processing/pdf-forms-integration/SKILL.md +662 -0
- package/skills-library/ecommerce/ADMIN_PRODUCTS_GUIDE.md +428 -0
- package/skills-library/ecommerce/ECOMMERCE_API_REFERENCE.md +776 -0
- package/skills-library/ecommerce/ECOMMERCE_COMPLETION_SUMMARY.md +673 -0
- package/skills-library/ecommerce/ECOMMERCE_IMPLEMENTATION_GUIDE.md +729 -0
- package/skills-library/ecommerce/ECOMMERCE_QUICK_REFERENCE.md +521 -0
- package/skills-library/ecommerce/ECOMMERCE_TESTING_CHECKLIST.md +565 -0
- package/skills-library/ecommerce/ECOMMERCE_WORKFLOW_GUIDE.md +1059 -0
- package/skills-library/ecommerce/PRODUCT_CREATION_EXPANDED.md +522 -0
- package/skills-library/ecommerce/agentic-commerce-protocol.md +203 -0
- package/skills-library/ecommerce/cart-abandonment-recovery.md +236 -0
- package/skills-library/ecommerce/cart-architecture-patterns.md +300 -0
- package/skills-library/ecommerce/cart-item-count-indicator.md +264 -0
- package/skills-library/ecommerce/checkout-ux-conversion.md +227 -0
- package/skills-library/ecommerce/composable-commerce-selection.md +166 -0
- package/skills-library/ecommerce/ecommerce-analytics-patterns.md +167 -0
- package/skills-library/ecommerce/fraud-detection-patterns.md +179 -0
- package/skills-library/ecommerce/inventory-stock-management.md +270 -0
- package/skills-library/ecommerce/order-saga-state-machine.md +336 -0
- package/skills-library/ecommerce/payment-provider-abstraction.md +245 -0
- package/skills-library/ecommerce/pci-compliance-checklist.md +192 -0
- package/skills-library/ecommerce/refund-chargeback-handling.md +177 -0
- package/skills-library/ecommerce/shipping-carrier-integration.md +218 -0
- package/skills-library/ecommerce/webhook-idempotency-patterns.md +253 -0
- package/skills-library/excalidraw-diagrams/.github/workflows/ci.yml +558 -0
- package/skills-library/excalidraw-diagrams/.github/workflows/prompt-gallery.yml +448 -0
- package/skills-library/excalidraw-diagrams/.github/workflows/release.yml +42 -0
- package/skills-library/excalidraw-diagrams/.github/workflows/test-reusable-ci.yml +25 -0
- package/skills-library/excalidraw-diagrams/CLAUDE.md +57 -0
- package/skills-library/excalidraw-diagrams/LICENSE +21 -0
- package/skills-library/excalidraw-diagrams/README.md +178 -0
- package/skills-library/excalidraw-diagrams/SKILL.md +715 -0
- package/skills-library/form-solutions/BUTTON_TYPE_FORM_SUBMISSION.md +336 -0
- package/skills-library/form-solutions/FILLABLE_PDF_IMPLEMENTATION.md +226 -0
- package/skills-library/form-solutions/SURVEYJS_QUESTIONNAIRE_SYSTEM.md +367 -0
- package/skills-library/form-solutions/tiptap-minimal-setup.md +690 -0
- package/skills-library/frontend/scholarly-classification-bubble-map.md +149 -0
- package/skills-library/infrastructure/ci-cd-pipeline-builder.md +517 -0
- package/skills-library/infrastructure/observability-designer.md +264 -0
- package/skills-library/infrastructure/performance-profiler.md +621 -0
- package/skills-library/installer-wizard-patterns.md +249 -0
- package/skills-library/integrations/CLAUDE_CODE_TOKEN_ANALYTICS.md +160 -0
- package/skills-library/integrations/CONFIGURABLE_AI_PROVIDER_SELECTION.md +728 -0
- package/skills-library/integrations/SOCKET_IO_BROADCAST_ALL_VS_ROOM.md +141 -0
- package/skills-library/integrations/VIRTUAL_MEETINGS_IMPLEMENTATION.md +374 -0
- package/skills-library/integrations/WORDPRESS_LEARNDASH_DATA_RECOVERY.md +53 -0
- package/skills-library/integrations/YOUTUBE_API_SETUP.md +141 -0
- package/skills-library/integrations/YOUTUBE_BOOKMARKING_EXPLANATION.md +252 -0
- package/skills-library/integrations/YOUTUBE_BOOKMARKING_SOLUTION.md +268 -0
- package/skills-library/integrations/YOUTUBE_OAUTH_SETUP_GUIDE.md +200 -0
- package/skills-library/integrations/YOUTUBE_VIDEO_FIX_COMPLETE.md +192 -0
- package/skills-library/integrations/ai-ml/GEMINI_AI_RAG_PIPELINE_COMPLETE_GUIDE.md +195 -0
- package/skills-library/integrations/ai-ml/GEMINI_IMAGE_GENERATION_SETUP.md +64 -0
- package/skills-library/integrations/cloudflare/cloudflare-turnstile-debugging.md +202 -0
- package/skills-library/integrations/cloudflare/cloudflare-turnstile-implementation.md +476 -0
- package/skills-library/integrations/cloudflare-turnstile-debugging.md +202 -0
- package/skills-library/integrations/cloudflare-turnstile-implementation.md +476 -0
- package/skills-library/integrations/ghost-creator-monetization-pattern.md +454 -0
- package/skills-library/integrations/headless-cms-architecture.md +484 -0
- package/skills-library/integrations/headless-cms-stack-selection.md +183 -0
- package/skills-library/integrations/payload-cms-patterns.md +674 -0
- package/skills-library/integrations/realtimestt-openwakeword-cuda-windows.md +229 -0
- package/skills-library/integrations/rss-podcast-integration.md +300 -0
- package/skills-library/integrations/wordpress/WORDPRESS_LEARNDASH_DATA_RECOVERY.md +53 -0
- package/skills-library/integrations/youtube/YOUTUBE_API_SETUP.md +141 -0
- package/skills-library/integrations/youtube/YOUTUBE_BOOKMARKING_EXPLANATION.md +252 -0
- package/skills-library/integrations/youtube/YOUTUBE_BOOKMARKING_SOLUTION.md +268 -0
- package/skills-library/integrations/youtube/YOUTUBE_OAUTH_SETUP_GUIDE.md +200 -0
- package/skills-library/integrations/youtube/YOUTUBE_VIDEO_FIX_COMPLETE.md +192 -0
- package/skills-library/marketing/campaign-analytics.md +97 -0
- package/skills-library/marketing/content-creator.md +105 -0
- package/skills-library/marketing/marketing-strategy-pmm.md +94 -0
- package/skills-library/marketing/social-media-analyzer.md +81 -0
- package/skills-library/methodology/ADVANCED_ORCHESTRATION_PATTERNS.md +401 -0
- package/skills-library/methodology/AGENT_SELF_IMPROVEMENT_LOOP.md +179 -0
- package/skills-library/methodology/BREATH_BASED_PARALLEL_EXECUTION.md +1 -1
- package/skills-library/methodology/CLEANSING_CYCLE.md +358 -0
- package/skills-library/methodology/CONFIDENCE_ANNOTATION_PATTERN.md +143 -0
- package/skills-library/methodology/CRITICAL_PATTERNS_DOCUMENTATION_COMPLETE.md +204 -0
- package/skills-library/methodology/DELIVERABLES_SUMMARY.md +341 -0
- package/skills-library/methodology/DIFFICULTY_AWARE_AGENT_ROUTING.md +252 -0
- package/skills-library/methodology/EVOLUTIONARY_SKILL_SYNTHESIS.md +219 -0
- package/skills-library/methodology/GLOMERULUS_DECISION_GATE.md +223 -0
- package/skills-library/methodology/HIBERNATION_SYSTEM.md +231 -0
- package/skills-library/methodology/INSTRUMENTATION_OVER_RESTRICTION.md +192 -0
- package/skills-library/methodology/MASTER_COMPLETION_SUMMARY.md +444 -0
- package/skills-library/methodology/MASTER_SESSION_COMPLETION.md +743 -0
- package/skills-library/methodology/MERN_QUICK_REFERENCE.md +358 -0
- package/skills-library/methodology/ORGAN_AGENT_MAPPING.md +177 -0
- package/skills-library/methodology/PARALLEL_WAVE_BASED_REFACTORING.md +440 -0
- package/skills-library/methodology/QUICK_REFERENCE.md +358 -0
- package/skills-library/methodology/SDFT_ONPOLICY_SELF_DISTILLATION.md +186 -0
- package/skills-library/methodology/SELF_QUESTIONING_TASK_GENERATION.md +270 -0
- package/skills-library/methodology/SESSION_COMPLETION_SUMMARY.md +304 -0
- package/skills-library/methodology/SESSION_SUMMARY.md +432 -0
- package/skills-library/methodology/WARRIOR_WORKFLOW_DEBUGGING_PROTOCOL.md +252 -0
- package/skills-library/methodology/tech-debt-tracker.md +570 -0
- package/skills-library/parallel-debug/SKILL.md +60 -0
- package/skills-library/patterns-standards/API_PATTERN_FIX_SUMMARY.md +236 -0
- package/skills-library/patterns-standards/BATCH_OPERATIONS_WITH_PROGRESS_MODAL.md +362 -0
- package/skills-library/patterns-standards/CRITICAL_CODING_PATTERNS.md +639 -0
- package/skills-library/patterns-standards/DARK_MODE_MODAL_VISIBILITY.md +258 -0
- package/skills-library/patterns-standards/ERROR_RESILIENCE_IMPLEMENTATION.md +375 -0
- package/skills-library/patterns-standards/ES_MODULE_IMPORT_HOISTING_DOTENV.md +298 -0
- package/skills-library/patterns-standards/NESTED_BACKDROP_FILTER_CSS_ARTIFACT_FIX.md +76 -0
- package/skills-library/patterns-standards/ORDERED_DETECTOR_PIPELINE_GRACEFUL_FALLBACK.md +333 -0
- package/skills-library/patterns-standards/PHASE_IMPORT_ERROR_DEBUGGING.md +271 -0
- package/skills-library/patterns-standards/PYNPUT_GLOBAL_HOTKEY_VK_MATCHING.md +252 -0
- package/skills-library/patterns-standards/REACT_USEEFFECT_CASCADE_RESET_FIX.md +132 -0
- package/skills-library/patterns-standards/SUBMENU_HOVER_DROPDOWN_PATTERN.md +225 -0
- package/skills-library/patterns-standards/TAILWIND_TEXT_VISIBILITY_OVERRIDE.md +322 -0
- package/skills-library/patterns-standards/THEME_AWARE_CSS_VARIABLES_PATTERN.md +209 -0
- package/skills-library/patterns-standards/THEME_USER_OBJECT_PROPERTY_NAMING.md +194 -0
- package/skills-library/patterns-standards/TOOLTIP_BLOCKING_CLICKS_FIX.md +267 -0
- package/skills-library/patterns-standards/claude-code-plugin-structure.md +235 -0
- package/skills-library/patterns-standards/react-i18next-setup.md +429 -0
- package/skills-library/patterns-standards/thesys-c1-generative-ui-integration.md +967 -0
- package/skills-library/plugin-development/CLAUDE_CODE_COMMAND_REGISTRATION_SILENT_FAILURE.md +315 -0
- package/skills-library/plugin-development/plugin-command-namespace-vs-global.md +390 -0
- package/skills-library/plugin-development/plugin-doc-auto-generation.md +172 -0
- package/skills-library/security/GITHUB_REPO_SECURITY_AUDIT.md +115 -0
- package/skills-library/security/admin-deletion-safety.md +396 -0
- package/skills-library/security/application-vuln-patterns.md +477 -0
- package/skills-library/security/env-secrets-manager.md +686 -0
- package/skills-library/security/secure-ai-application-templates.md +347 -0
- package/skills-library/security/sql-injection-prevention-postgresjs.md +151 -0
- package/skills-library/supabase-connection-pooler-fix.md +102 -0
- package/skills-library/system-context/POWERSHELL_BASH_INTEROP.md +82 -0
- package/skills-library/system-context/SERVICE_LIFECYCLE_MANAGEMENT.md +119 -0
- package/skills-library/system-context/SKILL.md +40 -0
- package/skills-library/system-context/WINDOWS_DEV_ENVIRONMENT.md +73 -0
- package/skills-library/testing/E2E_PLAYWRIGHT_PATTERNS.md +99 -0
- package/skills-library/testing/INTEGRATION_TEST_STRATEGY.md +82 -0
- package/skills-library/testing/RED_GREEN_BUGFIX_GATE.md +203 -0
- package/skills-library/testing/TEST_DATA_MANAGEMENT.md +69 -0
- package/skills-library/testing/VITEST_UNIT_TEST_PATTERNS.md +75 -0
- package/skills-library/testing/playwright-api-security-tests.md +202 -0
- package/skills-library/toolbox/SKILL.md +84 -0
- package/skills-library/toolbox/code-graph-and-web-scraping-mcps.md +237 -0
- package/skills-library/ui-ux-pro-max/ACCESSIBILITY_ESSENTIALS.md +115 -0
- package/skills-library/ui-ux-pro-max/DESIGN_SYSTEM_SCAFFOLDING.md +133 -0
- package/skills-library/ui-ux-pro-max/RESPONSIVE_LAYOUT_PATTERNS.md +119 -0
- package/skills-library/ui-ux-pro-max/SKILL.md +386 -0
- package/skills-library/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills-library/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills-library/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills-library/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills-library/ui-ux-pro-max/data/products.csv +97 -0
- package/skills-library/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills-library/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills-library/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills-library/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills-library/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills-library/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills-library/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills-library/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills-library/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills-library/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills-library/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills-library/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills-library/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills-library/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills-library/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills-library/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills-library/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills-library/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills-library/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills-library/wordpress-style-theme-components.md +1526 -0
- package/templates/ASSUMPTIONS.md +1 -1
- package/templates/DECISION_LOG.md +0 -1
- package/templates/phase-prompt.md +1 -1
- package/templates/phoenix-comparison.md +6 -6
- package/templates/skill-api-integration.md +106 -0
- package/templates/skill-architecture-pattern.md +92 -0
- package/templates/skill-debug-pattern.md +98 -0
- package/templates/skill-devops-recipe.md +107 -0
- package/templates/skill-general.md +65 -0
- package/templates/skill-ui-component.md +113 -0
- package/version.json +7 -3
- package/workflows/handoff-session.md +2 -2
- package/workflows/new-project.md +2 -2
- package/workflows/plan-phase.md +1 -1
- package/.claude-plugin/plugin.json +0 -64
- package/skills-library/_general/methodology/LIVE_BREADCRUMB_PROTOCOL.md +0 -242
- package/skills-library/_general/methodology/llm-judge-memory-crud.md +0 -241
- package/skills-library/methodology/REFLEXION_MEMORY_PATTERN.md +0 -183
- package/skills-library/methodology/RESEARCH_BACKED_WORKFLOW_UPGRADE.md +0 -263
- package/skills-library/methodology/SABBATH_REST_PATTERN.md +0 -267
- package/skills-library/methodology/STONE_AND_SCAFFOLD.md +0 -220
- package/skills-library/specialists/api-architecture/api-designer.md +0 -49
- package/skills-library/specialists/api-architecture/graphql-architect.md +0 -49
- package/skills-library/specialists/api-architecture/mcp-developer.md +0 -51
- package/skills-library/specialists/api-architecture/microservices-architect.md +0 -50
- package/skills-library/specialists/api-architecture/websocket-engineer.md +0 -48
- package/skills-library/specialists/backend/django-expert.md +0 -52
- package/skills-library/specialists/backend/fastapi-expert.md +0 -52
- package/skills-library/specialists/backend/laravel-specialist.md +0 -52
- package/skills-library/specialists/backend/nestjs-expert.md +0 -51
- package/skills-library/specialists/backend/rails-expert.md +0 -53
- package/skills-library/specialists/backend/spring-boot-engineer.md +0 -56
- package/skills-library/specialists/data-ml/fine-tuning-expert.md +0 -48
- package/skills-library/specialists/data-ml/ml-pipeline.md +0 -47
- package/skills-library/specialists/data-ml/pandas-pro.md +0 -47
- package/skills-library/specialists/data-ml/rag-architect.md +0 -51
- package/skills-library/specialists/data-ml/spark-engineer.md +0 -47
- package/skills-library/specialists/frontend/angular-architect.md +0 -52
- package/skills-library/specialists/frontend/flutter-expert.md +0 -51
- package/skills-library/specialists/frontend/nextjs-developer.md +0 -54
- package/skills-library/specialists/frontend/react-native-expert.md +0 -50
- package/skills-library/specialists/frontend/vue-expert.md +0 -51
- package/skills-library/specialists/infrastructure/chaos-engineer.md +0 -74
- package/skills-library/specialists/infrastructure/cloud-architect.md +0 -70
- package/skills-library/specialists/infrastructure/database-optimizer.md +0 -64
- package/skills-library/specialists/infrastructure/devops-engineer.md +0 -70
- package/skills-library/specialists/infrastructure/kubernetes-specialist.md +0 -52
- package/skills-library/specialists/infrastructure/monitoring-expert.md +0 -70
- package/skills-library/specialists/infrastructure/sre-engineer.md +0 -70
- package/skills-library/specialists/infrastructure/terraform-engineer.md +0 -51
- package/skills-library/specialists/languages/cpp-pro.md +0 -74
- package/skills-library/specialists/languages/csharp-developer.md +0 -69
- package/skills-library/specialists/languages/dotnet-core-expert.md +0 -54
- package/skills-library/specialists/languages/golang-pro.md +0 -51
- package/skills-library/specialists/languages/java-architect.md +0 -49
- package/skills-library/specialists/languages/javascript-pro.md +0 -68
- package/skills-library/specialists/languages/kotlin-specialist.md +0 -68
- package/skills-library/specialists/languages/php-pro.md +0 -49
- package/skills-library/specialists/languages/python-pro.md +0 -52
- package/skills-library/specialists/languages/react-expert.md +0 -51
- package/skills-library/specialists/languages/rust-engineer.md +0 -50
- package/skills-library/specialists/languages/sql-pro.md +0 -56
- package/skills-library/specialists/languages/swift-expert.md +0 -69
- package/skills-library/specialists/languages/typescript-pro.md +0 -51
- package/skills-library/specialists/platform/atlassian-mcp.md +0 -52
- package/skills-library/specialists/platform/embedded-systems.md +0 -53
- package/skills-library/specialists/platform/game-developer.md +0 -53
- package/skills-library/specialists/platform/salesforce-developer.md +0 -53
- package/skills-library/specialists/platform/shopify-expert.md +0 -49
- package/skills-library/specialists/platform/wordpress-pro.md +0 -49
- package/skills-library/specialists/quality/browser-use-expert.md +0 -210
- package/skills-library/specialists/quality/code-documenter.md +0 -51
- package/skills-library/specialists/quality/code-reviewer.md +0 -67
- package/skills-library/specialists/quality/debugging-wizard.md +0 -51
- package/skills-library/specialists/quality/fullstack-guardian.md +0 -51
- package/skills-library/specialists/quality/legacy-modernizer.md +0 -50
- package/skills-library/specialists/quality/playwright-expert.md +0 -65
- package/skills-library/specialists/quality/spec-miner.md +0 -56
- package/skills-library/specialists/quality/test-master.md +0 -65
- package/skills-library/specialists/security/secure-code-guardian.md +0 -55
- package/skills-library/specialists/security/security-reviewer.md +0 -53
- package/skills-library/specialists/workflow/architecture-designer.md +0 -53
- package/skills-library/specialists/workflow/cli-developer.md +0 -70
- package/skills-library/specialists/workflow/feature-forge.md +0 -65
- package/skills-library/specialists/workflow/prompt-engineer.md +0 -54
- package/skills-library/specialists/workflow/the-fool.md +0 -62
- /package/skills-library/{performance → _general/performance}/cache-augmented-generation.md +0 -0
- /package/skills-library/{debugging → parallel-debug}/FAILURE_TAXONOMY_CLASSIFICATION.md +0 -0
- /package/skills-library/{debugging → parallel-debug}/THREE_AGENT_HYPOTHESIS_DEBUGGING.md +0 -0
|
@@ -0,0 +1,1405 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: er-to-ddl-mapping
|
|
3
|
+
category: database-solutions
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
contributed: 2026-03-09
|
|
6
|
+
contributor: fire-research
|
|
7
|
+
last_updated: 2026-03-09
|
|
8
|
+
tags: [erd, ddl, sql, schema-generation, database-design, mapping-rules]
|
|
9
|
+
difficulty: hard
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ER-to-DDL Mapping
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Problem
|
|
16
|
+
|
|
17
|
+
Converting a visual ER diagram into executable SQL DDL requires deterministic mapping rules that handle every construct in the ER model: strong entities, weak entities, all relationship cardinalities, multivalued attributes, composite attributes, derived attributes, participation constraints, and n-ary relationships. The mapping must produce correct, dialect-specific SQL for PostgreSQL, MySQL, and SQLite while preserving referential integrity through appropriate FK actions. Ad-hoc or intuition-based conversion leads to missing junction tables, incorrect cascade rules, broken composite keys, and normalization violations.
|
|
18
|
+
|
|
19
|
+
This skill encodes the complete algorithm: the 7 canonical mapping rules, the generation pipeline, multi-database type mapping, FK action selection, AI-enhanced generation patterns, and a full TypeScript implementation suitable for an ERD Creator tool.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## The 7 Canonical Mapping Rules
|
|
24
|
+
|
|
25
|
+
### Rule 1: Strong Entity --> Table
|
|
26
|
+
|
|
27
|
+
Every strong entity becomes its own table. This is the foundation of the entire mapping.
|
|
28
|
+
|
|
29
|
+
**Attribute handling:**
|
|
30
|
+
- **Simple attributes** --> columns with appropriate data types
|
|
31
|
+
- **Key attribute** --> PRIMARY KEY constraint
|
|
32
|
+
- **Composite attributes** --> flatten to leaf-level columns (do NOT create a column for the parent)
|
|
33
|
+
- **Multivalued attributes** --> separate table (see Rule 7)
|
|
34
|
+
- **Derived attributes** --> skip (compute at query time) OR use generated columns
|
|
35
|
+
|
|
36
|
+
```sql
|
|
37
|
+
-- Entity: Employee
|
|
38
|
+
-- Attributes: emp_id (key), name (composite: first, last), age (derived from dob)
|
|
39
|
+
CREATE TABLE employees (
|
|
40
|
+
emp_id INTEGER PRIMARY KEY,
|
|
41
|
+
first_name VARCHAR(50) NOT NULL, -- flattened from composite "name"
|
|
42
|
+
last_name VARCHAR(50) NOT NULL, -- flattened from composite "name"
|
|
43
|
+
dob DATE NOT NULL,
|
|
44
|
+
salary NUMERIC(10,2),
|
|
45
|
+
-- age is DERIVED: skip or use generated column
|
|
46
|
+
age INTEGER GENERATED ALWAYS AS (
|
|
47
|
+
EXTRACT(YEAR FROM AGE(CURRENT_DATE, dob))
|
|
48
|
+
) STORED -- PostgreSQL syntax
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Edge cases:**
|
|
53
|
+
- Composite within composite: flatten recursively (Address.Street.Number becomes `street_number`)
|
|
54
|
+
- Multiple key attributes: composite PK unless one is designated as sole identifier
|
|
55
|
+
- No key attribute defined: error state -- every entity MUST have a key
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### Rule 2: Weak Entity --> Table with Composite PK
|
|
60
|
+
|
|
61
|
+
A weak entity cannot exist without its identifying (owner) entity. Its table gets a composite primary key: partial key + owner's primary key. The FK to the owner MUST use `ON DELETE CASCADE`.
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
-- Weak Entity: Dependent (of Employee)
|
|
65
|
+
-- Partial key: dependent_name
|
|
66
|
+
-- Owner: Employee (emp_id)
|
|
67
|
+
CREATE TABLE dependents (
|
|
68
|
+
dependent_name VARCHAR(50) NOT NULL,
|
|
69
|
+
emp_id INTEGER NOT NULL,
|
|
70
|
+
relationship VARCHAR(20),
|
|
71
|
+
birth_date DATE,
|
|
72
|
+
CONSTRAINT pk_dependent PRIMARY KEY (dependent_name, emp_id),
|
|
73
|
+
CONSTRAINT fk_dependent_employee FOREIGN KEY (emp_id)
|
|
74
|
+
REFERENCES employees(emp_id)
|
|
75
|
+
ON DELETE CASCADE -- MUST cascade: dependent cannot exist without employee
|
|
76
|
+
ON UPDATE CASCADE
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Edge cases:**
|
|
81
|
+
- Weak entity owned by another weak entity: chain the PKs (all owner PKs propagate down)
|
|
82
|
+
- Weak entity with its own multivalued attribute: apply Rule 7 using the composite PK as FK
|
|
83
|
+
- Multiple identifying relationships: include all owner PKs in the composite PK
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### Rule 3: 1:1 Relationship --> FK on Either Side
|
|
88
|
+
|
|
89
|
+
Add the PK of one entity as a FK in the other entity's table. The choice of which side gets the FK follows a priority:
|
|
90
|
+
|
|
91
|
+
1. **Total participation side** gets the FK (the entity that MUST participate -- guarantees no NULL FKs)
|
|
92
|
+
2. **If both total**: merge into a single table (valid when both entities always coexist)
|
|
93
|
+
3. **If both partial**: put FK on the side with fewer rows, or the side more frequently queried
|
|
94
|
+
|
|
95
|
+
Relationship attributes go on the table that receives the FK.
|
|
96
|
+
|
|
97
|
+
```sql
|
|
98
|
+
-- 1:1: Department (total) <--manages--> Employee (partial)
|
|
99
|
+
-- Every department MUST have a manager, but not every employee manages a department
|
|
100
|
+
-- FK goes on Department (total participation side)
|
|
101
|
+
ALTER TABLE departments
|
|
102
|
+
ADD COLUMN manager_id INTEGER NOT NULL UNIQUE, -- NOT NULL enforces total participation; UNIQUE enforces 1:1
|
|
103
|
+
ADD CONSTRAINT fk_dept_manager
|
|
104
|
+
FOREIGN KEY (manager_id) REFERENCES employees(emp_id)
|
|
105
|
+
ON DELETE RESTRICT; -- RESTRICT: can't delete employee who manages a department
|
|
106
|
+
-- Note: total participation = NOT NULL + RESTRICT. If participation is partial, use nullable + SET NULL.
|
|
107
|
+
|
|
108
|
+
-- Relationship attribute: start_date (when the employee started managing)
|
|
109
|
+
ALTER TABLE departments
|
|
110
|
+
ADD COLUMN management_start_date DATE;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Edge cases:**
|
|
114
|
+
- Both total with identical lifecycles: merge tables, avoid unnecessary join
|
|
115
|
+
- Recursive 1:1 (entity relates to itself): use a self-referencing FK with UNIQUE constraint
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Rule 4: 1:N Relationship --> FK on the "Many" Side
|
|
120
|
+
|
|
121
|
+
Add the PK of the "one" side as a FK column on the "many" side table. This is the most common relationship pattern in relational databases.
|
|
122
|
+
|
|
123
|
+
```sql
|
|
124
|
+
-- 1:N: Department (one) <--works_in--> Employee (many)
|
|
125
|
+
-- Each employee works in one department; a department has many employees
|
|
126
|
+
ALTER TABLE employees
|
|
127
|
+
ADD COLUMN department_id INTEGER,
|
|
128
|
+
ADD CONSTRAINT fk_emp_dept
|
|
129
|
+
FOREIGN KEY (department_id) REFERENCES departments(department_id)
|
|
130
|
+
ON DELETE SET NULL -- employee survives if department dissolved
|
|
131
|
+
ON UPDATE CASCADE;
|
|
132
|
+
|
|
133
|
+
-- Always index the FK column on the many side
|
|
134
|
+
CREATE INDEX idx_employees_dept ON employees(department_id);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Relationship attributes** go on the many-side table:
|
|
138
|
+
```sql
|
|
139
|
+
-- If the "works_in" relationship has a "start_date" attribute:
|
|
140
|
+
ALTER TABLE employees ADD COLUMN dept_start_date DATE;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Edge cases:**
|
|
144
|
+
- Total participation on many side: FK should be `NOT NULL`
|
|
145
|
+
- Partial participation on many side: FK is nullable
|
|
146
|
+
- Self-referencing 1:N (Employee supervises Employee): `supervisor_id INTEGER REFERENCES employees(emp_id)`
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### Rule 5: M:N Relationship --> Junction/Bridge Table
|
|
151
|
+
|
|
152
|
+
Create a new junction table with a composite PK consisting of both entity PKs. This is the ONLY correct way to represent M:N in relational schema.
|
|
153
|
+
|
|
154
|
+
```sql
|
|
155
|
+
-- M:N: Student <--enrolls_in--> Course
|
|
156
|
+
-- Relationship attributes: grade, enrollment_date
|
|
157
|
+
CREATE TABLE enrollments (
|
|
158
|
+
student_id INTEGER NOT NULL,
|
|
159
|
+
course_id INTEGER NOT NULL,
|
|
160
|
+
grade CHAR(2),
|
|
161
|
+
enrollment_date DATE DEFAULT CURRENT_DATE,
|
|
162
|
+
CONSTRAINT pk_enrollment PRIMARY KEY (student_id, course_id),
|
|
163
|
+
CONSTRAINT fk_enroll_student FOREIGN KEY (student_id)
|
|
164
|
+
REFERENCES students(student_id)
|
|
165
|
+
ON DELETE CASCADE, -- if student removed, remove their enrollments
|
|
166
|
+
CONSTRAINT fk_enroll_course FOREIGN KEY (course_id)
|
|
167
|
+
REFERENCES courses(course_id)
|
|
168
|
+
ON DELETE CASCADE -- if course removed, remove its enrollments
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
-- Index both FK columns (composite PK covers student_id; add index for course_id lookups)
|
|
172
|
+
CREATE INDEX idx_enrollment_course ON enrollments(course_id);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Edge cases:**
|
|
176
|
+
- Junction table with its own key (surrogate PK): valid when enrollments need independent identity (e.g., enrollment_id for referencing from other tables)
|
|
177
|
+
- M:N with attributes: attributes become columns in the junction table
|
|
178
|
+
- Self-referencing M:N (User friends User): junction table with two FKs to same table, add CHECK constraint to prevent self-friendship
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### Rule 6: N-ary Relationship --> Junction Table with N FKs
|
|
183
|
+
|
|
184
|
+
For ternary and higher relationships, create a junction table with FKs to ALL participating entities. The PK composition depends on the cardinality constraints.
|
|
185
|
+
|
|
186
|
+
```sql
|
|
187
|
+
-- Ternary: Supplier <--supplies--> Part <--for--> Project
|
|
188
|
+
-- A supplier provides specific parts for specific projects
|
|
189
|
+
CREATE TABLE supply (
|
|
190
|
+
supplier_id INTEGER NOT NULL,
|
|
191
|
+
part_id INTEGER NOT NULL,
|
|
192
|
+
project_id INTEGER NOT NULL,
|
|
193
|
+
quantity INTEGER DEFAULT 0,
|
|
194
|
+
unit_price NUMERIC(10,2),
|
|
195
|
+
CONSTRAINT pk_supply PRIMARY KEY (supplier_id, part_id, project_id),
|
|
196
|
+
CONSTRAINT fk_supply_supplier FOREIGN KEY (supplier_id)
|
|
197
|
+
REFERENCES suppliers(supplier_id) ON DELETE CASCADE,
|
|
198
|
+
CONSTRAINT fk_supply_part FOREIGN KEY (part_id)
|
|
199
|
+
REFERENCES parts(part_id) ON DELETE CASCADE,
|
|
200
|
+
CONSTRAINT fk_supply_project FOREIGN KEY (project_id)
|
|
201
|
+
REFERENCES projects(project_id) ON DELETE CASCADE
|
|
202
|
+
);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**PK composition rules for n-ary:**
|
|
206
|
+
- If all sides are "many": PK = all N FKs (most common)
|
|
207
|
+
- If one side is "one": that FK is NOT part of the PK but has a UNIQUE constraint per combination of other FKs
|
|
208
|
+
- Analyze the functional dependencies to determine the minimal PK
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### Rule 7: Multivalued Attribute --> Separate Table
|
|
213
|
+
|
|
214
|
+
A multivalued attribute (one that can have multiple values per entity instance) becomes its own table with a composite PK of the entity's PK + the attribute value.
|
|
215
|
+
|
|
216
|
+
```sql
|
|
217
|
+
-- Employee has multivalued attribute: phone_numbers
|
|
218
|
+
CREATE TABLE employee_phones (
|
|
219
|
+
emp_id INTEGER NOT NULL,
|
|
220
|
+
phone_number VARCHAR(20) NOT NULL,
|
|
221
|
+
phone_type VARCHAR(10) DEFAULT 'work', -- optional classification
|
|
222
|
+
CONSTRAINT pk_emp_phone PRIMARY KEY (emp_id, phone_number),
|
|
223
|
+
CONSTRAINT fk_emp_phone FOREIGN KEY (emp_id)
|
|
224
|
+
REFERENCES employees(emp_id)
|
|
225
|
+
ON DELETE CASCADE -- phones deleted when employee deleted
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Edge cases:**
|
|
230
|
+
- Multivalued composite attribute (e.g., previous_degrees with university + year + degree_name): all components become columns, composite PK includes entity PK + all components that form a unique combination
|
|
231
|
+
- Multivalued attribute of a weak entity: FK is the weak entity's composite PK
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## DDL Generation Algorithm
|
|
236
|
+
|
|
237
|
+
### Full Pseudocode
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
ALGORITHM: ER-to-DDL Mapping
|
|
241
|
+
INPUT: ERDModel (entities, relationships, attributes)
|
|
242
|
+
OUTPUT: SQL DDL string (ordered by dependency)
|
|
243
|
+
|
|
244
|
+
Phase 1: TABLE CREATION (entities)
|
|
245
|
+
────────────────────────────────────────────────────
|
|
246
|
+
for each strong_entity in model.entities where NOT weak:
|
|
247
|
+
CREATE TABLE entity.name (
|
|
248
|
+
for each attribute in entity.attributes:
|
|
249
|
+
if attribute.type == SIMPLE:
|
|
250
|
+
emit column: name, dataType, constraints
|
|
251
|
+
if attribute.type == KEY:
|
|
252
|
+
emit column: name, dataType, NOT NULL
|
|
253
|
+
collect for PRIMARY KEY
|
|
254
|
+
if attribute.type == COMPOSITE:
|
|
255
|
+
recursively flatten to leaf attributes
|
|
256
|
+
emit each leaf as column
|
|
257
|
+
if attribute.type == DERIVED:
|
|
258
|
+
if dialect supports GENERATED:
|
|
259
|
+
emit GENERATED ALWAYS AS (expression) STORED
|
|
260
|
+
else:
|
|
261
|
+
skip (compute at query time)
|
|
262
|
+
if attribute.type == MULTIVALUED:
|
|
263
|
+
defer to Phase 5
|
|
264
|
+
emit PRIMARY KEY constraint
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
Phase 2: WEAK ENTITY TABLES
|
|
268
|
+
────────────────────────────────────────────────────
|
|
269
|
+
for each weak_entity in model.entities where IS weak:
|
|
270
|
+
identify owner_entity via identifying_relationship
|
|
271
|
+
CREATE TABLE weak_entity.name (
|
|
272
|
+
emit weak entity's own attributes as columns
|
|
273
|
+
emit owner_pk as FK column
|
|
274
|
+
PRIMARY KEY (partial_key_columns, owner_pk_column)
|
|
275
|
+
FOREIGN KEY (owner_pk_column) REFERENCES owner_table(owner_pk)
|
|
276
|
+
ON DELETE CASCADE ON UPDATE CASCADE
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
Phase 3: 1:1 RELATIONSHIPS
|
|
280
|
+
────────────────────────────────────────────────────
|
|
281
|
+
for each relationship where cardinality == ONE_TO_ONE:
|
|
282
|
+
determine FK_side:
|
|
283
|
+
if one side has total participation: FK_side = that side
|
|
284
|
+
if both total: consider merging tables
|
|
285
|
+
if both partial: FK_side = side with fewer expected rows
|
|
286
|
+
ALTER TABLE FK_side
|
|
287
|
+
ADD COLUMN other_pk as FK (with UNIQUE constraint)
|
|
288
|
+
ADD relationship attributes as columns
|
|
289
|
+
FOREIGN KEY REFERENCES other_table
|
|
290
|
+
ON DELETE based on participation (CASCADE if total, SET NULL if partial)
|
|
291
|
+
|
|
292
|
+
Phase 4: 1:N RELATIONSHIPS
|
|
293
|
+
────────────────────────────────────────────────────
|
|
294
|
+
for each relationship where cardinality == ONE_TO_MANY:
|
|
295
|
+
many_side = entity on the "many" end
|
|
296
|
+
one_side = entity on the "one" end
|
|
297
|
+
ALTER TABLE many_side
|
|
298
|
+
ADD COLUMN one_side_pk as FK
|
|
299
|
+
ADD relationship attributes as columns
|
|
300
|
+
FOREIGN KEY REFERENCES one_side_table
|
|
301
|
+
ON DELETE (SET NULL if partial, RESTRICT if critical, CASCADE if dependent)
|
|
302
|
+
CREATE INDEX on many_side(FK_column)
|
|
303
|
+
|
|
304
|
+
Phase 5: M:N RELATIONSHIPS
|
|
305
|
+
────────────────────────────────────────────────────
|
|
306
|
+
for each relationship where cardinality == MANY_TO_MANY:
|
|
307
|
+
CREATE TABLE junction_name (
|
|
308
|
+
entity_a_pk column,
|
|
309
|
+
entity_b_pk column,
|
|
310
|
+
relationship attribute columns,
|
|
311
|
+
PRIMARY KEY (entity_a_pk, entity_b_pk),
|
|
312
|
+
FOREIGN KEY (entity_a_pk) REFERENCES entity_a ON DELETE CASCADE,
|
|
313
|
+
FOREIGN KEY (entity_b_pk) REFERENCES entity_b ON DELETE CASCADE
|
|
314
|
+
)
|
|
315
|
+
CREATE INDEX on junction_name(entity_b_pk) -- composite PK covers entity_a_pk
|
|
316
|
+
|
|
317
|
+
Phase 6: N-ARY RELATIONSHIPS
|
|
318
|
+
────────────────────────────────────────────────────
|
|
319
|
+
for each relationship where participants.length > 2:
|
|
320
|
+
CREATE TABLE junction_name (
|
|
321
|
+
for each participant: emit participant_pk as FK column
|
|
322
|
+
emit relationship attributes as columns
|
|
323
|
+
PRIMARY KEY = determine from cardinality analysis
|
|
324
|
+
for each participant: FOREIGN KEY REFERENCES participant ON DELETE CASCADE
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
Phase 7: MULTIVALUED ATTRIBUTES
|
|
328
|
+
────────────────────────────────────────────────────
|
|
329
|
+
for each multivalued_attribute collected in Phase 1:
|
|
330
|
+
CREATE TABLE entity_attribute_name (
|
|
331
|
+
owner_pk column(s),
|
|
332
|
+
attribute_value column(s),
|
|
333
|
+
PRIMARY KEY (owner_pk, attribute_value),
|
|
334
|
+
FOREIGN KEY (owner_pk) REFERENCES owner_table ON DELETE CASCADE
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
Phase 8: DEPENDENCY ORDERING
|
|
338
|
+
────────────────────────────────────────────────────
|
|
339
|
+
topological_sort all CREATE TABLE statements by FK dependencies
|
|
340
|
+
emit in order: tables with no FKs first, then tables referencing those, etc.
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## TypeScript Data Model
|
|
346
|
+
|
|
347
|
+
These are the type definitions for the internal ERD model that feeds the DDL generator.
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// ============================================================
|
|
351
|
+
// ERD Model Types — the internal representation of an ER diagram
|
|
352
|
+
// ============================================================
|
|
353
|
+
|
|
354
|
+
/** Supported SQL dialects for DDL generation */
|
|
355
|
+
type SQLDialect = 'postgresql' | 'mysql' | 'sqlite';
|
|
356
|
+
|
|
357
|
+
/** Attribute classification per ER theory */
|
|
358
|
+
type AttributeKind =
|
|
359
|
+
| 'simple'
|
|
360
|
+
| 'key'
|
|
361
|
+
| 'partial_key' // weak entity identifier
|
|
362
|
+
| 'composite'
|
|
363
|
+
| 'multivalued'
|
|
364
|
+
| 'derived';
|
|
365
|
+
|
|
366
|
+
/** Relationship cardinality */
|
|
367
|
+
type Cardinality = '1:1' | '1:N' | 'M:N';
|
|
368
|
+
|
|
369
|
+
/** Participation constraint */
|
|
370
|
+
type Participation = 'total' | 'partial';
|
|
371
|
+
|
|
372
|
+
/** FK referential actions */
|
|
373
|
+
type ReferentialAction = 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'SET DEFAULT' | 'NO ACTION';
|
|
374
|
+
|
|
375
|
+
// ── Attributes ──────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
interface ERDAttribute {
|
|
378
|
+
id: string;
|
|
379
|
+
name: string;
|
|
380
|
+
kind: AttributeKind;
|
|
381
|
+
dataType: string; // logical type: "string", "integer", "date", etc.
|
|
382
|
+
isNullable: boolean;
|
|
383
|
+
defaultValue?: string;
|
|
384
|
+
children?: ERDAttribute[]; // for composite attributes: leaf-level sub-attributes
|
|
385
|
+
derivedExpression?: string; // for derived attributes: the computation expression
|
|
386
|
+
multivaluedType?: string; // for multivalued: the value type (e.g., "VARCHAR(20)")
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── Entities ────────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
interface ERDEntity {
|
|
392
|
+
id: string;
|
|
393
|
+
name: string;
|
|
394
|
+
isWeak: boolean;
|
|
395
|
+
attributes: ERDAttribute[];
|
|
396
|
+
/** For weak entities: the ID of the owning strong entity */
|
|
397
|
+
ownerId?: string;
|
|
398
|
+
/** For weak entities: the identifying relationship ID */
|
|
399
|
+
identifyingRelationshipId?: string;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ── Relationships ───────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
interface ERDRelationshipParticipant {
|
|
405
|
+
entityId: string;
|
|
406
|
+
cardinality: '1' | 'N' | 'M';
|
|
407
|
+
participation: Participation;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
interface ERDRelationship {
|
|
411
|
+
id: string;
|
|
412
|
+
name: string;
|
|
413
|
+
participants: ERDRelationshipParticipant[]; // 2 for binary, 3+ for n-ary
|
|
414
|
+
attributes: ERDAttribute[]; // relationship can have its own attributes
|
|
415
|
+
isIdentifying: boolean; // true for weak entity identifying relationships
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ── The Complete Model ──────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
interface ERDModel {
|
|
421
|
+
name: string; // database/schema name
|
|
422
|
+
entities: ERDEntity[];
|
|
423
|
+
relationships: ERDRelationship[];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ── DDL Generation Options ──────────────────────────────────
|
|
427
|
+
|
|
428
|
+
interface DDLGeneratorOptions {
|
|
429
|
+
dialect: SQLDialect;
|
|
430
|
+
useNamedConstraints: boolean; // e.g., CONSTRAINT fk_emp_dept vs inline
|
|
431
|
+
includeIndexes: boolean; // auto-create indexes on FK columns
|
|
432
|
+
includeDropIfExists: boolean; // prepend DROP TABLE IF EXISTS
|
|
433
|
+
schemaName?: string; // e.g., "public" for PostgreSQL
|
|
434
|
+
idStrategy: 'serial' | 'uuid' | 'cuid'; // primary key generation strategy
|
|
435
|
+
timestampColumns: boolean; // auto-add created_at, updated_at
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Multi-Database Type Mapping
|
|
442
|
+
|
|
443
|
+
### Logical-to-Physical Type Map
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
const TYPE_MAP: Record<string, Record<SQLDialect, string>> = {
|
|
447
|
+
// ── Identifiers ─────────────────────────────────────────
|
|
448
|
+
'serial': { postgresql: 'SERIAL', mysql: 'INT AUTO_INCREMENT', sqlite: 'INTEGER' },
|
|
449
|
+
'bigserial': { postgresql: 'BIGSERIAL', mysql: 'BIGINT AUTO_INCREMENT', sqlite: 'INTEGER' },
|
|
450
|
+
'uuid': { postgresql: 'UUID', mysql: 'CHAR(36)', sqlite: 'TEXT' },
|
|
451
|
+
'cuid': { postgresql: 'VARCHAR(30)', mysql: 'VARCHAR(30)', sqlite: 'TEXT' },
|
|
452
|
+
|
|
453
|
+
// ── Strings ─────────────────────────────────────────────
|
|
454
|
+
'varchar': { postgresql: 'VARCHAR', mysql: 'VARCHAR', sqlite: 'TEXT' },
|
|
455
|
+
'text': { postgresql: 'TEXT', mysql: 'TEXT', sqlite: 'TEXT' },
|
|
456
|
+
'longtext': { postgresql: 'TEXT', mysql: 'LONGTEXT', sqlite: 'TEXT' },
|
|
457
|
+
'char': { postgresql: 'CHAR', mysql: 'CHAR', sqlite: 'TEXT' },
|
|
458
|
+
|
|
459
|
+
// ── Numbers ─────────────────────────────────────────────
|
|
460
|
+
'integer': { postgresql: 'INTEGER', mysql: 'INT', sqlite: 'INTEGER' },
|
|
461
|
+
'smallint': { postgresql: 'SMALLINT', mysql: 'SMALLINT', sqlite: 'INTEGER' },
|
|
462
|
+
'bigint': { postgresql: 'BIGINT', mysql: 'BIGINT', sqlite: 'INTEGER' },
|
|
463
|
+
'decimal': { postgresql: 'NUMERIC', mysql: 'DECIMAL', sqlite: 'REAL' },
|
|
464
|
+
'float': { postgresql: 'REAL', mysql: 'FLOAT', sqlite: 'REAL' },
|
|
465
|
+
'double': { postgresql: 'DOUBLE PRECISION', mysql: 'DOUBLE', sqlite: 'REAL' },
|
|
466
|
+
|
|
467
|
+
// ── Boolean ─────────────────────────────────────────────
|
|
468
|
+
'boolean': { postgresql: 'BOOLEAN', mysql: 'TINYINT(1)', sqlite: 'INTEGER' },
|
|
469
|
+
|
|
470
|
+
// ── Date/Time ───────────────────────────────────────────
|
|
471
|
+
'date': { postgresql: 'DATE', mysql: 'DATE', sqlite: 'TEXT' },
|
|
472
|
+
'time': { postgresql: 'TIME', mysql: 'TIME', sqlite: 'TEXT' },
|
|
473
|
+
'timestamp': { postgresql: 'TIMESTAMPTZ', mysql: 'DATETIME', sqlite: 'TEXT' },
|
|
474
|
+
'interval': { postgresql: 'INTERVAL', mysql: 'VARCHAR(50)', sqlite: 'TEXT' },
|
|
475
|
+
|
|
476
|
+
// ── JSON ────────────────────────────────────────────────
|
|
477
|
+
'json': { postgresql: 'JSONB', mysql: 'JSON', sqlite: 'TEXT' },
|
|
478
|
+
|
|
479
|
+
// ── Binary ──────────────────────────────────────────────
|
|
480
|
+
'blob': { postgresql: 'BYTEA', mysql: 'BLOB', sqlite: 'BLOB' },
|
|
481
|
+
|
|
482
|
+
// ── Enum (special handling) ─────────────────────────────
|
|
483
|
+
'enum': { postgresql: 'TEXT', mysql: 'ENUM', sqlite: 'TEXT' },
|
|
484
|
+
// PostgreSQL enums require CREATE TYPE; handled separately in generator
|
|
485
|
+
};
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Dialect-Specific Syntax Differences
|
|
489
|
+
|
|
490
|
+
| Feature | PostgreSQL | MySQL | SQLite |
|
|
491
|
+
|------------------------|--------------------------------|--------------------------------|---------------------------|
|
|
492
|
+
| Auto-increment PK | `SERIAL PRIMARY KEY` | `INT PRIMARY KEY AUTO_INCREMENT` | `INTEGER PRIMARY KEY` |
|
|
493
|
+
| UUID generation | `DEFAULT gen_random_uuid()` | `DEFAULT (UUID())` | Application-generated |
|
|
494
|
+
| Boolean literal | `TRUE / FALSE` | `1 / 0` | `1 / 0` |
|
|
495
|
+
| Current timestamp | `CURRENT_TIMESTAMP` | `CURRENT_TIMESTAMP` | `CURRENT_TIMESTAMP` |
|
|
496
|
+
| Generated column | `GENERATED ALWAYS AS (...) STORED` | `GENERATED ALWAYS AS (...) STORED` | Not supported |
|
|
497
|
+
| IF NOT EXISTS | `CREATE TABLE IF NOT EXISTS` | `CREATE TABLE IF NOT EXISTS` | `CREATE TABLE IF NOT EXISTS` |
|
|
498
|
+
| Drop + cascade | `DROP TABLE IF EXISTS t CASCADE` | `DROP TABLE IF EXISTS t` | `DROP TABLE IF EXISTS t` |
|
|
499
|
+
| Schema qualification | `schema.table` | `` `database`.`table` `` | Not applicable |
|
|
500
|
+
| ENUM type | `CREATE TYPE ... AS ENUM` | Inline `ENUM('a','b')` | Use CHECK constraint |
|
|
501
|
+
| Partial index | `WHERE condition` | Not supported (use generated column + index) | `WHERE condition` |
|
|
502
|
+
| Comment on column | `COMMENT ON COLUMN ...` | `COMMENT 'text'` inline | Not supported |
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## FK Referential Actions
|
|
507
|
+
|
|
508
|
+
### Action Matrix — When to Use Each
|
|
509
|
+
|
|
510
|
+
| Action | Behavior on Parent DELETE | Use When |
|
|
511
|
+
|----------------|----------------------------------------|-----------------------------------------------------------------|
|
|
512
|
+
| **CASCADE** | Delete all child rows automatically | Weak entities, junction tables, dependent data that has no meaning without parent |
|
|
513
|
+
| **SET NULL** | Set FK column to NULL | Optional relationships where child survives parent deletion (e.g., employee.department_id when department dissolved) |
|
|
514
|
+
| **RESTRICT** | Block parent deletion if children exist | **Safe default.** Critical references where deletion should be prevented (e.g., cannot delete a customer with open orders) |
|
|
515
|
+
| **SET DEFAULT**| Set FK to its DEFAULT value | Rare. When a fallback parent exists (e.g., "unassigned" category) |
|
|
516
|
+
| **NO ACTION** | Like RESTRICT but deferred-checkable | Same as RESTRICT; use when you need deferred constraint checking within a transaction |
|
|
517
|
+
|
|
518
|
+
### Decision Tree
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
Is the child entity DEPENDENT on the parent (weak entity, junction table)?
|
|
522
|
+
YES --> CASCADE
|
|
523
|
+
NO --> Can the child exist without the parent?
|
|
524
|
+
YES --> Is the FK nullable?
|
|
525
|
+
YES --> SET NULL
|
|
526
|
+
NO --> RESTRICT (or add a default parent)
|
|
527
|
+
NO --> CASCADE
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### ON UPDATE Actions
|
|
531
|
+
|
|
532
|
+
Most systems should use `ON UPDATE CASCADE` for all FKs. This handles the (rare) case where a parent PK is updated. Exception: if using immutable surrogate keys (UUID/CUID), ON UPDATE is effectively a no-op.
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## AI-Enhanced Generation
|
|
537
|
+
|
|
538
|
+
### Text2Schema Pipeline (arXiv 2025)
|
|
539
|
+
|
|
540
|
+
Multi-agent decomposition for natural language to DDL. The key insight: breaking the NL-to-DDL task into sub-tasks with specialized agents yields significantly better schemas than single-pass generation.
|
|
541
|
+
|
|
542
|
+
```
|
|
543
|
+
Input: "Build a university system where students enroll in courses
|
|
544
|
+
taught by professors in departments"
|
|
545
|
+
|
|
546
|
+
Agent Pipeline:
|
|
547
|
+
1. ENTITY EXTRACTOR
|
|
548
|
+
--> Student, Course, Professor, Department
|
|
549
|
+
|
|
550
|
+
2. RELATIONSHIP CLASSIFIER
|
|
551
|
+
--> Student M:N Course (enrollment)
|
|
552
|
+
--> Professor 1:N Course (teaches)
|
|
553
|
+
--> Department 1:N Professor (belongs_to)
|
|
554
|
+
--> Department 1:N Course (offered_by)
|
|
555
|
+
|
|
556
|
+
3. ATTRIBUTE ENRICHER
|
|
557
|
+
--> Student: student_id (PK), first_name, last_name, email, enrollment_date
|
|
558
|
+
--> Course: course_id (PK), title, credits, max_enrollment
|
|
559
|
+
--> Professor: prof_id (PK), first_name, last_name, email, hire_date
|
|
560
|
+
--> Department: dept_id (PK), name, building, budget
|
|
561
|
+
|
|
562
|
+
4. CONSTRAINT INTEGRATOR
|
|
563
|
+
--> enrollment junction: grade, semester (relationship attrs)
|
|
564
|
+
--> email UNIQUE on Student, Professor
|
|
565
|
+
--> credits CHECK (credits > 0 AND credits <= 6)
|
|
566
|
+
|
|
567
|
+
5. DDL CODE ARTICULATOR
|
|
568
|
+
--> Applies the 7 mapping rules
|
|
569
|
+
--> Outputs dialect-specific SQL
|
|
570
|
+
|
|
571
|
+
6. VERIFIER
|
|
572
|
+
--> Checks 3NF compliance
|
|
573
|
+
--> Validates FK references
|
|
574
|
+
--> Confirms no orphan tables
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### NOMAD Agent Roles (arXiv 2025)
|
|
578
|
+
|
|
579
|
+
Similar multi-agent approach but emphasizes the **articulation** step -- translating the abstract model into implementation-ready code. Key pattern: the code articulator agent receives the verified model and must produce syntactically valid, runnable DDL. The verifier then executes it against a test database to confirm.
|
|
580
|
+
|
|
581
|
+
### Prompt Template for LLM-Assisted Mapping
|
|
582
|
+
|
|
583
|
+
```
|
|
584
|
+
You are a database schema designer. Given the following ER model in JSON format,
|
|
585
|
+
generate SQL DDL for {dialect}.
|
|
586
|
+
|
|
587
|
+
Rules:
|
|
588
|
+
1. Strong entities become tables with their simple attributes as columns
|
|
589
|
+
2. Key attributes become PRIMARY KEY
|
|
590
|
+
3. Composite attributes are flattened to leaf columns
|
|
591
|
+
4. Weak entities get composite PK (partial_key + owner_pk) with ON DELETE CASCADE
|
|
592
|
+
5. 1:1 relationships: FK on total-participation side with UNIQUE constraint
|
|
593
|
+
6. 1:N relationships: FK on the many side
|
|
594
|
+
7. M:N relationships: junction table with composite PK
|
|
595
|
+
8. Multivalued attributes: separate table with composite PK
|
|
596
|
+
|
|
597
|
+
Output requirements:
|
|
598
|
+
- Named constraints (pk_, fk_, uq_, ck_ prefixes)
|
|
599
|
+
- Appropriate ON DELETE actions
|
|
600
|
+
- Indexes on all FK columns
|
|
601
|
+
- Tables ordered by dependency (no forward references)
|
|
602
|
+
|
|
603
|
+
ER Model:
|
|
604
|
+
{model_json}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## Implementation Architecture
|
|
610
|
+
|
|
611
|
+
### DrawDB Parser/Generator Pattern (Gold Standard)
|
|
612
|
+
|
|
613
|
+
DrawDB is the open-source reference implementation for visual ERD-to-DDL. Its architecture separates concerns cleanly:
|
|
614
|
+
|
|
615
|
+
```
|
|
616
|
+
Visual Editor (React Flow)
|
|
617
|
+
|
|
|
618
|
+
v
|
|
619
|
+
Internal JSON Model <-- the canonical representation
|
|
620
|
+
|
|
|
621
|
+
+--> PostgreSQL Generator
|
|
622
|
+
+--> MySQL Generator
|
|
623
|
+
+--> SQLite Generator
|
|
624
|
+
+--> SQL Server Generator
|
|
625
|
+
|
|
|
626
|
+
v
|
|
627
|
+
SQL DDL String Output
|
|
628
|
+
|
|
629
|
+
Reverse path:
|
|
630
|
+
SQL DDL String --> Parser --> Internal JSON Model --> Visual Editor
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**Key pattern:** Each dialect has its own generator module that reads the same JSON model. The generators share a base class with common logic (constraint naming, dependency sorting) and override dialect-specific methods (type mapping, auto-increment syntax, enum handling).
|
|
634
|
+
|
|
635
|
+
### node-sql-parser Integration
|
|
636
|
+
|
|
637
|
+
Bidirectional SQL parsing/generation using AST:
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
import { Parser } from 'node-sql-parser';
|
|
641
|
+
|
|
642
|
+
const parser = new Parser();
|
|
643
|
+
|
|
644
|
+
// DDL string --> AST (for importing existing schemas)
|
|
645
|
+
const ast = parser.astify('CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50));');
|
|
646
|
+
|
|
647
|
+
// AST --> DDL string (for generating from modified model)
|
|
648
|
+
const sql = parser.sqlify(ast);
|
|
649
|
+
|
|
650
|
+
// Supports dialect-specific parsing
|
|
651
|
+
const pgAst = parser.astify(ddlString, { database: 'PostgreSQL' });
|
|
652
|
+
const mysqlAst = parser.astify(ddlString, { database: 'MySQL' });
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### sql-ddl-to-json-schema
|
|
656
|
+
|
|
657
|
+
For importing existing DDL into the ERD model:
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
660
|
+
// Parse DDL into JSON Schema (ERD-friendly format)
|
|
661
|
+
import { Parser as DDLParser } from 'sql-ddl-to-json-schema';
|
|
662
|
+
|
|
663
|
+
const ddlParser = new DDLParser('mysql');
|
|
664
|
+
ddlParser.feed(existingDDL);
|
|
665
|
+
const jsonSchema = ddlParser.results;
|
|
666
|
+
// jsonSchema contains tables, columns, constraints, FKs
|
|
667
|
+
// Map this into the ERDModel interface for visualization
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### ERFlow MCP Pattern
|
|
671
|
+
|
|
672
|
+
ERFlow (MCP Server) demonstrates an agent-driven approach with 25+ tools for schema manipulation:
|
|
673
|
+
- Natural language schema edits ("add a status column to orders")
|
|
674
|
+
- Checkpoint-based migration generation (diff between schema states)
|
|
675
|
+
- Multi-dialect export from a single canonical model
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## Code Example: TypeScript DDL Generator
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
// ============================================================
|
|
683
|
+
// ERD-to-DDL Generator — complete implementation
|
|
684
|
+
// ============================================================
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Resolves a logical data type to a dialect-specific SQL type.
|
|
688
|
+
*/
|
|
689
|
+
function resolveType(
|
|
690
|
+
logicalType: string,
|
|
691
|
+
dialect: SQLDialect,
|
|
692
|
+
length?: number,
|
|
693
|
+
precision?: number,
|
|
694
|
+
scale?: number
|
|
695
|
+
): string {
|
|
696
|
+
const base = TYPE_MAP[logicalType.toLowerCase()]?.[dialect]
|
|
697
|
+
?? TYPE_MAP['text'][dialect]; // fallback to TEXT
|
|
698
|
+
|
|
699
|
+
// Apply length/precision modifiers
|
|
700
|
+
if (length && ['varchar', 'char'].includes(logicalType.toLowerCase())) {
|
|
701
|
+
return `${base}(${length})`;
|
|
702
|
+
}
|
|
703
|
+
if (precision && ['decimal', 'numeric'].includes(logicalType.toLowerCase())) {
|
|
704
|
+
return scale ? `${base}(${precision},${scale})` : `${base}(${precision})`;
|
|
705
|
+
}
|
|
706
|
+
return base;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Generates a constraint name following naming conventions.
|
|
711
|
+
*/
|
|
712
|
+
function constraintName(
|
|
713
|
+
type: 'pk' | 'fk' | 'uq' | 'ck' | 'idx',
|
|
714
|
+
tableName: string,
|
|
715
|
+
columnOrDetail: string
|
|
716
|
+
): string {
|
|
717
|
+
return `${type}_${tableName}_${columnOrDetail}`.toLowerCase();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Flattens composite attributes recursively into leaf-level columns.
|
|
722
|
+
*/
|
|
723
|
+
function flattenAttributes(attrs: ERDAttribute[], prefix = ''): ERDAttribute[] {
|
|
724
|
+
const result: ERDAttribute[] = [];
|
|
725
|
+
for (const attr of attrs) {
|
|
726
|
+
if (attr.kind === 'composite' && attr.children?.length) {
|
|
727
|
+
result.push(...flattenAttributes(attr.children, `${prefix}${attr.name}_`));
|
|
728
|
+
} else if (attr.kind !== 'multivalued') {
|
|
729
|
+
result.push({
|
|
730
|
+
...attr,
|
|
731
|
+
name: `${prefix}${attr.name}`,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
// multivalued attributes handled separately in Phase 7
|
|
735
|
+
}
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Collects all multivalued attributes from an entity (including nested in composites).
|
|
741
|
+
*/
|
|
742
|
+
function collectMultivalued(
|
|
743
|
+
entity: ERDEntity
|
|
744
|
+
): { entityId: string; entityName: string; attr: ERDAttribute }[] {
|
|
745
|
+
const results: { entityId: string; entityName: string; attr: ERDAttribute }[] = [];
|
|
746
|
+
|
|
747
|
+
function walk(attrs: ERDAttribute[]) {
|
|
748
|
+
for (const attr of attrs) {
|
|
749
|
+
if (attr.kind === 'multivalued') {
|
|
750
|
+
results.push({ entityId: entity.id, entityName: entity.name, attr });
|
|
751
|
+
}
|
|
752
|
+
if (attr.children) walk(attr.children);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
walk(entity.attributes);
|
|
757
|
+
return results;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Gets the primary key column names for an entity.
|
|
762
|
+
*/
|
|
763
|
+
function getPKColumns(entity: ERDEntity): string[] {
|
|
764
|
+
return entity.attributes
|
|
765
|
+
.filter(a => a.kind === 'key' || a.kind === 'partial_key')
|
|
766
|
+
.map(a => a.name);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Determines the appropriate ON DELETE action based on relationship context.
|
|
771
|
+
*/
|
|
772
|
+
function determineOnDelete(
|
|
773
|
+
participation: Participation,
|
|
774
|
+
isWeakEntityFK: boolean,
|
|
775
|
+
isJunctionFK: boolean
|
|
776
|
+
): ReferentialAction {
|
|
777
|
+
if (isWeakEntityFK || isJunctionFK) return 'CASCADE';
|
|
778
|
+
if (participation === 'total') return 'RESTRICT';
|
|
779
|
+
return 'SET NULL';
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Topologically sorts tables by FK dependencies.
|
|
784
|
+
* Tables with no FK dependencies come first.
|
|
785
|
+
*/
|
|
786
|
+
function topologicalSort(
|
|
787
|
+
tables: { name: string; dependsOn: string[] }[]
|
|
788
|
+
): string[] {
|
|
789
|
+
const sorted: string[] = [];
|
|
790
|
+
const visited = new Set<string>();
|
|
791
|
+
const visiting = new Set<string>();
|
|
792
|
+
const tableMap = new Map(tables.map(t => [t.name, t]));
|
|
793
|
+
|
|
794
|
+
function visit(name: string) {
|
|
795
|
+
if (visited.has(name)) return;
|
|
796
|
+
if (visiting.has(name)) {
|
|
797
|
+
// Circular dependency — emit as-is (will need ALTER TABLE for FK)
|
|
798
|
+
sorted.push(name);
|
|
799
|
+
visited.add(name);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
visiting.add(name);
|
|
803
|
+
const table = tableMap.get(name);
|
|
804
|
+
if (table) {
|
|
805
|
+
for (const dep of table.dependsOn) {
|
|
806
|
+
if (tableMap.has(dep)) visit(dep);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
visiting.delete(name);
|
|
810
|
+
visited.add(name);
|
|
811
|
+
sorted.push(name);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
for (const table of tables) {
|
|
815
|
+
visit(table.name);
|
|
816
|
+
}
|
|
817
|
+
return sorted;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ── Statement Builders ──────────────────────────────────────
|
|
821
|
+
|
|
822
|
+
interface TableStatement {
|
|
823
|
+
name: string;
|
|
824
|
+
sql: string;
|
|
825
|
+
dependsOn: string[];
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
interface IndexStatement {
|
|
829
|
+
sql: string;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Main generator function: ERDModel --> SQL DDL string.
|
|
834
|
+
*/
|
|
835
|
+
function generateDDL(model: ERDModel, options: DDLGeneratorOptions): string {
|
|
836
|
+
const tables: TableStatement[] = [];
|
|
837
|
+
const indexes: IndexStatement[] = [];
|
|
838
|
+
const entityTableMap = new Map<string, string>(); // entity.id -> table name
|
|
839
|
+
|
|
840
|
+
// Build entity ID -> table name lookup
|
|
841
|
+
for (const entity of model.entities) {
|
|
842
|
+
entityTableMap.set(entity.id, entity.name.toLowerCase());
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// ── Phase 1: Strong Entity Tables ─────────────────────────
|
|
846
|
+
|
|
847
|
+
for (const entity of model.entities.filter(e => !e.isWeak)) {
|
|
848
|
+
const tableName = entity.name.toLowerCase();
|
|
849
|
+
const flatAttrs = flattenAttributes(entity.attributes);
|
|
850
|
+
const pkCols = getPKColumns(entity);
|
|
851
|
+
const columns: string[] = [];
|
|
852
|
+
|
|
853
|
+
for (const attr of flatAttrs) {
|
|
854
|
+
const colName = attr.name.toLowerCase();
|
|
855
|
+
const colType = resolveType(attr.dataType, options.dialect);
|
|
856
|
+
const constraints: string[] = [];
|
|
857
|
+
|
|
858
|
+
if (attr.kind === 'key') constraints.push('NOT NULL');
|
|
859
|
+
if (!attr.isNullable && attr.kind !== 'key') constraints.push('NOT NULL');
|
|
860
|
+
if (attr.defaultValue) constraints.push(`DEFAULT ${attr.defaultValue}`);
|
|
861
|
+
|
|
862
|
+
if (attr.kind === 'derived' && attr.derivedExpression) {
|
|
863
|
+
if (options.dialect !== 'sqlite') {
|
|
864
|
+
columns.push(
|
|
865
|
+
` ${colName} ${colType} GENERATED ALWAYS AS (${attr.derivedExpression}) STORED`
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
// SQLite: skip derived columns
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
columns.push(` ${colName} ${colType}${constraints.length ? ' ' + constraints.join(' ') : ''}`);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Timestamp columns
|
|
876
|
+
if (options.timestampColumns) {
|
|
877
|
+
const tsType = resolveType('timestamp', options.dialect);
|
|
878
|
+
columns.push(` created_at ${tsType} NOT NULL DEFAULT CURRENT_TIMESTAMP`);
|
|
879
|
+
columns.push(` updated_at ${tsType} NOT NULL DEFAULT CURRENT_TIMESTAMP`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// PK constraint
|
|
883
|
+
const pkConstraint = options.useNamedConstraints
|
|
884
|
+
? ` CONSTRAINT ${constraintName('pk', tableName, pkCols.join('_'))} PRIMARY KEY (${pkCols.join(', ')})`
|
|
885
|
+
: ` PRIMARY KEY (${pkCols.join(', ')})`;
|
|
886
|
+
|
|
887
|
+
const ddl = [
|
|
888
|
+
`CREATE TABLE ${tableName} (`,
|
|
889
|
+
[...columns, pkConstraint].join(',\n'),
|
|
890
|
+
`);`,
|
|
891
|
+
].join('\n');
|
|
892
|
+
|
|
893
|
+
tables.push({ name: tableName, sql: ddl, dependsOn: [] });
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// ── Phase 2: Weak Entity Tables ───────────────────────────
|
|
897
|
+
|
|
898
|
+
for (const entity of model.entities.filter(e => e.isWeak)) {
|
|
899
|
+
const tableName = entity.name.toLowerCase();
|
|
900
|
+
const owner = model.entities.find(e => e.id === entity.ownerId);
|
|
901
|
+
if (!owner) continue;
|
|
902
|
+
|
|
903
|
+
const ownerTable = owner.name.toLowerCase();
|
|
904
|
+
const ownerPK = getPKColumns(owner);
|
|
905
|
+
const partialKey = entity.attributes.filter(a => a.kind === 'partial_key').map(a => a.name);
|
|
906
|
+
const flatAttrs = flattenAttributes(entity.attributes.filter(a => a.kind !== 'partial_key'));
|
|
907
|
+
const columns: string[] = [];
|
|
908
|
+
|
|
909
|
+
// Partial key columns
|
|
910
|
+
for (const pk of partialKey) {
|
|
911
|
+
const attr = entity.attributes.find(a => a.name === pk)!;
|
|
912
|
+
columns.push(` ${pk.toLowerCase()} ${resolveType(attr.dataType, options.dialect)} NOT NULL`);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Owner PK as FK columns
|
|
916
|
+
for (const ownerCol of ownerPK) {
|
|
917
|
+
const ownerAttr = owner.attributes.find(a => a.name === ownerCol)!;
|
|
918
|
+
columns.push(` ${ownerCol.toLowerCase()} ${resolveType(ownerAttr.dataType, options.dialect)} NOT NULL`);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Other attributes
|
|
922
|
+
for (const attr of flatAttrs) {
|
|
923
|
+
const colType = resolveType(attr.dataType, options.dialect);
|
|
924
|
+
const nullable = attr.isNullable ? '' : ' NOT NULL';
|
|
925
|
+
columns.push(` ${attr.name.toLowerCase()} ${colType}${nullable}`);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Composite PK
|
|
929
|
+
const compositePK = [...partialKey, ...ownerPK].map(c => c.toLowerCase());
|
|
930
|
+
const pkLine = options.useNamedConstraints
|
|
931
|
+
? ` CONSTRAINT ${constraintName('pk', tableName, compositePK.join('_'))} PRIMARY KEY (${compositePK.join(', ')})`
|
|
932
|
+
: ` PRIMARY KEY (${compositePK.join(', ')})`;
|
|
933
|
+
|
|
934
|
+
// FK to owner
|
|
935
|
+
const fkLine = options.useNamedConstraints
|
|
936
|
+
? ` CONSTRAINT ${constraintName('fk', tableName, ownerTable)} FOREIGN KEY (${ownerPK.join(', ')}) REFERENCES ${ownerTable}(${ownerPK.join(', ')}) ON DELETE CASCADE ON UPDATE CASCADE`
|
|
937
|
+
: ` FOREIGN KEY (${ownerPK.join(', ')}) REFERENCES ${ownerTable}(${ownerPK.join(', ')}) ON DELETE CASCADE ON UPDATE CASCADE`;
|
|
938
|
+
|
|
939
|
+
const ddl = [
|
|
940
|
+
`CREATE TABLE ${tableName} (`,
|
|
941
|
+
[...columns, pkLine, fkLine].join(',\n'),
|
|
942
|
+
`);`,
|
|
943
|
+
].join('\n');
|
|
944
|
+
|
|
945
|
+
tables.push({ name: tableName, sql: ddl, dependsOn: [ownerTable] });
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ── Phase 3-6: Relationships ──────────────────────────────
|
|
949
|
+
|
|
950
|
+
for (const rel of model.relationships) {
|
|
951
|
+
if (rel.isIdentifying) continue; // already handled in Phase 2
|
|
952
|
+
|
|
953
|
+
const participants = rel.participants;
|
|
954
|
+
|
|
955
|
+
if (participants.length === 2) {
|
|
956
|
+
const [a, b] = participants;
|
|
957
|
+
const cardA = a.cardinality;
|
|
958
|
+
const cardB = b.cardinality;
|
|
959
|
+
const entityA = model.entities.find(e => e.id === a.entityId)!;
|
|
960
|
+
const entityB = model.entities.find(e => e.id === b.entityId)!;
|
|
961
|
+
const tableA = entityA.name.toLowerCase();
|
|
962
|
+
const tableB = entityB.name.toLowerCase();
|
|
963
|
+
const pkA = getPKColumns(entityA);
|
|
964
|
+
const pkB = getPKColumns(entityB);
|
|
965
|
+
|
|
966
|
+
// ── 1:1 ──
|
|
967
|
+
if (cardA === '1' && cardB === '1') {
|
|
968
|
+
// FK goes on total participation side (or side A by default)
|
|
969
|
+
const fkSide = b.participation === 'total' ? 'b' : 'a';
|
|
970
|
+
const [fkTable, refTable, refPK] = fkSide === 'b'
|
|
971
|
+
? [tableB, tableA, pkA]
|
|
972
|
+
: [tableA, tableB, pkB];
|
|
973
|
+
const fkCol = `${refTable}_id`;
|
|
974
|
+
const onDelete = determineOnDelete(
|
|
975
|
+
fkSide === 'b' ? b.participation : a.participation, false, false
|
|
976
|
+
);
|
|
977
|
+
|
|
978
|
+
// Find the table statement and add FK column
|
|
979
|
+
const tableStmt = tables.find(t => t.name === fkTable);
|
|
980
|
+
if (tableStmt) {
|
|
981
|
+
const fkColType = resolveType('integer', options.dialect); // match ref PK type
|
|
982
|
+
const insertBefore = ');';
|
|
983
|
+
const fkLines = [
|
|
984
|
+
`,\n ${fkCol} ${fkColType} UNIQUE`,
|
|
985
|
+
options.useNamedConstraints
|
|
986
|
+
? `,\n CONSTRAINT ${constraintName('fk', fkTable, refTable)} FOREIGN KEY (${fkCol}) REFERENCES ${refTable}(${refPK.join(', ')}) ON DELETE ${onDelete}`
|
|
987
|
+
: `,\n FOREIGN KEY (${fkCol}) REFERENCES ${refTable}(${refPK.join(', ')}) ON DELETE ${onDelete}`,
|
|
988
|
+
];
|
|
989
|
+
// Add relationship attributes
|
|
990
|
+
for (const attr of rel.attributes) {
|
|
991
|
+
fkLines.unshift(`,\n ${attr.name.toLowerCase()} ${resolveType(attr.dataType, options.dialect)}`);
|
|
992
|
+
}
|
|
993
|
+
tableStmt.sql = tableStmt.sql.replace(insertBefore, fkLines.join('') + '\n' + insertBefore);
|
|
994
|
+
tableStmt.dependsOn.push(refTable);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// ── 1:N ──
|
|
999
|
+
else if (
|
|
1000
|
+
(cardA === '1' && (cardB === 'N' || cardB === 'M')) ||
|
|
1001
|
+
((cardA === 'N' || cardA === 'M') && cardB === '1')
|
|
1002
|
+
) {
|
|
1003
|
+
const [oneEntity, manyEntity, manyParticipation] =
|
|
1004
|
+
cardA === '1'
|
|
1005
|
+
? [entityA, entityB, b.participation]
|
|
1006
|
+
: [entityB, entityA, a.participation];
|
|
1007
|
+
const oneTable = oneEntity.name.toLowerCase();
|
|
1008
|
+
const manyTable = manyEntity.name.toLowerCase();
|
|
1009
|
+
const onePK = getPKColumns(oneEntity);
|
|
1010
|
+
const fkCol = `${oneTable}_id`;
|
|
1011
|
+
const onDelete = determineOnDelete(manyParticipation, false, false);
|
|
1012
|
+
const nullable = manyParticipation === 'partial';
|
|
1013
|
+
|
|
1014
|
+
const tableStmt = tables.find(t => t.name === manyTable);
|
|
1015
|
+
if (tableStmt) {
|
|
1016
|
+
const fkColType = resolveType('integer', options.dialect);
|
|
1017
|
+
const nullStr = nullable ? '' : ' NOT NULL';
|
|
1018
|
+
const insertBefore = ');';
|
|
1019
|
+
const fkLines = [
|
|
1020
|
+
`,\n ${fkCol} ${fkColType}${nullStr}`,
|
|
1021
|
+
options.useNamedConstraints
|
|
1022
|
+
? `,\n CONSTRAINT ${constraintName('fk', manyTable, oneTable)} FOREIGN KEY (${fkCol}) REFERENCES ${oneTable}(${onePK.join(', ')}) ON DELETE ${onDelete}`
|
|
1023
|
+
: `,\n FOREIGN KEY (${fkCol}) REFERENCES ${oneTable}(${onePK.join(', ')}) ON DELETE ${onDelete}`,
|
|
1024
|
+
];
|
|
1025
|
+
for (const attr of rel.attributes) {
|
|
1026
|
+
fkLines.unshift(`,\n ${attr.name.toLowerCase()} ${resolveType(attr.dataType, options.dialect)}`);
|
|
1027
|
+
}
|
|
1028
|
+
tableStmt.sql = tableStmt.sql.replace(insertBefore, fkLines.join('') + '\n' + insertBefore);
|
|
1029
|
+
tableStmt.dependsOn.push(oneTable);
|
|
1030
|
+
|
|
1031
|
+
if (options.includeIndexes) {
|
|
1032
|
+
indexes.push({
|
|
1033
|
+
sql: `CREATE INDEX ${constraintName('idx', manyTable, fkCol)} ON ${manyTable}(${fkCol});`,
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// ── M:N ──
|
|
1040
|
+
else if ((cardA === 'N' || cardA === 'M') && (cardB === 'N' || cardB === 'M')) {
|
|
1041
|
+
const junctionName = rel.name.toLowerCase() || `${tableA}_${tableB}`;
|
|
1042
|
+
const fkColA = `${tableA}_${pkA[0]}`.toLowerCase();
|
|
1043
|
+
const fkColB = `${tableB}_${pkB[0]}`.toLowerCase();
|
|
1044
|
+
const fkTypeA = resolveType('integer', options.dialect);
|
|
1045
|
+
const fkTypeB = resolveType('integer', options.dialect);
|
|
1046
|
+
|
|
1047
|
+
const columns: string[] = [
|
|
1048
|
+
` ${fkColA} ${fkTypeA} NOT NULL`,
|
|
1049
|
+
` ${fkColB} ${fkTypeB} NOT NULL`,
|
|
1050
|
+
];
|
|
1051
|
+
|
|
1052
|
+
// Relationship attributes
|
|
1053
|
+
for (const attr of rel.attributes) {
|
|
1054
|
+
const colType = resolveType(attr.dataType, options.dialect);
|
|
1055
|
+
const nullable = attr.isNullable ? '' : ' NOT NULL';
|
|
1056
|
+
const def = attr.defaultValue ? ` DEFAULT ${attr.defaultValue}` : '';
|
|
1057
|
+
columns.push(` ${attr.name.toLowerCase()} ${colType}${nullable}${def}`);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const pkLine = options.useNamedConstraints
|
|
1061
|
+
? ` CONSTRAINT ${constraintName('pk', junctionName, `${fkColA}_${fkColB}`)} PRIMARY KEY (${fkColA}, ${fkColB})`
|
|
1062
|
+
: ` PRIMARY KEY (${fkColA}, ${fkColB})`;
|
|
1063
|
+
|
|
1064
|
+
const fkLineA = options.useNamedConstraints
|
|
1065
|
+
? ` CONSTRAINT ${constraintName('fk', junctionName, tableA)} FOREIGN KEY (${fkColA}) REFERENCES ${tableA}(${pkA.join(', ')}) ON DELETE CASCADE`
|
|
1066
|
+
: ` FOREIGN KEY (${fkColA}) REFERENCES ${tableA}(${pkA.join(', ')}) ON DELETE CASCADE`;
|
|
1067
|
+
|
|
1068
|
+
const fkLineB = options.useNamedConstraints
|
|
1069
|
+
? ` CONSTRAINT ${constraintName('fk', junctionName, tableB)} FOREIGN KEY (${fkColB}) REFERENCES ${tableB}(${pkB.join(', ')}) ON DELETE CASCADE`
|
|
1070
|
+
: ` FOREIGN KEY (${fkColB}) REFERENCES ${tableB}(${pkB.join(', ')}) ON DELETE CASCADE`;
|
|
1071
|
+
|
|
1072
|
+
const ddl = [
|
|
1073
|
+
`CREATE TABLE ${junctionName} (`,
|
|
1074
|
+
[...columns, pkLine, fkLineA, fkLineB].join(',\n'),
|
|
1075
|
+
`);`,
|
|
1076
|
+
].join('\n');
|
|
1077
|
+
|
|
1078
|
+
tables.push({ name: junctionName, sql: ddl, dependsOn: [tableA, tableB] });
|
|
1079
|
+
|
|
1080
|
+
if (options.includeIndexes) {
|
|
1081
|
+
indexes.push({
|
|
1082
|
+
sql: `CREATE INDEX ${constraintName('idx', junctionName, fkColB)} ON ${junctionName}(${fkColB});`,
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// ── N-ary (3+ participants) ──
|
|
1089
|
+
else if (participants.length > 2) {
|
|
1090
|
+
const junctionName = rel.name.toLowerCase();
|
|
1091
|
+
const fkCols: { col: string; type: string; table: string; pk: string }[] = [];
|
|
1092
|
+
|
|
1093
|
+
for (const p of participants) {
|
|
1094
|
+
const entity = model.entities.find(e => e.id === p.entityId)!;
|
|
1095
|
+
const table = entity.name.toLowerCase();
|
|
1096
|
+
const pk = getPKColumns(entity);
|
|
1097
|
+
fkCols.push({
|
|
1098
|
+
col: `${table}_${pk[0]}`.toLowerCase(),
|
|
1099
|
+
type: resolveType('integer', options.dialect),
|
|
1100
|
+
table,
|
|
1101
|
+
pk: pk.join(', '),
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const columns = fkCols.map(fk => ` ${fk.col} ${fk.type} NOT NULL`);
|
|
1106
|
+
|
|
1107
|
+
for (const attr of rel.attributes) {
|
|
1108
|
+
columns.push(` ${attr.name.toLowerCase()} ${resolveType(attr.dataType, options.dialect)}`);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const pkLine = ` PRIMARY KEY (${fkCols.map(fk => fk.col).join(', ')})`;
|
|
1112
|
+
const fkLines = fkCols.map(fk =>
|
|
1113
|
+
options.useNamedConstraints
|
|
1114
|
+
? ` CONSTRAINT ${constraintName('fk', junctionName, fk.table)} FOREIGN KEY (${fk.col}) REFERENCES ${fk.table}(${fk.pk}) ON DELETE CASCADE`
|
|
1115
|
+
: ` FOREIGN KEY (${fk.col}) REFERENCES ${fk.table}(${fk.pk}) ON DELETE CASCADE`
|
|
1116
|
+
);
|
|
1117
|
+
|
|
1118
|
+
const ddl = [
|
|
1119
|
+
`CREATE TABLE ${junctionName} (`,
|
|
1120
|
+
[...columns, pkLine, ...fkLines].join(',\n'),
|
|
1121
|
+
`);`,
|
|
1122
|
+
].join('\n');
|
|
1123
|
+
|
|
1124
|
+
tables.push({
|
|
1125
|
+
name: junctionName,
|
|
1126
|
+
sql: ddl,
|
|
1127
|
+
dependsOn: fkCols.map(fk => fk.table),
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// ── Phase 7: Multivalued Attributes ───────────────────────
|
|
1133
|
+
|
|
1134
|
+
for (const entity of model.entities) {
|
|
1135
|
+
const multivaluedAttrs = collectMultivalued(entity);
|
|
1136
|
+
for (const { entityName, attr } of multivaluedAttrs) {
|
|
1137
|
+
const ownerTable = entityName.toLowerCase();
|
|
1138
|
+
const tableName = `${ownerTable}_${attr.name.toLowerCase()}`;
|
|
1139
|
+
const ownerPK = getPKColumns(entity);
|
|
1140
|
+
const ownerPKType = resolveType('integer', options.dialect);
|
|
1141
|
+
const valueType = resolveType(attr.multivaluedType ?? attr.dataType, options.dialect);
|
|
1142
|
+
|
|
1143
|
+
const ddl = [
|
|
1144
|
+
`CREATE TABLE ${tableName} (`,
|
|
1145
|
+
` ${ownerPK[0].toLowerCase()} ${ownerPKType} NOT NULL,`,
|
|
1146
|
+
` ${attr.name.toLowerCase()} ${valueType} NOT NULL,`,
|
|
1147
|
+
options.useNamedConstraints
|
|
1148
|
+
? ` CONSTRAINT ${constraintName('pk', tableName, `${ownerPK[0]}_${attr.name}`)} PRIMARY KEY (${ownerPK[0].toLowerCase()}, ${attr.name.toLowerCase()}),`
|
|
1149
|
+
: ` PRIMARY KEY (${ownerPK[0].toLowerCase()}, ${attr.name.toLowerCase()}),`,
|
|
1150
|
+
options.useNamedConstraints
|
|
1151
|
+
? ` CONSTRAINT ${constraintName('fk', tableName, ownerTable)} FOREIGN KEY (${ownerPK[0].toLowerCase()}) REFERENCES ${ownerTable}(${ownerPK[0].toLowerCase()}) ON DELETE CASCADE`
|
|
1152
|
+
: ` FOREIGN KEY (${ownerPK[0].toLowerCase()}) REFERENCES ${ownerTable}(${ownerPK[0].toLowerCase()}) ON DELETE CASCADE`,
|
|
1153
|
+
`);`,
|
|
1154
|
+
].join('\n');
|
|
1155
|
+
|
|
1156
|
+
tables.push({ name: tableName, sql: ddl, dependsOn: [ownerTable] });
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// ── Phase 8: Dependency Ordering + Output ─────────────────
|
|
1161
|
+
|
|
1162
|
+
const orderedNames = topologicalSort(tables);
|
|
1163
|
+
const orderedTables = orderedNames
|
|
1164
|
+
.map(name => tables.find(t => t.name === name)!)
|
|
1165
|
+
.filter(Boolean);
|
|
1166
|
+
|
|
1167
|
+
const header = [
|
|
1168
|
+
`-- Generated by Dominion Flow ERD Creator`,
|
|
1169
|
+
`-- Database: ${model.name}`,
|
|
1170
|
+
`-- Dialect: ${options.dialect}`,
|
|
1171
|
+
`-- Generated: ${new Date().toISOString().split('T')[0]}`,
|
|
1172
|
+
``,
|
|
1173
|
+
].join('\n');
|
|
1174
|
+
|
|
1175
|
+
const dropStatements = options.includeDropIfExists
|
|
1176
|
+
? orderedNames
|
|
1177
|
+
.reverse()
|
|
1178
|
+
.map(name => {
|
|
1179
|
+
const cascade = options.dialect === 'postgresql' ? ' CASCADE' : '';
|
|
1180
|
+
return `DROP TABLE IF EXISTS ${name}${cascade};`;
|
|
1181
|
+
})
|
|
1182
|
+
.join('\n') + '\n\n'
|
|
1183
|
+
: '';
|
|
1184
|
+
|
|
1185
|
+
// Re-reverse for CREATE order (drops go in reverse dependency order)
|
|
1186
|
+
if (options.includeDropIfExists) orderedNames.reverse();
|
|
1187
|
+
|
|
1188
|
+
const createStatements = orderedTables.map(t => t.sql).join('\n\n');
|
|
1189
|
+
const indexStatements = indexes.length
|
|
1190
|
+
? '\n\n-- Indexes\n' + indexes.map(i => i.sql).join('\n')
|
|
1191
|
+
: '';
|
|
1192
|
+
|
|
1193
|
+
return header + dropStatements + createStatements + indexStatements + '\n';
|
|
1194
|
+
}
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
### Usage Example
|
|
1198
|
+
|
|
1199
|
+
```typescript
|
|
1200
|
+
const universityModel: ERDModel = {
|
|
1201
|
+
name: 'university_system',
|
|
1202
|
+
entities: [
|
|
1203
|
+
{
|
|
1204
|
+
id: 'e1', name: 'departments', isWeak: false,
|
|
1205
|
+
attributes: [
|
|
1206
|
+
{ id: 'a1', name: 'department_id', kind: 'key', dataType: 'serial', isNullable: false },
|
|
1207
|
+
{ id: 'a2', name: 'name', kind: 'simple', dataType: 'varchar', isNullable: false },
|
|
1208
|
+
{ id: 'a3', name: 'building', kind: 'simple', dataType: 'varchar', isNullable: true },
|
|
1209
|
+
{ id: 'a4', name: 'budget', kind: 'simple', dataType: 'decimal', isNullable: true, defaultValue: '0.00' },
|
|
1210
|
+
],
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
id: 'e2', name: 'professors', isWeak: false,
|
|
1214
|
+
attributes: [
|
|
1215
|
+
{ id: 'a5', name: 'professor_id', kind: 'key', dataType: 'serial', isNullable: false },
|
|
1216
|
+
{ id: 'a6', name: 'name', kind: 'composite', dataType: 'varchar', isNullable: false,
|
|
1217
|
+
children: [
|
|
1218
|
+
{ id: 'a7', name: 'first_name', kind: 'simple', dataType: 'varchar', isNullable: false },
|
|
1219
|
+
{ id: 'a8', name: 'last_name', kind: 'simple', dataType: 'varchar', isNullable: false },
|
|
1220
|
+
]},
|
|
1221
|
+
{ id: 'a9', name: 'email', kind: 'simple', dataType: 'varchar', isNullable: false },
|
|
1222
|
+
{ id: 'a10', name: 'phone_numbers', kind: 'multivalued', dataType: 'varchar', isNullable: false,
|
|
1223
|
+
multivaluedType: 'VARCHAR(20)' },
|
|
1224
|
+
],
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
id: 'e3', name: 'students', isWeak: false,
|
|
1228
|
+
attributes: [
|
|
1229
|
+
{ id: 'a11', name: 'student_id', kind: 'key', dataType: 'serial', isNullable: false },
|
|
1230
|
+
{ id: 'a12', name: 'first_name', kind: 'simple', dataType: 'varchar', isNullable: false },
|
|
1231
|
+
{ id: 'a13', name: 'last_name', kind: 'simple', dataType: 'varchar', isNullable: false },
|
|
1232
|
+
{ id: 'a14', name: 'dob', kind: 'simple', dataType: 'date', isNullable: false },
|
|
1233
|
+
{ id: 'a15', name: 'age', kind: 'derived', dataType: 'integer', isNullable: true,
|
|
1234
|
+
derivedExpression: "EXTRACT(YEAR FROM AGE(CURRENT_DATE, dob))" },
|
|
1235
|
+
],
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
id: 'e4', name: 'courses', isWeak: false,
|
|
1239
|
+
attributes: [
|
|
1240
|
+
{ id: 'a16', name: 'course_id', kind: 'key', dataType: 'serial', isNullable: false },
|
|
1241
|
+
{ id: 'a17', name: 'title', kind: 'simple', dataType: 'varchar', isNullable: false },
|
|
1242
|
+
{ id: 'a18', name: 'credits', kind: 'simple', dataType: 'integer', isNullable: false },
|
|
1243
|
+
],
|
|
1244
|
+
},
|
|
1245
|
+
],
|
|
1246
|
+
relationships: [
|
|
1247
|
+
{
|
|
1248
|
+
id: 'r1', name: 'belongs_to', isIdentifying: false,
|
|
1249
|
+
participants: [
|
|
1250
|
+
{ entityId: 'e1', cardinality: '1', participation: 'partial' },
|
|
1251
|
+
{ entityId: 'e2', cardinality: 'N', participation: 'total' },
|
|
1252
|
+
],
|
|
1253
|
+
attributes: [],
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
id: 'r2', name: 'enrollments', isIdentifying: false,
|
|
1257
|
+
participants: [
|
|
1258
|
+
{ entityId: 'e3', cardinality: 'M', participation: 'partial' },
|
|
1259
|
+
{ entityId: 'e4', cardinality: 'N', participation: 'partial' },
|
|
1260
|
+
],
|
|
1261
|
+
attributes: [
|
|
1262
|
+
{ id: 'ra1', name: 'grade', kind: 'simple', dataType: 'char', isNullable: true },
|
|
1263
|
+
{ id: 'ra2', name: 'enrollment_date', kind: 'simple', dataType: 'date', isNullable: false, defaultValue: 'CURRENT_DATE' },
|
|
1264
|
+
],
|
|
1265
|
+
},
|
|
1266
|
+
],
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
const ddl = generateDDL(universityModel, {
|
|
1270
|
+
dialect: 'postgresql',
|
|
1271
|
+
useNamedConstraints: true,
|
|
1272
|
+
includeIndexes: true,
|
|
1273
|
+
includeDropIfExists: true,
|
|
1274
|
+
idStrategy: 'serial',
|
|
1275
|
+
timestampColumns: true,
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
console.log(ddl);
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
### Expected Output
|
|
1282
|
+
|
|
1283
|
+
```sql
|
|
1284
|
+
-- Generated by Dominion Flow ERD Creator
|
|
1285
|
+
-- Database: university_system
|
|
1286
|
+
-- Dialect: postgresql
|
|
1287
|
+
-- Generated: 2026-03-09
|
|
1288
|
+
|
|
1289
|
+
DROP TABLE IF EXISTS enrollments CASCADE;
|
|
1290
|
+
DROP TABLE IF EXISTS professors_phone_numbers CASCADE;
|
|
1291
|
+
DROP TABLE IF EXISTS courses CASCADE;
|
|
1292
|
+
DROP TABLE IF EXISTS students CASCADE;
|
|
1293
|
+
DROP TABLE IF EXISTS professors CASCADE;
|
|
1294
|
+
DROP TABLE IF EXISTS departments CASCADE;
|
|
1295
|
+
|
|
1296
|
+
CREATE TABLE departments (
|
|
1297
|
+
department_id SERIAL NOT NULL,
|
|
1298
|
+
name VARCHAR NOT NULL,
|
|
1299
|
+
building VARCHAR,
|
|
1300
|
+
budget NUMERIC DEFAULT 0.00,
|
|
1301
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1302
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1303
|
+
CONSTRAINT pk_departments_department_id PRIMARY KEY (department_id)
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1306
|
+
CREATE TABLE professors (
|
|
1307
|
+
professor_id SERIAL NOT NULL,
|
|
1308
|
+
name_first_name VARCHAR NOT NULL,
|
|
1309
|
+
name_last_name VARCHAR NOT NULL,
|
|
1310
|
+
email VARCHAR NOT NULL,
|
|
1311
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1312
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1313
|
+
CONSTRAINT pk_professors_professor_id PRIMARY KEY (professor_id),
|
|
1314
|
+
departments_id INTEGER NOT NULL,
|
|
1315
|
+
CONSTRAINT fk_professors_departments FOREIGN KEY (departments_id)
|
|
1316
|
+
REFERENCES departments(department_id) ON DELETE RESTRICT
|
|
1317
|
+
);
|
|
1318
|
+
|
|
1319
|
+
CREATE TABLE students (
|
|
1320
|
+
student_id SERIAL NOT NULL,
|
|
1321
|
+
first_name VARCHAR NOT NULL,
|
|
1322
|
+
last_name VARCHAR NOT NULL,
|
|
1323
|
+
dob DATE NOT NULL,
|
|
1324
|
+
age INTEGER GENERATED ALWAYS AS (EXTRACT(YEAR FROM AGE(CURRENT_DATE, dob))) STORED,
|
|
1325
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1326
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1327
|
+
CONSTRAINT pk_students_student_id PRIMARY KEY (student_id)
|
|
1328
|
+
);
|
|
1329
|
+
|
|
1330
|
+
CREATE TABLE courses (
|
|
1331
|
+
course_id SERIAL NOT NULL,
|
|
1332
|
+
title VARCHAR NOT NULL,
|
|
1333
|
+
credits INTEGER NOT NULL,
|
|
1334
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1335
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1336
|
+
CONSTRAINT pk_courses_course_id PRIMARY KEY (course_id)
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
CREATE TABLE enrollments (
|
|
1340
|
+
students_student_id INTEGER NOT NULL,
|
|
1341
|
+
courses_course_id INTEGER NOT NULL,
|
|
1342
|
+
grade CHAR,
|
|
1343
|
+
enrollment_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
|
1344
|
+
CONSTRAINT pk_enrollments_students_student_id_courses_course_id
|
|
1345
|
+
PRIMARY KEY (students_student_id, courses_course_id),
|
|
1346
|
+
CONSTRAINT fk_enrollments_students FOREIGN KEY (students_student_id)
|
|
1347
|
+
REFERENCES students(student_id) ON DELETE CASCADE,
|
|
1348
|
+
CONSTRAINT fk_enrollments_courses FOREIGN KEY (courses_course_id)
|
|
1349
|
+
REFERENCES courses(course_id) ON DELETE CASCADE
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
CREATE TABLE professors_phone_numbers (
|
|
1353
|
+
professor_id INTEGER NOT NULL,
|
|
1354
|
+
phone_numbers VARCHAR(20) NOT NULL,
|
|
1355
|
+
CONSTRAINT pk_professors_phone_numbers_professor_id_phone_numbers
|
|
1356
|
+
PRIMARY KEY (professor_id, phone_numbers),
|
|
1357
|
+
CONSTRAINT fk_professors_phone_numbers_professors
|
|
1358
|
+
FOREIGN KEY (professor_id) REFERENCES professors(professor_id)
|
|
1359
|
+
ON DELETE CASCADE
|
|
1360
|
+
);
|
|
1361
|
+
|
|
1362
|
+
-- Indexes
|
|
1363
|
+
CREATE INDEX idx_professors_departments_id ON professors(departments_id);
|
|
1364
|
+
CREATE INDEX idx_enrollments_courses_course_id ON enrollments(courses_course_id);
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
---
|
|
1368
|
+
|
|
1369
|
+
## When to Use
|
|
1370
|
+
|
|
1371
|
+
- Building an ERD Creator / visual database design tool
|
|
1372
|
+
- Implementing a schema-from-diagram code generation feature
|
|
1373
|
+
- Porting an existing ER diagram from a whiteboard or academic tool to running SQL
|
|
1374
|
+
- Teaching database design -- the 7 rules are the canonical reference
|
|
1375
|
+
- Validating that a schema correctly implements an ER model
|
|
1376
|
+
- Generating multi-dialect DDL from a single source-of-truth model
|
|
1377
|
+
|
|
1378
|
+
## When NOT to Use
|
|
1379
|
+
|
|
1380
|
+
- **Reverse engineering existing SQL to ERD** -- use `sql-ddl-to-json-schema` or `node-sql-parser` instead, then map the JSON into ERDModel types
|
|
1381
|
+
- **Schema migration / diffing** -- use Prisma Migrate, Drizzle Kit, or Alembic; this skill generates initial DDL, not incremental changes
|
|
1382
|
+
- **NoSQL / document databases** -- these mapping rules are exclusively for relational databases
|
|
1383
|
+
- **Denormalization for performance** -- this produces normalized schemas; deliberate denormalization is a separate concern (see data warehousing patterns)
|
|
1384
|
+
- **ORM schema definition** -- if you already use Prisma/Drizzle/TypeORM, define schemas in their DSL and let the ORM handle DDL
|
|
1385
|
+
|
|
1386
|
+
## Related Skills
|
|
1387
|
+
|
|
1388
|
+
- `database-schema-designer.md` -- broader schema design patterns (multi-tenancy, RLS, audit trails, seeding)
|
|
1389
|
+
- `erd-creator-textbook-research.md` -- raw research findings that feed this skill
|
|
1390
|
+
- `normalization-validator.md` -- planned: 1NF/2NF/3NF violation detection
|
|
1391
|
+
- `regex-alternation-ordering-sql-types.md` -- SQL type parsing for DDL import
|
|
1392
|
+
- `reserved-word-context-aware-quoting.md` -- identifier quoting across dialects
|
|
1393
|
+
- `postgresql-to-mysql-runtime-translation.md` -- runtime SQL translation patterns
|
|
1394
|
+
|
|
1395
|
+
## References
|
|
1396
|
+
|
|
1397
|
+
1. **LibreTexts "Database Design"** -- Dr. Sarah North, Chapters 6-10. CC BY 4.0. Canonical ER mapping rules.
|
|
1398
|
+
2. **Text2Schema (arXiv 2025)** -- Multi-agent NL-to-DDL decomposition. Entity/relationship/constraint extraction pipeline.
|
|
1399
|
+
3. **NOMAD (arXiv 2025)** -- Agent roles for schema generation: extractor, classifier, integrator, code articulator, verifier.
|
|
1400
|
+
4. **DrawDB** (github.com/drawdb-io/drawdb) -- OSS visual database designer. Gold standard for dialect-specific DDL generators from JSON model.
|
|
1401
|
+
5. **ChartDB** (github.com/chartdb/chartdb) -- AI-powered DDL export via LLM prompting.
|
|
1402
|
+
6. **ERFlow** -- MCP Server with 25+ tools for NL schema edits and checkpoint-based migration generation.
|
|
1403
|
+
7. **node-sql-parser** (npm) -- Bidirectional SQL-to-AST parsing. `parser.astify()` / `parser.sqlify()`.
|
|
1404
|
+
8. **sql-ddl-to-json-schema** (npm) -- DDL parser outputting ERD-friendly JSON Schema.
|
|
1405
|
+
9. **Peter Chen (1976)** -- "The Entity-Relationship Model: Toward a Unified View of Data." Original ER notation.
|