Add the ability to fully customize jet generated files.

This commit is contained in:
go-jet 2021-07-27 17:39:21 +02:00
parent caa81930dc
commit 8864667f47
40 changed files with 2274 additions and 882 deletions

View file

@ -0,0 +1,223 @@
package template
var autoGenWarningTemplate = `
//
// Code generated by go-jet DO NOT EDIT.
//
// WARNING: Changes to this file may cause incorrect behavior
// and will be lost if the code is regenerated
//
`
var tableSQLBuilderTemplate = `
{{define "column-list" -}}
{{- range $i, $c := . }}
{{- $field := columnField $c}}
{{- if gt $i 0 }}, {{end}}{{$field.Name}}Column
{{- end}}
{{- end}}
package {{package}}
import (
"github.com/go-jet/jet/v2/{{dialect.PackageName}}"
)
var {{tableTemplate.InstanceName}} = new{{tableTemplate.TypeName}}("{{schemaName}}", "{{.Name}}", "")
type {{tableTemplate.TypeName}} struct {
{{dialect.PackageName}}.Table
//Columns
{{- range $i, $c := .Columns}}
{{- $field := columnField $c}}
{{$field.Name}} {{dialect.PackageName}}.Column{{$field.Type}}
{{- end}}
AllColumns {{dialect.PackageName}}.ColumnList
MutableColumns {{dialect.PackageName}}.ColumnList
}
// AS creates new {{tableTemplate.TypeName}} with assigned alias
func (a {{tableTemplate.TypeName}}) AS(alias string) {{tableTemplate.TypeName}} {
return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName(), alias)
}
// Schema creates new {{tableTemplate.TypeName}} with assigned schema name
func (a {{tableTemplate.TypeName}}) FromSchema(schemaName string) {{tableTemplate.TypeName}} {
return new{{tableTemplate.TypeName}}(schemaName, a.TableName(), a.Alias())
}
func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) {{tableTemplate.TypeName}} {
var (
{{- range $i, $c := .Columns}}
{{- $field := columnField $c}}
{{$field.Name}}Column = {{dialect.PackageName}}.{{$field.Type}}Column("{{$c.Name}}")
{{- end}}
allColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .Columns}} }
mutableColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .MutableColumns}} }
)
return {{tableTemplate.TypeName}}{
Table: {{dialect.PackageName}}.NewTable(schemaName, tableName, alias, allColumns...),
//Columns
{{- range $i, $c := .Columns}}
{{- $field := columnField $c}}
{{$field.Name}}: {{$field.Name}}Column,
{{- end}}
AllColumns: allColumns,
MutableColumns: mutableColumns,
}
}
`
var tablePostgreSQLBuilderTemplate = `
{{define "column-list" -}}
{{- range $i, $c := . }}
{{- $field := columnField $c}}
{{- if gt $i 0 }}, {{end}}{{$field.Name}}Column
{{- end}}
{{- end}}
package {{package}}
import (
"github.com/go-jet/jet/v2/{{dialect.PackageName}}"
)
var {{tableTemplate.InstanceName}} = new{{tableTemplate.TypeName}}("{{schemaName}}", "{{.Name}}", "")
type {{structImplName}} struct {
{{dialect.PackageName}}.Table
//Columns
{{- range $i, $c := .Columns}}
{{- $field := columnField $c}}
{{$field.Name}} {{dialect.PackageName}}.Column{{$field.Type}}
{{- end}}
AllColumns {{dialect.PackageName}}.ColumnList
MutableColumns {{dialect.PackageName}}.ColumnList
}
type {{tableTemplate.TypeName}} struct {
{{structImplName}}
EXCLUDED {{structImplName}}
}
// AS creates new {{tableTemplate.TypeName}} with assigned alias
func (a {{tableTemplate.TypeName}}) AS(alias string) *{{tableTemplate.TypeName}} {
return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName(), alias)
}
// Schema creates new {{tableTemplate.TypeName}} with assigned schema name
func (a {{tableTemplate.TypeName}}) FromSchema(schemaName string) *{{tableTemplate.TypeName}} {
return new{{tableTemplate.TypeName}}(schemaName, a.TableName(), a.Alias())
}
func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) *{{tableTemplate.TypeName}} {
return &{{tableTemplate.TypeName}}{
{{structImplName}}: new{{tableTemplate.TypeName}}Impl(schemaName, tableName, alias),
EXCLUDED: new{{tableTemplate.TypeName}}Impl("", "excluded", ""),
}
}
func new{{tableTemplate.TypeName}}Impl(schemaName, tableName, alias string) {{structImplName}} {
var (
{{- range $i, $c := .Columns}}
{{- $field := columnField $c}}
{{$field.Name}}Column = {{dialect.PackageName}}.{{$field.Type}}Column("{{$c.Name}}")
{{- end}}
allColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .Columns}} }
mutableColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .MutableColumns}} }
)
return {{structImplName}}{
Table: {{dialect.PackageName}}.NewTable(schemaName, tableName, alias, allColumns...),
//Columns
{{- range $i, $c := .Columns}}
{{- $field := columnField $c}}
{{$field.Name}}: {{$field.Name}}Column,
{{- end}}
AllColumns: allColumns,
MutableColumns: mutableColumns,
}
}
`
var tableModelFileTemplate = `package {{package}}
{{ with modelImports }}
import (
{{- range .}}
"{{.}}"
{{- end}}
)
{{end}}
{{$modelTableTemplate := tableTemplate}}
type {{$modelTableTemplate.TypeName}} struct {
{{- range .Columns}}
{{- $field := structField .}}
{{$field.Name}} {{$field.Type.Name}} ` + "{{$field.TagsString}}" + `
{{- end}}
}
`
var enumSQLBuilderTemplate = `package {{package}}
import "github.com/go-jet/jet/v2/{{dialect.PackageName}}"
var {{enumTemplate.InstanceName}} = &struct {
{{- range $index, $value := .Values}}
{{enumValueName $value}} {{dialect.PackageName}}.StringExpression
{{- end}}
} {
{{- range $index, $value := .Values}}
{{enumValueName $value}}: {{dialect.PackageName}}.NewEnumValue("{{$value}}"),
{{- end}}
}
`
var enumModelTemplate = `package {{package}}
{{- $enumTemplate := enumTemplate}}
import "errors"
type {{$enumTemplate.TypeName}} string
const (
{{- range $_, $value := .Values}}
{{valueName $value}} {{$enumTemplate.TypeName}} = "{{$value}}"
{{- end}}
)
func (e *{{$enumTemplate.TypeName}}) Scan(value interface{}) error {
if v, ok := value.(string); !ok {
return errors.New("jet: Invalid scan value for {{$enumTemplate.TypeName}} enum. Enum value has to be of type string")
} else {
switch string(v) {
{{- range $_, $value := .Values}}
case "{{$value}}":
*e = {{valueName $value}}
{{- end}}
default:
return errors.New("jet: Invalid scan value '" + string(v) + "' for {{$enumTemplate.TypeName}} enum")
}
return nil
}
}
func (e {{$enumTemplate.TypeName}}) String() string {
return string(e)
}
`

