feat: mark generated and identity columns across writers and DBML
- bun: scanonly+generated for GENERATED columns; scanonly+identity for ALWAYS identity (PK gets identity only) - gorm: <-:false+generated / identity, same primary key rule - dbml: write GENERATED/IDENTITY column notes and read them back into the model - pgsql: emit generation/identity clauses in migration create-table and add-column, no DEFAULT - readmes updated
This commit is contained in:
@@ -203,6 +203,14 @@ The writer automatically generates relationship fields:
|
||||
- Relationship field names use 3-letter prefixes
|
||||
- Includes proper `gorm` tags with `foreignKey` and `references`
|
||||
|
||||
## Read-only Columns
|
||||
|
||||
| Column | Tags |
|
||||
|---|---|
|
||||
| `GENERATED ... STORED` | `<-:false;generated` |
|
||||
| `GENERATED ALWAYS AS IDENTITY`, not PK | `<-:false;identity` |
|
||||
| `GENERATED ALWAYS AS IDENTITY`, PK | `identity` only |
|
||||
|
||||
## Notes
|
||||
|
||||
- Model names are prefixed with "Model" (e.g., `ModelUser`)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
)
|
||||
|
||||
func TestBuildGormTag_GeneratedColumn(t *testing.T) {
|
||||
mapper := NewTypeMapper("")
|
||||
|
||||
generated := models.InitColumn("full_name", "users", "public")
|
||||
generated.Type = "text"
|
||||
generated.Generated = true
|
||||
|
||||
plain := models.InitColumn("first", "users", "public")
|
||||
plain.Type = "text"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
column *models.Column
|
||||
want bool
|
||||
}{
|
||||
{"generated column is write-disabled and marked", generated, true},
|
||||
{"ordinary column is untouched", plain, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tag := mapper.BuildGormTag(tt.column, nil)
|
||||
parts := strings.Split(tag, ";")
|
||||
has := func(s string) bool {
|
||||
for _, p := range parts {
|
||||
if p == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if has("<-:false") != tt.want || has("generated") != tt.want {
|
||||
t.Errorf("tag %q: <-:false/generated present = %v/%v, want %v", tag, has("<-:false"), has("generated"), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
)
|
||||
|
||||
func TestBuildGormTag_IdentityColumn(t *testing.T) {
|
||||
mapper := NewTypeMapper("")
|
||||
|
||||
build := func(pk bool, identity bool, generation string) *models.Column {
|
||||
col := models.InitColumn("id", "users", "public")
|
||||
col.Type = "bigint"
|
||||
col.IsPrimaryKey = pk
|
||||
col.Identity = identity
|
||||
col.IdentityGeneration = generation
|
||||
return col
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
column *models.Column
|
||||
wantFlag bool
|
||||
wantMarker bool
|
||||
}{
|
||||
{"always identity non-pk is write-blocked", build(false, true, "ALWAYS"), true, true},
|
||||
{"always identity pk is only marked", build(true, true, "ALWAYS"), false, true},
|
||||
{"by default identity is writable", build(false, true, "BY DEFAULT"), false, false},
|
||||
{"non-identity is untouched", build(false, false, ""), false, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tag := mapper.BuildGormTag(tt.column, nil)
|
||||
has := func(s string) bool {
|
||||
for _, p := range strings.Split(tag, ";") {
|
||||
if p == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if has("<-:false") != tt.wantFlag || has("identity") != tt.wantMarker {
|
||||
t.Errorf("tag %q: <-:false/identity = %v/%v, want %v/%v", tag, has("<-:false"), has("identity"), tt.wantFlag, tt.wantMarker)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -401,6 +401,21 @@ func (tm *TypeMapper) BuildGormTag(column *models.Column, table *models.Table) s
|
||||
parts = append(parts, "autoIncrement")
|
||||
}
|
||||
|
||||
// GENERATED ALWAYS ... STORED columns are computed by the database and cannot be
|
||||
// written: disable write permission so GORM leaves them out of INSERT/UPDATE, and mark why.
|
||||
if column.Generated {
|
||||
parts = append(parts, "<-:false", "generated")
|
||||
}
|
||||
|
||||
// GENERATED ALWAYS AS IDENTITY rejects explicit values. A primary key only gets the
|
||||
// marker so it keeps its normal create/return behavior.
|
||||
if column.Identity && strings.EqualFold(column.IdentityGeneration, "ALWAYS") {
|
||||
if !column.IsPrimaryKey {
|
||||
parts = append(parts, "<-:false")
|
||||
}
|
||||
parts = append(parts, "identity")
|
||||
}
|
||||
|
||||
// Not null (skip if primary key, as it's implied)
|
||||
if column.NotNull && !column.IsPrimaryKey {
|
||||
parts = append(parts, "not null")
|
||||
|
||||
Reference in New Issue
Block a user