From d7a5adb239cdeda640fd6cf555adebfe3bee8092 Mon Sep 17 00:00:00 2001 From: go-jet Date: Fri, 21 Jul 2023 14:11:31 +0200 Subject: [PATCH] Break utils package into subpackages. --- cmd/jet/main.go | 8 +- generator/mysql/mysql_generator.go | 5 +- generator/postgres/postgres_generator.go | 3 +- generator/sqlite/sqlite_generator.go | 3 +- generator/template/model_template.go | 16 +- generator/template/process.go | 20 +- generator/template/sql_builder_template.go | 18 +- internal/jet/clause.go | 8 +- internal/jet/sql_builder.go | 4 +- internal/jet/table.go | 8 +- internal/jet/utils.go | 9 +- internal/utils/datetime/duration.go | 22 ++ internal/utils/dbidentifier/dbidentifier.go | 24 +++ .../dbidentifier_test.go} | 27 +-- internal/utils/filesys/filesys.go | 85 ++++++++ internal/utils/is/is.go | 8 + internal/utils/must/must.go | 41 ++++ internal/utils/strslice/strslice.go | 12 ++ internal/utils/utils.go | 203 ------------------ mysql/interval.go | 4 +- postgres/interval_expression.go | 6 +- qrm/qrm.go | 17 +- qrm/utill.go | 7 +- tests/mysql/generator_template_test.go | 18 +- tests/postgres/generator_template_test.go | 18 +- 25 files changed, 276 insertions(+), 318 deletions(-) create mode 100644 internal/utils/datetime/duration.go create mode 100644 internal/utils/dbidentifier/dbidentifier.go rename internal/utils/{utils_test.go => dbidentifier/dbidentifier_test.go} (72%) create mode 100644 internal/utils/filesys/filesys.go create mode 100644 internal/utils/is/is.go create mode 100644 internal/utils/must/must.go create mode 100644 internal/utils/strslice/strslice.go delete mode 100644 internal/utils/utils.go diff --git a/cmd/jet/main.go b/cmd/jet/main.go index 3acc411..7508cab 100644 --- a/cmd/jet/main.go +++ b/cmd/jet/main.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "github.com/go-jet/jet/v2/internal/utils/errfmt" + "github.com/go-jet/jet/v2/internal/utils/strslice" "os" "strings" @@ -13,7 +14,6 @@ import ( sqlitegen "github.com/go-jet/jet/v2/generator/sqlite" "github.com/go-jet/jet/v2/generator/template" "github.com/go-jet/jet/v2/internal/jet" - "github.com/go-jet/jet/v2/internal/utils" "github.com/go-jet/jet/v2/mysql" postgres2 "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/sqlite" @@ -232,15 +232,15 @@ func parseList(list string) []string { func genTemplate(dialect jet.Dialect, ignoreTables []string, ignoreViews []string, ignoreEnums []string) template.Template { shouldSkipTable := func(table metadata.Table) bool { - return utils.StringSliceContains(ignoreTables, strings.ToLower(table.Name)) + return strslice.Contains(ignoreTables, strings.ToLower(table.Name)) } shouldSkipView := func(view metadata.Table) bool { - return utils.StringSliceContains(ignoreViews, strings.ToLower(view.Name)) + return strslice.Contains(ignoreViews, strings.ToLower(view.Name)) } shouldSkipEnum := func(enum metadata.Enum) bool { - return utils.StringSliceContains(ignoreEnums, strings.ToLower(enum.Name)) + return strslice.Contains(ignoreEnums, strings.ToLower(enum.Name)) } return template.Default(dialect). diff --git a/generator/mysql/mysql_generator.go b/generator/mysql/mysql_generator.go index 173fa64..635bcbc 100644 --- a/generator/mysql/mysql_generator.go +++ b/generator/mysql/mysql_generator.go @@ -8,7 +8,6 @@ import ( "github.com/go-jet/jet/v2/generator/metadata" "github.com/go-jet/jet/v2/generator/template" - "github.com/go-jet/jet/v2/internal/utils" "github.com/go-jet/jet/v2/mysql" mysqldr "github.com/go-sql-driver/mysql" ) @@ -35,7 +34,7 @@ func Generate(destDir string, dbConn DBConnection, generatorTemplate ...template if err != nil { return fmt.Errorf("failed to open db connection: %w", err) } - defer utils.DBClose(db) + defer db.Close() err = generate(db, dbConn.DBName, destDir, generatorTemplate...) if err != nil { @@ -67,7 +66,7 @@ func GenerateDSN(dsn, destDir string, templates ...template.Template) error { if err != nil { return fmt.Errorf("failed to open db connection: %w", err) } - defer utils.DBClose(db) + defer db.Close() err = generate(db, cfg.DBName, destDir, templates...) if err != nil { diff --git a/generator/postgres/postgres_generator.go b/generator/postgres/postgres_generator.go index c7cbfa1..ce6acd1 100644 --- a/generator/postgres/postgres_generator.go +++ b/generator/postgres/postgres_generator.go @@ -9,7 +9,6 @@ import ( "github.com/go-jet/jet/v2/generator/metadata" "github.com/go-jet/jet/v2/generator/template" - "github.com/go-jet/jet/v2/internal/utils" "github.com/go-jet/jet/v2/postgres" "github.com/jackc/pgconn" ) @@ -54,7 +53,7 @@ func GenerateDSN(dsn, schema, destDir string, templates ...template.Template) er if err != nil { return fmt.Errorf("failed to open db connection: %w", err) } - defer utils.DBClose(db) + defer db.Close() fmt.Println("Retrieving schema information...") generatorTemplate := template.Default(postgres.Dialect) diff --git a/generator/sqlite/sqlite_generator.go b/generator/sqlite/sqlite_generator.go index 990c77c..eadc72b 100644 --- a/generator/sqlite/sqlite_generator.go +++ b/generator/sqlite/sqlite_generator.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/go-jet/jet/v2/generator/metadata" "github.com/go-jet/jet/v2/generator/template" - "github.com/go-jet/jet/v2/internal/utils" "github.com/go-jet/jet/v2/sqlite" ) @@ -15,7 +14,7 @@ func GenerateDSN(dsn, destDir string, templates ...template.Template) error { if err != nil { return fmt.Errorf("failed to open sqlite connection: %w", err) } - defer utils.DBClose(db) + defer db.Close() fmt.Println("Retrieving schema information...") diff --git a/generator/template/model_template.go b/generator/template/model_template.go index 032afc8..47f0339 100644 --- a/generator/template/model_template.go +++ b/generator/template/model_template.go @@ -3,7 +3,7 @@ package template import ( "fmt" "github.com/go-jet/jet/v2/generator/metadata" - "github.com/go-jet/jet/v2/internal/utils" + "github.com/go-jet/jet/v2/internal/utils/dbidentifier" "github.com/google/uuid" "path" "reflect" @@ -77,8 +77,8 @@ 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), + FileName: dbidentifier.ToGoFileName(tableMetaData.Name), + TypeName: dbidentifier.ToGoIdentifier(tableMetaData.Name), Field: DefaultTableModelField, } } @@ -142,13 +142,13 @@ func (em EnumModel) UseTypeName(typeName string) EnumModel { // DefaultEnumModel returns default implementation for EnumModel func DefaultEnumModel(enumMetaData metadata.Enum) EnumModel { - typeName := utils.ToGoIdentifier(enumMetaData.Name) + typeName := dbidentifier.ToGoIdentifier(enumMetaData.Name) return EnumModel{ - FileName: utils.ToGoFileName(enumMetaData.Name), + FileName: dbidentifier.ToGoFileName(enumMetaData.Name), TypeName: typeName, ValueName: func(value string) string { - return typeName + "_" + utils.ToGoIdentifier(value) + return typeName + "_" + dbidentifier.ToGoIdentifier(value) }, } } @@ -169,7 +169,7 @@ func DefaultTableModelField(columnMetaData metadata.Column) TableModelField { } return TableModelField{ - Name: utils.ToGoIdentifier(columnMetaData.Name), + Name: dbidentifier.ToGoIdentifier(columnMetaData.Name), Type: getType(columnMetaData), Tags: tags, } @@ -247,7 +247,7 @@ func getType(columnMetadata metadata.Column) Type { func getUserDefinedType(column metadata.Column) string { switch column.DataType.Kind { case metadata.EnumType: - return utils.ToGoIdentifier(column.DataType.Name) + return dbidentifier.ToGoIdentifier(column.DataType.Name) case metadata.UserDefinedType, metadata.ArrayType: return "string" } diff --git a/generator/template/process.go b/generator/template/process.go index 4c6386d..3f3798a 100644 --- a/generator/template/process.go +++ b/generator/template/process.go @@ -4,13 +4,13 @@ import ( "bytes" "errors" "fmt" + "github.com/go-jet/jet/v2/internal/utils/filesys" "path" "strings" "text/template" "github.com/go-jet/jet/v2/generator/metadata" "github.com/go-jet/jet/v2/internal/jet" - "github.com/go-jet/jet/v2/internal/utils" ) // ProcessSchema will process schema metadata and constructs go files using generator Template @@ -24,7 +24,7 @@ func ProcessSchema(dirPath string, schemaMetaData metadata.Schema, generatorTemp fmt.Println("Destination directory:", schemaPath) fmt.Println("Cleaning up destination directory...") - err := utils.CleanUpGeneratedFiles(schemaPath) + err := filesys.RemoveDir(schemaPath) if err != nil { return errors.New("failed to cleanup generated files") } @@ -52,7 +52,7 @@ func processModel(dirPath string, schemaMetaData metadata.Schema, schemaTemplate modelDirPath := path.Join(dirPath, modelTemplate.Path) - err := utils.EnsureDirPath(modelDirPath) + err := filesys.EnsureDirPathExist(modelDirPath) if err != nil { return fmt.Errorf("destination dir path does not exist: %w", err) } @@ -119,7 +119,7 @@ func processEnumSQLBuilder(dirPath string, dialect jet.Dialect, enumsMetaData [] enumSQLBuilderPath := path.Join(dirPath, enumTemplate.Path) - err := utils.EnsureDirPath(enumSQLBuilderPath) + err := filesys.EnsureDirPathExist(enumSQLBuilderPath) if err != nil { return fmt.Errorf("failed to create enum sql builder directory - %s: %w", enumSQLBuilderPath, err) } @@ -145,7 +145,7 @@ func processEnumSQLBuilder(dirPath string, dialect jet.Dialect, enumsMetaData [] return fmt.Errorf("failed to generete enum type %s: %w", enumTemplate.FileName, err) } - err = utils.SaveGoFile(enumSQLBuilderPath, enumTemplate.FileName, text) + err = filesys.FormatAndSaveGoFile(enumSQLBuilderPath, enumTemplate.FileName, text) if err != nil { return fmt.Errorf("failed to format and save '%s' enum type : %w", enumTemplate.FileName, err) } @@ -183,7 +183,7 @@ func processTableSQLBuilder(fileTypes, dirPath string, tableSQLBuilderPath := path.Join(dirPath, tableSQLBuilder.Path) - err := utils.EnsureDirPath(tableSQLBuilderPath) + err := filesys.EnsureDirPathExist(tableSQLBuilderPath) if err != nil { return fmt.Errorf("failed to create table sql builder directory - %s: %w", tableSQLBuilderPath, err) } @@ -220,7 +220,7 @@ func processTableSQLBuilder(fileTypes, dirPath string, return fmt.Errorf("failed to generate table sql builder type %s: %w", tableSQLBuilder.TypeName, err) } - err = utils.SaveGoFile(tableSQLBuilderPath, tableSQLBuilder.FileName, text) + err = filesys.FormatAndSaveGoFile(tableSQLBuilderPath, tableSQLBuilder.FileName, text) if err != nil { return fmt.Errorf("failed to format and save generated sql builder type '%s': %w", tableSQLBuilder.FileName, err) } @@ -256,7 +256,7 @@ func generateUseSchemaFunc(dirPath, fileTypes string, builders []TableSQLBuilder basePath := path.Join(dirPath, builders[0].Path) fileName := fileTypes + "_use_schema" - err = utils.SaveGoFile(basePath, fileName, text) + err = filesys.FormatAndSaveGoFile(basePath, fileName, text) if err != nil { return fmt.Errorf("failed to save %s file: %w", fileName, err) } @@ -312,7 +312,7 @@ func processTableModels(fileTypes, modelDirPath string, tablesMetaData []metadat return fmt.Errorf("failed to generate model type '%s': %w", tableMetaData.Name, err) } - err = utils.SaveGoFile(modelDirPath, tableTemplate.FileName, text) + err = filesys.FormatAndSaveGoFile(modelDirPath, tableTemplate.FileName, text) if err != nil { return fmt.Errorf("failed to save '%s' model type: %w", tableTemplate.FileName, err) } @@ -353,7 +353,7 @@ func processEnumModels(modelDir string, enumsMetaData []metadata.Enum, modelTemp return fmt.Errorf("failed to generate enum type '%s': %w", enumMetaData.Name, err) } - err = utils.SaveGoFile(modelDir, enumTemplate.FileName, text) + err = filesys.FormatAndSaveGoFile(modelDir, enumTemplate.FileName, text) if err != nil { return fmt.Errorf("failed to save '%s' enum type: %w", enumTemplate.FileName, err) } diff --git a/generator/template/sql_builder_template.go b/generator/template/sql_builder_template.go index 099c0e3..4b68f21 100644 --- a/generator/template/sql_builder_template.go +++ b/generator/template/sql_builder_template.go @@ -3,7 +3,7 @@ package template import ( "fmt" "github.com/go-jet/jet/v2/generator/metadata" - "github.com/go-jet/jet/v2/internal/utils" + "github.com/go-jet/jet/v2/internal/utils/dbidentifier" "path" "strings" "unicode" @@ -69,9 +69,9 @@ type ViewSQLBuilder = 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", + FileName: dbidentifier.ToGoFileName(tableMetaData.Name), + InstanceName: dbidentifier.ToGoIdentifier(tableMetaData.Name), + TypeName: dbidentifier.ToGoIdentifier(tableMetaData.Name) + "Table", Column: DefaultTableSQLBuilderColumn, } } @@ -127,7 +127,7 @@ type TableSQLBuilderColumn struct { // DefaultTableSQLBuilderColumn returns default implementation of TableSQLBuilderColumn func DefaultTableSQLBuilderColumn(columnMetaData metadata.Column) TableSQLBuilderColumn { return TableSQLBuilderColumn{ - Name: utils.ToGoIdentifier(columnMetaData.Name), + Name: dbidentifier.ToGoIdentifier(columnMetaData.Name), Type: getSqlBuilderColumnType(columnMetaData), } } @@ -185,8 +185,8 @@ type EnumSQLBuilder struct { func DefaultEnumSQLBuilder(enumMetaData metadata.Enum) EnumSQLBuilder { return EnumSQLBuilder{ Path: "/enum", - FileName: utils.ToGoFileName(enumMetaData.Name), - InstanceName: utils.ToGoIdentifier(enumMetaData.Name), + FileName: dbidentifier.ToGoFileName(enumMetaData.Name), + InstanceName: dbidentifier.ToGoIdentifier(enumMetaData.Name), ValueName: func(enumValue string) string { return defaultEnumValueName(enumMetaData.Name, enumValue) }, @@ -217,9 +217,9 @@ func (e EnumSQLBuilder) UseInstanceName(name string) EnumSQLBuilder { } func defaultEnumValueName(enumName, enumValue string) string { - enumValueName := utils.ToGoIdentifier(enumValue) + enumValueName := dbidentifier.ToGoIdentifier(enumValue) if !unicode.IsLetter([]rune(enumValueName)[0]) { - return utils.ToGoIdentifier(enumName) + enumValueName + return dbidentifier.ToGoIdentifier(enumName) + enumValueName } return enumValueName diff --git a/internal/jet/clause.go b/internal/jet/clause.go index 3708607..1619124 100644 --- a/internal/jet/clause.go +++ b/internal/jet/clause.go @@ -1,8 +1,6 @@ package jet -import ( - "github.com/go-jet/jet/v2/internal/utils" -) +import "github.com/go-jet/jet/v2/internal/utils/is" // Clause interface type Clause interface { @@ -322,7 +320,7 @@ func (u *ClauseUpdate) Serialize(statementType StatementType, out *SQLBuilder, o out.WriteString("UPDATE") u.OptimizerHints.Serialize(statementType, out, options...) - if utils.IsNil(u.Table) { + if is.Nil(u.Table) { panic("jet: table to update is nil") } @@ -387,7 +385,7 @@ func (i *ClauseInsert) GetColumns() []Column { // Serialize serializes clause into SQLBuilder func (i *ClauseInsert) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) { - if utils.IsNil(i.Table) { + if is.Nil(i.Table) { panic("jet: table is nil for INSERT clause") } diff --git a/internal/jet/sql_builder.go b/internal/jet/sql_builder.go index 96ef2e6..46f47ad 100644 --- a/internal/jet/sql_builder.go +++ b/internal/jet/sql_builder.go @@ -5,7 +5,7 @@ import ( "database/sql/driver" "fmt" "github.com/go-jet/jet/v2/internal/3rdparty/pq" - "github.com/go-jet/jet/v2/internal/utils" + "github.com/go-jet/jet/v2/internal/utils/is" "github.com/google/uuid" "reflect" "sort" @@ -206,7 +206,7 @@ func (s *SQLBuilder) insertRawQuery(raw string, namedArg map[string]interface{}) } func argToString(value interface{}) string { - if utils.IsNil(value) { + if is.Nil(value) { return "NULL" } diff --git a/internal/jet/table.go b/internal/jet/table.go index c69a1dc..e14b962 100644 --- a/internal/jet/table.go +++ b/internal/jet/table.go @@ -1,8 +1,6 @@ package jet -import ( - "github.com/go-jet/jet/v2/internal/utils" -) +import "github.com/go-jet/jet/v2/internal/utils/is" // SerializerTable interface type SerializerTable interface { @@ -158,7 +156,7 @@ func (t *joinTableImpl) serialize(statement StatementType, out *SQLBuilder, opti panic("jet: Join table is nil. ") } - if utils.IsNil(t.lhs) { + if is.Nil(t.lhs) { panic("jet: left hand side of join operation is nil table") } @@ -179,7 +177,7 @@ func (t *joinTableImpl) serialize(statement StatementType, out *SQLBuilder, opti out.WriteString("CROSS JOIN") } - if utils.IsNil(t.rhs) { + if is.Nil(t.rhs) { panic("jet: right hand side of join operation is nil table") } diff --git a/internal/jet/utils.go b/internal/jet/utils.go index 4ab3fae..fe29a09 100644 --- a/internal/jet/utils.go +++ b/internal/jet/utils.go @@ -1,7 +1,8 @@ package jet import ( - "github.com/go-jet/jet/v2/internal/utils" + "github.com/go-jet/jet/v2/internal/utils/dbidentifier" + "github.com/go-jet/jet/v2/internal/utils/must" "reflect" "strings" ) @@ -150,11 +151,11 @@ func UnwindRowFromModel(columns []Column, data interface{}) []Serializer { row := []Serializer{} - utils.ValueMustBe(structValue, reflect.Struct, "jet: data has to be a struct") + must.ValueBeOfTypeKind(structValue, reflect.Struct, "jet: data has to be a struct") for _, column := range columns { columnName := column.Name() - structFieldName := utils.ToGoIdentifier(columnName) + structFieldName := dbidentifier.ToGoIdentifier(columnName) structField := structValue.FieldByName(structFieldName) @@ -179,7 +180,7 @@ func UnwindRowFromModel(columns []Column, data interface{}) []Serializer { // UnwindRowsFromModels func func UnwindRowsFromModels(columns []Column, data interface{}) [][]Serializer { sliceValue := reflect.Indirect(reflect.ValueOf(data)) - utils.ValueMustBe(sliceValue, reflect.Slice, "jet: data has to be a slice.") + must.ValueBeOfTypeKind(sliceValue, reflect.Slice, "jet: data has to be a slice.") rows := [][]Serializer{} diff --git a/internal/utils/datetime/duration.go b/internal/utils/datetime/duration.go new file mode 100644 index 0000000..11cc57f --- /dev/null +++ b/internal/utils/datetime/duration.go @@ -0,0 +1,22 @@ +package datetime + +import "time" + +// ExtractTimeComponents extracts number of days, hours, minutes, seconds, microseconds from duration +func ExtractTimeComponents(duration time.Duration) (days, hours, minutes, seconds, microseconds int64) { + days = int64(duration / (24 * time.Hour)) + reminder := duration % (24 * time.Hour) + + hours = int64(reminder / time.Hour) + reminder = reminder % time.Hour + + minutes = int64(reminder / time.Minute) + reminder = reminder % time.Minute + + seconds = int64(reminder / time.Second) + reminder = reminder % time.Second + + microseconds = int64(reminder / time.Microsecond) + + return +} diff --git a/internal/utils/dbidentifier/dbidentifier.go b/internal/utils/dbidentifier/dbidentifier.go new file mode 100644 index 0000000..c4278f6 --- /dev/null +++ b/internal/utils/dbidentifier/dbidentifier.go @@ -0,0 +1,24 @@ +package dbidentifier + +import ( + "github.com/go-jet/jet/v2/internal/3rdparty/snaker" + "strings" +) + +// ToGoIdentifier converts database identifier to Go identifier. +func ToGoIdentifier(databaseIdentifier string) string { + return snaker.SnakeToCamel(replaceInvalidChars(databaseIdentifier)) +} + +// ToGoFileName converts database identifier to Go file name. +func ToGoFileName(databaseIdentifier string) string { + return strings.ToLower(replaceInvalidChars(databaseIdentifier)) +} + +func replaceInvalidChars(str string) string { + str = strings.Replace(str, " ", "_", -1) + str = strings.Replace(str, "-", "_", -1) + str = strings.Replace(str, ".", "_", -1) + + return str +} diff --git a/internal/utils/utils_test.go b/internal/utils/dbidentifier/dbidentifier_test.go similarity index 72% rename from internal/utils/utils_test.go rename to internal/utils/dbidentifier/dbidentifier_test.go index f374929..339fb5a 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/dbidentifier/dbidentifier_test.go @@ -1,7 +1,6 @@ -package utils +package dbidentifier import ( - "fmt" "github.com/stretchr/testify/require" "testing" ) @@ -24,27 +23,3 @@ func TestToGoIdentifier(t *testing.T) { require.Equal(t, ToGoIdentifier("My Table"), "MyTable") require.Equal(t, ToGoIdentifier("My-Table"), "MyTable") } - -func TestErrorCatchErr(t *testing.T) { - var err error - - func() { - defer ErrorCatch(&err) - - panic(fmt.Errorf("newError")) - }() - - require.Error(t, err, "newError") -} - -func TestErrorCatchNonErr(t *testing.T) { - var err error - - func() { - defer ErrorCatch(&err) - - panic(11) - }() - - require.Error(t, err, "11") -} diff --git a/internal/utils/filesys/filesys.go b/internal/utils/filesys/filesys.go new file mode 100644 index 0000000..d904be8 --- /dev/null +++ b/internal/utils/filesys/filesys.go @@ -0,0 +1,85 @@ +package filesys + +import ( + "fmt" + "go/format" + "os" + "path/filepath" + "strings" +) + +// FormatAndSaveGoFile saves go file at folder dir, with name fileName and contents text. +func FormatAndSaveGoFile(dirPath, fileName string, text []byte) error { + newGoFilePath := filepath.Join(dirPath, fileName) + + if !strings.HasSuffix(newGoFilePath, ".go") { + newGoFilePath += ".go" + } + + file, err := os.Create(newGoFilePath) + + if err != nil { + return err + } + + defer file.Close() + + p, err := format.Source(text) + + // if there is a format error we will write unformulated text for debug purposes + if err != nil { + file.Write(text) + return fmt.Errorf("failed to format '%s', check '%s' for syntax errors: %w", fileName, newGoFilePath, err) + } + + _, err = file.Write(p) + if err != nil { + return fmt.Errorf("failed to save '%s' file: %w", newGoFilePath, err) + } + + return nil +} + +// EnsureDirPathExist ensures dir path exists. If path does not exist, creates new path. +func EnsureDirPathExist(dirPath string) error { + if _, err := os.Stat(dirPath); os.IsNotExist(err) { + err := os.MkdirAll(dirPath, os.ModePerm) + + if err != nil { + return fmt.Errorf("can't create directory - %s: %w", dirPath, err) + } + } + + return nil +} + +// RemoveDir deletes everything at folder dir. +func RemoveDir(dir string) error { + exist, err := DirExists(dir) + + if err != nil { + return err + } + + if exist { + err := os.RemoveAll(dir) + + if err != nil { + return err + } + } + + return nil +} + +// DirExists checks if folder at path exist. +func DirExists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return true, err +} diff --git a/internal/utils/is/is.go b/internal/utils/is/is.go new file mode 100644 index 0000000..8824b06 --- /dev/null +++ b/internal/utils/is/is.go @@ -0,0 +1,8 @@ +package is + +import "reflect" + +// Nil check if v is nil +func Nil(v interface{}) bool { + return v == nil || (reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil()) +} diff --git a/internal/utils/must/must.go b/internal/utils/must/must.go new file mode 100644 index 0000000..306d2f2 --- /dev/null +++ b/internal/utils/must/must.go @@ -0,0 +1,41 @@ +package must + +import ( + "github.com/go-jet/jet/v2/internal/utils/is" + "reflect" +) + +// BeTrue panics when condition is false +func BeTrue(condition bool, errorStr string) { + if !condition { + panic(errorStr) + } +} + +// BeTypeKind panics with errorStr error, if v interface is not of reflect kind +func BeTypeKind(v interface{}, kind reflect.Kind, errorStr string) { + if reflect.TypeOf(v).Kind() != kind { + panic(errorStr) + } +} + +// ValueBeOfTypeKind panics with errorStr error, if v value is not of reflect kind +func ValueBeOfTypeKind(v reflect.Value, kind reflect.Kind, errorStr string) { + if v.Kind() != kind { + panic(errorStr) + } +} + +// TypeBeOfKind panics with errorStr error, if v type is not of reflect kind +func TypeBeOfKind(v reflect.Type, kind reflect.Kind, errorStr string) { + if v.Kind() != kind { + panic(errorStr) + } +} + +// BeInitializedPtr panics with errorStr if val interface is nil +func BeInitializedPtr(val interface{}, errorStr string) { + if is.Nil(val) { + panic(errorStr) + } +} diff --git a/internal/utils/strslice/strslice.go b/internal/utils/strslice/strslice.go new file mode 100644 index 0000000..757e423 --- /dev/null +++ b/internal/utils/strslice/strslice.go @@ -0,0 +1,12 @@ +package strslice + +// Contains checks if slice of strings contains a string +func Contains(strings []string, contains string) bool { + for _, str := range strings { + if str == contains { + return true + } + } + + return false +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go deleted file mode 100644 index a9eb8c8..0000000 --- a/internal/utils/utils.go +++ /dev/null @@ -1,203 +0,0 @@ -package utils - -import ( - "database/sql" - "fmt" - "github.com/go-jet/jet/v2/internal/3rdparty/snaker" - "go/format" - "os" - "path/filepath" - "reflect" - "strings" - "time" -) - -// ToGoIdentifier converts database to Go identifier. -func ToGoIdentifier(databaseIdentifier string) string { - return snaker.SnakeToCamel(replaceInvalidChars(databaseIdentifier)) -} - -// ToGoFileName converts database identifier to Go file name. -func ToGoFileName(databaseIdentifier string) string { - return strings.ToLower(replaceInvalidChars(databaseIdentifier)) -} - -// SaveGoFile saves go file at folder dir, with name fileName and contents text. -func SaveGoFile(dirPath, fileName string, text []byte) error { - newGoFilePath := filepath.Join(dirPath, fileName) - - if !strings.HasSuffix(newGoFilePath, ".go") { - newGoFilePath += ".go" - } - - file, err := os.Create(newGoFilePath) - - if err != nil { - return err - } - - defer file.Close() - - p, err := format.Source(text) - - // if there is a format error we will write unformulated text for debug purposes - if err != nil { - file.Write(text) - return fmt.Errorf("failed to format '%s', check '%s' for syntax errors: %w", fileName, newGoFilePath, err) - } - - _, err = file.Write(p) - if err != nil { - return fmt.Errorf("failed to save '%s' file: %w", newGoFilePath, err) - } - - return nil -} - -// EnsureDirPath ensures dir path exists. If path does not exist, creates new path. -func EnsureDirPath(dirPath string) error { - if _, err := os.Stat(dirPath); os.IsNotExist(err) { - err := os.MkdirAll(dirPath, os.ModePerm) - - if err != nil { - return fmt.Errorf("can't create directory - %s: %w", dirPath, err) - } - } - - return nil -} - -// CleanUpGeneratedFiles deletes everything at folder dir. -func CleanUpGeneratedFiles(dir string) error { - exist, err := DirExists(dir) - - if err != nil { - return err - } - - if exist { - err := os.RemoveAll(dir) - - if err != nil { - return err - } - } - - return nil -} - -// DBClose closes non nil db connection -func DBClose(db *sql.DB) { - if db == nil { - return - } - - db.Close() -} - -// DirExists checks if folder at path exist. -func DirExists(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return true, err -} - -func replaceInvalidChars(str string) string { - str = strings.Replace(str, " ", "_", -1) - str = strings.Replace(str, "-", "_", -1) - str = strings.Replace(str, ".", "_", -1) - - return str -} - -// IsNil check if v is nil -func IsNil(v interface{}) bool { - return v == nil || (reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil()) -} - -// MustBeTrue panics when condition is false -func MustBeTrue(condition bool, errorStr string) { - if !condition { - panic(errorStr) - } -} - -// MustBe panics with errorStr error, if v interface is not of reflect kind -func MustBe(v interface{}, kind reflect.Kind, errorStr string) { - if reflect.TypeOf(v).Kind() != kind { - panic(errorStr) - } -} - -// ValueMustBe panics with errorStr error, if v value is not of reflect kind -func ValueMustBe(v reflect.Value, kind reflect.Kind, errorStr string) { - if v.Kind() != kind { - panic(errorStr) - } -} - -// TypeMustBe panics with errorStr error, if v type is not of reflect kind -func TypeMustBe(v reflect.Type, kind reflect.Kind, errorStr string) { - if v.Kind() != kind { - panic(errorStr) - } -} - -// MustBeInitializedPtr panics with errorStr if val interface is nil -func MustBeInitializedPtr(val interface{}, errorStr string) { - if IsNil(val) { - panic(errorStr) - } -} - -// ErrorCatch is used in defer to recover from panics and to set err -func ErrorCatch(err *error) { - recovered := recover() - - if recovered == nil { - return - } - - recoveredErr, isError := recovered.(error) - - if isError { - *err = recoveredErr - } else { - *err = fmt.Errorf("%v", recovered) - } -} - -// StringSliceContains checks if slice of strings contains a string -func StringSliceContains(strings []string, contains string) bool { - for _, str := range strings { - if str == contains { - return true - } - } - - return false -} - -// ExtractDateTimeComponents extracts number of days, hours, minutes, seconds, microseconds from duration -func ExtractDateTimeComponents(duration time.Duration) (days, hours, minutes, seconds, microseconds int64) { - days = int64(duration / (24 * time.Hour)) - reminder := duration % (24 * time.Hour) - - hours = int64(reminder / time.Hour) - reminder = reminder % time.Hour - - minutes = int64(reminder / time.Minute) - reminder = reminder % time.Minute - - seconds = int64(reminder / time.Second) - reminder = reminder % time.Second - - microseconds = int64(reminder / time.Microsecond) - - return -} diff --git a/mysql/interval.go b/mysql/interval.go index 23dcd36..ce1c609 100644 --- a/mysql/interval.go +++ b/mysql/interval.go @@ -2,11 +2,11 @@ package mysql import ( "fmt" + "github.com/go-jet/jet/v2/internal/utils/datetime" "regexp" "time" "github.com/go-jet/jet/v2/internal/jet" - "github.com/go-jet/jet/v2/internal/utils" ) type unitType string @@ -112,7 +112,7 @@ func INTERVALd(duration time.Duration) Interval { duration = -duration } - days, hours, minutes, sec, microsec := utils.ExtractDateTimeComponents(duration) + days, hours, minutes, sec, microsec := datetime.ExtractTimeComponents(duration) if days != 0 { switch { diff --git a/postgres/interval_expression.go b/postgres/interval_expression.go index 323aa31..91d8d8f 100644 --- a/postgres/interval_expression.go +++ b/postgres/interval_expression.go @@ -3,7 +3,7 @@ package postgres import ( "fmt" "github.com/go-jet/jet/v2/internal/jet" - "github.com/go-jet/jet/v2/internal/utils" + "github.com/go-jet/jet/v2/internal/utils/datetime" "strconv" "strings" "time" @@ -148,9 +148,9 @@ func INTERVAL(quantityAndUnit ...quantityAndUnit) IntervalExpression { // INTERVALd creates interval expression from time.Duration func INTERVALd(duration time.Duration) IntervalExpression { - days, hours, minutes, seconds, microseconds := utils.ExtractDateTimeComponents(duration) + days, hours, minutes, seconds, microseconds := datetime.ExtractTimeComponents(duration) - quantityAndUnits := []quantityAndUnit{} + var quantityAndUnits []quantityAndUnit if days > 0 { quantityAndUnits = append(quantityAndUnits, quantityAndUnit(days)) diff --git a/qrm/qrm.go b/qrm/qrm.go index 9e4ec11..edd9387 100644 --- a/qrm/qrm.go +++ b/qrm/qrm.go @@ -5,9 +5,8 @@ import ( "database/sql" "errors" "fmt" + "github.com/go-jet/jet/v2/internal/utils/must" "reflect" - - "github.com/go-jet/jet/v2/internal/utils" ) // ErrNoRows is returned by Query when query result set is empty @@ -19,9 +18,9 @@ var ErrNoRows = errors.New("qrm: no rows in result set") // If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows. func Query(ctx context.Context, db Queryable, query string, args []interface{}, destPtr interface{}) (rowsProcessed int64, err error) { - utils.MustBeInitializedPtr(db, "jet: db is nil") - utils.MustBeInitializedPtr(destPtr, "jet: destination is nil") - utils.MustBe(destPtr, reflect.Ptr, "jet: destination has to be a pointer to slice or pointer to struct") + must.BeInitializedPtr(db, "jet: db is nil") + must.BeInitializedPtr(destPtr, "jet: destination is nil") + must.BeTypeKind(destPtr, reflect.Ptr, "jet: destination has to be a pointer to slice or pointer to struct") destinationPtrType := reflect.TypeOf(destPtr) @@ -64,8 +63,8 @@ func Query(ctx context.Context, db Queryable, query string, args []interface{}, // ScanOneRowToDest will scan one row into struct destination func ScanOneRowToDest(scanContext *ScanContext, rows *sql.Rows, destPtr interface{}) error { - utils.MustBeInitializedPtr(destPtr, "jet: destination is nil") - utils.MustBe(destPtr, reflect.Ptr, "jet: destination has to be a pointer to slice or pointer to struct") + must.BeInitializedPtr(destPtr, "jet: destination is nil") + must.BeTypeKind(destPtr, reflect.Ptr, "jet: destination has to be a pointer to slice or pointer to struct") if len(scanContext.row) == 0 { return errors.New("empty row slice") @@ -149,7 +148,7 @@ func mapRowToSlice( return } - utils.TypeMustBe(sliceElemType, reflect.Struct, "jet: unsupported slice element type"+fieldToString(field)) + must.TypeBeOfKind(sliceElemType, reflect.Struct, "jet: unsupported slice element type"+fieldToString(field)) structGroupKey := scanContext.getGroupKey(sliceElemType, field) @@ -324,7 +323,7 @@ func mapRowToDestinationPtr( destPtrValue reflect.Value, structField *reflect.StructField) (updated bool, err error) { - utils.ValueMustBe(destPtrValue, reflect.Ptr, "jet: internal error. Destination is not pointer.") + must.ValueBeOfTypeKind(destPtrValue, reflect.Ptr, "jet: internal error. Destination is not pointer.") destValueKind := destPtrValue.Elem().Kind() diff --git a/qrm/utill.go b/qrm/utill.go index e536659..b43ee29 100644 --- a/qrm/utill.go +++ b/qrm/utill.go @@ -3,7 +3,8 @@ package qrm import ( "database/sql" "fmt" - "github.com/go-jet/jet/v2/internal/utils" + "github.com/go-jet/jet/v2/internal/utils/must" + "github.com/go-jet/jet/v2/internal/utils/strslice" "github.com/go-jet/jet/v2/qrm/internal" "github.com/google/uuid" "reflect" @@ -50,7 +51,7 @@ func getSliceElemPtrAt(slicePtrValue reflect.Value, index int) reflect.Value { } func appendElemToSlice(slicePtrValue reflect.Value, objPtrValue reflect.Value) error { - utils.MustBeTrue(!slicePtrValue.IsNil(), "jet: internal, slice is nil") + must.BeTrue(!slicePtrValue.IsNil(), "jet: internal, slice is nil") sliceValue := slicePtrValue.Elem() sliceElemType := sliceValue.Type().Elem() @@ -306,7 +307,7 @@ func setZeroValue(value reflect.Value) { func isPrimaryKey(field reflect.StructField, primaryKeyOverwrites []string) bool { if len(primaryKeyOverwrites) > 0 { - return utils.StringSliceContains(primaryKeyOverwrites, field.Name) + return strslice.Contains(primaryKeyOverwrites, field.Name) } sqlTag := field.Tag.Get("sql") diff --git a/tests/mysql/generator_template_test.go b/tests/mysql/generator_template_test.go index 6af636b..a47a21d 100644 --- a/tests/mysql/generator_template_test.go +++ b/tests/mysql/generator_template_test.go @@ -7,7 +7,7 @@ import ( mysql2 "github.com/go-jet/jet/v2/generator/mysql" "github.com/go-jet/jet/v2/generator/template" "github.com/go-jet/jet/v2/internal/3rdparty/snaker" - "github.com/go-jet/jet/v2/internal/utils" + "github.com/go-jet/jet/v2/internal/utils/dbidentifier" postgres2 "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/dbconfig" file2 "github.com/go-jet/jet/v2/tests/internal/utils/file" @@ -157,17 +157,17 @@ func TestGeneratorTemplate_Model_RenameFilesAndTypes(t *testing.T) { UseTable(func(table metadata.Table) template.TableModel { return template.DefaultTableModel(table). UseFileName(schemaMetaData.Name + "_" + table.Name). - UseTypeName(utils.ToGoIdentifier(table.Name) + "Table") + UseTypeName(dbidentifier.ToGoIdentifier(table.Name) + "Table") }). UseView(func(table metadata.Table) template.ViewModel { return template.DefaultViewModel(table). UseFileName(schemaMetaData.Name + "_" + table.Name + "_view"). - UseTypeName(utils.ToGoIdentifier(table.Name) + "View") + UseTypeName(dbidentifier.ToGoIdentifier(table.Name) + "View") }). UseEnum(func(enumMetaData metadata.Enum) template.EnumModel { return template.DefaultEnumModel(enumMetaData). UseFileName(enumMetaData.Name + "_enum"). - UseTypeName(utils.ToGoIdentifier(enumMetaData.Name) + "Enum") + UseTypeName(dbidentifier.ToGoIdentifier(enumMetaData.Name) + "Enum") }), ) }), @@ -256,19 +256,19 @@ func TestGeneratorTemplate_SQLBuilder_ChangeTypeAndFileName(t *testing.T) { UseTable(func(table metadata.Table) template.TableSQLBuilder { return template.DefaultTableSQLBuilder(table). UseFileName(schemaMetaData.Name + "_" + table.Name + "_table"). - UseTypeName(utils.ToGoIdentifier(table.Name) + "TableSQLBuilder"). - UseInstanceName("T_" + utils.ToGoIdentifier(table.Name)) + UseTypeName(dbidentifier.ToGoIdentifier(table.Name) + "TableSQLBuilder"). + UseInstanceName("T_" + dbidentifier.ToGoIdentifier(table.Name)) }). UseView(func(table metadata.Table) template.ViewSQLBuilder { return template.DefaultViewSQLBuilder(table). UseFileName(schemaMetaData.Name + "_" + table.Name + "_view"). - UseTypeName(utils.ToGoIdentifier(table.Name) + "ViewSQLBuilder"). - UseInstanceName("V_" + utils.ToGoIdentifier(table.Name)) + UseTypeName(dbidentifier.ToGoIdentifier(table.Name) + "ViewSQLBuilder"). + UseInstanceName("V_" + dbidentifier.ToGoIdentifier(table.Name)) }). UseEnum(func(enum metadata.Enum) template.EnumSQLBuilder { return template.DefaultEnumSQLBuilder(enum). UseFileName(schemaMetaData.Name + "_" + enum.Name + "_enum"). - UseInstanceName(utils.ToGoIdentifier(enum.Name) + "EnumSQLBuilder") + UseInstanceName(dbidentifier.ToGoIdentifier(enum.Name) + "EnumSQLBuilder") }), ) }), diff --git a/tests/postgres/generator_template_test.go b/tests/postgres/generator_template_test.go index e064e31..db788ee 100644 --- a/tests/postgres/generator_template_test.go +++ b/tests/postgres/generator_template_test.go @@ -8,7 +8,7 @@ import ( "github.com/go-jet/jet/v2/generator/template" "github.com/go-jet/jet/v2/internal/3rdparty/snaker" "github.com/go-jet/jet/v2/internal/testutils" - "github.com/go-jet/jet/v2/internal/utils" + "github.com/go-jet/jet/v2/internal/utils/dbidentifier" postgres2 "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/dbconfig" file2 "github.com/go-jet/jet/v2/tests/internal/utils/file" @@ -144,17 +144,17 @@ func TestGeneratorTemplate_Model_RenameFilesAndTypes(t *testing.T) { UseTable(func(table metadata.Table) template.TableModel { return template.DefaultTableModel(table). UseFileName(schemaMetaData.Name + "_" + table.Name). - UseTypeName(utils.ToGoIdentifier(table.Name) + "Table") + UseTypeName(dbidentifier.ToGoIdentifier(table.Name) + "Table") }). UseView(func(table metadata.Table) template.ViewModel { return template.DefaultViewModel(table). UseFileName(schemaMetaData.Name + "_" + table.Name + "_view"). - UseTypeName(utils.ToGoIdentifier(table.Name) + "View") + UseTypeName(dbidentifier.ToGoIdentifier(table.Name) + "View") }). UseEnum(func(enumMetaData metadata.Enum) template.EnumModel { return template.DefaultEnumModel(enumMetaData). UseFileName(enumMetaData.Name + "_enum"). - UseTypeName(utils.ToGoIdentifier(enumMetaData.Name) + "Enum") + UseTypeName(dbidentifier.ToGoIdentifier(enumMetaData.Name) + "Enum") }), ) }), @@ -280,19 +280,19 @@ func TestGeneratorTemplate_SQLBuilder_ChangeTypeAndFileName(t *testing.T) { UseTable(func(table metadata.Table) template.TableSQLBuilder { return template.DefaultTableSQLBuilder(table). UseFileName(schemaMetaData.Name + "_" + table.Name + "_table"). - UseTypeName(utils.ToGoIdentifier(table.Name) + "TableSQLBuilder"). - UseInstanceName("T_" + utils.ToGoIdentifier(table.Name)) + UseTypeName(dbidentifier.ToGoIdentifier(table.Name) + "TableSQLBuilder"). + UseInstanceName("T_" + dbidentifier.ToGoIdentifier(table.Name)) }). UseView(func(table metadata.Table) template.ViewSQLBuilder { return template.DefaultViewSQLBuilder(table). UseFileName(schemaMetaData.Name + "_" + table.Name + "_view"). - UseTypeName(utils.ToGoIdentifier(table.Name) + "ViewSQLBuilder"). - UseInstanceName("V_" + utils.ToGoIdentifier(table.Name)) + UseTypeName(dbidentifier.ToGoIdentifier(table.Name) + "ViewSQLBuilder"). + UseInstanceName("V_" + dbidentifier.ToGoIdentifier(table.Name)) }). UseEnum(func(enum metadata.Enum) template.EnumSQLBuilder { return template.DefaultEnumSQLBuilder(enum). UseFileName(schemaMetaData.Name + "_" + enum.Name + "_enum"). - UseInstanceName(utils.ToGoIdentifier(enum.Name) + "EnumSQLBuilder") + UseInstanceName(dbidentifier.ToGoIdentifier(enum.Name) + "EnumSQLBuilder") }), ) }),