View file

@ -0,0 +1,60 @@
package template
import (
"github.com/go-jet/jet/v2/generator/metadata"
"github.com/go-jet/jet/v2/internal/jet"
)
// Template is generator template used for file generation
type Template struct {
Dialect jet.Dialect
Schema func(schemaMetaData metadata.Schema) Schema
}
// Default is default generator template implementation
func Default(dialect jet.Dialect) Template {
return Template{
Dialect: dialect,
Schema: DefaultSchema,
}
}
// UseSchema replaces current schema generate function with a new implementation and returns new generator template
func (t Template) UseSchema(schemaFunc func(schemaMetaData metadata.Schema) Schema) Template {
t.Schema = schemaFunc
return t
}
// Schema is schema generator template used to generate schema(model and sql builder) files
type Schema struct {
Path string
Model Model
SQLBuilder SQLBuilder
}
// UsePath replaces path and returns new schema template
func (s Schema) UsePath(path string) Schema {
s.Path = path
return s
}
// UseModel returns new schema template with replaced template for model files generation
func (s Schema) UseModel(model Model) Schema {
s.Model = model
return s
}
// UseSQLBuilder returns new schema with replaced template for sql builder files generation
func (s Schema) UseSQLBuilder(sqlBuilder SQLBuilder) Schema {
s.SQLBuilder = sqlBuilder
return s
}
// DefaultSchema returns default schema template implementation
func DefaultSchema(schemaMetaData metadata.Schema) Schema {
return Schema{
Path: schemaMetaData.Name,
Model: DefaultModel(),
SQLBuilder: DefaultSQLBuilder(),
}
}

