Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ func GenerateClient(sch schema.Schema, pkgName string, parentImportPath string,
return false
},
"trimPrefix": strings.TrimPrefix,
"isKnownDefaultFunc": func(funcName string) bool {
switch funcName {
case "autoincrement", "cuid", "uuid", "now":
return true
}
return false
},
"defaultFuncCall": func(funcName string) string {
switch funcName {
case "cuid":
return "generateCUID()"
case "uuid":
return "generateUUID()"
case "now":
return "time.Now()"
}
return ""
},
"hasStringField": func(m *schema.Model) bool {
for _, sf := range m.ScalarFields {
if sf.GoType == "string" || strings.Contains(sf.GoType, "string") {
Expand Down
33 changes: 17 additions & 16 deletions generator/templates/model_create.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func validate{{ .Model.Name }}Create(assignments []FieldAssignment) error {
}
{{- end }}
{{- end }}
{{- else }}
if _, ok := a.Val.({{ trimPrefix $field.GoType "*" }}); !ok {
errs.Add("{{ $field.Name }}", a.Val, "type", "field {{ $field.Name }} must be of type {{ trimPrefix $field.GoType "*" }}")
}
{{- end }}
{{- end }}
}
Expand Down Expand Up @@ -173,17 +177,15 @@ func (s *{{ .Model.Name }}Create) ToRowMap() map[string]any {
if s.{{ $fieldName }} != nil {
m["{{ $col }}"] = *s.{{ $fieldName }}
}
{{- else }}
{{- else if $field.Default.FuncName | isKnownDefaultFunc }}
if s.{{ $fieldName }} != nil {
m["{{ $col }}"] = *s.{{ $fieldName }}
} else {
{{- if eq $field.Default.FuncName "cuid" }}
m["{{ $col }}"] = generateCUID()
{{- else if eq $field.Default.FuncName "uuid" }}
m["{{ $col }}"] = generateUUID()
{{- else if eq $field.Default.FuncName "now" }}
m["{{ $col }}"] = time.Now()
{{- end }}
m["{{ $col }}"] = {{ defaultFuncCall $field.Default.FuncName }}
}
{{- else }}
if s.{{ $fieldName }} != nil {
m["{{ $col }}"] = *s.{{ $fieldName }}
}
{{- end }}
{{- else if or $field.Optional (ne $field.Default nil) }}
Expand Down Expand Up @@ -250,19 +252,18 @@ func (q *Queries) execute{{ .Model.Name }}Create(ctx context.Context, assignment
cols = append(cols, "{{ $col }}")
vals = append(vals, *input.{{ $fieldName }})
}
{{- else }}
{{- else if $field.Default.FuncName | isKnownDefaultFunc }}
if input.{{ $fieldName }} != nil {
cols = append(cols, "{{ $col }}")
vals = append(vals, *input.{{ $fieldName }})
} else {
cols = append(cols, "{{ $col }}")
{{- if eq $field.Default.FuncName "cuid" }}
vals = append(vals, generateCUID())
{{- else if eq $field.Default.FuncName "uuid" }}
vals = append(vals, generateUUID())
{{- else if eq $field.Default.FuncName "now" }}
vals = append(vals, time.Now())
{{- end }}
vals = append(vals, {{ defaultFuncCall $field.Default.FuncName }})
}
{{- else }}
if input.{{ $fieldName }} != nil {
cols = append(cols, "{{ $col }}")
vals = append(vals, *input.{{ $fieldName }})
}
{{- end }}
{{- else if or $field.Optional (ne $field.Default nil) }}
Expand Down
28 changes: 26 additions & 2 deletions integration/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"integration/valk/post"
"integration/valk/profile"
"integration/valk/user"
"time"

"log"

Expand Down Expand Up @@ -101,11 +102,34 @@ func seed(db *valk.DB, ctx context.Context) *SeedData {
prof, err := db.Profile.Create(
profile.Bio.Set("BLEH"),
profile.UserId.Set(referred.Id),
profile.CreatedAt.Set(time.Now()),
).Exec(ctx)
if err != nil {
log.Fatalf("failed to create profile: %v", err)
}
_ = prof
fmt.Println("PROFILE:")
printJSON(prof)

categoryTest, err := db.Category.Create(
category.Name.Set("TEST"),
).Exec(ctx)

if err != nil {
log.Fatalf("failed to create category: %v", err)
}
fmt.Println("CATEGORY:")
printJSON(categoryTest)

AnothercategoryTest, err := db.Category.Create(
category.Id.Set(24),
category.Name.Set("TEST2"),
).Exec(ctx)

if err != nil {
log.Fatalf("failed to create category: %v", err)
}
fmt.Println("CATEGORY2:")
printJSON(AnothercategoryTest)

p, err := db.Post.Create(
post.Title.Set("Valkyrie ORM Deep Dive"),
Expand Down Expand Up @@ -274,7 +298,7 @@ func main() {
}

func openConn() *valk.DB {
db, err := valk.Open("sqlite", "file::memory:?_pragma=foreign_keys(1)")
db, err := valk.Open("sqlite", "file::memory:?_pragma=foreign_keys(1)&_time_format=sqlite")
if err != nil {
log.Fatalf("failed to open db: %v", err)
}
Expand Down
9 changes: 5 additions & 4 deletions integration/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ model User {
}

model Profile {
id String @id @default(cuid())
bio String?
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
id String @id @default(cuid())
bio String?
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
}

model Post {
Expand Down
7 changes: 4 additions & 3 deletions integration/valk/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,10 @@ type Queries struct {
User *UserDelegate
// Profile provides CRUD operations for Profile.
//
// id string default: cuid()
// bio string optional
// userId string required
// id string default: cuid()
// bio string optional
// userId string required
// createdAt time.Time default: now()
Profile *ProfileDelegate
// Post provides CRUD operations for Post.
//
Expand Down
3 changes: 3 additions & 0 deletions integration/valk/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ func validateCommentCreate(assignments []FieldAssignment) error {
}
}
case "meta":
if _, ok := a.Val.(json.RawMessage); !ok {
errs.Add("meta", a.Val, "type", "field meta must be of type json.RawMessage")
}
}
}
if !provided["dummy3"] {
Expand Down
1 change: 1 addition & 0 deletions integration/valk/migrations/00001_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ CREATE TABLE `Profile` (
`id` text NOT NULL,
`bio` text NULL,
`userId` text NOT NULL,
`createdAt` timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
PRIMARY KEY (`id`),
CONSTRAINT `Profile_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User` (`id`) ON UPDATE NO ACTION ON DELETE CASCADE
);
Expand Down
6 changes: 6 additions & 0 deletions integration/valk/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,13 @@ func validatePostCreate(assignments []FieldAssignment) error {
}
}
case "content":
if _, ok := a.Val.(string); !ok {
errs.Add("content", a.Val, "type", "field content must be of type string")
}
case "published":
if _, ok := a.Val.(bool); !ok {
errs.Add("published", a.Val, "type", "field published must be of type bool")
}
case "authorId":
if v, ok := a.Val.(string); ok {
if v == "" {
Expand Down
67 changes: 50 additions & 17 deletions integration/valk/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,43 @@ import (
"fmt"
"slices"
"strings"
"time"
"unicode/utf8"
)

// Profile represents the database model
type Profile struct {
Id string `db:"id" json:"id"`
Bio *string `db:"bio" json:"bio,omitempty"`
UserId string `db:"userId" json:"userId"`
User *User `json:"user,omitempty"`
Id string `db:"id" json:"id"`
Bio *string `db:"bio" json:"bio,omitempty"`
UserId string `db:"userId" json:"userId"`
CreatedAt time.Time `db:"createdAt" json:"createdAt"`
User *User `json:"user,omitempty"`
}

// ProfileCreate is used for hooks only — the Create API uses FieldAssignment
type ProfileCreate struct {
Id *string `json:"id"`
Bio *string `json:"bio"`
UserId string `json:"userId"`
Id *string `json:"id"`
Bio *string `json:"bio"`
UserId string `json:"userId"`
CreatedAt *time.Time `json:"createdAt"`
}

// ProfileSelect specifies which fields to include
type ProfileSelect struct {
Id bool `json:"id"`
Bio bool `json:"bio"`
UserId bool `json:"userId"`
User *UserSelect `json:"user,omitempty"`
Id bool `json:"id"`
Bio bool `json:"bio"`
UserId bool `json:"userId"`
CreatedAt bool `json:"createdAt"`
User *UserSelect `json:"user,omitempty"`
}

// ProfileOmit specifies which fields to exclude
type ProfileOmit struct {
Id bool `json:"id"`
Bio bool `json:"bio"`
UserId bool `json:"userId"`
User *UserOmit `json:"user,omitempty"`
Id bool `json:"id"`
Bio bool `json:"bio"`
UserId bool `json:"userId"`
CreatedAt bool `json:"createdAt"`
User *UserOmit `json:"user,omitempty"`
}

type ProfileDelegate struct {
Expand Down Expand Up @@ -68,6 +73,8 @@ func (m *Profile) ScanFields(cols []string) []any {
targets[i] = &m.Bio
case "userId":
targets[i] = &m.UserId
case "createdAt":
targets[i] = &m.CreatedAt
}
}
return targets
Expand All @@ -77,19 +84,21 @@ var profileDefaultCols = []string{
"id",
"bio",
"userId",
"createdAt",
}

func (q *Queries) selectProfileCols(selects *ProfileSelect, omits *ProfileOmit, forceCols ...string) []string {
if selects == nil && omits == nil && len(forceCols) == 0 {
return profileDefaultCols
}

anySelected := selects != nil && (selects.Id || selects.Bio || selects.UserId || selects.User != nil)
anySelected := selects != nil && (selects.Id || selects.Bio || selects.UserId || selects.CreatedAt || selects.User != nil)

specs := []colSpec{
{"id", selects != nil && selects.Id, omits != nil && omits.Id, selects != nil && selects.hasAnyRelation()},
{"bio", selects != nil && selects.Bio, omits != nil && omits.Bio, false},
{"userId", selects != nil && selects.UserId, omits != nil && omits.UserId, selects != nil && selects.User != nil},
{"createdAt", selects != nil && selects.CreatedAt, omits != nil && omits.CreatedAt, false},
}

cols := computeCols(specs, selects != nil, anySelected)
Expand All @@ -107,6 +116,7 @@ var ProfileColOrder = []string{
"id",
"bio",
"userId",
"createdAt",
}

func (s *ProfileSelect) hasAnyRelation() bool {
Expand Down Expand Up @@ -141,6 +151,9 @@ func validateProfileCreate(assignments []FieldAssignment) error {
}
}
case "bio":
if _, ok := a.Val.(string); !ok {
errs.Add("bio", a.Val, "type", "field bio must be of type string")
}
case "userId":
if v, ok := a.Val.(string); ok {
if v == "" {
Expand All @@ -153,6 +166,10 @@ func validateProfileCreate(assignments []FieldAssignment) error {
errs.Add("userId", v, "safety", "string must be valid UTF-8")
}
}
case "createdAt":
if _, ok := a.Val.(time.Time); !ok {
errs.Add("createdAt", a.Val, "type", "field createdAt must be of type time.Time")
}
}
}
if !provided["userId"] {
Expand Down Expand Up @@ -181,13 +198,17 @@ func assignmentsToProfileCreate(assignments []FieldAssignment) ProfileCreate {
if v, ok := a.Val.(string); ok {
input.UserId = v
}
case "createdAt":
if v, ok := a.Val.(time.Time); ok {
input.CreatedAt = &v
}
}
}
return input
}

func (s *ProfileCreate) ToRowMap() map[string]any {
m := make(map[string]any, 3)
m := make(map[string]any, 4)
if s.Id != nil {
m["id"] = *s.Id
} else {
Expand All @@ -197,6 +218,11 @@ func (s *ProfileCreate) ToRowMap() map[string]any {
m["bio"] = *s.Bio
}
m["userId"] = s.UserId
if s.CreatedAt != nil {
m["createdAt"] = *s.CreatedAt
} else {
m["createdAt"] = time.Now()
}
return m
}

Expand Down Expand Up @@ -228,6 +254,13 @@ func (q *Queries) executeProfileCreate(ctx context.Context, assignments []FieldA
}
cols = append(cols, "userId")
vals = append(vals, input.UserId)
if input.CreatedAt != nil {
cols = append(cols, "createdAt")
vals = append(vals, *input.CreatedAt)
} else {
cols = append(cols, "createdAt")
vals = append(vals, time.Now())
}

returningCols := q.selectProfileCols(selects, omits)

Expand Down
3 changes: 3 additions & 0 deletions integration/valk/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package profile
import (
"fmt"
"integration/valk"
"time"
)

type UniquePredicate struct {
Expand Down Expand Up @@ -42,3 +43,5 @@ var Id = valk.StringUniqueField{Column: "id"}
var Bio = valk.StringField{Column: "bio"}

var UserId = valk.StringUniqueField{Column: "userId"}

var CreatedAt = valk.Field[time.Time]{Column: "createdAt"}
Loading
Loading