Skip to content

Schema Guide

Arsfy edited this page Jul 26, 2026 · 5 revisions

Schema Guide

GCORM schema files use the .gcorm extension. They are the source of truth for generated Go models, query builders, and database DDL.

File Structure

A schema can contain:

datasource  Database provider and URL
generator   Code generation settings
model       Database table model
enum        Enumeration type

GCORM supports multiple .gcorm files. Files are discovered from the configured schema roots and merged in deterministic path order.

Datasource

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  schema   = "public"
}

Fields:

  • provider: required. One of postgresql, mysql, or sqlite.
  • url: required for commands that connect to a database. Use a literal string or env("NAME").
  • schema: optional PostgreSQL schema or namespace.

Examples:

url = env("DATABASE_URL")
url = "postgresql://localhost/app?sslmode=disable"

Avoid committing production credentials in schema files. Prefer env().

Generator

generator client {
  provider = "gco-go"
  output   = "./gen"
  package  = "db"
}

Fields:

  • provider: use gco-go.
  • output: output directory for generated Go code.
  • package: generated package base name.

Models

model Post {
  id        String   @id @default(uuid())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  String
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([authorId])
  @@map("posts")
}

A model maps to a table. A scalar field maps to a column. Relation fields describe relationships and are not always stored as standalone columns.

Scalar Types

Schema type Go type Common SQL mapping
String string TEXT
Int int32 INTEGER
SmallInt int16 SMALLINT / INTEGER
BigInt int64 BIGINT
Float float64 DOUBLE / REAL
Decimal float64 DECIMAL
Boolean bool BOOLEAN / TINYINT(1)
DateTime time.Time TIMESTAMP / DATETIME
Bytes []byte BYTEA / BLOB
Json json.RawMessage JSONB / JSON / TEXT
UUID string UUID / VARCHAR(36) / TEXT

Type Modifiers

name  String?
posts Post[]
  • ? marks a nullable field.
  • [] marks a list, usually used for relation fields.

Field Attributes

id        String   @id @default(uuid())
email     String   @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name      String   @map("display_name")
author    User     @relation(fields: [authorId], references: [id])

Common attributes:

  • @id: primary key field.
  • @unique: unique constraint.
  • @default(value): database or application default.
  • @updatedAt: update timestamp managed by generated code.
  • @map("column_name"): map field to a database column name.
  • @relation(...): relationship definition.
  • @db.*: native type annotation, such as @db.VarChar(255) or @db.Text.

Supported default examples:

@default(uuid())
@default(cuid())
@default(now())
@default(autoincrement())
@default(0)
@default(0.0)
@default("")
@default(false)
@default(USER)

Model Attributes

@@id([tenantId, id])
@@unique([email])
@@unique([tenantId, slug])
@@index([createdAt])
@@map("users")
@@schema("public")

Common model attributes:

  • @@id([...]): composite primary key.
  • @@unique([...]): composite unique constraint.
  • @@index([...]): index.
  • @@map("table_name"): map model to a database table name.
  • @@schema("name"): PostgreSQL schema name.

Advanced Indexes

@@index supports partial indexes, PostgreSQL expressions, and per-column index options. @@unique also supports PostgreSQL expressions:

model Announcement {
  id          Int
  status      Int
  publishedAt DateTime?

  @@index(
    [status, publishedAt],
    name: "idx_announcements_user",
    where: "status = 1 AND published_at IS NOT NULL",
    sort: [Desc, Asc],
    nulls: [Last, Last],
    opclass: ["int8_ops", "timestamptz_ops"],
    collate: ["pg_catalog.default", "pg_catalog.default"]
  )
}

model User {
  id    Int
  email String

  @@unique([email], name: "uq_users_email_ci", expression: "lower(email)")
}

Index arguments:

  • name: explicit database index name.
  • where: raw SQL predicate for a partial or filtered index.
  • sort or order: Asc or Desc, either one value or one value per field.
  • nulls: First or Last, either one value or one value per field.
  • opclass, opclasses, or ops: PostgreSQL operator class names.
  • collate or collation: collation names, such as pg_catalog.default.
  • expression or expressions: raw PostgreSQL index expressions. An expression replaces the field at the same position in the generated index.

Indexes and unique keys that use expression or expressions must provide an explicit, non-empty name. This keeps multiple expressions over the same field unambiguous during schema diffing.

For a composite expression index, provide one expression entry per field. Use an empty string for positions that should remain ordinary columns:

@@unique(
  [tenantId, email],
  name: "uq_users_tenant_email_ci",
  expressions: ["", "lower(email)"]
)

This generates a PostgreSQL unique index on tenantId, lower(email). PostgreSQL implements expression uniqueness with CREATE UNIQUE INDEX, rather than a table-level UNIQUE constraint.

Single-column example:

@@index([clickhouseRecordedAt], name: "idx_ihce_retry", where: "clickhouse_recorded_at IS NULL")

where and expression(s) are database SQL, not GCORM expressions. Use database column names, including names declared with @map, in these values. Partial indexes are supported by PostgreSQL and SQLite. Expression options are PostgreSQL-only; schemas using them with MySQL or SQLite fail validation. MySQL does not support partial indexes, so GCORM will not emit a valid MySQL partial index for schemas that use where.

Relations

One-to-many:

model User {
  id    String @id @default(uuid())
  posts Post[]
}

model Post {
  id       String @id @default(uuid())
  authorId String
  author   User   @relation(fields: [authorId], references: [id])
}

One-to-one:

model User {
  id      String   @id @default(uuid())
  profile Profile?
}

model Profile {
  id     String @id @default(uuid())
  userId String @unique
  user   User   @relation(fields: [userId], references: [id])
}

Many-to-many is represented by list relations on both sides:

model Post {
  id   String @id @default(uuid())
  tags Tag[]
}

model Tag {
  id    String @id @default(uuid())
  posts Post[]
}

For production systems, an explicit join model is often easier to migrate, index, and extend:

model PostTag {
  postId String
  tagId  String

  @@id([postId, tagId])
  @@index([tagId])
}

Enums

enum Role {
  USER
  ADMIN
  MODERATOR
}

Enums generate Go string types and constants. They can be used as field types:

model User {
  id   String @id @default(uuid())
  role Role   @default(USER)
}

Naming And Mapping

Use clear model and field names for generated Go APIs. Use @map and @@map when the database name differs from the Go-facing schema name.

model User {
  id        String @id @default(uuid())
  createdAt DateTime @map("created_at")

  @@map("users")
}

Clone this wiki locally