github.com/easysoft/zendata@v0.0.0-20240513203326-705bd5a7fd67/internal/pkg/service/from-sql.go (about)

     1  package service
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/easysoft/zendata/internal/pkg/domain"
    10  	"github.com/easysoft/zendata/internal/pkg/helper"
    11  	"github.com/easysoft/zendata/internal/pkg/model"
    12  	fileUtils "github.com/easysoft/zendata/pkg/utils/file"
    13  	i118Utils "github.com/easysoft/zendata/pkg/utils/i118"
    14  	logUtils "github.com/easysoft/zendata/pkg/utils/log"
    15  	"github.com/easysoft/zendata/pkg/utils/vari"
    16  	"gopkg.in/yaml.v2"
    17  )
    18  
    19  type SqlParseService struct {
    20  }
    21  
    22  func (s *SqlParseService) GenYamlFromSql(file string) {
    23  	startTime := time.Now().Unix()
    24  
    25  	sql := fileUtils.ReadFile(file)
    26  	statementMap, pkMap, fkMap := s.getCreateStatement(sql)
    27  
    28  	// gen key yaml files
    29  	inst := model.ZdInstances{Title: "keys", Desc: "automated export"}
    30  
    31  	for tableName, keyCol := range pkMap {
    32  		item := model.ZdInstancesItem{}
    33  
    34  		item.Instance = fmt.Sprintf("%s_%s", tableName, keyCol)
    35  		item.Range = "1-100000"
    36  
    37  		inst.Instances = append(inst.Instances, item)
    38  	}
    39  
    40  	bytes, _ := yaml.Marshal(&inst)
    41  	content := strings.ReplaceAll(string(bytes), "'-'", "\"\"")
    42  
    43  	if vari.GlobalVars.Output != "" {
    44  		vari.GlobalVars.Output = fileUtils.AddSepIfNeeded(vari.GlobalVars.Output)
    45  		outFile := vari.GlobalVars.Output + "keys.yaml"
    46  		fileUtils.WriteFile(outFile, content)
    47  	} else {
    48  		logUtils.PrintTo(content)
    49  	}
    50  
    51  	// gen table yaml files
    52  	for tableName, statement := range statementMap {
    53  		createSql := statement
    54  
    55  		columns, types := s.getColumnsFromCreateStatement(createSql, nil)
    56  
    57  		def := domain.DefSimple{}
    58  		def.Init(tableName, "automated export", "", "1.0")
    59  
    60  		for _, col := range columns {
    61  			field := domain.FieldSimple{Field: col}
    62  
    63  			// pk
    64  			isPk := col == pkMap[tableName]
    65  			fkInfo, isFk := fkMap[col]
    66  
    67  			if isPk {
    68  				field.From = "keys.yaml"
    69  				field.Use = fmt.Sprintf("%s_%s", tableName, col)
    70  			} else if isFk {
    71  				field.From = "keys.yaml"
    72  				field.Use = fmt.Sprintf("%s_%s{:1}", fkInfo[0], fkInfo[1])
    73  			} else {
    74  				field.Range = types[col].Rang
    75  
    76  				field.Type = types[col].Type
    77  				field.Format = types[col].Format
    78  				field.From = types[col].From
    79  				field.Use = types[col].Use
    80  				field.From = types[col].From
    81  				field.Select = types[col].From
    82  				field.Prefix = types[col].Prefix
    83  
    84  				field.Note = types[col].Note
    85  			}
    86  
    87  			def.Fields = append(def.Fields, field)
    88  		}
    89  
    90  		bytes, _ := yaml.Marshal(&def)
    91  		content := strings.ReplaceAll(string(bytes), "'", "")
    92  		if vari.GlobalVars.Output != "" {
    93  			vari.GlobalVars.Output = fileUtils.AddSepIfNeeded(vari.GlobalVars.Output)
    94  			outFile := vari.GlobalVars.Output + tableName + ".yaml"
    95  			fileUtils.WriteFile(outFile, content)
    96  		} else {
    97  			logUtils.PrintTo(content)
    98  		}
    99  	}
   100  
   101  	entTime := time.Now().Unix()
   102  	logUtils.PrintTo(i118Utils.I118Prt.Sprintf("generate_yaml", len(statementMap), vari.GlobalVars.Output, entTime-startTime))
   103  }
   104  
   105  func (s *SqlParseService) getCreateStatement(content string) (statementMap map[string]string, pkMap map[string]string, fkMap map[string][2]string) {
   106  	statementMap = map[string]string{}
   107  	pkMap = map[string]string{}
   108  	fkMap = map[string][2]string{}
   109  
   110  	re := regexp.MustCompile("(?siU)(CREATE TABLE.*;)")
   111  	arr := re.FindAllString(string(content), -1)
   112  	for _, item := range arr {
   113  		re := regexp.MustCompile("(?i)CREATE TABLE.*\\s+(\\S+)\\s+\\(") // get table name
   114  		firstLine := strings.Split(item, "\n")[0]
   115  		arr2 := re.FindAllStringSubmatch(firstLine, -1)
   116  
   117  		if len(arr2) > 0 && len(arr2[0]) > 1 {
   118  			tableName := arr2[0][1]
   119  			tableName = strings.ReplaceAll(tableName, "`", "")
   120  			statementMap[tableName] = item
   121  
   122  			re3 := regexp.MustCompile("(?i)PRIMARY KEY\\s+\\((\\S+)\\)")
   123  			arr3 := re3.FindAllStringSubmatch(item, -1)
   124  			if len(arr3) > 0 {
   125  				for _, childArr := range arr3 {
   126  					pkMap[tableName] = strings.ReplaceAll(childArr[1], "`", "")
   127  				}
   128  			}
   129  
   130  			re4 := regexp.MustCompile("(?i)FOREIGN KEY\\s+\\((\\S+)\\) REFERENCES (\\S+) \\((\\S+)\\)")
   131  			arr4 := re4.FindAllStringSubmatch(item, -1)
   132  			if len(arr4) > 0 {
   133  				for _, childArr := range arr4 {
   134  					col := strings.ReplaceAll(childArr[1], "`", "")
   135  					toTable := strings.ReplaceAll(childArr[2], "`", "")
   136  					toCol := strings.ReplaceAll(childArr[3], "`", "")
   137  
   138  					fkMap[col] = [2]string{toTable, toCol}
   139  				}
   140  			}
   141  
   142  		}
   143  	}
   144  
   145  	return
   146  }
   147  
   148  func (s *SqlParseService) getColumnsFromCreateStatement(ddl string, recordsMap map[string][]interface{}) (
   149  	fieldLines []string, fieldInfo map[string]helper.FieldTypeInfo) {
   150  	fieldInfo = map[string]helper.FieldTypeInfo{}
   151  
   152  	re := regexp.MustCompile("(?iU)\\s*(\\S+)\\s.*\n")
   153  	results := re.FindAllStringSubmatch(ddl, -1)
   154  
   155  	for _, items := range results {
   156  		temp := strings.ToLower(items[0])
   157  		if strings.Contains(temp, " table ") || strings.Contains(temp, " key ") {
   158  			continue
   159  		}
   160  
   161  		typ, name, param := s.getFieldData(items)
   162  		fieldInfo[name] = helper.GenerateFieldDefByMetadata(typ, param, name, recordsMap[name])
   163  
   164  		fieldLines = append(fieldLines, name)
   165  
   166  	}
   167  
   168  	return fieldLines, fieldInfo
   169  }
   170  
   171  func (s *SqlParseService) getFieldData(item []string) (typ, name string, param string) {
   172  	colName := item[1]
   173  	name = strings.ReplaceAll(colName, "`", "")
   174  
   175  	myExp := regexp.MustCompile(colName + `\s([a-zA-Z]+)\((?U:(.*))\)`)
   176  	result := myExp.FindStringSubmatch(item[0])
   177  
   178  	if result != nil { // type with length like int(10)
   179  		typ = result[1]
   180  		param = result[2]
   181  	} else {
   182  		typ = strings.Split(strings.Fields(item[0])[1], "(")[0]
   183  	}
   184  
   185  	typ = strings.TrimSuffix(typ, ",")
   186  
   187  	typ = strings.ToLower(typ)
   188  	name = strings.ToLower(name)
   189  
   190  	return
   191  }
   192  
   193  func (s *SqlParseService) genKeysYaml(pkMap map[string]string) {
   194  	// gen key yaml files
   195  	inst := model.ZdInstances{Title: "keys", Desc: "automated export"}
   196  
   197  	for tableName, keyCol := range pkMap {
   198  		item := model.ZdInstancesItem{}
   199  
   200  		item.Instance = fmt.Sprintf("%s_%s", tableName, keyCol)
   201  		item.Range = "1-100000"
   202  
   203  		inst.Instances = append(inst.Instances, item)
   204  	}
   205  
   206  	bytes, _ := yaml.Marshal(&inst)
   207  	content := strings.ReplaceAll(string(bytes), "'-'", "\"\"")
   208  
   209  	if vari.GlobalVars.Output != "" {
   210  		vari.GlobalVars.Output = fileUtils.AddSepIfNeeded(vari.GlobalVars.Output)
   211  		outFile := vari.GlobalVars.Output + "keys.yaml"
   212  		fileUtils.WriteFile(outFile, content)
   213  
   214  	} else {
   215  		logUtils.PrintTo(content)
   216  	}
   217  }
   218  
   219  func (s *SqlParseService) genTablesYaml(statementMap map[string]string,
   220  	pkMap map[string]string, fkMap map[string][2]string, recordsMap map[string]map[string][]interface{}) {
   221  	for tableName, statement := range statementMap {
   222  		createStr := statement
   223  
   224  		columns, types := s.getColumnsFromCreateStatement(createStr, recordsMap[tableName])
   225  
   226  		def := domain.DefSimple{}
   227  		def.Init(tableName, "automated export", "", "1.0")
   228  
   229  		for _, col := range columns {
   230  			field := domain.FieldSimple{Field: col}
   231  
   232  			// pk
   233  			isPk := col == pkMap[tableName]
   234  			fkInfo, isFk := fkMap[col]
   235  
   236  			if isPk {
   237  				field.From = "keys.yaml"
   238  				field.Use = fmt.Sprintf("%s_%s", tableName, col)
   239  			} else if isFk {
   240  				field.From = "keys.yaml"
   241  				field.Use = fmt.Sprintf("%s_%s{:1}", fkInfo[0], fkInfo[1])
   242  			} else {
   243  				field.Range = types[col].Rang
   244  
   245  				field.Type = types[col].Type
   246  				field.Loop = types[col].Loop
   247  				field.Format = types[col].Format
   248  				field.From = types[col].From
   249  				field.Use = types[col].Use
   250  				field.From = types[col].From
   251  				field.Select = types[col].Select
   252  				field.Prefix = types[col].Prefix
   253  
   254  				field.Note = types[col].Note
   255  			}
   256  
   257  			def.Fields = append(def.Fields, field)
   258  		}
   259  
   260  		bytes, _ := yaml.Marshal(&def)
   261  		content := strings.ReplaceAll(string(bytes), "'", "")
   262  		if vari.GlobalVars.Output != "" {
   263  			vari.GlobalVars.Output = fileUtils.AddSepIfNeeded(vari.GlobalVars.Output)
   264  			outFile := vari.GlobalVars.Output + tableName + ".yaml"
   265  			fileUtils.WriteFile(outFile, content)
   266  		} else {
   267  			logUtils.PrintTo(content)
   268  		}
   269  	}
   270  }
   271  
   272  type TableInfo struct {
   273  	Field   string
   274  	Type    string
   275  	Null    string
   276  	Key     string
   277  	Default string
   278  	Extra   string
   279  }