diff --git a/db/MakeDao.go b/db/MakeDao.go new file mode 100644 index 0000000..dc616bc --- /dev/null +++ b/db/MakeDao.go @@ -0,0 +1,663 @@ +package db + +import ( + _ "embed" + "github.com/ssgo/db" + "github.com/ssgo/log" + "github.com/ssgo/u" + "io/ioutil" + "os" + "path" + "regexp" + "strings" + "text/template" +) + +//go:embed dao.js +var daoTpl string + +//go:embed dao_ext.js +var daoExtTpl string + +type DaoData struct { + DBName string + FixedDBName string + RandomTag string + VersionField string + //TableNames []string + Tables []TableData + FixedTables []string +} + +type TableData struct { + DBName string + TableName string + FixedTableName string + IsAutoId bool + AutoIdField string + AutoIdFieldType string + PrimaryKey *IndexField + UniqueKeys map[string]*IndexField + IndexKeys map[string]*IndexField + Fields []FieldData + PointFields []FieldData + //FieldsWithoutAutoId []FieldData + SelectFields string + ValidFieldConfig ValidFieldConfig + ValidField string + ValidWhere string + ValidSet string + InvalidSet string + VersionField string + HasVersion bool + AutoGenerated []string + AutoGeneratedOnUpdate []string +} + +type TableDesc struct { + Field string + Type string + Null string + Key string + Default string + Extra string + After string +} + +type TableIndex struct { + Non_unique int + Key_name string + Seq_in_index int + Column_name string +} + +type FieldData struct { + Name string + Type string + Default string + Options map[string]string +} + +type IndexField struct { + Name string + Where string + Args string + Params string + ItemArgs string + StringArgs string +} + +type ValidFieldConfig struct { + Field string + Type string + ValidOperator string + ValidValue string + ValidSetOperator string + ValidSetValue string + InvalidSetOperator string + InvalidSetValue string +} + +type DaoConfig struct { + VersionField string + ValidFields []ValidFieldConfig +} + +func MakeDao(outputPath string, conn *db.DB, conf *DaoConfig, logger *log.Logger) error { + if conf == nil { + conf = &DaoConfig{} + } + if logger == nil { + logger = log.DefaultLogger + } + + if conf.VersionField == "" { + conf.VersionField = "version" + } + + if conf.ValidFields == nil { + conf.ValidFields = []ValidFieldConfig{ + { + Field: "isValid", + Type: "tinyint", + ValidOperator: "!=", + ValidValue: "0", + ValidSetOperator: "=", + ValidSetValue: "1", + InvalidSetOperator: "=", + InvalidSetValue: "0", + }, + { + Field: "isActive", + Type: "tinyint", + ValidOperator: "!=", + ValidValue: "0", + ValidSetOperator: "=", + ValidSetValue: "1", + InvalidSetOperator: "=", + InvalidSetValue: "0", + }, + { + Field: "deleted", + Type: "tinyint", + ValidOperator: "=", + ValidValue: "0", + ValidSetOperator: "=", + ValidSetValue: "0", + InvalidSetOperator: "=", + InvalidSetValue: "1", + }, + { + Field: "status", + Type: "tinyint", + ValidOperator: "!=", + ValidValue: "0", + ValidSetOperator: "=", + ValidSetValue: "1", + InvalidSetOperator: "=", + InvalidSetValue: "0", + }, + } + } + + numberTester := regexp.MustCompile("^[0-9]+$") + for k, validFieldInfo := range conf.ValidFields { + if !numberTester.MatchString(validFieldInfo.ValidValue) { + conf.ValidFields[k].ValidValue = "'" + validFieldInfo.ValidValue + "'" + } + if !numberTester.MatchString(validFieldInfo.InvalidSetValue) { + conf.ValidFields[k].InvalidSetValue = "'" + validFieldInfo.InvalidSetValue + "'" + } + } + + var tableListResult *db.QueryResult + if conn.Config.Type == "sqlite3" { + tableListResult = conn.Query("SELECT name FROM sqlite_master WHERE type='table'") + } else { //if conn.Config.Type == "mysql" { + tableListResult = conn.Query("SHOW TABLES") + } + if tableListResult.Error != nil { + logger.Error("failed to connect to db: " + tableListResult.Error.Error()) + return tableListResult.Error + } + + tableNames := make([]string, 0) + fixedTables := make([]string, 0) + for _, table := range tableListResult.StringsOnC1() { + if strings.HasPrefix(table, "_") || strings.HasPrefix(table, ".") { + continue + } + tableNames = append(tableNames, table) + fixedTables = append(fixedTables, strings.ToUpper(table[0:1])+table[1:]) + } + + dbName := conn.Config.DB + dbFile := path.Join(outputPath, "dao.js") + u.CheckPath(dbFile) + + if files, err := ioutil.ReadDir(outputPath); err == nil { + for _, file := range files { + if strings.HasPrefix(file.Name(), "a_") && strings.HasSuffix(file.Name(), ".go") { + _ = os.Remove(path.Join(outputPath, file.Name())) + } + } + } + + tableDataList := make([]TableData, 0) + + enumTypeExists := map[string]bool{} + for i, table := range tableNames { + fixedTableName := fixedTables[i] + //tableFile := path.Join(outputPath, "a_"+table+".go") + + descs := make([]TableDesc, 0) + indexs := make([]TableIndex, 0) + var err error + if conn.Config.Type == "sqlite3" { + if table == "sqlite_sequence" { + continue + } + tableSql := conn.Query("SELECT `sql` FROM `sqlite_master` WHERE `type`='table' AND `name`='" + table + "'").StringOnR1C1() + + tableM := ddlTableMatcher.FindStringSubmatch(u.String(tableSql)) + //fmt.Println(u.JsonP(tableM), 111) + if tableM != nil { + fieldsM := ddlFieldMatcher.FindAllStringSubmatch(tableM[2], 2000) + //fmt.Println(tableM[2], u.JsonP(fieldsM), 111) + if fieldsM != nil { + for _, m := range fieldsM { + extra := "" + //if m[1] == "PRIMARY" && m[2] == "KEY" { + if strings.Contains(m[2], " AUTOINCREMENT") { + m[2] = strings.Replace(m[2], " AUTOINCREMENT", "", 1) + extra = "auto_increment" + } + if strings.Contains(m[2], " PRIMARY KEY") { + m[2] = strings.Replace(m[2], " PRIMARY KEY", "", 1) + indexs = append(indexs, TableIndex{ + Non_unique: 0, + Key_name: "PRIMARY", + Column_name: m[1], + }) + } + //if m[1] == "PRIMARY" && m[2] == "KEY" { + // keysM := ddlKeyMatcher.FindAllStringSubmatch(m[3], 20) + // if keysM != nil { + // for _, km := range keysM { + // indexs = append(indexs, TableIndex{ + // Non_unique: 0, + // Key_name: "PRIMARY", + // Column_name: km[1], + // }) + // //oldIndexInfos = append(oldIndexInfos, &TableKeyDesc{ + // // Key_name: "PRIMARY", + // // Column_name: km[1], + // //}) + // } + // } + //} + + nullSet := "NULL" + //fmt.Println(" =====", m[0], m[1], m[2]) + if ddlNotNullMatcher.MatchString(m[2]) { + m[2] = ddlNotNullMatcher.ReplaceAllString(m[2], "") + nullSet = "NOT NULL" + } else if ddlNullMatcher.MatchString(m[2]) { + m[2] = ddlNullMatcher.ReplaceAllString(m[2], "") + nullSet = "NULL" + } + //fmt.Println(" =====", m[2], "|", nullSet) + + //if m[] + descs = append(descs, TableDesc{ + Field: m[1], + Type: m[2], + Null: u.StringIf(nullSet == "NOT NULL", "NO", "YES"), + Key: "", + Default: "", + Extra: extra, + After: "", + }) + //oldFieldList = append(oldFieldList, &TableFieldDesc{ + // Field: m[1], + // Type: m[2], + // //Null: u.StringIf(strings.Contains(m[3], "NOT NULL"), "NO", "YES"), + // Null: u.StringIf(nullSet == "NOT NULL", "NO", "YES"), + // Key: "", + // Default: "", + // Extra: "", + // After: "", + //}) + } + } + //fmt.Println(u.JsonP(fieldsM), 222) + } + + // 读取索引信息 + for _, indexInfo := range conn.Query("SELECT `name`, `sql` FROM `sqlite_master` WHERE `type`='index' AND `tbl_name`='" + table + "'").StringMapResults() { + //fmt.Println(u.JsonP(indexInfo), 777) + indexM := ddlIndexMatcher.FindStringSubmatch(indexInfo["sql"]) + if indexM != nil { + //fmt.Println(u.JsonP(indexM), 666) + isNotUnique := 1 + if strings.Contains(indexM[1], "UNIQUE") { + isNotUnique = 0 + } + indexFieldM := ddlIndexFieldMatcher.FindAllStringSubmatch(indexM[4], 20) + //fmt.Println(u.JsonP(indexFieldM), 555) + if indexFieldM != nil { + for _, km := range indexFieldM { + indexs = append(indexs, TableIndex{ + Non_unique: isNotUnique, + Key_name: indexInfo["name"], + Column_name: km[1], + }) + //oldIndexInfos = append(oldIndexInfos, &TableKeyDesc{ + // Key_name: indexInfo["name"], + // Column_name: km[1], + //}) + } + } + } + } + } else { + err = conn.Query("DESC `" + table + "`").To(&descs) + if err == nil { + err = conn.Query("SHOW INDEX FROM `" + table + "`").To(&indexs) + } + } + if err != nil { + logger.Error("failed to get table info: "+err.Error(), "db", dbName, "table", table) + continue + } + //fmt.Println(u.JsonP(indexs), 123) + tableData := TableData{ + DBName: dbName, + TableName: table, + FixedTableName: fixedTableName, + IsAutoId: false, + AutoIdField: "", + AutoIdFieldType: "", + PrimaryKey: nil, + UniqueKeys: make(map[string]*IndexField), + IndexKeys: make(map[string]*IndexField), + Fields: make([]FieldData, 0), + PointFields: make([]FieldData, 0), + SelectFields: "", + ValidField: "", + ValidWhere: "", + ValidFieldConfig: ValidFieldConfig{}, + ValidSet: "", + InvalidSet: "", + VersionField: conf.VersionField, + HasVersion: false, + AutoGenerated: make([]string, 0), + AutoGeneratedOnUpdate: make([]string, 0), + } + fields := make([]string, 0) + fieldTypesForId := map[string]string{} + idFields := make([]string, 0) + idFieldsUpper := make([]string, 0) + idFieldParams := make([]string, 0) + idFieldItemArgs := make([]string, 0) + uniqueFields := map[string][]string{} + uniqueFieldsUpper := map[string][]string{} + uniqueFieldParams := map[string][]string{} + uniqueFieldItemArgs := map[string][]string{} + indexFields := map[string][]string{} + indexFieldsUpper := map[string][]string{} + indexFieldParams := map[string][]string{} + indexFieldItemArgs := map[string][]string{} + + for _, desc := range descs { + if strings.Contains(desc.Extra, "auto_increment") { + tableData.IsAutoId = true + //tableData.AutoIdField = u.GetUpperName(desc.Field) + tableData.AutoIdField = desc.Field + tableData.AutoGenerated = append(tableData.AutoGenerated, desc.Field) + } + + // DEFAULT_GENERATED on update CURRENT_TIMESTAMP + if strings.Contains(desc.Extra, "DEFAULT_GENERATED") { + if strings.Contains(desc.Extra, "on update") { + tableData.AutoGeneratedOnUpdate = append(tableData.AutoGeneratedOnUpdate, desc.Field) + } else { + tableData.AutoGenerated = append(tableData.AutoGenerated, desc.Field) + } + } + + if desc.Field == conf.VersionField && (conn.Config.Type == "sqlite3" || (strings.Contains(desc.Type, "bigint") && strings.Contains(desc.Type, "unsigned"))) { + tableData.HasVersion = true + } + + for _, validFieldInfo := range conf.ValidFields { + if desc.Field == validFieldInfo.Field && (conn.Config.Type == "sqlite3" || strings.Contains(desc.Type, validFieldInfo.Type)) { + tableData.ValidField = validFieldInfo.Field + tableData.ValidFieldConfig = validFieldInfo + tableData.ValidWhere = " AND `" + validFieldInfo.Field + "`" + validFieldInfo.ValidOperator + validFieldInfo.ValidValue + tableData.ValidSet = "`" + validFieldInfo.Field + "`" + validFieldInfo.ValidSetOperator + validFieldInfo.ValidSetValue + tableData.InvalidSet = "`" + validFieldInfo.Field + "`" + validFieldInfo.InvalidSetOperator + validFieldInfo.InvalidSetValue + } + } + + fields = append(fields, desc.Field) + + typ := "" + defaultValue := "0" + options := map[string]string{} + if strings.Contains(desc.Type, "bigint") { + typ = "int64" + } else if strings.Contains(desc.Type, "int") { + typ = "int" + } else if strings.Contains(desc.Type, "float") { + typ = "float32" + } else if strings.Contains(desc.Type, "double") { + typ = "float64" + } else if desc.Type == "datetime" { + typ = "Datetime" + defaultValue = "\"0000-00-00 00:00:00\"" + } else if desc.Type == "date" { + typ = "Date" + defaultValue = "\"0000-00-00\"" + } else if desc.Type == "time" { + typ = "Time" + defaultValue = "\"00:00:00\"" + } else if strings.HasPrefix(desc.Type, "enum(") { + typ = u.GetUpperName(desc.Field) + if !enumTypeExists[typ] { + enumTypeExists[typ] = true + a := u.SplitWithoutNone(desc.Type[5:len(desc.Type)-1], ",") + for _, v := range a { + if strings.HasPrefix(v, "'") && strings.HasSuffix(v, "'") { + v = v[1 : len(v)-1] + } + options[typ+u.GetUpperName(v)] = v + } + } + defaultValue = "\"\"" + } else { + typ = "string" + defaultValue = "\"\"" + } + if strings.Contains(desc.Type, " unsigned") && strings.HasPrefix(typ, "int") { + typ = "u" + typ + } + fieldTypesForId[desc.Field] = typ // 用于ID的类型不加指针 + + if strings.Contains(desc.Extra, "auto_increment") && tableData.IsAutoId { + tableData.AutoIdFieldType = typ + } + + //if desc.Null == "YES" || desc.Default != nil || desc.Extra == "auto_increment" { + if desc.Null == "YES" || strings.Contains(desc.Extra, "auto_increment") { + tableData.PointFields = append(tableData.PointFields, FieldData{ + //Name: u.GetUpperName(desc.Field), + Name: desc.Field, + Type: typ, + Default: defaultValue, + Options: options, + }) + typ = "*" + typ + } + tableData.Fields = append(tableData.Fields, FieldData{ + //Name: u.GetUpperName(desc.Field), + Name: desc.Field, + Type: typ, + Default: defaultValue, + Options: options, + }) + //if desc.Key != "PRI" { + // tableData.FieldsWithoutAutoId = append(tableData.FieldsWithoutAutoId, FieldData{ + // Name: u.GetUpperName(desc.Field), + // Type: typ, + // }) + //} + } + + for _, index := range indexs { + if index.Key_name == "PRIMARY" { + idFields = append(idFields, index.Column_name) + idFieldsUpper = append(idFieldsUpper, u.GetUpperName(index.Column_name)) + idFieldParams = append(idFieldParams, fixParamName(index.Column_name)+" "+fieldTypesForId[index.Column_name]) + idFieldItemArgs = append(idFieldItemArgs, "this."+index.Column_name) + } else if index.Non_unique == 0 { + if uniqueFields[index.Key_name] == nil { + uniqueFields[index.Key_name] = make([]string, 0) + uniqueFieldsUpper[index.Key_name] = make([]string, 0) + uniqueFieldParams[index.Key_name] = make([]string, 0) + uniqueFieldItemArgs[index.Key_name] = make([]string, 0) + } + uniqueFields[index.Key_name] = append(uniqueFields[index.Key_name], index.Column_name) + uniqueFieldsUpper[index.Key_name] = append(uniqueFieldsUpper[index.Key_name], u.GetUpperName(index.Column_name)) + uniqueFieldParams[index.Key_name] = append(uniqueFieldParams[index.Key_name], fixParamName(index.Column_name)+" "+fieldTypesForId[index.Column_name]) + uniqueFieldItemArgs[index.Key_name] = append(uniqueFieldItemArgs[index.Key_name], u.StringIf(tableData.IsAutoId, "*", "")+"item."+u.GetUpperName(index.Column_name)) + } else { + if indexFields[index.Key_name] == nil { + indexFields[index.Key_name] = make([]string, 0) + indexFieldsUpper[index.Key_name] = make([]string, 0) + indexFieldParams[index.Key_name] = make([]string, 0) + indexFieldItemArgs[index.Key_name] = make([]string, 0) + } + indexFields[index.Key_name] = append(indexFields[index.Key_name], index.Column_name) + indexFieldsUpper[index.Key_name] = append(indexFieldsUpper[index.Key_name], u.GetUpperName(index.Column_name)) + indexFieldParams[index.Key_name] = append(indexFieldParams[index.Key_name], fixParamName(index.Column_name)+" "+fieldTypesForId[index.Column_name]) + indexFieldItemArgs[index.Key_name] = append(indexFieldItemArgs[index.Key_name], u.StringIf(tableData.IsAutoId, "*", "")+"item."+u.GetUpperName(index.Column_name)) + } + } + //fmt.Println("keys: ", u.JsonP(idFields), u.JsonP(uniqueFields), u.JsonP(indexFields)) + + if len(idFields) > 0 { + tableData.PrimaryKey = &IndexField{ + Name: strings.Join(idFieldsUpper, ""), + Where: "(`" + strings.Join(idFields, "`=? AND `") + "`=?)", + Args: fixJoinParams(idFields, ", "), + Params: fixJoinParams(idFieldParams, ", "), + ItemArgs: strings.Join(idFieldItemArgs, ", "), + StringArgs: "\"" + fixJoinParams(idFields, "\", \"") + "\"", + } + + // 将复合主键中的索引添加到 NewQuery().ByXXX + for i := len(idFields) - 1; i >= 0; i-- { + name2 := strings.Join(idFieldsUpper[0:i+1], "") + k2 := "Index_" + name2 + // 唯一索引和普通索引中都不存在时创建 + if tableData.UniqueKeys[k2] == nil && tableData.IndexKeys[k2] == nil { + tableData.IndexKeys[k2] = &IndexField{ + Name: name2, + Where: "(`" + strings.Join(idFields[0:i+1], "`=? AND `") + "`=?)", + Args: fixJoinParams(idFields[0:i+1], ", "), + Params: fixJoinParams(idFieldParams[0:i+1], ", "), + ItemArgs: strings.Join(idFieldItemArgs[0:i+1], ", "), + StringArgs: "\"" + fixJoinParams(idFields[0:i+1], "\", \"") + "\"", + } + } + } + } + + for k, fieldNames := range uniqueFields { + name1 := strings.Join(uniqueFieldsUpper[k], "") + k1 := "Unique_" + name1 + if tableData.UniqueKeys[k1] == nil { + tableData.UniqueKeys[k1] = &IndexField{ + Name: name1, + Where: "(`" + strings.Join(fieldNames, "`=? AND `") + "`=?)", + Args: fixJoinParams(fieldNames, ", "), + Params: fixJoinParams(uniqueFieldParams[k], ", "), + ItemArgs: strings.Join(uniqueFieldItemArgs[k], ", "), + StringArgs: "\"" + fixJoinParams(fieldNames, "\", \"") + "\"", + } + } + + // 将复合唯一索引中的索引添加到 NewQuery().ByXXX + for i := len(fieldNames) - 1; i >= 0; i-- { + name2 := strings.Join(uniqueFieldsUpper[k][0:i+1], "") + k2 := "Index_" + name2 + // 唯一索引和普通索引中都不存在时创建 + if tableData.UniqueKeys[k2] == nil && tableData.IndexKeys[k2] == nil { + tableData.IndexKeys[k2] = &IndexField{ + Name: name2, + Where: "(`" + strings.Join(fieldNames[0:i+1], "`=? AND `") + "`=?)", + Args: fixJoinParams(fieldNames[0:i+1], ", "), + Params: fixJoinParams(uniqueFieldParams[k][0:i+1], ", "), + ItemArgs: strings.Join(uniqueFieldItemArgs[k][0:i+1], ", "), + StringArgs: "\"" + fixJoinParams(fieldNames[0:i+1], "\", \"") + "\"", + } + } + } + } + + // 将其他索引添加到 NewQuery().ByXXX + for k, fieldNames := range indexFields { + for i := range fieldNames { + name := strings.Join(indexFieldsUpper[k][0:i+1], "") + k2 := "Index_" + name + // 唯一索引和普通索引中都不存在时创建 + if tableData.UniqueKeys[k2] == nil && tableData.IndexKeys[k2] == nil { + tableData.IndexKeys[k2] = &IndexField{ + Name: name, + Where: "(`" + strings.Join(fieldNames[0:i+1], "`=? AND `") + "`=?)", + Args: fixJoinParams(fieldNames[0:i+1], ", "), + Params: fixJoinParams(indexFieldParams[k][0:i+1], ", "), + ItemArgs: strings.Join(indexFieldItemArgs[k][0:i+1], ", "), + StringArgs: "\"" + fixJoinParams(fieldNames[0:i+1], "\", \"") + "\"", + } + } + } + } + tableData.SelectFields = "`" + strings.Join(fields, "`, `") + "`" + tableDataList = append(tableDataList, tableData) + } + + daoData := DaoData{ + DBName: dbName, + FixedDBName: u.GetUpperName(dbName), + VersionField: conf.VersionField, + //TableNames: tableNames, + Tables: tableDataList, + FixedTables: fixedTables, + } + + daoFile := path.Join(outputPath, "dao.js") + err := writeWithTpl(daoFile, daoTpl, daoData) + //if err == nil { + // queryFile := path.Join(outputPath, "query.go") + // err = writeWithTpl(queryFile, queryTpl, daoData) + //} + if err != nil { + logger.Error("make dao failed: "+err.Error(), "db", dbName) + return err + } + + logger.Info("make dao success", "db", dbName) + daoExtFile := path.Join(outputPath, "dao_ext.js") + if !u.FileExists(daoExtFile) { + _ = writeWithTpl(daoExtFile, daoExtTpl, daoData) + _ = os.Chmod(daoExtFile, 0664) + } + + //err := writeWithTpl(tableFile, tableTpl, tableData) + //if err != nil { + // fmt.Println(" -", table, u.Red(err.Error())) + //} else { + // fmt.Println(" -", table, u.Green("OK")) + //} + return nil +} + +func fixParamName(in string) string { + switch in { + case "type": + return "typ" + } + return in +} + +func fixJoinParams(elems []string, sep string) string { + a := make([]string, len(elems)) + for i := len(elems) - 1; i >= 0; i-- { + a[i] = fixParamName(elems[i]) + } + return strings.Join(a, sep) +} + +func writeWithTpl(filename, tplContent string, data interface{}) error { + tpl, err := template.New(filename).Parse(tplContent) + if err == nil { + exists := u.FileExists(filename) + if exists { + _ = os.Chmod(filename, 0644) + } + + var fp *os.File + fp, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0444) + if err == nil { + err = tpl.Execute(fp, data) + _ = fp.Close() + } + + if exists { + _ = os.Chmod(filename, 0444) + } + } + return err +} diff --git a/db/MakeTable.go b/db/MakeTable.go new file mode 100644 index 0000000..6076690 --- /dev/null +++ b/db/MakeTable.go @@ -0,0 +1,622 @@ +package db + +import ( + _ "embed" + "fmt" + "github.com/ssgo/db" + "github.com/ssgo/log" + "github.com/ssgo/u" + "os" + "regexp" + "strings" + "text/template" +) + +type TableFieldDesc struct { + Field string + Type string + Null string + Key string + Default string + Extra string + After string +} + +type TableKeyDesc struct { + Key_name string + Column_name string +} + +type TableField struct { + Name string + Type string + Index string + IndexGroup string + Default string + Comment string + IsNull bool + Extra string + desc string + Null string +} + +type ERGroup struct { + Group string + Comment string + Tables []TableStruct +} + +type TableStruct struct { + Name string + Comment string + Fields []TableField +} + +//go:embed er.html +var erTpl string + +func (field *TableField) Parse(tableType string) { + //if field.Index == "autoId" { + // field.Type += " unsigned" + //} + + if field.IsNull { + field.Null = "NULL" + } else { + field.Null = "NOT NULL" + } + + if tableType == "sqlite3" { + // sqlite3 不能修改字段,统一使用NULL + field.Null = "NULL" + if field.Extra == "AUTO_INCREMENT" { + field.Extra = "PRIMARY KEY AUTOINCREMENT" + field.Type = "integer" + field.Null = "NOT NULL" + } + } + + a := make([]string, 0) + a = append(a, fmt.Sprintf("`%s` %s", field.Name, field.Type)) + + if tableType == "mysql" { + lowerType := strings.ToLower(field.Type) + if strings.Contains(lowerType, "varchar") || strings.Contains(lowerType, "text") { + a = append(a, " COLLATE utf8mb4_general_ci") + } + } + //if field.Index == "autoId" { + // a = append(a, " AUTO_INCREMENT") + // field.Index = "pk" + // //a = append(a, " NOT NULL") + //} + + //if field.Index == "uniqueId" { + // field.Index = "pk" + // //a = append(a, " NOT NULL") + //} + + if field.Extra != "" { + a = append(a, " "+field.Extra) + } + a = append(a, " "+field.Null) + + if field.Default != "" { + if strings.Contains(field.Default, "CURRENT_TIMESTAMP") { + a = append(a, " DEFAULT "+field.Default) + } else { + a = append(a, " DEFAULT '"+field.Default+"'") + } + } + if tableType == "sqlite3" { + field.Comment = "" + field.Type = "numeric" + } else if tableType == "mysql" { + if field.Comment != "" { + a = append(a, " COMMENT '"+field.Comment+"'") + } + } + field.desc = strings.Join(a, "") +} + +var ddlTableMatcher = regexp.MustCompile("(?is)^\\s*CREATE\\s+TABLE\\s+`?([\\w]+)`?\\s*\\(\\s*(.*?)\\s*\\);?\\s*$") +var ddlFieldMatcher = regexp.MustCompile("(?s)\\s*[`\\[]?([\\w]+)[`\\]]?\\s+\\[?([\\w() ]+)\\]?\\s*(.*?)(,|$)") +var ddlKeyMatcher = regexp.MustCompile("[`\\[]?([\\w]+)[`\\]]?\\s*(,|\\))") +var ddlNotNullMatcher = regexp.MustCompile("(?i)\\s+NOT NULL") +var ddlNullMatcher = regexp.MustCompile("(?i)\\s+NULL") + +//var ddlDefaultMatcher = regexp.MustCompile("(?i)\\s+DEFAULT\\s+(.*?)$") +var ddlIndexMatcher = regexp.MustCompile("(?is)^\\s*CREATE\\s+([A-Za-z ]+)\\s+`?([\\w]+)`?\\s+ON\\s+`?([\\w]+)`?\\s*\\(\\s*(.*?)\\s*\\);?\\s*$") +var ddlIndexFieldMatcher = regexp.MustCompile("[`\\[]?([\\w]+)[`\\]]?\\s*(,|$)") + +// MakeER 创建ER图文件 +func MakeER(groups []ERGroup, outputFile *string, tplFile *string, logger *log.Logger) error { + tplStr := "" + if tplFile == nil || *tplFile == "" { + tplStr = erTpl + } else { + tplStr = *tplFile + } + erOutputFile := "er.html" + if outputFile != nil && *outputFile != "" { + erOutputFile = *outputFile + } + tpl := template.New(erOutputFile).Funcs(template.FuncMap{ + "short": func(in string) string { + switch in { + case "NULL": + return "n" + case "NOT NULL": + return "nn" + case "AUTO_INCREMENT": + return "ai" + case "CURRENT_TIMESTAMP": + return "ct" + case "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP": + return "ctu" + } + return in + }, + }) + var err error + tpl, err = tpl.Parse(tplStr) + if err == nil { + var fp *os.File + fp, err = os.OpenFile(erOutputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) + if err == nil { + err = tpl.Execute(fp, map[string]interface{}{ + "title": "ER", + "groups": groups, + }) + _ = fp.Close() + } + } + if err != nil { + logger.Error(err.Error()) + } + return err +} + +func MakeTable(conn *db.DB, table *TableStruct, logger *log.Logger) ([]string, error) { + //fmt.Println(u.JsonP(ddlKeyMatcher.FindAllStringSubmatch("(`key`,id, `name` )", 100)), "====================") + fieldSets := make([]string, 0) + //fieldSetBy := make(map[string]string) + pks := make([]string, 0) + keySets := make([]string, 0) + keySetBy := make(map[string]string) + keySetFields := make(map[string]string) + for i, field := range table.Fields { + field.Parse(conn.Config.Type) + table.Fields[i] = field + + if conn.Config.Type == "sqlite3" { + if field.Index == "pk" && field.Extra != "PRIMARY KEY AUTOINCREMENT" { + // sqlite3 用 unique 代替 pk + field.Index = "unique" + field.IndexGroup = "0" + field.Null = "NULL" + } + } + + switch field.Index { + case "pk": + if conn.Config.Type == "sqlite3" { + if field.Extra != "PRIMARY KEY AUTOINCREMENT" { + pks = append(pks, field.Name) + } + } else { + pks = append(pks, field.Name) + } + case "unique": + keyName := fmt.Sprint("uk_", table.Name, "_", field.Name) + if field.IndexGroup != "" { + keyName = fmt.Sprint("uk_", table.Name, "_", field.IndexGroup) + } + if keySetBy[keyName] != "" { + keySetFields[keyName] += " " + field.Name + // 复合索引 + if conn.Config.Type == "sqlite3" { + keySetBy[keyName] = strings.Replace(keySetBy[keyName], ")", ", `"+field.Name+"`)", 1) + } else if conn.Config.Type == "mysql" { + keySetBy[keyName] = strings.Replace(keySetBy[keyName], ") COMMENT", ", `"+field.Name+"`) COMMENT", 1) + } + } else { + keySetFields[keyName] = field.Name + keySet := "" + if conn.Config.Type == "sqlite3" { + keySet = fmt.Sprintf("CREATE UNIQUE INDEX `%s` ON `%s` (`%s`)", keyName, table.Name, field.Name) + } else if conn.Config.Type == "mysql" { + keySet = fmt.Sprintf("UNIQUE KEY `%s` (`%s`) COMMENT '%s'", keyName, field.Name, field.Comment) + } + keySets = append(keySets, keySet) + keySetBy[keyName] = keySet + } + case "fulltext": + if conn.Config.Type == "mysql" { + keyName := fmt.Sprint("tk_", table.Name, "_", field.Name) + keySet := fmt.Sprintf("FULLTEXT KEY `%s` (`%s`) COMMENT '%s'", keyName, field.Name, field.Comment) + keySets = append(keySets, keySet) + keySetBy[keyName] = keySet + } + case "index": + keyName := fmt.Sprint("ik_", table.Name, "_", field.Name) + if field.IndexGroup != "" { + keyName = fmt.Sprint("ik_", table.Name, "_", field.IndexGroup) + } + if keySetBy[keyName] != "" { + keySetFields[keyName] += " " + field.Name + // 复合索引 + if conn.Config.Type == "sqlite3" { + keySetBy[keyName] = strings.Replace(keySetBy[keyName], ")", ", `"+field.Name+"`)", 1) + } else if conn.Config.Type == "mysql" { + keySetBy[keyName] = strings.Replace(keySetBy[keyName], ") COMMENT", ", `"+field.Name+"`) COMMENT", 1) + } + } else { + keySetFields[keyName] = field.Name + keySet := "" + if conn.Config.Type == "sqlite3" { + keySet = fmt.Sprintf("CREATE INDEX `%s` ON `%s` (`%s`)", keyName, table.Name, field.Name) + } else if conn.Config.Type == "mysql" { + keySet = fmt.Sprintf("KEY `%s` (`%s`) COMMENT '%s'", keyName, field.Name, field.Comment) + } + keySets = append(keySets, keySet) + keySetBy[keyName] = keySet + } + } + + fieldSets = append(fieldSets, field.desc) + //fieldSetBy[field.Name] = field.desc + } + //fmt.Println(u.JsonP(table.Fields)) + //fmt.Println(u.JsonP(keySetBy), 3) + //fmt.Println(u.JsonP(keySets), 4) + + outSql := make([]string, 0) + + var result *db.ExecResult + var tableInfo map[string]interface{} + if conn.Config.Type == "sqlite3" { + tableInfo = conn.Query("SELECT `name`, `sql` FROM `sqlite_master` WHERE `type`='table' AND `name`='" + table.Name + "'").MapOnR1() + tableInfo["comment"] = "" + } else if conn.Config.Type == "mysql" { + tableInfo = conn.Query("SELECT TABLE_NAME name, TABLE_COMMENT comment FROM information_schema.TABLES WHERE TABLE_SCHEMA='" + conn.Config.DB + "' AND TABLE_NAME='" + table.Name + "'").MapOnR1() + } + oldTableComment := u.String(tableInfo["comment"]) + + if tableInfo["name"] != nil && tableInfo["name"] != "" { + // 合并字段 + oldFieldList := make([]*TableFieldDesc, 0) + oldFields := make(map[string]*TableFieldDesc) + oldIndexes := make(map[string]string) + oldIndexInfos := make([]*TableKeyDesc, 0) + + oldComments := map[string]string{} + if conn.Config.Type == "sqlite3" { + tableM := ddlTableMatcher.FindStringSubmatch(u.String(tableInfo["sql"])) + if tableM != nil { + fieldsM := ddlFieldMatcher.FindAllStringSubmatch(tableM[2], 2000) + if fieldsM != nil { + for _, m := range fieldsM { + if m[1] == "PRIMARY" && m[2] == "KEY" { + keysM := ddlKeyMatcher.FindAllStringSubmatch(m[3], 20) + if keysM != nil { + for _, km := range keysM { + oldIndexInfos = append(oldIndexInfos, &TableKeyDesc{ + Key_name: "PRIMARY", + Column_name: km[1], + }) + } + } + } else { + Null := "NULL" + //fmt.Println(" =====", m[0], m[1], m[2]) + if ddlNotNullMatcher.MatchString(m[2]) { + m[2] = ddlNotNullMatcher.ReplaceAllString(m[2], "") + Null = "NOT NULL" + } else if ddlNullMatcher.MatchString(m[2]) { + m[2] = ddlNullMatcher.ReplaceAllString(m[2], "") + Null = "NULL" + } + //fmt.Println(" =====", m[2], "|", Null) + + oldFieldList = append(oldFieldList, &TableFieldDesc{ + Field: m[1], + Type: m[2], + //Null: u.StringIf(strings.Contains(m[3], "NOT NULL"), "NO", "YES"), + Null: u.StringIf(Null == "NOT NULL", "NO", "YES"), + Key: "", + Default: "", + Extra: "", + After: "", + }) + } + } + } + //fmt.Println(u.JsonP(fieldsM), 222) + } + + // 读取索引信息 + for _, indexInfo := range conn.Query("SELECT `name`, `sql` FROM `sqlite_master` WHERE `type`='index' AND `tbl_name`='" + table.Name + "'").StringMapResults() { + //fmt.Println(u.JsonP(indexInfo), 777) + indexM := ddlIndexMatcher.FindStringSubmatch(indexInfo["sql"]) + if indexM != nil { + //fmt.Println(u.JsonP(indexM), 666) + indexFieldM := ddlIndexFieldMatcher.FindAllStringSubmatch(indexM[4], 20) + //fmt.Println(u.JsonP(indexFieldM), 555) + if indexFieldM != nil { + for _, km := range indexFieldM { + oldIndexInfos = append(oldIndexInfos, &TableKeyDesc{ + Key_name: indexInfo["name"], + Column_name: km[1], + }) + } + } + } + } + + //fmt.Println(u.JsonP(oldFieldList), 1) + //fmt.Println(u.JsonP(oldIndexInfos), 2) + } else if conn.Config.Type == "mysql" { + conn.Query("SELECT column_name, column_comment FROM information_schema.columns WHERE TABLE_SCHEMA='" + conn.Config.DB + "' AND TABLE_NAME='" + table.Name + "'").ToKV(&oldComments) + _ = conn.Query("DESC `" + table.Name + "`").To(&oldFieldList) + _ = conn.Query("SHOW INDEX FROM `" + table.Name + "`").To(&oldIndexInfos) + } + //fmt.Println(u.JsonP(oldComments), 111) + + for _, indexInfo := range oldIndexInfos { + if oldIndexes[indexInfo.Key_name] == "" { + oldIndexes[indexInfo.Key_name] = indexInfo.Column_name + } else { + oldIndexes[indexInfo.Key_name] += " " + indexInfo.Column_name + } + } + //fmt.Println(u.JsonP(oldIndexes), 111) + //fmt.Println(u.JsonP(keySetFields), 222) + //fmt.Println(u.JsonP(keySetBy), 333) + + // 先后顺序 + prevFieldId := "" + for _, field := range oldFieldList { + if conn.Config.Type == "sqlite3" { + field.Type = "numeric" + } else if conn.Config.Type == "mysql" { + field.After = prevFieldId + } + prevFieldId = field.Field + oldFields[field.Field] = field + } + //fmt.Println(111, u.JsonP(oldFields), 111) + + actions := make([]string, 0) + for keyId := range oldIndexes { + if keyId != "PRIMARY" && strings.ToLower(keySetFields[keyId]) != strings.ToLower(oldIndexes[keyId]) { + if conn.Config.Type == "sqlite3" { + actions = append(actions, "DROP INDEX `"+keyId+"`") + } else if conn.Config.Type == "mysql" { + actions = append(actions, "DROP KEY `"+keyId+"`") + } + } + } + //fmt.Println(" =>>>>>>>>", oldIndexes, pks) + if oldIndexes["PRIMARY"] != "" && strings.ToLower(oldIndexes["PRIMARY"]) != strings.ToLower(strings.Join(pks, " ")) { + if conn.Config.Type == "sqlite3" { + } else if conn.Config.Type == "mysql" { + actions = append(actions, "DROP PRIMARY KEY") + } + } + //for fieldId, fieldSet := range fieldSetBy { + newFieldExists := map[string]bool{} + prevFieldId = "" + for _, field := range table.Fields { + newFieldExists[field.Name] = true + oldField := oldFields[field.Name] + + // 修复部分数据库的特殊性 + if oldField == nil { + if conn.Config.Type == "sqlite3" { + actions = append(actions, "ALTER TABLE `"+table.Name+"` ADD COLUMN "+field.desc) + } else if conn.Config.Type == "mysql" { + actions = append(actions, "ADD COLUMN "+field.desc) + } + } else { + oldField.Type = strings.TrimSpace(strings.ReplaceAll(oldField.Type, " (", "(")) + fixedOldDefault := u.String(oldField.Default) + if fixedOldDefault == "CURRENT_TIMESTAMP" && strings.Contains(oldField.Extra, "on update CURRENT_TIMESTAMP") { + fixedOldDefault = "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" + } + fixedOldNull := "NOT NULL" + if oldField.Null == "YES" { + fixedOldNull = "NULL" + } + //fmt.Println(" ==", field.Type, "!=", oldField.Type, "||", field.Default, "!=", fixedOldDefault, "||", field.Null, "!=", fixedOldNull, "||", oldField.After, "!=", prevFieldId, "||", oldComments[field.Name], "!=", field.Comment) + //fmt.Println(" ==", strings.ToLower(field.Type) != strings.ToLower(oldField.Type), strings.ToLower(field.Default) != strings.ToLower(fixedOldDefault), strings.ToLower(field.Null) != strings.ToLower(fixedOldNull), strings.ToLower(oldField.After) != strings.ToLower(prevFieldId), strings.ToLower(oldComments[field.Name]) != strings.ToLower(field.Comment)) + if strings.ToLower(field.Type) != strings.ToLower(oldField.Type) || strings.ToLower(field.Default) != strings.ToLower(fixedOldDefault) || strings.ToLower(field.Null) != strings.ToLower(fixedOldNull) || strings.ToLower(oldField.After) != strings.ToLower(prevFieldId) || strings.ToLower(oldComments[field.Name]) != strings.ToLower(field.Comment) { + //fmt.Println(" > > > > ", u.JsonP(oldField), 1111) + // `t4f34` varchar(100) COLLATE utf8mb4_general_ci COMMENT '' + // f34, varchar(100), YES, , , + //fmt.Println(111111, u.JsonP(field), 1111) + // 为什么Desc是空???? + after := "" + if conn.Config.Type == "mysql" { + if oldField.After != prevFieldId { + if prevFieldId == "" { + after = " FIRST" + } else { + after = " AFTER `" + prevFieldId + "`" + } + } + } + //DROP INDEX `uk_config_key`; + //ALTER TABLE `config` RENAME COLUMN `key` TO `keyOld`; + //ALTER TABLE `config` ADD COLUMN `key` varchar(30) NULL; + //UPDATE `config` SET `key`=`keyOld`; + //ALTER TABLE `config` DROP COLUMN `keyOld`; + //CREATE INDEX `uk_config_key` ON `config` (`key`); + if conn.Config.Type == "sqlite3" { + // 不支持修改字段,所以要先创建然后复制数据再删除 + + // 方案一(已放弃)重新创建表实现修改 + //actions = append(actions, fmt.Sprintf("CREATE TABLE `%s_temp` (\n%s\n)", table.Name, strings.Join(fieldSets, ",\n"))) + //actions = append(actions, fmt.Sprintf("INSERT INTO `%s_temp` SELECT * FROM `%s`", table.Name, table.Name)) + //actions = append(actions, fmt.Sprintf("DROP TABLE `%s`", table.Name)) + //actions = append(actions, fmt.Sprintf("ALTER TABLE `%s_temp` RENAME TO `%s`", table.Name, table.Name)) + //INSERT INTO t1_new SELECT foo, bar, baz FROM t1; + //DROP TABLE t1; + //ALTER TABLE t1_new RENAME TO t1; + + // 方案二(已放弃)创建新字段复制数据后删除(部分Sqlite不支持DROP COLUMN) + //redoIndexes := make([]string, 0) + //for oldIndexName, oldIndex := range oldIndexes { + // if u.StringIn(strings.Split(oldIndex, " "), field.Name) { + // indexSql := conn.Query("SELECT `sql` FROM `sqlite_master` WHERE `type`='index' AND `name`='" + oldIndexName + "'").StringOnR1C1() + // redoIndexes = append(redoIndexes, indexSql) + // actions = append(actions, "DROP INDEX `"+oldIndexName+"`") + // } + //} + //oldPostfix := u.UniqueId() + //actions = append(actions, "ALTER TABLE `"+table.Name+"` RENAME COLUMN `"+field.Name+"` TO `d_"+field.Name+"_"+oldPostfix+"`") + //actions = append(actions, "ALTER TABLE `"+table.Name+"` ADD COLUMN "+field.Desc+after) + //actions = append(actions, "UPDATE `"+table.Name+"` SET `"+field.Name+"`=`d_"+field.Name+"_"+oldPostfix+"`") + ////actions = append(actions, "ALTER TABLE `"+table.Name+"` DROP COLUMN `"+field.Name+"Old`") + //for _, redoIndex := range redoIndexes { + // actions = append(actions, redoIndex) + //} + + // 方案三 不修改字段类型,Sqlite可以兼容 + + //actions = append(actions, "ALTER TABLE `"+table.Name+"` ADD COLUMN "+field.desc) + } else if conn.Config.Type == "mysql" { + actions = append(actions, "CHANGE `"+field.Name+"` "+field.desc+after) + } + } + } + + if conn.Config.Type == "mysql" { + prevFieldId = field.Name + } + } + + for oldFieldName := range oldFields { + if newFieldExists[oldFieldName] != true { + if conn.Config.Type == "sqlite3" { + //actions = append(actions, "ALTER TABLE `"+table.Name+"` DROP COLUMN `"+oldFieldName+"`") + } else if conn.Config.Type == "mysql" { + actions = append(actions, "DROP COLUMN `"+oldFieldName+"`") + } + } + } + + // sqlite3 不支持添加主键 + if conn.Config.Type == "mysql" { + if len(pks) > 0 && strings.ToLower(oldIndexes["PRIMARY"]) != strings.ToLower(strings.Join(pks, " ")) { + actions = append(actions, "ADD PRIMARY KEY(`"+strings.Join(pks, "`,`")+"`)") + } + } + + //fmt.Println(111, u.JsonP(oldIndexes), 222 ) + //fmt.Println(222, u.JsonP(keySetBy), 222 ) + for keyId, keySet := range keySetBy { + if oldIndexes[keyId] == "" || strings.ToLower(oldIndexes[keyId]) != strings.ToLower(keySetFields[keyId]) { + if conn.Config.Type == "sqlite3" { + actions = append(actions, keySet) + } else if conn.Config.Type == "mysql" { + actions = append(actions, "ADD "+keySet) + } + } + } + + //fmt.Println(" =>", table.Comment, "|", oldTableComment ) + if conn.Config.Type == "mysql" { + if table.Comment != oldTableComment { + actions = append(actions, "COMMENT '"+table.Comment+"'") + } + } + + if len(actions) == 0 { + // 不需要更新 + return outSql, nil + } + + tx := conn.Begin() + defer tx.CheckFinished() + if conn.Config.Type == "sqlite3" { + for _, action := range actions { + //fmt.Println(u.Dim("\t" + strings.ReplaceAll(action, "\n", "\n\t"))) + outSql = append(outSql, action) + result = tx.Exec(action) + if result.Error != nil { + break + } + } + } else if conn.Config.Type == "mysql" { + sql := "ALTER TABLE `" + table.Name + "` " + strings.Join(actions, "\n,") + ";" + //fmt.Println(u.Dim("\t" + strings.ReplaceAll(sql, "\n", "\n\t"))) + outSql = append(outSql, sql) + result = tx.Exec(sql) + } + if result.Error != nil { + _ = tx.Rollback() + } else { + _ = tx.Commit() + } + } else { + // 创建新表 + if len(pks) > 0 { + fieldSets = append(fieldSets, "PRIMARY KEY (`"+strings.Join(pks, "`,`")+"`)") + } + + indexSets := make([]string, 0) // sqlite3 额外创建索引的sql + if conn.Config.Type == "sqlite3" { + for _, indexSql := range keySetBy { + indexSets = append(indexSets, indexSql) + } + } else if conn.Config.Type == "mysql" { + for _, key := range keySets { + fieldSets = append(fieldSets, key) + } + } + + sql := "" + + if conn.Config.Type == "sqlite3" { + sql = fmt.Sprintf("CREATE TABLE `%s` (\n %s\n);", table.Name, strings.Join(fieldSets, ",\n ")) + } else if conn.Config.Type == "mysql" { + sql = fmt.Sprintf("CREATE TABLE `%s` (\n %s\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='%s';", table.Name, strings.Join(fieldSets, ",\n "), table.Comment) + } + tx := conn.Begin() + defer tx.CheckFinished() + //fmt.Println(u.Dim("\t" + strings.ReplaceAll(sql, "\n", "\n\t"))) + outSql = append(outSql, sql) + result = tx.Exec(sql) + + if result.Error == nil { + if conn.Config.Type == "sqlite3" { + for _, indexSet := range indexSets { + //fmt.Println(indexSet) + //fmt.Println(u.Dim("\t" + strings.ReplaceAll(indexSet, "\n", "\n\t"))) + outSql = append(outSql, indexSet) + r := tx.Exec(indexSet) + if r.Error != nil { + result = r + } + } + } + } + if result.Error != nil { + _ = tx.Rollback() + } else { + _ = tx.Commit() + } + + } + + if result == nil { + return outSql, nil + } + + if result.Error != nil { + logger.Error(result.Error.Error()) + } + + return outSql, result.Error +} diff --git a/db/dao.js b/db/dao.js new file mode 100644 index 0000000..e8413d8 --- /dev/null +++ b/db/dao.js @@ -0,0 +1,714 @@ +{{$dao := . -}} +let _{{$dao.DBName}}Dao = {_daoInstances:{}, _daoActions:{}} + +function _make{{$dao.DBName}}Dao(conn, tx, rd) { + let dao = {_tableInstances:{}} + let daoActions = _{{$dao.DBName}}Dao._daoActions + // let tableActions = _{{$dao.DBName}}Dao._tableActions + for(let k in _{{$dao.DBName}}Dao) if(typeof _{{$dao.DBName}}Dao[k]==='function') dao[k] = _{{$dao.DBName}}Dao[k] + for(let k in daoActions) if(typeof daoActions[k]==='function') dao[k] = daoActions[k] + {{- range $t := .Tables }} + Object.defineProperty(dao, "{{$t.TableName}}", {get: function(){return dao._getTable("{{$t.TableName}}")}}) + {{- end }} + //for(let tableName in _{{$dao.DBName}}Dao._table) Object.defineProperty(dao, tableName, {get: function(){return dao._getTable(tableName)}}) + dao._conn = conn + dao._tx = tx + dao._rd = rd + return dao +} + +function get{{$dao.FixedDBName}}Dao(connectionName, redisName) { + let cachedDaoName = (connectionName||'')+'_'+(redisName||'') + if(_{{$dao.DBName}}Dao._daoInstances[cachedDaoName]) return _{{$dao.DBName}}Dao._daoInstances[cachedDaoName] + let conn = typeof db==='object' ? (connectionName ? db.fetch(connectionName) : db.fetch()) : null + let rd = typeof redis==='object' ? (redisName ? redis.fetch(redisName) : redis.fetch()) : null + let dao = _make{{$dao.DBName}}Dao(conn, null, rd) + _{{$dao.DBName}}Dao._daoInstances[cachedDaoName] = dao + return dao +} + +_{{$dao.DBName}}Dao._getTable = function(tableName){ + if(this._tableInstances[tableName]) return this._tableInstances[tableName] + let tb = _{{$dao.DBName}}Dao[tableName] + let t = {} + let daoActions = _{{$dao.DBName}}Dao._daoActions + for(let k in tb) if(typeof tb[k]==='function') t[k] = tb[k] + for(let k in daoActions) if(typeof daoActions[k]==='function') t[k] = daoActions[k] + // for(let k in tableActions) if(typeof tableActions[k]==='function') t[k] = tableActions[k] + t._tableName = tableName + t._conn = this._conn + t._tx = this._tx + t._rd = this._rd + this._tableInstances[tableName] = t + return t +} + +_{{$dao.DBName}}Dao.begin = function() { + return _make{{$dao.DBName}}Dao(this._conn, this._conn.begin(), this._rd) +} + +_{{$dao.DBName}}Dao._daoActions.query = function(sql, ...args) { + if(this._tx) return this._tx.query(sql, ...args) + if(this._conn) return this._conn.query(sql, ...args) + return null +} + +_{{$dao.DBName}}Dao._daoActions.query1 = function(sql, ...args) { + if(this._tx) return this._tx.query1(sql, ...args) + let aa = this._conn.query1(sql, ...args) + if(this._conn) return this._conn.query1(sql, ...args) + return null +} + +_{{$dao.DBName}}Dao._daoActions.query11 = function(sql, ...args) { + if(this._tx) return this._tx.query11(sql, ...args) + if(this._conn) return this._conn.query11(sql, ...args) + return null +} + +_{{$dao.DBName}}Dao._daoActions.query1a = function(sql, ...args) { + if(this._tx) return this._tx.query1a(sql, ...args) + if(this._conn) return this._conn.query1a(sql, ...args) + return null +} + +_{{$dao.DBName}}Dao._daoActions.exec = function(sql, ...args) { + if(this._tx) return this._tx.exec(sql, ...args) + if(this._conn) return this._conn.exec(sql, ...args) + return null +} + +_{{$dao.DBName}}Dao._daoActions.insertTo = function(table, data) { + if(this._tx) return this._tx.insert(table, data) + if(this._conn) return this._conn.insert(table, data) + return null +} + +_{{$dao.DBName}}Dao._daoActions.replaceTo = function(table, data) { + if(this._tx) return this._tx.replace(table, data) + if(this._conn) return this._conn.replace(table, data) + return null +} + +_{{$dao.DBName}}Dao._daoActions.updateTo = function(table, data, sql, ...args) { + if(this._tx) return this._tx.update(table, data, sql, ...args) + if(this._conn) return this._conn.update(table, data, sql, ...args) + return null +} + +_{{$dao.DBName}}Dao._daoActions.deleteFrom = function(table, where, ...args) { + if(this._tx) return this._tx.delete(table, where, ...args) + if(this._conn) return this._conn.delete(table, where, ...args) + return null +} + + +{{- range $t := .Tables }} + +_{{$dao.DBName}}Dao.{{$t.TableName}} = {} +_{{$dao.DBName}}Dao.{{$t.TableName}}Item = {} +_{{$dao.DBName}}Dao.{{$t.TableName}}Query = {} +_{{$dao.DBName}}Dao.{{$t.TableName}}QueryList = {} + +_{{$dao.DBName}}Dao.{{$t.TableName}}.attach = function(item){ + if(!item || typeof item !== 'object') return null + let o = {} + for(let k in item) o[k] = item[k] + let itemSet = _{{$dao.DBName}}Dao.{{$t.TableName}}Item + for(let k in itemSet) if(typeof itemSet[k]==='function') o[k] = itemSet[k] + // item._conn = this._conn + // item._tx = this._tx + // item._rd = this._rd + o._table = this + return o +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}.attachList = function(list){ + if(!list || typeof list !== 'object' || !(list instanceof Array)) return [] + let a = [] + for(let i=0; i 1){ + this._rd.setEX("_DATA_VERSION_DOING_{{.TableName}}_"+version, 10, true) + return version + } + + this._rd.del("_DATA_VERSION_{{.TableName}}") + version = 0 + } else { + logger.warn("use version but not configured redis", {db:"{{.DBName}}", table:"{{.TableName}}"}) + } + + let maxVersion = parseInt(this.query11("SELECT MAX(`{{.VersionField}}`) FROM `{{.TableName}}`") || '0') + version = maxVersion+1 + if(this._rd){ + this._rd.mSet("_DATA_VERSION_{{.TableName}}", version, "_DATA_MAX_VERSION_{{.TableName}}", version) + this._rd.setEX("_DATA_VERSION_DOING_{{.TableName}}_"+version, 10, true) + } + return version +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}._commitVersion = function(version) { + if(this._rd){ + this._rd.del("_DATA_VERSION_DOING_{{.TableName}}_"+version) + let seqVersion = parseInt(this._rd.get("_DATA_VERSION_{{.TableName}}") || '0') + let currentMaxVersion = parseInt(this._rd.get("_DATA_MAX_VERSION_{{.TableName}}") || '0') + for(let i=currentMaxVersion; i<=seqVersion; i++){ + if(this._rd.exists("_DATA_VERSION_DOING_{{.TableName}}_"+i)){ + break + } else { + this._rd.set("_DATA_MAX_VERSION_{{.TableName}}", i) + } + } + } +} +{{- end }} + +_{{$dao.DBName}}Dao.{{$t.TableName}}.newQuery = function(){ + let query = { + _validWhere: "{{.ValidWhere}}", + _sql: "", + _fields: "{{.SelectFields}}", + _where: "", + _extraSql: "", + _extraArgs: [], + _args: [], + _leftJoins: [], + _leftJoinArgs: [], + _result: null, + _lastSql: "", + _lastSqlArgs: [], + } + let querySet = _{{$dao.DBName}}Dao.{{$t.TableName}}Query + for(let k in querySet) if(typeof querySet[k]==='function') query[k] = querySet[k] + query._table = this + // query._conn = this._conn + // query._tx = this._tx + // query._rd = this._rd + return query +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query._parseFields = function(fields, table) { + let fieldArr = fields + if(typeof fields !== 'object' || !(fields instanceof Array)) { + if(typeof fields !== 'string') fields = fields.toString() + if (fields==="" || fields.indexOf('(') !== -1 || fields.indexOf('`') !== -1) return fields + fieldArr = fields.split(",") + } + for(let i=0; i 0){ + leftJoinsStr = " " + this._leftJoins.join(" ") + this._args.unshift(...this._leftJoinArgs) + validWhere = validWhere.replace(/ AND /g, " AND `{{.TableName}}`.") + } + + if(this._where==="" && validWhere.startsWith(" AND ")) validWhere = validWhere.substring(5) + if(this._extraArgs) this._args.push(...this._extraArgs) + return {sql:"SELECT " + fields + " FROM `{{.TableName}}`" + leftJoinsStr + " WHERE " + this._where + validWhere + this._extraSql, args:this._args} +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.sql = function(sql, ...args){ + this._sql = sql + this._args = args + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.fields = function(fields){ + this._fields = this._parseFields(fields, "") + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.appendFields = function(fields){ + if(this._fields) this._fields += ", " + this._fields += this._parseFields(fields, "") + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.where = function(where, ...args){ + this._where = where + this._args = args + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.in = function(field, ...values){ + if(field.indexOf("`")===-1) field = "`"+field+"`" + this._where = field+" IN "+this._conn.inKeys(values.length) + this._args = values + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.and = function(where, ...args){ + if(this._where) this._where += " AND " + this._where += where + this._args = this._args.push(...args) + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.or = function(where, ...args){ + if(this._where) this._where += " OR " + this._where += where + this._args = this._args.push(...args) + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.andIn = function(field, ...values){ + if(field.indexOf("`")===-1) field = "`"+field+"`" + if(this._where) this._where += " AND " + this._where += field + " IN "+this._conn.inKeys(values.length) + this._args = this._args.push(...values) + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.orIn = function(field, ...values){ + if(field.indexOf("`")===-1) field = "`"+field+"`" + if(this._where) this._where += " OR " + this._where += field + " IN "+this._conn.inKeys(values.length) + this._args = this._args.push(...values) + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.orderBy = function(orderBy){ + this._extraSql += " ORDER BY " + orderBy + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.groupBy = function(groupBy){ + this._extraSql += " GROUP BY " + groupBy + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.limit = function(start, num){ + if(num===undefined){ + num = start + start = 0 + } + this._extraSql += " LIMIT ?,?" + this._extraArgs.push(start, num) + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.having = function(where, ...args){ + this._extraSql += " HAVING "+where + this._extraArgs = this._extraArgs.push(...args) + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.extra = function(sql, ...args){ + this._extraSql += sql + this._extraArgs = this._extraArgs.push(...args) + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.leftJoin = function(joinTable, fields, on, ...args){ + if(this._fields.indexOf("`{{.TableName}}`.")===-1){ + this._fields = "`{{.TableName}}`."+this._fields.replace(/`, `/g, "`, `{{.TableName}}`.`") + } + if(fields) this._fields += ", "+this._parseFields(fields, joinTable) + this._leftJoins = this._leftJoins.push("LEFT JOIN `" + joinTable + "` ON " + on) + this._leftJoinArgs = this._leftJoinArgs.push(...args) + return this +} + +{{range $idx := .IndexKeys}} +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.by{{$idx.Name}} = function({{$idx.Args}}){ + return this.where("{{$idx.Where}}", {{$idx.Args}}) +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.and{{$idx.Name}} = function({{$idx.Args}}){ + if(this._where) this._where += " AND " + this._where += "{{$idx.Where}}" + this._args = this._args.push({{$idx.Args}}) + return this +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.or{{$idx.Name}} = function({{$idx.Args}}){ + if(this._where){ + this._where += " OR " + } + this._where += "{{$idx.Where}}" + this._args = this._args.push({{$idx.Args}}) + return this +} +{{- end }} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.query = function(){ + let {sql,args} = this._parse("") + this._lastSql = sql + this._lastSqlArgs = args + this._result = this._table.query(sql, ...args).result + return this +} + +{{- if .ValidSet }} +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.queryWithValid = function(){ + let {sql,args} = this._parse("ALL") + this._lastSql = sql + this._lastSqlArgs = args + this._result = this._table.query(sql, ...args).result + return this +} +{{- end }} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.count = function(){ + let {sql,args} = this._parse("COUNT") + this._lastSql = sql + this._lastSqlArgs = args + return parseInt(this._table.query11(sql, ...args) || '0') +} + +{{- if .ValidSet }} +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.countAll = function(){ + let {sql,args} = this._parse("COUNT_ALL") + this._lastSql = sql + this._lastSqlArgs = args + return parseInt(this._table.query11(sql, ...args) || '0') +} +{{- end }} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.queryByPage = function(start, num){ + this.limit(start, num) + return this.query() +} + +{{- if .ValidSet }} +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.queryWithValidByPage = function(start, num){ + this.limit(start, num) + return this.queryWithValid() +} +{{- end }} + +{{- if .HasVersion }} + +{{- if .ValidSet }} +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.queryByVersion = function(minVersion, maxVersion, limit, withInvalid){ + if(minVersion > 0) withInvalid = true + let parseTag = u.StringIf(withInvalid, "ALL_VERSION", "VERSION") + +{{- else }} +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.queryByVersion = function(minVersion, maxVersion, limit){ + let parseTag = "VERSION" +{{- end }} + + if(maxVersion===0){ + if(this._rd){ + maxVersion = parseInt(this._rd.GET("_DATA_MAX_VERSION_{{.TableName}}") || '0') + } else { + logger.warn("use version but not configured redis", {db:"{{.DBName}}", table:"{{.TableName}}"}) + } + if(maxVersion===0){ + maxVersion = parseInt(this._table.query11("SELECT MAX(`{{.VersionField}}`) FROM `{{.TableName}}`") || '0') + this._lastSql = "SELECT MAX(`{{.VersionField}}`) FROM `{{.TableName}}`" + this._lastSqlArgs = [] + } + } + + this.and("`version` BETWEEN ? AND ?", minVersion+1, maxVersion ) + if(limit > 0){ + this.orderBy("`{{.VersionField}}`") + this.limit(0, limit) + } + + let {sql,args} = this._parse(parseTag) + this._lastSql = sql + this._lastSqlArgs = args + this._result = this._table.query(sql, ...args).result + this.maxVersion = maxVersion + return this +} +{{- end }} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.first = function(){ + let list = this.list() + if(list.length > 0) return this._table.attach(list[0]) + return null +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.list = function(){ + if(this._result===null) this.query() + return this._table.attachList(this._result) +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.listBy = function(...fields){ + if(this._result===null) this.query() + let list = [] + for(let item of this._result){ + let o = {} + for(let k in item) if(fields.indexOf(k)!==-1) o[k] = item[k] + list.push(o) + } + return list +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.getSql = function(){ + return this._lastSql +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}Query.getSqlArgs = function(){ + return this._lastSqlArgs +} + +_{{$dao.DBName}}Dao.{{$t.TableName}}QueryList.getData = function(){ + let list = [] + for(let i=0; i 0 { return results[0], r.Error } else { - return map[string]interface{}{}, r.Error + return nil, r.Error } } @@ -147,39 +254,53 @@ func (db *DB) Query1a(requestSql string, args ...interface{}) ([]interface{}, er // * table 表名 // * data 数据对象(Key-Value格式) // Insert return 如果是INSERT到含有自增字段的表中返回插入的自增ID,否则返回影响的行数 -func (db *DB) Insert(table string, data map[string]interface{}) (int64, error) { +func (db *DB) Insert(table string, data map[string]interface{}) (ExecResult, error) { r := db.pool.Insert(table, data) - out := r.Id() - if out == 0 { - out = r.Changes() - } - return out, r.Error + return ExecResult{ + Id: r.Id(), + Changes: r.Changes(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } // Replace 替换数据 // Replace return 如果是REPLACE到含有自增字段的表中返回插入的自增ID,否则返回影响的行数 -func (db *DB) Replace(table string, data map[string]interface{}) (int64, error) { +func (db *DB) Replace(table string, data map[string]interface{}) (ExecResult, error) { r := db.pool.Replace(table, data) out := r.Id() if out == 0 { out = r.Changes() } - return out, r.Error + return ExecResult{ + Id: r.Id(), + Changes: r.Changes(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } // Update 更新数据 // * wheres 条件(SQL中WHERE后面的部分) // Update return 返回影响的行数 -func (db *DB) Update(table string, data map[string]interface{}, wheres string, args ...interface{}) (int64, error) { +func (db *DB) Update(table string, data map[string]interface{}, wheres string, args ...interface{}) (ExecResult, error) { r := db.pool.Update(table, data, wheres, args...) - return r.Changes(), r.Error + return ExecResult{ + Changes: r.Changes(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } // Delete 删除数据 // Delete return 返回影响的行数 -func (db *DB) Delete(table string, wheres string, args ...interface{}) (int64, error) { +func (db *DB) Delete(table string, wheres string, args ...interface{}) (ExecResult, error) { r := db.pool.Delete(table, wheres, args...) - return r.Changes(), r.Error + return ExecResult{ + Changes: r.Changes(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } // MakeId 生成指定字段不唯一的ID @@ -189,7 +310,7 @@ func (db *DB) Delete(table string, wheres string, args ...interface{}) (int64, e func (db *DB) MakeId(table string, idField string, idSize uint) (string, error) { var id string var err error - for i:=0; i<100; i++ { + for i := 0; i < 100; i++ { if idSize > 20 { id = u.UniqueId() } else if idSize > 14 { @@ -235,14 +356,26 @@ func (tx *Tx) CheckFinished() error { return tx.conn.CheckFinished() } -func (tx *Tx) Exec(requestSql string, args ...interface{}) (int64, error) { +func (tx *Tx) Exec(requestSql string, args ...interface{}) (ExecResult, error) { r := tx.conn.Exec(requestSql, args...) - return r.Changes(), r.Error + out := ExecResult{} + lSql := strings.ToLower(requestSql) + if strings.Contains(lSql, "insert into") || strings.Contains(lSql, "replace into") { + out.Id = r.Id() + } + out.Changes = r.Changes() + out.Sql = *r.Sql + out.SqlArgs = r.Args + return out, r.Error } -func (tx *Tx) Query(requestSql string, args ...interface{}) ([]map[string]interface{}, error) { +func (tx *Tx) Query(requestSql string, args ...interface{}) (QueryResult, error) { r := tx.conn.Query(requestSql, args...) - return r.MapResults(), r.Error + return QueryResult{ + Result: r.MapResults(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } func (tx *Tx) Query1(requestSql string, args ...interface{}) (map[string]interface{}, error) { @@ -251,7 +384,7 @@ func (tx *Tx) Query1(requestSql string, args ...interface{}) (map[string]interfa if len(results) > 0 { return results[0], r.Error } else { - return map[string]interface{}{}, r.Error + return nil, r.Error } } @@ -281,22 +414,40 @@ func (tx *Tx) Query1a(requestSql string, args ...interface{}) ([]interface{}, er return a, r.Error } -func (tx *Tx) Insert(table string, data map[string]interface{}) (int64, error) { +func (tx *Tx) Insert(table string, data map[string]interface{}) (ExecResult, error) { r := tx.conn.Insert(table, data) - return r.Id(), r.Error + return ExecResult{ + Id: r.Id(), + Changes: r.Changes(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } -func (tx *Tx) Replace(table string, data map[string]interface{}) (int64, error) { +func (tx *Tx) Replace(table string, data map[string]interface{}) (ExecResult, error) { r := tx.conn.Replace(table, data) - return r.Id(), r.Error + return ExecResult{ + Id: r.Id(), + Changes: r.Changes(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } -func (tx *Tx) Update(table string, data map[string]interface{}, wheres string, args ...interface{}) (int64, error) { +func (tx *Tx) Update(table string, data map[string]interface{}, wheres string, args ...interface{}) (ExecResult, error) { r := tx.conn.Update(table, data, wheres, args...) - return r.Changes(), r.Error + return ExecResult{ + Changes: r.Changes(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } -func (tx *Tx) Delete(table string, wheres string, args ...interface{}) (int64, error) { +func (tx *Tx) Delete(table string, wheres string, args ...interface{}) (ExecResult, error) { r := tx.conn.Delete(table, wheres, args...) - return r.Changes(), r.Error + return ExecResult{ + Changes: r.Changes(), + Sql: *r.Sql, + SqlArgs: r.Args, + }, r.Error } diff --git a/db/er.html b/db/er.html new file mode 100644 index 0000000..b5011c9 --- /dev/null +++ b/db/er.html @@ -0,0 +1,134 @@ + + + + + + + + {{.title}} + + + + +
+ +{{range .groups}} +
+
{{.Group}}
+ {{range .Tables}} +
+
{{.Name}}
+
+ {{range .Fields}} + {{if eq .Index "pk"}} +
{{.Name}}
+ {{end}} + {{end}} +
+
+ {{range .Fields}} + {{if ne .Index "pk"}} +
{{.Name}}
+ {{end}} + {{end}} +
+
+ {{end}} +
+{{end}} + + diff --git a/go.mod b/go.mod index b1a3f4b..c2275ae 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,12 @@ require ( github.com/emmansun/gmsm v0.21.1 github.com/gorilla/websocket v1.5.1 github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 - github.com/ssgo/db v0.6.11 - github.com/ssgo/discover v0.6.11 - github.com/ssgo/httpclient v0.6.11 - github.com/ssgo/log v0.6.11 - github.com/ssgo/redis v0.6.11 - github.com/ssgo/u v0.6.11 + github.com/ssgo/db v0.6.12 + github.com/ssgo/discover v0.6.12 + github.com/ssgo/httpclient v0.6.12 + github.com/ssgo/log v0.6.12 + github.com/ssgo/redis v0.6.12 + github.com/ssgo/u v0.6.12 gopkg.in/yaml.v3 v3.0.1 ) @@ -21,8 +21,8 @@ require ( github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gomodule/redigo v1.8.8 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/ssgo/config v0.6.11 // indirect - github.com/ssgo/standard v0.6.11 // indirect + github.com/ssgo/config v0.6.12 // indirect + github.com/ssgo/standard v0.6.12 // indirect github.com/stretchr/testify v1.8.1 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect diff --git a/http/http.go b/http/http.go index 8d2f3c2..45408b7 100644 --- a/http/http.go +++ b/http/http.go @@ -33,7 +33,7 @@ func init() { "newWithoutRedirect": NewHTTPWithoutRedirect, "newH2CWithoutRedirect": NewH2CHTTPWithoutRedirect, "setBaseURL": defaultClient.SetBaseURL, - "SetGlobalHeaders": defaultClient.SetGlobalHeaders, + "setGlobalHeaders": defaultClient.SetGlobalHeaders, "get": defaultClient.Get, "post": defaultClient.Post, "put": defaultClient.Put, diff --git a/tests.sh b/tests.sh new file mode 100755 index 0000000..ac947fa --- /dev/null +++ b/tests.sh @@ -0,0 +1,7 @@ +cd tests +go test -v test.go db_test.go | logv +go test -v test.go redis_test.go | logv +go test -v test.go crypto_test.go | logv +go test -v test.go file_test.go | logv +go test -v test.go http_test.go | logv +cd .. diff --git a/tests/TestDB.yml b/tests/TestDB.yml new file mode 100644 index 0000000..df8e9f7 --- /dev/null +++ b/tests/TestDB.yml @@ -0,0 +1,29 @@ +- group: account + comment: 账号相关 + tables: + - name: user + comment: 用户表 + fields: + - name: id + comment: 用户编号 + type: INTEGER + index: pk + extra: AUTO_INCREMENT + - name: name + comment: 姓名 + type: VARCHAR(30) + - name: phone + comment: 手机号 + type: VARCHAR(20) + index: unique + - name: addDate + comment: 创建时间 + type: DATETIME + index: index + - name: version + comment: 数据版本号 + type: INTEGER + index: index + - name: isValid + comment: 是否有效(1-启用,0-禁用) + type: INTEGER diff --git a/tests/db_test.go b/tests/db_test.go index 6858b79..0f66146 100644 --- a/tests/db_test.go +++ b/tests/db_test.go @@ -4,47 +4,164 @@ import ( "apigo.cloud/git/apigo/gojs" "apigo.cloud/git/apigo/plugin" _ "apigo.cloud/git/apigo/plugins/db" + _ "apigo.cloud/git/apigo/plugins/file" + _ "apigo.cloud/git/apigo/plugins/redis" _ "github.com/mattn/go-sqlite3" "github.com/ssgo/u" "os" + "path" "testing" ) -func TestSqlite(t *testing.T) { +func TestSql(t *testing.T) { rt := gojs.New(nil, nil) defer rt.Close() gojs.SetPluginsConfig(map[string]plugin.Config{ "db": { - "default": "sqlite3://test.db", + "default": "sqlite3://Test.db", }, }) - defer os.Remove("test.db") - r, err, _ := rt.Run(` - db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(30))') - id1 = db.insert('test', {name:'Tom'}) - id2 = db.replace('test', {name:'Kitty'}) - id3 = db.insert('test', {name:'Lucy'}) - return id1 + id2 + id3 + defer func() { + _ = os.Remove("Test.db") + }() + var r interface{} = false + err, _ := rt.Exec(` + TestTable = file.loadYaml('./TestDB.yml') + out = db.make(TestTable) + //console.info(...out) `) - Test(t, "create and insert", u.Int(r) == 6, r, err) + if err == nil { + r, err, _ = rt.Run(` + let r1 = db.insert('user', {name:'Tom'}) + let r2 = db.replace('user', {name:'Kitty'}) + let r3 = db.insert('user', {name:'Lucy'}) + return r1.id + ',' + r2.id + ',' + r3.id + ' | ' + r1.changes + ',' + r2.changes + ',' + r3.changes + `) + } + Test(t, "create and insert", r == "1,2,3 | 1,1,1", r, err) r, err, _ = rt.Run(` - changes = db.fetch().update('test', {name:'Lucy Wang'}, 'id=?', 3) - return changes + let r = db.fetch().update('user', {name:'Lucy Wang'}, 'id=?', 3) + return r.changes `) Test(t, "update", u.Int(r) == 1, r, err) r, err, _ = rt.Run(` - changes = db.fetch().delete('test', 'id=?', 2) - return changes + let r = db.fetch().delete('user', 'id=?', 2) + return r.changes `) - Test(t, "update", u.Int(r) == 1, r, err) + Test(t, "delete", u.Int(r) == 1, r, err) r, err, _ = rt.Run(` - list = db.fetch().query('select id,name from test where id > ?', 1) - return list + let r = db.fetch().query('select id,name from user where id > ?', 1) + return r.result + `) + list := make([]struct { + Id int + Name string + }, 0) + u.Convert(r, &list) + Test(t, "list", len(list) == 1 && list[0].Name == "Lucy Wang", r, err) + + rt.Exec("db.destroy()") +} + +func TestDao(t *testing.T) { + rdServer := StartRedis("16379") + defer rdServer.Close() + + rt := gojs.New(nil, nil) + defer rt.Close() + + gojs.SetPluginsConfig(map[string]plugin.Config{ + "db": { + "default": "sqlite3://Test.db", + }, + "redis": { + "default": "redis://localhost:16379", + }, + }) + + defer func() { + _ = os.Remove("Test.db") + _ = os.Remove("er.html") + _ = os.RemoveAll("Test") + }() + //_ = os.Remove("Test.db") + var r interface{} = false + err, _ := rt.Exec(` + let TestDB = file.loadYaml('./TestDB.yml') + out = db.make(TestDB) + db.makeDao() + // console.info(...out) + `) + + daoCode := "" + daoFile := path.Join("Test", "dao.js") + daoExtFile := path.Join("Test", "dao_ext.js") + _ = u.WriteFile(daoExtFile, ` + _TestDao.user.getFirstUserName = function(){ + let q = this.newQuery() + let u = q.limit(1).fields('id,name').first() + //logger.info(q.getSql()) + return u.name + } + _TestDao.userItem.getFullName = function(){ + return this.name+'('+this.id+')' + } +`) + if u.FileExists(daoFile) { + code, _ := u.ReadFile(daoFile) + daoCode += code + "\n" + } + if u.FileExists(daoExtFile) { + code, _ := u.ReadFile(daoExtFile) + daoCode += code + "\n" + } + + if err == nil { + r, err, _ = rt.Run(daoCode + ` + let dao = getTestDao() + let r1 = dao.user.insert({name:'Tom'}) + let r2 = dao.user.replace({name:'Kitty'}) + //r3 = dao.user.insert({name:'Lucy'}) + let u3 = dao.user.new({phone:'18612341234'}) + u3.name = 'Lucy' + let r3 = u3.save() + return r1.id + ',' + r2.id + ',' + r3.id + ' | ' + r1.version + ',' + r2.version + ',' + r3.version + `) + } + Test(t, "create and insert", r == "1,2,3 | 1,2,3", r, err) + + r, err, _ = rt.Run(daoCode + ` + let item = getTestDao().user.get(1) + return item.name + `) + Test(t, "get", r == "Tom", r, err) + + r, err, _ = rt.Run(daoCode + ` + let u = getTestDao().user.getByPhone('1861234123') + if(u!==null) return 'not null' + u = getTestDao().user.getByPhone('18612341234') + u.name = 'Lucy Wang' + let r = u.save() + return r.changes +','+ r.version + `) + Test(t, "update", r == "1,4", r, err) + + r, err, _ = rt.Run(daoCode + ` + getTestDao() + let r = getTestDao().user.disable(2) + return r.changes + `) + Test(t, "disable", u.Int(r) == 1, r, err) + + r, err, _ = rt.Run(daoCode+` + let q = getTestDao().user.newQuery() + let list = q.where('id>?',1).list() + return list `) list := make([]struct{ Id int @@ -53,4 +170,21 @@ func TestSqlite(t *testing.T) { u.Convert(r, &list) Test(t, "list", len(list) == 1 && list[0].Name == "Lucy Wang", r, err) + r, err, _ = rt.Run(daoCode+` + return getTestDao().user.getFirstUserName() + `) + Test(t, "table ext", r == "Tom", r, err) + + r, err, _ = rt.Run(daoCode+` + return getTestDao().user.newQuery().list()[0].getFullName() + `) + Test(t, "item ext", r == "Tom(1)", r, err) + + r, err, _ = rt.Run(daoCode+` + db.makeER(TestDB) + let erStr = file.read('er.html') + return erStr.match(/