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
39 changes: 39 additions & 0 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ type templateData struct {
DefaultDiskPath string
Schema schema.Schema
DefaultLogs []string
NeedCUID bool
NeedCUID2 bool
NeedUUID bool
NeedUUID7 bool
NeedULID bool
NeedNanoID bool
}

type modelTemplateData struct {
Expand Down Expand Up @@ -114,13 +120,39 @@ func GenerateClient(sch schema.Schema, pkgName string, parentImportPath string,
"hasNetField": hasNetField,
"hasHstoreField": hasHstoreField,
"hasHstoreAnywhere": hasHstoreAnywhere,
"hasNetAnywhere": hasNetAnywhere,
"hstoreExpr": hstoreExpr,
})
tmpl, err := tmpl.ParseFS(templatesFS, "templates/*.gotpl")
if err != nil {
return nil, err
}

var needCUID, needUUID, needUUID7, needCUID2, needULID, needNanoID bool
for _, m := range sch.Models {
for _, sf := range m.ScalarFields {
if sf.Default != nil && sf.Default.Kind == schema.DefaultFunc {
switch sf.Default.FuncName {
case "cuid", "cuid(1)":
needCUID = true
case "cuid(2)":
needCUID2 = true
case "uuid", "uuid(4)":
needUUID = true
case "uuid(7)":
needUUID7 = true
case "ulid":
needULID = true
case "nanoid":
needNanoID = true
}
}
if sf.IsID && sf.GoType == "string" && sf.Default == nil {
needCUID = true
}
}
}