View file

@ -0,0 +1,327 @@
package template
import (
"fmt"
"github.com/go-jet/jet/v2/generator/metadata"
"github.com/go-jet/jet/v2/internal/utils"
"github.com/google/uuid"
"path"
"reflect"
"strings"
"time"
)
// Model is template for model files generation
type Model struct {
Skip bool
Path string
Table func(table metadata.Table) TableModel
View func(table metadata.Table) ViewModel
Enum func(enum metadata.Enum) EnumModel
}
// PackageName returns package name of model types
func (m Model) PackageName() string {
return path.Base(m.Path)
}
// UsePath returns new Model template with replaced file path
func (m Model) UsePath(path string) Model {
m.Path = path
return m
}
// UseTable returns new Model template with replaced template for table model files generation
func (m Model) UseTable(tableModelFunc func(table metadata.Table) TableModel) Model {
m.Table = tableModelFunc
return m
}
// UseView returns new Model template with replaced template for view model files generation
func (m Model) UseView(tableModelFunc func(table metadata.Table) TableModel) Model {
m.View = tableModelFunc
return m
}
// UseEnum returns new Model template with replaced template for enum model files generation
func (m Model) UseEnum(enumFunc func(enumMetaData metadata.Enum) EnumModel) Model {
m.Enum = enumFunc
return m
}
// DefaultModel returns default Model template implementation
func DefaultModel() Model {
return Model{
Skip: false,
Path: "/model",
Table: DefaultTableModel,
View: DefaultViewModel,
Enum: DefaultEnumModel,
}
}
// TableModel is template for table model files generation
type TableModel struct {
Skip bool
FileName string
TypeName string
Field func(columnMetaData metadata.Column) TableModelField
}
// ViewModel is template for view model files generation
type ViewModel = TableModel
// DefaultViewModel is default view template implementation
var DefaultViewModel = DefaultTableModel
// DefaultTableModel is default table template implementation
func DefaultTableModel(tableMetaData metadata.Table) TableModel {
return TableModel{
FileName: utils.ToGoFileName(tableMetaData.Name),
TypeName: utils.ToGoIdentifier(tableMetaData.Name),
Field: DefaultTableModelField,
}
}
// UseFileName returns new TableModel with new file name set
func (t TableModel) UseFileName(fileName string) TableModel {
t.FileName = fileName
return t
}
// UseTypeName returns new TableModel with new type name set
func (t TableModel) UseTypeName(typeName string) TableModel {
t.TypeName = typeName
return t
}
// UseField returns new TableModel with new TableModelField template function
func (t TableModel) UseField(structFieldFunc func(columnMetaData metadata.Column) TableModelField) TableModel {
t.Field = structFieldFunc
return t
}
func getTableModelImports(modelType TableModel, tableMetaData metadata.Table) []string {
importPaths := map[string]bool{}
for _, columnMetaData := range tableMetaData.Columns {
field := modelType.Field(columnMetaData)
importPath := field.Type.ImportPath
if importPath != "" {
importPaths[importPath] = true
}
}
var ret []string
for importPath := range importPaths {
ret = append(ret, importPath)
}
return ret
}
// EnumModel is template for enum model files generation
type EnumModel struct {
Skip bool
FileName string
TypeName string
ValueName func(value string) string
}
// UseFileName returns new EnumModel with new file name set
func (em EnumModel) UseFileName(fileName string) EnumModel {
em.FileName = fileName
return em
}
// UseTypeName returns new EnumModel with new type name set
func (em EnumModel) UseTypeName(typeName string) EnumModel {
em.TypeName = typeName
return em
}
// DefaultEnumModel returns default implementation for EnumModel
func DefaultEnumModel(enumMetaData metadata.Enum) EnumModel {
typeName := utils.ToGoIdentifier(enumMetaData.Name)
return EnumModel{
FileName: utils.ToGoFileName(enumMetaData.Name),
TypeName: typeName,
ValueName: func(value string) string {
return typeName + "_" + utils.ToGoIdentifier(value)
},
}
}
// TableModelField is template for table model field generation
type TableModelField struct {
Name string
Type Type
Tags []string
}
// DefaultTableModelField returns default TableModelField implementation
func DefaultTableModelField(columnMetaData metadata.Column) TableModelField {
var tags []string
if columnMetaData.IsPrimaryKey {
tags = append(tags, `sql:"primary_key"`)
}
return TableModelField{
Name: utils.ToGoIdentifier(columnMetaData.Name),
Type: getType(columnMetaData),
Tags: tags,
}
}
// UseType returns new TypeModelField with a new field type set
func (f TableModelField) UseType(t Type) TableModelField {
f.Type = t
return f
}
// UseName returns new TableModelField implementation with new field name set
func (f TableModelField) UseName(name string) TableModelField {
f.Name = name
return f
}
// UseTags returns new TableModelField implementation with additional tags added.
func (f TableModelField) UseTags(tags ...string) TableModelField {
f.Tags = append(f.Tags, tags...)
return f
}
// TagsString returns tags string representation
func (f TableModelField) TagsString() string {
if len(f.Tags) == 0 {
return ""
}
return fmt.Sprintf("`%s`", strings.Join(f.Tags, " "))
}
// Type represents type of the struct field
type Type struct {
ImportPath string
Name string
}
// NewType creates new type for dummy object
func NewType(dummyObject interface{}) Type {
return Type{
ImportPath: getImportPath(dummyObject),
Name: getTypeName(dummyObject),
}
}
func getTypeName(t interface{}) string {
typeStr := reflect.TypeOf(t).String()
typeStr = strings.Replace(typeStr, "[]uint8", "[]byte", -1)
return typeStr
}
func getImportPath(dummyData interface{}) string {
dataType := reflect.TypeOf(dummyData)
if dataType.Kind() == reflect.Ptr {
return dataType.Elem().PkgPath()
}
return dataType.PkgPath()
}
func getType(columnMetadata metadata.Column) Type {
userDefinedType := getUserDefinedType(columnMetadata)
if userDefinedType != "" {
if columnMetadata.IsNullable {
return Type{Name: "*" + userDefinedType}
}
return Type{Name: userDefinedType}
}
return NewType(getGoType(columnMetadata))
}
func getUserDefinedType(column metadata.Column) string {
switch column.DataType.Kind {
case metadata.EnumType:
return utils.ToGoIdentifier(column.DataType.Name)
case metadata.UserDefinedType, metadata.ArrayType:
return "string"
}
return ""
}
func getGoType(column metadata.Column) interface{} {
defaultGoType := toGoType(column)
if column.IsNullable {
return reflect.New(reflect.TypeOf(defaultGoType)).Interface()
}
return defaultGoType
}
// toGoType returns model type for column info.
func toGoType(column metadata.Column) interface{} {
switch column.DataType.Name {
case "USER-DEFINED", "enum":
return ""
case "boolean", "bool":
return false
case "tinyint":
if column.DataType.IsUnsigned {
return uint8(0)
}
return int8(0)
case "smallint", "int2",
"year":
if column.DataType.IsUnsigned {
return uint16(0)
}
return int16(0)
case "integer", "int4",
"mediumint", "int": //MySQL
if column.DataType.IsUnsigned {
return uint32(0)
}
return int32(0)
case "bigint", "int8":
if column.DataType.IsUnsigned {
return uint64(0)
}
return int64(0)
case "date",
"timestamp without time zone", "timestamp",
"timestamp with time zone", "timestamptz",
"time without time zone", "time",
"time with time zone", "timetz",
"datetime": // MySQL
return time.Time{}
case "bytea",
"binary", "varbinary", "tinyblob", "blob", "mediumblob", "longblob": //MySQL
return []byte("")
case "text",
"character", "bpchar",
"character varying", "varchar",
"tsvector", "bit", "bit varying", "varbit",
"money", "json", "jsonb",
"xml", "point", "interval", "line", "ARRAY",
"char", "tinytext", "mediumtext", "longtext": // MySQL
return ""
case "real", "float4":
return float32(0.0)
case "numeric", "decimal",
"double precision", "float8", "float",
"double": // MySQL
return float64(0.0)
case "uuid":
return uuid.UUID{}
default:
fmt.Println("- [Model ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
return ""
}
}

View file

@ -0,0 +1,45 @@
package template
import (
"github.com/go-jet/jet/v2/generator/metadata"
"github.com/stretchr/testify/require"
"testing"
)
func Test_TableModelField(t *testing.T) {
require.Equal(t, DefaultTableModelField(metadata.Column{
Name: "col_name",
IsPrimaryKey: true,
IsNullable: true,
DataType: metadata.DataType{
Name: "smallint",
Kind: "base",
IsUnsigned: true,
},
}), TableModelField{
Name: "ColName",
Type: Type{
ImportPath: "",
Name: "*uint16",
},
Tags: []string{"sql:\"primary_key\""},
})
require.Equal(t, DefaultTableModelField(metadata.Column{
Name: "time_column_1",
IsPrimaryKey: false,
IsNullable: true,
DataType: metadata.DataType{
Name: "timestamp with time zone",
Kind: "base",
IsUnsigned: false,
},
}), TableModelField{
Name: "TimeColumn1",
Type: Type{
ImportPath: "time",
Name: "*time.Time",
},
Tags: nil,
})
}

View file

@ -0,0 +1,269 @@
package template
import (
"bytes"
"fmt"
"github.com/go-jet/jet/v2/generator/metadata"
"github.com/go-jet/jet/v2/internal/jet"
"github.com/go-jet/jet/v2/internal/utils"
"github.com/go-jet/jet/v2/internal/utils/throw"
"path"
"strings"
"text/template"
)
// ProcessSchema will process schema metadata and constructs go files using generator Template
func ProcessSchema(dirPath string, schemaMetaData metadata.Schema, generatorTemplate Template) {
if schemaMetaData.IsEmpty() {
return
}
schemaTemplate := generatorTemplate.Schema(schemaMetaData)
schemaPath := path.Join(dirPath, schemaTemplate.Path)
fmt.Println("Destination directory:", schemaPath)
fmt.Println("Cleaning up destination directory...")
err := utils.CleanUpGeneratedFiles(schemaPath)
throw.OnError(err)
processModel(schemaPath, schemaMetaData, schemaTemplate)
processSQLBuilder(schemaPath, generatorTemplate.Dialect, schemaMetaData, schemaTemplate)
}
func processModel(dirPath string, schemaMetaData metadata.Schema, schemaTemplate Schema) {
modelTemplate := schemaTemplate.Model
if modelTemplate.Skip {
fmt.Println("Skipping the generation of model types.")
return
}
modelDirPath := path.Join(dirPath, modelTemplate.Path)
err := utils.EnsureDirPath(modelDirPath)
throw.OnError(err)
processTableModels("table", modelDirPath, schemaMetaData.TablesMetaData, modelTemplate)
processTableModels("view", modelDirPath, schemaMetaData.ViewsMetaData, modelTemplate)
processEnumModels(modelDirPath, schemaMetaData.EnumsMetaData, modelTemplate)
}
func processSQLBuilder(dirPath string, dialect jet.Dialect, schemaMetaData metadata.Schema, schemaTemplate Schema) {
sqlBuilderTemplate := schemaTemplate.SQLBuilder
if sqlBuilderTemplate.Skip {
fmt.Println("Skipping the generation of SQL Builder types.")
return
}
sqlBuilderPath := path.Join(dirPath, sqlBuilderTemplate.Path)
processTableSQLBuilder("table", sqlBuilderPath, dialect, schemaMetaData, schemaMetaData.TablesMetaData, sqlBuilderTemplate)
processTableSQLBuilder("view", sqlBuilderPath, dialect, schemaMetaData, schemaMetaData.ViewsMetaData, sqlBuilderTemplate)
processEnumSQLBuilder(sqlBuilderPath, dialect, schemaMetaData.EnumsMetaData, sqlBuilderTemplate)
}
func processEnumSQLBuilder(dirPath string, dialect jet.Dialect, enumsMetaData []metadata.Enum, sqlBuilder SQLBuilder) {
if len(enumsMetaData) == 0 {
return
}
fmt.Printf("Generating enum sql builder files\n")
for _, enumMetaData := range enumsMetaData {
enumTemplate := sqlBuilder.Enum(enumMetaData)
if enumTemplate.Skip {
continue
}
enumSQLBuilderPath := path.Join(dirPath, enumTemplate.Path)
err := utils.EnsureDirPath(enumSQLBuilderPath)
throw.OnError(err)
text, err := generateTemplate(
autoGenWarningTemplate+enumSQLBuilderTemplate,
enumMetaData,
template.FuncMap{
"package": func() string {
return enumTemplate.PackageName()
},
"dialect": func() jet.Dialect {
return dialect
},
"enumTemplate": func() EnumSQLBuilder {
return enumTemplate
},
"enumValueName": func(enumValue string) string {
return enumTemplate.ValueName(enumValue)
},
})
throw.OnError(err)
err = utils.SaveGoFile(enumSQLBuilderPath, enumTemplate.FileName, text)
throw.OnError(err)
}
}
func processTableSQLBuilder(fileTypes, dirPath string,
dialect jet.Dialect,
schemaMetaData metadata.Schema,
tablesMetaData []metadata.Table,
sqlBuilderTemplate SQLBuilder) {
if len(tablesMetaData) == 0 {
return
}
fmt.Printf("Generating %s sql builder files\n", fileTypes)
for _, tableMetaData := range tablesMetaData {
var tableSQLBuilderTemplate TableSQLBuilder
if fileTypes == "view" {
tableSQLBuilderTemplate = sqlBuilderTemplate.View(tableMetaData)
} else {
tableSQLBuilderTemplate = sqlBuilderTemplate.Table(tableMetaData)
}
if tableSQLBuilderTemplate.Skip {
continue
}
tableSQLBuilderPath := path.Join(dirPath, tableSQLBuilderTemplate.Path)
err := utils.EnsureDirPath(tableSQLBuilderPath)
throw.OnError(err)
text, err := generateTemplate(
autoGenWarningTemplate+getTableSQLBuilderTemplate(dialect),
tableMetaData,
template.FuncMap{
"package": func() string {
return tableSQLBuilderTemplate.PackageName()
},
"dialect": func() jet.Dialect {
return dialect
},
"schemaName": func() string {
return schemaMetaData.Name
},
"tableTemplate": func() TableSQLBuilder {
return tableSQLBuilderTemplate
},
"structImplName": func() string { // postgres only
structName := tableSQLBuilderTemplate.TypeName
return string(strings.ToLower(structName)[0]) + structName[1:]
},
"columnField": func(columnMetaData metadata.Column) TableSQLBuilderColumn {
return tableSQLBuilderTemplate.Column(columnMetaData)
},
})
throw.OnError(err)
err = utils.SaveGoFile(tableSQLBuilderPath, tableSQLBuilderTemplate.FileName, text)
throw.OnError(err)
}
}
func getTableSQLBuilderTemplate(dialect jet.Dialect) string {
if dialect.Name() == "PostgreSQL" {
return tablePostgreSQLBuilderTemplate
}
return tableSQLBuilderTemplate
}
func processTableModels(fileTypes, modelDirPath string, tablesMetaData []metadata.Table, modelTemplate Model) {
if len(tablesMetaData) == 0 {
return
}
fmt.Printf("Generating %s model files...\n", fileTypes)
for _, tableMetaData := range tablesMetaData {
var tableTemplate TableModel
if fileTypes == "table" {
tableTemplate = modelTemplate.Table(tableMetaData)
} else {
tableTemplate = modelTemplate.View(tableMetaData)
}
if tableTemplate.Skip {
continue
}
text, err := generateTemplate(
autoGenWarningTemplate+tableModelFileTemplate,
tableMetaData,
template.FuncMap{
"package": func() string {
return modelTemplate.PackageName()
},
"modelImports": func() []string {
return getTableModelImports(tableTemplate, tableMetaData)
},
"tableTemplate": func() TableModel {
return tableTemplate
},
"structField": func(columnMetaData metadata.Column) TableModelField {
return tableTemplate.Field(columnMetaData)
},
})
throw.OnError(err)
err = utils.SaveGoFile(modelDirPath, tableTemplate.FileName, text)
throw.OnError(err)
}
}
func processEnumModels(modelDir string, enumsMetaData []metadata.Enum, modelTemplate Model) {
if len(enumsMetaData) == 0 {
return
}
fmt.Print("Generating enum model files...\n")
for _, enumMetaData := range enumsMetaData {
enumTemplate := modelTemplate.Enum(enumMetaData)
if enumTemplate.Skip {
continue
}
text, err := generateTemplate(
autoGenWarningTemplate+enumModelTemplate,
enumMetaData,
template.FuncMap{
"package": func() string {
return modelTemplate.PackageName()
},
"enumTemplate": func() EnumModel {
return enumTemplate
},
"valueName": func(value string) string {
return enumTemplate.ValueName(value)
},
})
throw.OnError(err)
err = utils.SaveGoFile(modelDir, enumTemplate.FileName, text)
throw.OnError(err)
}
}
func generateTemplate(templateText string, templateData interface{}, funcMap template.FuncMap) ([]byte, error) {
t, err := template.New("sqlBuilderTableTemplate").Funcs(funcMap).Parse(templateText)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := t.Execute(&buf, templateData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

View file

@ -0,0 +1,225 @@
package template
import (
"fmt"
"github.com/go-jet/jet/v2/generator/metadata"
"github.com/go-jet/jet/v2/internal/utils"
"path"
"unicode"
)
// SQLBuilder is template for generating sql builder files
type SQLBuilder struct {
Skip bool
Path string
Table func(table metadata.Table) TableSQLBuilder
View func(view metadata.Table) TableSQLBuilder
Enum func(enum metadata.Enum) EnumSQLBuilder
}
// DefaultSQLBuilder returns default SQLBuilder implementation
func DefaultSQLBuilder() SQLBuilder {
return SQLBuilder{
Path: "",
Table: DefaultTableSQLBuilder,
View: DefaultViewSQLBuilder,
Enum: DefaultEnumSQLBuilder,
}
}
// UsePath returns new SQLBuilder with new relative path set
func (sb SQLBuilder) UsePath(path string) SQLBuilder {
sb.Path = path
return sb
}
// UseTable returns new SQLBuilder with new TableSQLBuilder template function set
func (sb SQLBuilder) UseTable(tableFunc func(table metadata.Table) TableSQLBuilder) SQLBuilder {
sb.Table = tableFunc
return sb
}
// UseView returns new SQLBuilder with new ViewSQLBuilder template function set
func (sb SQLBuilder) UseView(viewFunc func(table metadata.Table) ViewSQLBuilder) SQLBuilder {
sb.View = viewFunc
return sb
}
// UseEnum returns new SQLBuilder with new EnumSQLBuilder template function set
func (sb SQLBuilder) UseEnum(enumFunc func(enum metadata.Enum) EnumSQLBuilder) SQLBuilder {
sb.Enum = enumFunc
return sb
}
// TableSQLBuilder is template for generating table SQLBuilder files
type TableSQLBuilder struct {
Skip bool
Path string
FileName string
InstanceName string
TypeName string
Column func(columnMetaData metadata.Column) TableSQLBuilderColumn
}
// ViewSQLBuilder is template for generating view SQLBuilder files
type ViewSQLBuilder = TableSQLBuilder
// DefaultTableSQLBuilder returns default implementation for TableSQLBuilder
func DefaultTableSQLBuilder(tableMetaData metadata.Table) TableSQLBuilder {
return TableSQLBuilder{
Path: "/table",
FileName: utils.ToGoFileName(tableMetaData.Name),
InstanceName: utils.ToGoIdentifier(tableMetaData.Name),
TypeName: utils.ToGoIdentifier(tableMetaData.Name) + "Table",
Column: DefaultTableSQLBuilderColumn,
}
}
// DefaultViewSQLBuilder returns default implementation for ViewSQLBuilder
func DefaultViewSQLBuilder(viewMetaData metadata.Table) ViewSQLBuilder {
tableSQLBuilder := DefaultTableSQLBuilder(viewMetaData)
tableSQLBuilder.Path = "/view"
return tableSQLBuilder
}
// PackageName returns package name of table sql builder types
func (tb TableSQLBuilder) PackageName() string {
return path.Base(tb.Path)
}
// UsePath returns new TableSQLBuilder with new relative path set
func (tb TableSQLBuilder) UsePath(path string) TableSQLBuilder {
tb.Path = path
return tb
}
// UseFileName returns new TableSQLBuilder with new file name set
func (tb TableSQLBuilder) UseFileName(name string) TableSQLBuilder {
tb.FileName = name
return tb
}
// UseInstanceName returns new TableSQLBuilder with new instance name set
func (tb TableSQLBuilder) UseInstanceName(name string) TableSQLBuilder {
tb.InstanceName = name
return tb
}
// UseTypeName returns new TableSQLBuilder with new type name set
func (tb TableSQLBuilder) UseTypeName(name string) TableSQLBuilder {
tb.TypeName = name
return tb
}
// UseColumn returns new TableSQLBuilder with new column template function set
func (tb TableSQLBuilder) UseColumn(columnsFunc func(column metadata.Column) TableSQLBuilderColumn) TableSQLBuilder {
tb.Column = columnsFunc
return tb
}
// TableSQLBuilderColumn is template for table sql builder column
type TableSQLBuilderColumn struct {
Name string
Type string
}
// DefaultTableSQLBuilderColumn returns default implementation of TableSQLBuilderColumn
func DefaultTableSQLBuilderColumn(columnMetaData metadata.Column) TableSQLBuilderColumn {
return TableSQLBuilderColumn{
Name: utils.ToGoIdentifier(columnMetaData.Name),
Type: getSqlBuilderColumnType(columnMetaData),
}
}
// getSqlBuilderColumnType returns type of jet sql builder column
func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
if columnMetaData.DataType.Kind != metadata.BaseType {
return "String"
}
switch columnMetaData.DataType.Name {
case "boolean":
return "Bool"
case "smallint", "integer", "bigint",
"tinyint", "mediumint", "int", "year": //MySQL
return "Integer"
case "date":
return "Date"
case "timestamp without time zone",
"timestamp", "datetime": //MySQL:
return "Timestamp"
case "timestamp with time zone":
return "Timestampz"
case "time without time zone",
"time": //MySQL
return "Time"
case "time with time zone":
return "Timez"
case "interval":
return "Interval"
case "USER-DEFINED", "enum", "text", "character", "character varying", "bytea", "uuid",
"tsvector", "bit", "bit varying", "money", "json", "jsonb", "xml", "point", "line", "ARRAY",
"char", "varchar", "binary", "varbinary",
"tinyblob", "blob", "mediumblob", "longblob", "tinytext", "mediumtext", "longtext": // MySQL
return "String"
case "real", "numeric", "decimal", "double precision", "float",
"double": // MySQL
return "Float"
default:
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
return "String"
}
}
// EnumSQLBuilder is template for generating enum SQLBuilder files
type EnumSQLBuilder struct {
Skip bool
Path string
FileName string
InstanceName string
ValueName func(enumValue string) string
}
// DefaultEnumSQLBuilder returns default implementation of EnumSQLBuilder
func DefaultEnumSQLBuilder(enumMetaData metadata.Enum) EnumSQLBuilder {
return EnumSQLBuilder{
Path: "/enum",
FileName: utils.ToGoFileName(enumMetaData.Name),
InstanceName: utils.ToGoIdentifier(enumMetaData.Name),
ValueName: func(enumValue string) string {
return defaultEnumValueName(enumMetaData.Name, enumValue)
},
}
}
// PackageName returns enum sql builder package name
func (e EnumSQLBuilder) PackageName() string {
return path.Base(e.Path)
}
// UsePath returns new EnumSQLBuilder with new path set
func (e EnumSQLBuilder) UsePath(path string) EnumSQLBuilder {
e.Path = path
return e
}
// UseFileName returns new EnumSQLBuilder with new file name set
func (e EnumSQLBuilder) UseFileName(name string) EnumSQLBuilder {
e.FileName = name
return e
}
// UseInstanceName returns new EnumSQLBuilder with instance name set
func (e EnumSQLBuilder) UseInstanceName(name string) EnumSQLBuilder {
e.InstanceName = name
return e
}
func defaultEnumValueName(enumName, enumValue string) string {
enumValueName := utils.ToGoIdentifier(enumValue)
if !unicode.IsLetter([]rune(enumValueName)[0]) {
return utils.ToGoIdentifier(enumName) + enumValueName
}
return enumValueName
}

View file

@ -0,0 +1,11 @@
package template
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestToGoEnumValueIdentifier(t *testing.T) {
require.Equal(t, defaultEnumValueName("enum_name", "enum_value"), "EnumValue")
require.Equal(t, defaultEnumValueName("NumEnum", "100"), "NumEnum100")
}