2021-07-27 17:39:21 +02:00
|
|
|
package template
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2024-09-03 15:39:36 +02:00
|
|
|
"github.com/lib/pq"
|
2024-11-26 15:40:34 +06:00
|
|
|
"path/filepath"
|
2021-07-27 17:39:21 +02:00
|
|
|
"reflect"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
2025-10-16 15:09:07 +02:00
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
"github.com/jackc/pgtype"
|
|
|
|
|
|
2026-05-14 16:26:47 +00:00
|
|
|
"source.gleipnir.technology/Gleipnir/jet/generator/metadata"
|
|
|
|
|
"source.gleipnir.technology/Gleipnir/jet/internal/utils/dbidentifier"
|
2021-07-27 17:39:21 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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 {
|
2024-11-26 15:40:34 +06:00
|
|
|
return filepath.Base(m.Path)
|
2021-07-27 17:39:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-10 09:02:23 -04:00
|
|
|
// ShouldSkip returns new Model template with new skip flag set
|
|
|
|
|
func (m Model) ShouldSkip(skip bool) Model {
|
|
|
|
|
m.Skip = skip
|
|
|
|
|
return m
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-27 17:39:21 +02:00
|
|
|
// 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{
|
2023-07-21 14:11:31 +02:00
|
|
|
FileName: dbidentifier.ToGoFileName(tableMetaData.Name),
|
|
|
|
|
TypeName: dbidentifier.ToGoIdentifier(tableMetaData.Name),
|
2021-07-27 17:39:21 +02:00
|
|
|
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)
|
2025-03-27 17:42:27 +02:00
|
|
|
for _, importPath := range append([]string{field.Type.ImportPath}, field.Type.AdditionalImportPaths...) {
|
|
|
|
|
if importPath != "" {
|
|
|
|
|
importPaths[importPath] = true
|
|
|
|
|
}
|
2021-07-27 17:39:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2023-07-21 14:11:31 +02:00
|
|
|
typeName := dbidentifier.ToGoIdentifier(enumMetaData.Name)
|
2021-07-27 17:39:21 +02:00
|
|
|
|
|
|
|
|
return EnumModel{
|
2023-07-21 14:11:31 +02:00
|
|
|
FileName: dbidentifier.ToGoFileName(enumMetaData.Name),
|
2021-07-27 17:39:21 +02:00
|
|
|
TypeName: typeName,
|
|
|
|
|
ValueName: func(value string) string {
|
2023-07-21 14:11:31 +02:00
|
|
|
return typeName + "_" + dbidentifier.ToGoIdentifier(value)
|
2021-07-27 17:39:21 +02:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TableModelField is template for table model field generation
|
|
|
|
|
type TableModelField struct {
|
|
|
|
|
Name string
|
|
|
|
|
Type Type
|
|
|
|
|
Tags []string
|
2024-12-26 15:03:42 +01:00
|
|
|
Skip bool
|
2021-07-27 17:39:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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{
|
2023-07-21 14:11:31 +02:00
|
|
|
Name: dbidentifier.ToGoIdentifier(columnMetaData.Name),
|
2021-07-27 17:39:21 +02:00
|
|
|
Type: getType(columnMetaData),
|
|
|
|
|
Tags: tags,
|
2024-12-26 15:03:42 +01:00
|
|
|
Skip: false,
|
2021-07-27 17:39:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 {
|
2025-03-27 17:42:27 +02:00
|
|
|
ImportPath string
|
|
|
|
|
AdditionalImportPaths []string
|
|
|
|
|
Name string
|
2021-07-27 17:39:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 != "" {
|
2025-10-16 15:09:07 +02:00
|
|
|
var importPath string
|
|
|
|
|
|
|
|
|
|
if columnMetadata.DataType.IsArray() {
|
|
|
|
|
userDefinedType = "pq.StringArray"
|
|
|
|
|
importPath = "github.com/lib/pq"
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-27 17:39:21 +02:00
|
|
|
if columnMetadata.IsNullable {
|
2025-10-16 15:09:07 +02:00
|
|
|
userDefinedType = "*" + userDefinedType
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Type{
|
|
|
|
|
Name: userDefinedType,
|
|
|
|
|
ImportPath: importPath,
|
2021-07-27 17:39:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NewType(getGoType(columnMetadata))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getUserDefinedType(column metadata.Column) string {
|
|
|
|
|
switch column.DataType.Kind {
|
|
|
|
|
case metadata.EnumType:
|
2023-07-21 14:11:31 +02:00
|
|
|
return dbidentifier.ToGoIdentifier(column.DataType.Name)
|
2024-09-03 15:39:36 +02:00
|
|
|
case metadata.UserDefinedType:
|
2021-07-27 17:39:21 +02:00
|
|
|
return "string"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getGoType(column metadata.Column) interface{} {
|
2025-10-16 15:09:07 +02:00
|
|
|
goType := toGoType(column)
|
|
|
|
|
|
|
|
|
|
if column.DataType.IsArray() {
|
|
|
|
|
goType = toGoArrayType(goType, column)
|
|
|
|
|
}
|
2021-07-27 17:39:21 +02:00
|
|
|
|
|
|
|
|
if column.IsNullable {
|
2025-10-16 15:09:07 +02:00
|
|
|
return reflect.New(reflect.TypeOf(goType)).Interface()
|
2021-07-27 17:39:21 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-16 15:09:07 +02:00
|
|
|
return goType
|
2021-07-27 17:39:21 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-16 15:09:07 +02:00
|
|
|
func toGoArrayType(elemType any, column metadata.Column) any {
|
2024-09-03 15:39:36 +02:00
|
|
|
if column.DataType.Dimensions > 1 {
|
2025-10-16 15:09:07 +02:00
|
|
|
return "" // unsupported multidimensional arrays
|
2024-09-03 15:39:36 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-16 15:09:07 +02:00
|
|
|
switch elemType.(type) {
|
|
|
|
|
case bool:
|
|
|
|
|
return pq.BoolArray{}
|
|
|
|
|
case int32:
|
|
|
|
|
return pq.Int32Array{}
|
|
|
|
|
case int64:
|
|
|
|
|
return pq.Int64Array{}
|
|
|
|
|
case float32:
|
|
|
|
|
return pq.Float32Array{}
|
|
|
|
|
case float64:
|
|
|
|
|
return pq.Float64Array{}
|
|
|
|
|
case []byte:
|
|
|
|
|
return pq.ByteaArray{}
|
|
|
|
|
default:
|
|
|
|
|
return pq.StringArray{}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// toGoType returns model type for column info.
|
|
|
|
|
func toGoType(column metadata.Column) interface{} {
|
|
|
|
|
|
2021-10-21 13:21:01 +02:00
|
|
|
switch strings.ToLower(column.DataType.Name) {
|
|
|
|
|
case "user-defined", "enum":
|
2021-07-27 17:39:21 +02:00
|
|
|
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",
|
2021-10-21 13:21:01 +02:00
|
|
|
"character varying", "varchar", "nvarchar",
|
2021-07-27 17:39:21 +02:00
|
|
|
"tsvector", "bit", "bit varying", "varbit",
|
|
|
|
|
"money", "json", "jsonb",
|
2021-10-21 13:21:01 +02:00
|
|
|
"xml", "point", "interval", "line", "array",
|
2021-07-27 17:39:21 +02:00
|
|
|
"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{}
|
2024-01-31 15:30:09 +01:00
|
|
|
case "daterange":
|
|
|
|
|
return pgtype.Daterange{}
|
|
|
|
|
case "tsrange":
|
|
|
|
|
return pgtype.Tsrange{}
|
|
|
|
|
case "tstzrange":
|
|
|
|
|
return pgtype.Tstzrange{}
|
|
|
|
|
case "int4range":
|
|
|
|
|
return pgtype.Int4range{}
|
|
|
|
|
case "int8range":
|
|
|
|
|
return pgtype.Int8range{}
|
|
|
|
|
case "numrange":
|
|
|
|
|
return pgtype.Numrange{}
|
2021-07-27 17:39:21 +02:00
|
|
|
default:
|
|
|
|
|
fmt.Println("- [Model ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
}
|