var embedDir string
if embedPath != "" {
embedDir = filepath.ToSlash(filepath.Dir(embedPath))
Expand All @@ -133,6 +165,12 @@ func GenerateClient(sch schema.Schema, pkgName string, parentImportPath string,
DefaultDiskPath: defaultDiskPath,
Schema: sch,
DefaultLogs: defaultLogs,
NeedCUID: needCUID,
NeedCUID2: needCUID2,
NeedUUID: needUUID,
NeedUUID7: needUUID7,
NeedULID: needULID,
NeedNanoID: needNanoID,
}

outputs := make(map[string]string)
Expand All @@ -141,6 +179,7 @@ func GenerateClient(sch schema.Schema, pkgName string, parentImportPath string,
files := []string{
"header.gotpl",
"enums.gotpl",
"runtime.gotpl",
"client.gotpl",
"tx.gotpl",
"builders_create.gotpl",
Expand Down
13 changes: 6 additions & 7 deletions generator/helpers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package generator

import (
"slices"
"strings"

"github.com/voidclancy/valk/schema"
Expand Down Expand Up @@ -114,16 +115,14 @@ func hasHstoreField(m *schema.Model) bool {
return false
}
func hasHstoreAnywhere(sch schema.Schema) bool {
for _, m := range sch.Models {
if hasHstoreField(m) {
return true
}
}
return false
return slices.ContainsFunc(sch.Models, hasHstoreField)
}
func hasNetAnywhere(sch schema.Schema) bool {
return slices.ContainsFunc(sch.Models, hasNetField)
}
func hstoreExpr(goType string, expr string) string {
if strings.TrimPrefix(goType, "*") == "map[string]*string" {
return "toHstore(" + expr + ")"
return "ToHstore(" + expr + ")"
}
return expr
}
14 changes: 0 additions & 14 deletions generator/templates/client.gotpl
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
type Dialect interface {
Quote(ident string) string
BindVar(idx int) string
SupportsReturning() bool
SupportsBulkInsert() bool
}

{{- if or (eq .Schema.Datasource.Provider "postgres") (eq .Schema.Datasource.Provider "postgresql") }}
type postgresDialect struct{}
func (postgresDialect) Quote(ident string) string { return `"` + ident + `"` }
Expand All @@ -21,13 +14,6 @@ func (sqliteDialect) SupportsReturning() bool { return true }
func (sqliteDialect) SupportsBulkInsert() bool { return false }
{{- end }}

type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}

type Queries struct {
db DBTX
provider string
Expand Down
129 changes: 37 additions & 92 deletions generator/templates/header.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,63 @@ package {{ .PackageName }}

import (
"context"
{{- if or .NeedCUID .NeedCUID2 .NeedULID .NeedNanoID }}
"crypto/rand"
{{- end }}
"database/sql"
"encoding/json"
{{- if .EmbedPath }}
"embed"
{{- end }}
"fmt"
{{- if hasHstoreAnywhere .Schema }}
"github.com/lib/pq/hstore"
{{- end }}
{{- if hasAnyLog }}
"log"
{{- end }}
{{- if hasNetAnywhere .Schema }}
"net"
{{- end }}
{{- if or .NeedCUID .NeedCUID2 .NeedULID }}
"strconv"
{{- end }}
"strings"
"time"
"unicode/utf8"

"github.com/google/uuid"
{{- if hasHstoreAnywhere .Schema }}
"github.com/lib/pq/hstore"
{{- end }}
"github.com/pressly/goose/v3"

{{- if or .NeedUUID .NeedUUID7 }}
"github.com/google/uuid"
{{- end }}
)

var _ = time.Time{}
{{- if hasHstoreAnywhere .Schema }}
var _ = hstore.Hstore{}
{{- end }}
{{- if hasNetAnywhere .Schema }}
var _ = net.ParseIP
{{- end }}
var _ = json.RawMessage{}
var _ = strings.Join
{{- if or .NeedUUID .NeedUUID7 }}
var _ = uuid.New
var _ = uuid.NewV7
{{- end }}
{{- if or .NeedCUID .NeedCUID2 .NeedULID .NeedNanoID }}
var _ = rand.Read
{{- end }}
{{- if or .NeedCUID .NeedCUID2 .NeedULID }}
var _ = strconv.AppendUint
{{- end }}

{{- if .EmbedPath }}
//go:embed {{ .EmbedPath }}
var migrationsFS embed.FS
{{- end }}

{{- if .NeedCUID }}
func generateCUID() string {
now := uint64(time.Now().UnixMilli())
b := make([]byte, 8)
Expand All @@ -51,19 +73,25 @@ func generateCUID() string {
}
return string(buf)
}
{{- end }}

{{- if .NeedUUID }}
func generateUUID() string {
return uuid.New().String()
}
{{- end }}

{{- if .NeedUUID7 }}
func generateUUID7() string {
id, err := uuid.NewV7()
if err != nil {
return uuid.New().String()
}
return id.String()
}
{{- end }}

{{- if .NeedCUID2 }}
func generateCUID2() string {
now := uint64(time.Now().UnixMilli())
b := make([]byte, 12)
Expand All @@ -77,7 +105,9 @@ func generateCUID2() string {
}
return string(buf)
}
{{- end }}

{{- if .NeedULID }}
func generateULID() string {
now := uint64(time.Now().UnixMilli())
b := make([]byte, 10)
Expand Down Expand Up @@ -105,7 +135,9 @@ func generateULID() string {
}
return string(buf[:])
}
{{- end }}

{{- if .NeedNanoID }}
func generateNanoID() string {
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-"
b := make([]byte, 21)
Expand All @@ -115,91 +147,4 @@ func generateNanoID() string {
}
return string(b)
}

{{- if hasHstoreAnywhere .Schema }}
func toHstore(m map[string]*string) hstore.Hstore {
result := hstore.Hstore{Map: make(map[string]sql.NullString, len(m))}
for k, v := range m {
if v == nil {
result.Map[k] = sql.NullString{Valid: false}
} else {
result.Map[k] = sql.NullString{String: *v, Valid: true}
}
}
return result
}

type hstoreScan struct {
p **map[string]*string
}

func (s hstoreScan) Scan(src any) error {
var h hstore.Hstore
if err := h.Scan(src); err != nil {
return err
}
if h.Map == nil {
*s.p = nil
return nil
}
m := make(map[string]*string, len(h.Map))
for k, v := range h.Map {
if v.Valid {
val := v.String
m[k] = &val
} else {
m[k] = nil
}
}
*s.p = &m
return nil
}
{{- end }}

// FieldError represents a single validation failure on a specific field.
type FieldError struct {
Field string
Value any
Rule string
Msg string
}

func (e FieldError) Error() string {
return fmt.Sprintf("field %s: %s (value: %v, rule: %s)", e.Field, e.Msg, e.Value, e.Rule)
}

// ValidationError collects multiple validation errors during an operation.
type ValidationError struct {
Errors []FieldError
}

func (e ValidationError) Error() string {
var msgs []string
for _, err := range e.Errors {
msgs = append(msgs, err.Error())
}
return fmt.Sprintf("validation failed: %s", strings.Join(msgs, "; "))
}

func (e *ValidationError) Add(field string, value any, rule string, msg string) {
e.Errors = append(e.Errors, FieldError{
Field: field,
Value: value,
Rule: rule,
Msg: msg,
})
}

func (e *ValidationError) HasErrors() bool {
return len(e.Errors) > 0
}

type FieldAssignment struct {
Col string
Val any
}

type RecordInput struct {
Assignments []FieldAssignment
}

Loading
Loading