package template import ( "fmt" "path/filepath" "slices" "strings" "unicode" "github.com/Gleipnir-Technology/jet/generator/metadata" "github.com/Gleipnir-Technology/jet/internal/jet" "github.com/Gleipnir-Technology/jet/internal/utils/dbidentifier" ) // 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 } // ShouldSkip returns new SQLBuilder with new skip flag set func (sb SQLBuilder) ShouldSkip(skip bool) SQLBuilder { sb.Skip = skip return sb } // TableSQLBuilder is template for generating table SQLBuilder files type TableSQLBuilder struct { Column func(dialect jet.Dialect, columnMetaData metadata.Column) TableSQLBuilderColumn DefaultAlias string Imports []string InstanceName string FileName string Path string Skip bool TypeName string } // ViewSQLBuilder is template for generating view SQLBuilder files type ViewSQLBuilder = TableSQLBuilder // DefaultTableSQLBuilder returns default implementation for TableSQLBuilder func DefaultTableSQLBuilder(tableMetaData metadata.Table) TableSQLBuilder { tableNameGoIdentifier := dbidentifier.ToGoIdentifier(tableMetaData.Name) return TableSQLBuilder{ Column: DefaultTableSQLBuilderColumn, DefaultAlias: "", FileName: dbidentifier.ToGoFileName(tableMetaData.Name), Imports: []string{}, InstanceName: tableNameGoIdentifier, Path: "/table", TypeName: tableNameGoIdentifier + "Table", } } // 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 filepath.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 } // UseDefaultAlias returns new TableSQLBuilder with new default alias set func (tb TableSQLBuilder) UseDefaultAlias(defaultAlias string) TableSQLBuilder { tb.DefaultAlias = defaultAlias return tb } // UseColumn returns new TableSQLBuilder with new column template function set func (tb TableSQLBuilder) UseColumn(columnsFunc func(dialect jet.Dialect, column metadata.Column) TableSQLBuilderColumn) TableSQLBuilder { tb.Column = columnsFunc return tb } // TableSQLBuilderColumn is template for table sql builder column type TableSQLBuilderColumn struct { Import string Name string PackageName string Skip bool Type string TypeFactory string } var reservedKeywords = []string{"TableName", "Table", "SchemaName", "Alias", "AllColumns", "MutableColumns", "DefaultColumns"} func renameIfReserved(name string) string { if slices.Contains(reservedKeywords, name) { return name + "_" } return name } // DefaultTableSQLBuilderColumn returns default implementation of TableSQLBuilderColumn func DefaultTableSQLBuilderColumn(dialect jet.Dialect, columnMetaData metadata.Column) TableSQLBuilderColumn { package_name := dialect.PackageName() return TableSQLBuilderColumn{ Import: "github.com/Gleipnir-Technology/jet/" + package_name, Name: renameIfReserved(dbidentifier.ToGoIdentifier(columnMetaData.Name)), PackageName: package_name, Type: "Column" + getSqlBuilderColumnType(columnMetaData), TypeFactory: getSqlBuilderColumnType(columnMetaData)+"Column", } } // getSqlBuilderColumnType returns type of jet sql builder column func getSqlBuilderColumnType(columnMetaData metadata.Column) string { switch columnMetaData.DataType.Kind { case metadata.EnumType, metadata.UserDefinedType: if columnMetaData.DataType.IsArray() { return "StringArray" } return "String" } columnType := sqlToColumnType(columnMetaData) if columnMetaData.DataType.IsArray() { if columnMetaData.DataType.Dimensions > 1 { fmt.Println("- [SQL Builder] Unsupported sql array with multiple dimensions column '" + columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.") return "String" } columnType = columnType + "Array" } return columnType } // sqlToColumnType maps the type of a SQL column type to a go jet sql builder column. The second return value returns // whether the given type is supported. func sqlToColumnType(columnMetaData metadata.Column) string { switch strings.ToLower(columnMetaData.DataType.Name) { case "boolean", "bool": return "Bool" case "smallint", "integer", "bigint", "int2", "int4", "int8", "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", "timestamptz": return "Timestampz" case "time without time zone", "time": //MySQL return "Time" case "time with time zone", "timetz": return "Timez" case "interval": return "Interval" case "user-defined", "enum", "text", "character", "character varying", "uuid", "tsvector", "bit", "bit varying", "money", "json", "jsonb", "xml", "point", "line", "ARRAY", "char", "varchar", "nvarchar", "bpchar", "varbit", "tinytext", "mediumtext", "longtext": // MySQL return "String" case "bytea": // postgres return "Bytea" case "binary", "varbinary", "tinyblob", "mediumblob", "longblob", "blob": // mysql and sqlite return "Blob" case "real", "numeric", "decimal", "double precision", "float", "float4", "float8", "double": // MySQL return "Float" case "daterange": return "DateRange" case "tsrange": return "TimestampRange" case "tstzrange": return "TimestampzRange" case "int4range": return "Int4Range" case "int8range": return "Int8Range" case "numrange": return "NumericRange" case "geometry": return "Geometry" default: fmt.Println("- [SQL Builder] Unsupported sql column '" + columnMetaData.Name + "' with type '" + 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: dbidentifier.ToGoFileName(enumMetaData.Name), InstanceName: dbidentifier.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 filepath.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 := dbidentifier.ToGoIdentifier(enumValue) if !unicode.IsLetter([]rune(enumValueName)[0]) { return dbidentifier.ToGoIdentifier(enumName) + enumValueName } return enumValueName }