273 lines
6.3 KiB
Markdown
273 lines
6.3 KiB
Markdown
# GraphQL Schema Writer
|
|
|
|
The GraphQL writer converts RelSpec's internal database model into GraphQL Schema Definition Language (SDL) files.
|
|
|
|
## Features
|
|
|
|
- **Table to Type mapping**: Database tables become GraphQL types
|
|
- **Column to Field mapping**: Table columns become type fields
|
|
- **Enum support**: Database enums are preserved
|
|
- **Custom scalar declarations**: Automatically declares DateTime, JSON, Date scalars
|
|
- **Implicit relationships**: Generates relationship fields from foreign keys
|
|
- **Many-to-many support**: Handles junction tables intelligently
|
|
- **Clean output**: Proper formatting, field ordering, and comments
|
|
|
|
## Type Mappings
|
|
|
|
### SQL to GraphQL
|
|
|
|
| SQL Type | GraphQL Type | Notes |
|
|
|----------|--------------|-------|
|
|
| bigint, integer, serial (PK) | ID | Primary keys map to ID |
|
|
| bigint, integer, int | Int | |
|
|
| text, varchar, char | String | |
|
|
| uuid (PK) | ID | UUID primary keys also map to ID |
|
|
| uuid | String | Non-PK UUIDs map to String |
|
|
| double precision, numeric, float | Float | |
|
|
| boolean | Boolean | |
|
|
| timestamp, timestamptz | DateTime | Custom scalar |
|
|
| jsonb, json | JSON | Custom scalar |
|
|
| date | Date | Custom scalar |
|
|
| Enum types | Enum | Preserves enum name |
|
|
| Arrays (e.g., text[]) | [Type] | Mapped to GraphQL lists |
|
|
|
|
## Relationship Handling
|
|
|
|
The writer intelligently generates relationship fields based on foreign key constraints:
|
|
|
|
### Forward Relationships (FK on this table)
|
|
```sql
|
|
-- Post table has authorId FK to User.id
|
|
CREATE TABLE post (
|
|
id bigint PRIMARY KEY,
|
|
title text NOT NULL,
|
|
author_id bigint NOT NULL REFERENCES user(id)
|
|
);
|
|
```
|
|
|
|
```graphql
|
|
type Post {
|
|
id: ID!
|
|
title: String!
|
|
author: User! # Generated from authorId FK
|
|
}
|
|
```
|
|
|
|
### Reverse Relationships (FK on other table)
|
|
```graphql
|
|
type User {
|
|
id: ID!
|
|
email: String!
|
|
posts: [Post!]! # Reverse relationship (Post has FK to User)
|
|
}
|
|
```
|
|
|
|
### Many-to-Many Relationships
|
|
|
|
Junction tables (tables with only PKs and FKs) are automatically detected and hidden:
|
|
|
|
```sql
|
|
CREATE TABLE post_tag (
|
|
post_id bigint NOT NULL REFERENCES post(id),
|
|
tag_id bigint NOT NULL REFERENCES tag(id),
|
|
PRIMARY KEY (post_id, tag_id)
|
|
);
|
|
```
|
|
|
|
```graphql
|
|
type Post {
|
|
id: ID!
|
|
tags: [Tag!]! # Many-to-many through PostTag junction table
|
|
}
|
|
|
|
type Tag {
|
|
id: ID!
|
|
posts: [Post!]! # Reverse many-to-many
|
|
}
|
|
|
|
# Note: PostTag junction table is NOT included in output
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Basic Usage
|
|
|
|
```go
|
|
import (
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
|
"git.warky.dev/wdevs/relspecgo/pkg/writers/graphql"
|
|
)
|
|
|
|
opts := &writers.WriterOptions{
|
|
OutputPath: "schema.graphql",
|
|
}
|
|
|
|
writer := graphql.NewWriter(opts)
|
|
err := writer.WriteDatabase(db)
|
|
```
|
|
|
|
### With Metadata Options
|
|
|
|
```go
|
|
opts := &writers.WriterOptions{
|
|
OutputPath: "schema.graphql",
|
|
Metadata: map[string]any{
|
|
"includeScalarDeclarations": true, // Include scalar declarations
|
|
"includeComments": true, // Include field/table comments
|
|
},
|
|
}
|
|
|
|
writer := graphql.NewWriter(opts)
|
|
err := writer.WriteDatabase(db)
|
|
```
|
|
|
|
### Write to Stdout
|
|
|
|
```go
|
|
opts := &writers.WriterOptions{
|
|
OutputPath: "", // Empty path writes to stdout
|
|
}
|
|
|
|
writer := graphql.NewWriter(opts)
|
|
err := writer.WriteDatabase(db)
|
|
```
|
|
|
|
## CLI Usage
|
|
|
|
```bash
|
|
# Convert PostgreSQL database to GraphQL
|
|
relspec convert --from pgsql \
|
|
--from-conn "postgres://user:pass@localhost:5432/mydb" \
|
|
--to graphql --to-path schema.graphql
|
|
|
|
# Convert GORM models to GraphQL
|
|
relspec convert --from gorm --from-path ./models \
|
|
--to graphql --to-path schema.graphql
|
|
|
|
# Convert JSON to GraphQL
|
|
relspec convert --from json --from-path schema.json \
|
|
--to graphql --to-path schema.graphql
|
|
```
|
|
|
|
## Output Format
|
|
|
|
The generated GraphQL schema follows this structure:
|
|
|
|
1. **Header comment** (if enabled)
|
|
2. **Custom scalar declarations** (if any custom scalars are used)
|
|
3. **Enum definitions** (alphabetically sorted)
|
|
4. **Type definitions** (with fields ordered: ID first, then scalars alphabetically, then relationships)
|
|
|
|
### Example Output
|
|
|
|
```graphql
|
|
# Generated GraphQL Schema
|
|
# Database: myapp
|
|
|
|
scalar DateTime
|
|
scalar JSON
|
|
scalar Date
|
|
|
|
enum Role {
|
|
ADMIN
|
|
USER
|
|
MODERATOR
|
|
}
|
|
|
|
type User {
|
|
id: ID!
|
|
createdAt: DateTime!
|
|
email: String!
|
|
name: String!
|
|
role: Role!
|
|
|
|
posts: [Post!]!
|
|
profile: Profile
|
|
}
|
|
|
|
type Post {
|
|
id: ID!
|
|
content: String
|
|
published: Boolean!
|
|
publishedAt: Date
|
|
title: String!
|
|
|
|
author: User!
|
|
tags: [Tag!]!
|
|
}
|
|
|
|
type Tag {
|
|
id: ID!
|
|
name: String!
|
|
|
|
posts: [Post!]!
|
|
}
|
|
```
|
|
|
|
## Metadata Options
|
|
|
|
| Option | Type | Description | Default |
|
|
|--------|------|-------------|---------|
|
|
| `includeScalarDeclarations` | bool | Include `scalar DateTime`, etc. declarations | true |
|
|
| `includeComments` | bool | Include table/field descriptions as comments | true |
|
|
| `preservePKType` | bool | Use Int/String for PKs instead of ID | false |
|
|
|
|
## Field Naming Conventions
|
|
|
|
- **FK columns**: Foreign key columns like `authorId` are removed from the output; instead, a relationship field `author` is generated
|
|
- **Relationship pluralization**: Reverse one-to-many relationships are pluralized (e.g., `posts`, `tags`)
|
|
- **CamelCase**: Field names are kept in their original casing from the database
|
|
|
|
## Junction Table Detection
|
|
|
|
A table is considered a junction table if it:
|
|
1. Has exactly 2 foreign key constraints
|
|
2. All columns are either primary keys or foreign keys
|
|
3. Has a composite primary key on the FK columns
|
|
|
|
Junction tables are automatically hidden from the GraphQL output, and many-to-many relationship fields are generated on the related types instead.
|
|
|
|
## Limitations
|
|
|
|
- All tables in all schemas are flattened into a single GraphQL schema
|
|
- No support for GraphQL-specific features like directives, interfaces, or unions
|
|
- Nullable vs non-nullable is determined solely by the `NOT NULL` constraint
|
|
|
|
## Example Conversion
|
|
|
|
**Input** (Database Schema):
|
|
```sql
|
|
CREATE TABLE user (
|
|
id bigint PRIMARY KEY,
|
|
email text NOT NULL,
|
|
created_at timestamp NOT NULL
|
|
);
|
|
|
|
CREATE TABLE post (
|
|
id bigint PRIMARY KEY,
|
|
title text NOT NULL,
|
|
author_id bigint NOT NULL REFERENCES user(id)
|
|
);
|
|
```
|
|
|
|
**Output** (GraphQL Schema):
|
|
```graphql
|
|
scalar DateTime
|
|
|
|
type User {
|
|
id: ID!
|
|
createdAt: DateTime!
|
|
email: String!
|
|
|
|
posts: [Post!]!
|
|
}
|
|
|
|
type Post {
|
|
id: ID!
|
|
title: String!
|
|
|
|
author: User!
|
|
}
|
|
```
|