├── .gitignore ├── LICENSE ├── Makefile ├── cmd ├── app.go ├── compare.go ├── create.go ├── root.go ├── tablemeta.go └── version.go ├── connect └── connect.go ├── example.yml ├── go.mod ├── go.sum ├── image └── logo.png ├── macos_compile_remote.sh ├── main.go ├── readme.md ├── readme_cn.md └── test └── _cmd ├── app.go_ ├── root.go_ └── tablemeta.go_ /.gitignore: -------------------------------------------------------------------------------- 1 | /dbcfg.yml 2 | .idea 3 | mysqlDataSyncTool.exe 4 | /2023_*/*.log 5 | linux_* 6 | win_* 7 | /log/* 8 | release 9 | *.tar.gz 10 | proxy.txt 11 | compare.sh 12 | instantclient 13 | binary -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 iverycd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # usage 2 | # eg. make release VERSION=V0.1.5,cross compile only work fine with github.com/sijms/go-ora 3 | # Binary name 4 | BINARY=OracleSync2MySQL 5 | # Builds the project 6 | build: 7 | GO111MODULE=on go build -o ${BINARY} -ldflags "-X main.Version=${VERSION}" 8 | GO111MODULE=on go test -v 9 | # Installs our project: copies binaries 10 | install: 11 | GO111MODULE=on go install 12 | release: 13 | # Clean 14 | go clean 15 | rm -rf *.gz 16 | # Build for mac 17 | GO111MODULE=on GOOS=darwin go build -ldflags "-s -w -X main.Version=${VERSION}" 18 | tar czvf ${BINARY}-MacOS-x64-${VERSION}.tar.gz ./${BINARY} ./example.yml 19 | # Build for arm 20 | go clean 21 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" 22 | tar czvf ${BINARY}-linux-arm64-${VERSION}.tar.gz ./${BINARY} ./example.yml 23 | # Build for linux 24 | go clean 25 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" 26 | tar czvf ${BINARY}-linux-x64-${VERSION}.tar.gz ./${BINARY} ./example.yml 27 | # Build for win 28 | go clean 29 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" 30 | tar czvf ${BINARY}-win-x64-${VERSION}.tar.gz ./${BINARY}.exe ./example.yml 31 | go clean 32 | # Cleans our projects: deletes binaries 33 | clean: 34 | go clean 35 | rm -rf *.gz 36 | 37 | .PHONY: clean build -------------------------------------------------------------------------------- /cmd/app.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "OracleSync2MySQL/connect" 5 | "bufio" 6 | "database/sql" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/go-sql-driver/mysql" 10 | _ "github.com/go-sql-driver/mysql" 11 | "github.com/godror/godror" 12 | "github.com/spf13/viper" 13 | "net" 14 | "os" 15 | "path/filepath" 16 | "strconv" 17 | "time" 18 | //_ "github.com/sijms/go-ora/v2" 19 | _ "github.com/godror/godror" 20 | ) 21 | 22 | var srcDb *sql.DB 23 | var destDb *sql.DB 24 | var oracleConnStr godror.ConnectionParams 25 | 26 | func getConn() (connStr *connect.DbConnStr) { 27 | connStr = new(connect.DbConnStr) 28 | connStr.SrcHost = viper.GetString("src.host") 29 | connStr.SrcUserName = viper.GetString("src.username") 30 | connStr.SrcPassword = viper.GetString("src.password") 31 | connStr.SrcDatabase = viper.GetString("src.database") 32 | connStr.SrcPort = viper.GetInt("src.port") 33 | connStr.DestHost = viper.GetString("dest.host") 34 | connStr.DestPort = viper.GetInt("dest.port") 35 | connStr.DestUserName = viper.GetString("dest.username") 36 | connStr.DestPassword = viper.GetString("dest.password") 37 | connStr.DestDatabase = viper.GetString("dest.database") 38 | connStr.DestParams = viper.GetStringMapString("dest.params") 39 | return connStr 40 | } 41 | 42 | func PrepareSrc(connStr *connect.DbConnStr) { 43 | // 生成源库连接 44 | srcHost := connStr.SrcHost 45 | srcUserName := connStr.SrcUserName 46 | srcPassword := connStr.SrcPassword 47 | srcDatabase := connStr.SrcDatabase 48 | srcPort := connStr.SrcPort 49 | //srcConn := fmt.Sprintf("oracle://%s:%s@%s:%d/%s?LOB FETCH=POST", srcUserName, srcPassword, srcHost, srcPort, srcDatabase) 50 | //fmt.Println(srcConn) 51 | var err error 52 | //srcDb, err = sql.Open("oracle", srcConn) //go-ora 53 | //srcDb, err = sql.Open("godror", `user="one" password="oracle" connectString="192.168.189.200:1521/orcl" libDir="/Users/kay/Documents/database/oracle/instantclient_19_8_mac"`)//直接连接方式 54 | oracleConnStr.LibDir = "instantclient" 55 | oracleConnStr.Username = srcUserName 56 | oracleConnStr.Password = godror.NewPassword(srcPassword) 57 | oracleConnStr.ConnectString = fmt.Sprintf("%s:%s/%s", srcHost, strconv.Itoa(srcPort), srcDatabase) 58 | srcDb = sql.OpenDB(godror.NewConnector(oracleConnStr)) 59 | if err != nil { 60 | log.Fatal("please check SourceDB yml file", err) 61 | } 62 | c := srcDb.Ping() 63 | if c != nil { 64 | log.Fatal("connect Source database failed ", c) 65 | } 66 | srcDb.SetConnMaxLifetime(2 * time.Hour) // 一个连接被使用的最长时间,过一段时间之后会被强制回收 67 | srcDb.SetMaxIdleConns(0) // 最大空闲连接数,0为不限制 68 | srcDb.SetMaxOpenConns(0) // 设置连接池最大连接数 69 | log.Info("connect Source ", srcHost, " success") 70 | } 71 | 72 | func PrepareDest(connStr *connect.DbConnStr) { 73 | // 生成目标连接 74 | destHost := connStr.DestHost 75 | destUserName := connStr.DestUserName 76 | destPassword := connStr.DestPassword 77 | destDatabase := connStr.DestDatabase 78 | destPort := connStr.DestPort 79 | //destConn := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=utf8&maxAllowedPacket=0", destUserName, destPassword, destHost, destPort, destDatabase) 80 | destConf := mysql.NewConfig() 81 | destConf.DBName = destDatabase 82 | destConf.User = destUserName 83 | destConf.Passwd = destPassword 84 | destConf.Net = "tcp" 85 | destConf.Addr = net.JoinHostPort(destHost, strconv.Itoa(destPort)) 86 | destConf.ParseTime = true 87 | destConf.Loc = time.Local 88 | //destConf.Params = map[string]string{"charset": "utf8", "maxAllowedPacket": "0"} 89 | if connStr.DestParams != nil && len(connStr.DestParams) > 0 { 90 | destConf.Params = connStr.DestParams 91 | } 92 | var err error 93 | destDriver, err := mysql.NewConnector(destConf) 94 | if err != nil { 95 | log.Fatal("please check MySQL yml file", err) 96 | } 97 | //destDb, err = sql.Open("mysql", destConn) 98 | destDb = sql.OpenDB(destDriver) 99 | 100 | c := destDb.Ping() 101 | if c != nil { 102 | log.Fatal("connect target MySQL failed ", c) 103 | } 104 | destDb.SetConnMaxLifetime(2 * time.Hour) // 一个连接被使用的最长时间,过一段时间之后会被强制回收 105 | destDb.SetMaxIdleConns(0) // 最大空闲连接数,0为不限制 106 | destDb.SetMaxOpenConns(0) // 设置连接池最大连接数 107 | log.Info("connect MySQL ", destHost, " success") 108 | } 109 | 110 | func LogError(logDir string, logName string, strContent string, errInfo error) { 111 | f, errFile := os.OpenFile(logDir+"/"+logName+".log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) 112 | if errFile != nil { 113 | log.Fatal(errFile) 114 | } 115 | defer func() { 116 | if errFile := f.Close(); errFile != nil { 117 | log.Fatal(errFile) // 或设置到函数返回值中 118 | } 119 | }() 120 | // create new buffer 121 | buffer := bufio.NewWriter(f) 122 | _, errFile = buffer.WriteString(strContent + " -- ErrorInfo " + StrVal(errInfo) + "\n") 123 | if errFile != nil { 124 | log.Fatal(errFile) 125 | } 126 | // flush buffered data to the file 127 | if errFile := buffer.Flush(); errFile != nil { 128 | log.Fatal(errFile) 129 | } 130 | } 131 | 132 | func LogOutput(logDir string, logName string, strContent string) { 133 | f, errFile := os.OpenFile(logDir+"/"+logName+".log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) 134 | if errFile != nil { 135 | log.Fatal(errFile) 136 | } 137 | defer func() { 138 | if errFile := f.Close(); errFile != nil { 139 | log.Fatal(errFile) // 或设置到函数返回值中 140 | } 141 | }() 142 | // create new buffer 143 | buffer := bufio.NewWriter(f) 144 | _, errFile = buffer.WriteString(strContent + "\n") 145 | if errFile != nil { 146 | log.Fatal(errFile) 147 | } 148 | // flush buffered data to the file 149 | if errFile := buffer.Flush(); errFile != nil { 150 | log.Fatal(errFile) 151 | } 152 | } 153 | 154 | // StrVal 155 | // 获取变量的字符串值,目前用于interface类型转成字符串类型 156 | // 浮点型 3.0将会转换成字符串3, "3" 157 | // 非数值或字符类型的变量将会被转换成JSON格式字符串 158 | func StrVal(value interface{}) string { 159 | var key string 160 | if value == nil { 161 | return key 162 | } 163 | 164 | switch value.(type) { 165 | case float64: 166 | ft := value.(float64) 167 | key = strconv.FormatFloat(ft, 'f', -1, 64) 168 | case float32: 169 | ft := value.(float32) 170 | key = strconv.FormatFloat(float64(ft), 'f', -1, 64) 171 | case int: 172 | it := value.(int) 173 | key = strconv.Itoa(it) 174 | case uint: 175 | it := value.(uint) 176 | key = strconv.Itoa(int(it)) 177 | case int8: 178 | it := value.(int8) 179 | key = strconv.Itoa(int(it)) 180 | case uint8: 181 | it := value.(uint8) 182 | key = strconv.Itoa(int(it)) 183 | case int16: 184 | it := value.(int16) 185 | key = strconv.Itoa(int(it)) 186 | case uint16: 187 | it := value.(uint16) 188 | key = strconv.Itoa(int(it)) 189 | case int32: 190 | it := value.(int32) 191 | key = strconv.Itoa(int(it)) 192 | case uint32: 193 | it := value.(uint32) 194 | key = strconv.Itoa(int(it)) 195 | case int64: 196 | it := value.(int64) 197 | key = strconv.FormatInt(it, 10) 198 | case uint64: 199 | it := value.(uint64) 200 | key = strconv.FormatUint(it, 10) 201 | case string: 202 | key = value.(string) 203 | case []byte: 204 | key = string(value.([]byte)) 205 | default: 206 | newValue, _ := json.Marshal(value) 207 | key = string(newValue) 208 | } 209 | 210 | return key 211 | } 212 | 213 | func cleanDBconn() { 214 | // 遍历正在执行的客户端,使用kill query 命令kill所有查询id,避免目标数据库仍在执行额外sql 215 | rows, err := destDb.Query("select id from information_schema.PROCESSLIST where info like '/* goapp%';") 216 | if err != nil { 217 | log.Error(err) 218 | } 219 | defer rows.Close() 220 | for rows.Next() { 221 | var id string 222 | err = rows.Scan(&id) 223 | if err != nil { 224 | log.Error("rows.Scan(&id) failed!", err) 225 | } 226 | destDb.Exec("kill query " + id) 227 | log.Info("kill thread id ", id) 228 | } 229 | } 230 | 231 | // 监控来自终端的信号,如果按下了ctrl+c,断开数据库查询以及退出程序 232 | func exitHandle(exitChan chan os.Signal) { 233 | 234 | for { 235 | select { 236 | case sig := <-exitChan: 237 | fmt.Println("receive system signal:", sig) 238 | cleanDBconn() // 调用清理数据库连接的方法 239 | os.Exit(1) //如果ctrl+c 关不掉程序,使用os.Exit强行关掉 240 | } 241 | } 242 | 243 | } 244 | 245 | // CreateDateDir 根据当前日期来创建文件夹 246 | func CreateDateDir(basePath string) string { 247 | folderName := "log/" + time.Now().Format("2006_01_02_15_04_05") 248 | folderPath := filepath.Join(basePath, folderName) 249 | if _, err := os.Stat(folderPath); os.IsNotExist(err) { 250 | // 必须分成两步 251 | // 先创建文件夹 252 | err := os.MkdirAll(folderPath, 0777) //级联创建目录 253 | if err != nil { 254 | fmt.Println("create directory log failed ", err) 255 | } 256 | // 再修改权限 257 | err = os.Chmod(folderPath, 0777) 258 | if err != nil { 259 | fmt.Println("chmod directory log failed ", err) 260 | } 261 | } 262 | return folderPath 263 | } 264 | -------------------------------------------------------------------------------- /cmd/compare.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/liushuochen/gotable" 6 | "github.com/spf13/cobra" 7 | "github.com/spf13/viper" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | var dbRowsSlice [][]string 16 | 17 | //var dbRowsSlice2 []string 18 | 19 | func init() { 20 | rootCmd.AddCommand(compareDbCmd) 21 | } 22 | 23 | var compareDbCmd = &cobra.Command{ 24 | Use: "compareDb", 25 | Short: "Compare entire source and target database table rows", 26 | Long: ``, 27 | Run: func(cmd *cobra.Command, args []string) { 28 | // 获取配置文件中的数据库连接字符串 29 | connStr := getConn() 30 | // 每页的分页记录数,仅全库迁移时有效 31 | pageSize := viper.GetInt("pageSize") 32 | // 从配置文件中获取需要排除的表 33 | excludeTab := viper.GetStringSlice("exclude") 34 | PrepareSrc(connStr) 35 | PrepareDest(connStr) 36 | var tableMap map[string][]string 37 | // 获取源库的所有表 38 | if selFromYml { // 如果用了-s选项,从配置文件中获取表名以及sql语句 39 | tableMap = viper.GetStringMapStringSlice("tables") 40 | } else { // 不指定-s选项,查询源库所有表名 41 | tableMap = fetchTableMap(pageSize, excludeTab) 42 | } 43 | // 创建运行日志目录 44 | logDir, _ := filepath.Abs(CreateDateDir("")) 45 | f, err := os.OpenFile(logDir+"/"+"run.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | defer func() { 50 | if err := f.Close(); err != nil { 51 | log.Fatal(err) // 或设置到函数返回值中 52 | } 53 | }() 54 | // log信息重定向到平面文件 55 | multiWriter := io.MultiWriter(os.Stdout, f) 56 | log.SetOutput(multiWriter) 57 | // 以下开始调用比对表行数的方法 58 | start := time.Now() 59 | // 用于控制协程goroutine运行时候的并发数,例如3个一批,3个一批的goroutine并发运行 60 | ch := make(chan struct{}, viper.GetInt("maxParallel")) 61 | //遍历tableMap 62 | for tableName := range tableMap { //获取单个表名 63 | ch <- struct{}{} 64 | wg2.Add(1) 65 | go compareTable(tableName, ch) 66 | } 67 | // 这里等待上面所有迁移数据的goroutine协程任务完成才会接着运行下面的主程序,如果这里不wait,上面还在迁移行数据的goroutine会被强制中断 68 | wg2.Wait() 69 | cost := time.Since(start) 70 | // 输出全库数量的表 71 | tableTotal, err := gotable.Create("Table", "SourceRows", "DestRows", "DestIsExist", "isOk") 72 | // 输出比对信息失败的表 73 | tableFailed, err := gotable.Create("Table", "SourceRows", "DestRows", "DestIsExist", "isOk") 74 | if err != nil { 75 | fmt.Println("Create table failed: ", err.Error()) 76 | return 77 | } 78 | for _, r := range dbRowsSlice { 79 | if r[4] == "NO" { 80 | _ = tableFailed.AddRow(r) 81 | } 82 | _ = tableTotal.AddRow(r) 83 | } 84 | fmt.Println("Table Compare Total Result") 85 | tableTotal.Align("Table", 1) 86 | tableTotal.Align("SourceRows", 1) 87 | tableTotal.Align("DestRows", 1) 88 | tableTotal.Align("isOk", 1) 89 | tableTotal.Align("DestIsExist", 1) 90 | fmt.Println(tableTotal) 91 | tableFailed.Align("Table", 1) 92 | tableFailed.Align("SourceRows", 1) 93 | tableFailed.Align("DestRows", 1) 94 | tableFailed.Align("isOk", 1) 95 | tableFailed.Align("DestIsExist", 1) 96 | fmt.Println("Table Compare Result (Only Not Ok Displayed)") 97 | fmt.Println(tableFailed) 98 | fmt.Println("Table Compare finish elapsed time ", cost) 99 | }, 100 | } 101 | 102 | func compareTable(tableName string, ch chan struct{}) { 103 | var ( 104 | srcRows int // 源表行数 105 | destRows int // 目标行数 106 | ret []string // 比对结果切片 107 | ) 108 | isOk := "YES" // 行数是否相同 109 | destIsExist := "YES" // 目标表是否存在 110 | defer wg2.Done() 111 | // 查询源库表行数 112 | srcSql := fmt.Sprintf("select count(*) from \"%s\"", tableName) 113 | err := srcDb.QueryRow(srcSql).Scan(&srcRows) 114 | if err != nil { 115 | log.Error(err) 116 | } 117 | // 查询目标表行数 118 | destSql := fmt.Sprintf("select count(*) from `%s`", tableName) 119 | err = destDb.QueryRow(destSql).Scan(&destRows) 120 | if err != nil { 121 | log.Error(err) 122 | isOk, destIsExist = "NO", "NO" // 查询失败就是目标表不存在 123 | } 124 | if srcRows != destRows { 125 | isOk = "NO" 126 | } 127 | // 单行比对结果的切片 128 | ret = append(ret, tableName, strconv.Itoa(srcRows), strconv.Itoa(destRows), destIsExist, isOk) 129 | // 把每个单行切片追加到用于表格输出的切片里面 130 | dbRowsSlice = append(dbRowsSlice, ret) 131 | <-ch 132 | } 133 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/liushuochen/gotable" 6 | "github.com/spf13/viper" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var tableOnly bool 18 | 19 | func init() { 20 | rootCmd.AddCommand(createTableCmd) 21 | //rootCmd.AddCommand(seqOnlyCmd) 22 | //rootCmd.AddCommand(idxOnlyCmd) 23 | //rootCmd.AddCommand(viewOnlyCmd) 24 | rootCmd.AddCommand(onlyDataCmd) 25 | createTableCmd.Flags().BoolVarP(&tableOnly, "tableOnly", "t", false, "only create table true") 26 | } 27 | 28 | var createTableCmd = &cobra.Command{ 29 | Use: "createTable", 30 | Short: "Create meta table and no table data rows", 31 | Long: ``, 32 | Run: func(cmd *cobra.Command, args []string) { 33 | // 获取配置文件中的数据库连接字符串 34 | connStr := getConn() 35 | // 每页的分页记录数,仅全库迁移时有效 36 | pageSize := viper.GetInt("pageSize") 37 | // 从配置文件中获取需要排除的表 38 | excludeTab := viper.GetStringSlice("exclude") 39 | PrepareSrc(connStr) 40 | PrepareDest(connStr) 41 | var tableMap map[string][]string 42 | // 以下是迁移数据前的准备工作,获取要迁移的表名以及该表查询源库的sql语句(如果有主键生成该表的分页查询切片集合,没有主键的统一是全表查询sql) 43 | if selFromYml { // 如果用了-s选项,从配置文件中获取表名以及sql语句 44 | tableMap = viper.GetStringMapStringSlice("tables") 45 | } else { // 不指定-s选项,查询源库所有表名 46 | tableMap = fetchTableMap(pageSize, excludeTab) 47 | } 48 | // 创建运行日志目录 49 | logDir, _ := filepath.Abs(CreateDateDir("")) 50 | f, err := os.OpenFile(logDir+"/"+"run.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | defer func() { 55 | if err := f.Close(); err != nil { 56 | log.Fatal(err) // 或设置到函数返回值中 57 | } 58 | }() 59 | // log信息重定向到平面文件 60 | multiWriter := io.MultiWriter(os.Stdout, f) 61 | log.SetOutput(multiWriter) 62 | // 实例初始化,调用接口中创建目标表的方法 63 | var db Database 64 | start := time.Now() 65 | db = new(Table) 66 | // 用于控制协程goroutine运行时候的并发数,例如3个一批,3个一批的goroutine并发运行 67 | ch := make(chan struct{}, viper.GetInt("maxParallel")) 68 | //遍历tableMap 69 | for tableName := range tableMap { //获取单个表名 70 | if selFromYml { //-s自定义迁移表的时候,统一把yml文件的表名转为大写(否则查询语句的表名都是小写),原因是map键值对(key:value),key的值始终为小写的值 71 | tableName = strings.ToUpper(tableName) 72 | } 73 | ch <- struct{}{} 74 | wg2.Add(1) 75 | go db.TableCreate(logDir, tableName, ch) 76 | } 77 | // 这里等待上面所有迁移数据的goroutine协程任务完成才会接着运行下面的主程序,如果这里不wait,上面还在迁移行数据的goroutine会被强制中断 78 | wg2.Wait() 79 | cost := time.Since(start) 80 | log.Info("Table structure synced finish,Source Table Total ", tableCount, " Failed Total ", strconv.Itoa(failedCount)) 81 | fmt.Println("Table Create finish elapsed time ", cost) 82 | }, 83 | } 84 | 85 | var onlyDataCmd = &cobra.Command{ 86 | Use: "onlyData", 87 | Short: "only transfer table data rows", 88 | Long: ``, 89 | Run: func(cmd *cobra.Command, args []string) { 90 | // 获取配置文件中的数据库连接字符串 91 | connStr := getConn() 92 | // 创建运行日志目录 93 | logDir, _ := filepath.Abs(CreateDateDir("")) 94 | // 输出调用文件以及方法位置 95 | log.SetReportCaller(true) 96 | f, err := os.OpenFile(logDir+"/"+"run.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | defer func() { 101 | if err := f.Close(); err != nil { 102 | log.Fatal(err) 103 | } 104 | }() 105 | // log信息重定向到平面文件 106 | multiWriter := io.MultiWriter(os.Stdout, f) 107 | log.SetOutput(multiWriter) 108 | start := time.Now() 109 | // map结构,表名以及该表用来迁移查询源库的语句 110 | var tableMap map[string][]string 111 | // 从配置文件中获取需要排除的表 112 | excludeTab := viper.GetStringSlice("exclude") 113 | log.Info("running SourceDB check connect") 114 | // 生成源库数据库连接 115 | PrepareSrc(connStr) 116 | defer srcDb.Close() 117 | // 每页的分页记录数,仅全库迁移时有效 118 | pageSize := viper.GetInt("pageSize") 119 | log.Info("running TargetDB check connect") 120 | // 生成目标库的数据库连接 121 | PrepareDest(connStr) 122 | defer destDb.Close() 123 | // 以下是迁移数据前的准备工作,获取要迁移的表名以及该表查询源库的sql语句(如果有主键生成该表的分页查询切片集合,没有主键的统一是全表查询sql) 124 | if selFromYml { // 如果用了-s选项,从配置文件中获取表名以及sql语句 125 | tableMap = viper.GetStringMapStringSlice("tables") 126 | } else { // 不指定-s选项,查询源库所有表名 127 | tableMap = fetchTableMap(pageSize, excludeTab) 128 | } 129 | // 从yml配置文件中获取迁移数据时最大运行协程数 130 | maxParallel := viper.GetInt("maxParallel") 131 | // 用于控制协程goroutine运行时候的并发数,例如3个一批,3个一批的goroutine并发运行 132 | ch := make(chan struct{}, maxParallel) 133 | // 同时执行goroutine的数量,这里是每个表查询语句切片集合的长度 134 | var goroutineSize int 135 | //遍历每个表需要执行的切片查询SQL,累计起来获得总的goroutine并发大小,即所有goroutine协程的数量 136 | for _, sqlList := range tableMap { 137 | goroutineSize += len(sqlList) 138 | } 139 | //遍历tableMap,先遍历表,再遍历该表的sql切片集合 140 | migDataStart := time.Now() 141 | for tableName, sqlFullSplit := range tableMap { //获取单个表名 142 | colName, colType, tableNotExist := preMigData(tableName, sqlFullSplit) //获取单表的列名,列字段类型 143 | if !tableNotExist { //目标表存在就执行数据迁移 144 | // 遍历该表的sql切片(多个分页查询或者全表查询sql) 145 | for index, sqlSplitSql := range sqlFullSplit { 146 | log.Info("Table ", tableName, " total task ", len(sqlFullSplit)) 147 | ch <- struct{}{} //在没有被接收的情况下,至多发送n个消息到通道则被阻塞,若缓存区满,则阻塞,这里相当于占位置排队 148 | wg.Add(1) // 每运行一个goroutine等待组加1 149 | go runMigration(logDir, index, tableName, sqlSplitSql, ch, colName, colType) 150 | } 151 | } else { //目标表不存在就往通道写1 152 | log.Info("table not exists ", tableName) 153 | } 154 | } 155 | // 这里等待上面所有迁移数据的goroutine协程任务完成才会接着运行下面的主程序,如果这里不wait,上面还在迁移行数据的goroutine会被强制中断 156 | wg.Wait() 157 | // 单独计算迁移表行数据的耗时 158 | migDataEnd := time.Now() 159 | migCost := migDataEnd.Sub(migDataStart) 160 | tableDataRet := []string{"TableData", migDataStart.Format("2006-01-02 15:04:05.000000"), migDataEnd.Format("2006-01-02 15:04:05.000000"), " - ", migCost.String()} 161 | // 数据库对象的迁移结果 162 | var rowsAll = [][]string{{}} 163 | // 表结构创建以及数据迁移结果追加到切片,进行整合 164 | rowsAll = append(rowsAll, tableDataRet) 165 | // 输出配置文件信息 166 | fmt.Println("------------------------------------------------------------------------------------------------------------------------------") 167 | Info() 168 | tblConfig, err := gotable.Create("SourceDb", "DestDb", "MaxParallel", "PageSize", "ExcludeCount") 169 | if err != nil { 170 | fmt.Println("Create tblConfig failed: ", err.Error()) 171 | return 172 | } 173 | ymlConfig := []string{connStr.SrcHost + "-" + connStr.SrcUserName, connStr.DestHost + "-" + connStr.DestDatabase, strconv.Itoa(maxParallel), strconv.Itoa(pageSize), strconv.Itoa(len(excludeTab))} 174 | tblConfig.AddRow(ymlConfig) 175 | fmt.Println(tblConfig) 176 | // 输出迁移摘要 177 | table, err := gotable.Create("Object", "BeginTime", "EndTime", "FailedTotal", "ElapsedTime") 178 | if err != nil { 179 | fmt.Println("Create table failed: ", err.Error()) 180 | return 181 | } 182 | for _, r := range rowsAll { 183 | _ = table.AddRow(r) 184 | } 185 | table.Align("Object", 1) 186 | table.Align("FailedTotal", 1) 187 | table.Align("ElapsedTime", 1) 188 | fmt.Println(table) 189 | // 总耗时 190 | cost := time.Since(start) 191 | log.Info(fmt.Sprintf("All complete totalTime %s The Report Dir %s", cost, logDir)) 192 | }, 193 | } 194 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "fmt" 7 | "github.com/mitchellh/go-homedir" 8 | "io" 9 | "math" 10 | "os" 11 | "os/signal" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | "syscall" 17 | "time" 18 | 19 | "github.com/sirupsen/logrus" 20 | "github.com/spf13/cobra" 21 | 22 | "OracleSync2MySQL/connect" 23 | "github.com/liushuochen/gotable" 24 | "github.com/spf13/viper" 25 | ) 26 | 27 | var log = logrus.New() 28 | var cfgFile string 29 | var selFromYml bool 30 | var metaData bool 31 | 32 | var wg sync.WaitGroup 33 | var wg2 sync.WaitGroup 34 | 35 | // rootCmd represents the base command when called without any subcommands 36 | var rootCmd = &cobra.Command{ 37 | Use: "OracleSync2MySQL", 38 | Short: "", 39 | Long: ``, 40 | Run: func(cmd *cobra.Command, args []string) { 41 | // 获取配置文件中的数据库连接字符串 42 | connStr := getConn() 43 | startDataTransfer(connStr) 44 | }, 45 | } 46 | 47 | func startDataTransfer(connStr *connect.DbConnStr) { 48 | // 自动侦测终端是否输入Ctrl+c,若按下,主动关闭目标数据库剩余连接 49 | exitChan := make(chan os.Signal) 50 | signal.Notify(exitChan, os.Interrupt, os.Kill, syscall.SIGTERM) 51 | go exitHandle(exitChan) 52 | // 创建运行日志目录 53 | logDir, _ := filepath.Abs(CreateDateDir("")) 54 | // 输出调用文件以及方法位置 55 | log.SetReportCaller(true) 56 | f, err := os.OpenFile(logDir+"/"+"run.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | defer func() { 61 | if err := f.Close(); err != nil { 62 | log.Fatal(err) 63 | } 64 | }() 65 | // log信息重定向到平面文件 66 | multiWriter := io.MultiWriter(os.Stdout, f) 67 | log.SetOutput(multiWriter) 68 | start := time.Now() 69 | // map结构,表名以及该表用来迁移查询源库的语句 70 | var tableMap map[string][]string 71 | // 从配置文件中获取需要排除的表 72 | excludeTab := viper.GetStringSlice("exclude") 73 | log.Info("running SourceDB check connect") 74 | // 生成源库数据库连接 75 | PrepareSrc(connStr) 76 | defer srcDb.Close() 77 | // 每页的分页记录数,仅全库迁移时有效 78 | pageSize := viper.GetInt("pageSize") 79 | log.Info("running TargetDB check connect") 80 | // 生成目标库的数据库连接 81 | PrepareDest(connStr) 82 | defer destDb.Close() 83 | // 以下是迁移数据前的准备工作,获取要迁移的表名以及该表查询源库的sql语句(如果有主键生成该表的分页查询切片集合,没有主键的统一是全表查询sql) 84 | if selFromYml { // 如果用了-s选项,从配置文件中获取表名以及sql语句 85 | tableMap = viper.GetStringMapStringSlice("tables") 86 | } else { // 不指定-s选项,查询源库所有表名 87 | tableMap = fetchTableMap(pageSize, excludeTab) 88 | } 89 | // 实例初始化,调用接口中创建目标表的方法 90 | var db Database 91 | db = new(Table) 92 | // 从yml配置文件中获取迁移数据时最大运行协程数 93 | maxParallel := viper.GetInt("maxParallel") 94 | // 用于控制协程goroutine运行时候的并发数,例如3个一批,3个一批的goroutine并发运行 95 | ch := make(chan struct{}, maxParallel) 96 | startTbl := time.Now() 97 | for tableName := range tableMap { //获取单个表名 98 | ch <- struct{}{} 99 | wg2.Add(1) 100 | go db.TableCreate(logDir, tableName, ch) 101 | } 102 | wg2.Wait() 103 | endTbl := time.Now() 104 | tableCost := time.Since(startTbl) 105 | // 创建表完毕 106 | log.Info("Table structure synced finish ,Source Table Total ", tableCount, " Failed Total ", strconv.Itoa(failedCount)) 107 | tabRet = append(tabRet, "Table", startTbl.Format("2006-01-02 15:04:05.000000"), endTbl.Format("2006-01-02 15:04:05.000000"), strconv.Itoa(failedCount), tableCost.String()) 108 | fmt.Println("Table Create finish elapsed time ", tableCost) 109 | // 创建表之后,开始准备迁移表行数据 110 | // 同时执行goroutine的数量,这里是每个表查询语句切片集合的长度 111 | var goroutineSize int 112 | //遍历每个表需要执行的切片查询SQL,累计起来获得总的goroutine并发大小,即所有goroutine协程的数量 113 | for _, sqlList := range tableMap { 114 | goroutineSize += len(sqlList) 115 | } 116 | //遍历tableMap,先遍历表,再遍历该表的sql切片集合 117 | migDataStart := time.Now() 118 | if !metaData { 119 | for tableName, sqlFullSplit := range tableMap { //获取单个表名 120 | colName, colType, tableNotExist := preMigData(tableName, sqlFullSplit) //获取单表的列名,列字段类型 121 | if !tableNotExist { //目标表存在就执行数据迁移 122 | // 遍历该表的sql切片(多个分页查询或者全表查询sql) 123 | for index, sqlSplitSql := range sqlFullSplit { 124 | log.Info("Table ", tableName, " total task ", len(sqlFullSplit)) 125 | ch <- struct{}{} //在没有被接收的情况下,至多发送n个消息到通道则被阻塞,若缓存区满,则阻塞,这里相当于占位置排队 126 | wg.Add(1) // 每运行一个goroutine等待组加1 127 | go runMigration(logDir, index, tableName, sqlSplitSql, ch, colName, colType) 128 | } 129 | } else { //目标表不存在就往通道写1 130 | log.Info("table not exists ", tableName) 131 | } 132 | } 133 | // 这里等待上面所有迁移数据的goroutine协程任务完成才会接着运行下面的主程序,如果这里不wait,上面还在迁移行数据的goroutine会被强制中断 134 | wg.Wait() 135 | } 136 | // 单独计算迁移表行数据的耗时 137 | migDataEnd := time.Now() 138 | migCost := migDataEnd.Sub(migDataStart) 139 | tableDataRet := []string{"TableData", migDataStart.Format("2006-01-02 15:04:05.000000"), migDataEnd.Format("2006-01-02 15:04:05.000000"), " - ", migCost.String()} 140 | // 数据库对象的迁移结果 141 | var rowsAll = [][]string{{}} 142 | // 表结构创建以及数据迁移结果追加到切片,进行整合 143 | rowsAll = append(rowsAll, tabRet, tableDataRet) 144 | // 如果指定-s模式不创建下面对象 145 | if selFromYml != true { 146 | // 创建索引、约束(主键、唯一约束) 147 | ch := make(chan struct{}, maxParallel) 148 | id := 0 149 | failedCount = 0 150 | var idxRet []string 151 | startTime := time.Now() 152 | for tableName := range tableMap { //获取单个表名 153 | id += 1 154 | ch <- struct{}{} 155 | wg2.Add(1) 156 | go db.IdxCreate(logDir, tableName, ch, id) 157 | } 158 | wg2.Wait() 159 | endTime := time.Now() 160 | cost := time.Since(startTime) 161 | idxRet = append(idxRet, "Index", startTime.Format("2006-01-02 15:04:05.000000"), endTime.Format("2006-01-02 15:04:05.000000"), strconv.Itoa(failedCount), cost.String()) 162 | // 把源数据库触发器+序列形式批量改成目标数据库的自增列形式 163 | seqRet := db.SeqCreate(logDir) 164 | // 创建外键 165 | fkRet := db.FkCreate(logDir) 166 | // 创建normal类型的索引,如create index idx_id on test(id desc) 167 | nomalIdxRet := db.NormalIdx(logDir) 168 | // 创建注释 169 | commentRet := db.CommentCreate(logDir) 170 | // 创建视图 171 | viewRet := db.ViewCreate(logDir) 172 | // 转储源数据库的函数、存储过程等对象到平面文件 173 | db.PrintDbFunc(logDir) 174 | // 以上对象迁移结果追加到切片,进行整合 175 | rowsAll = append(rowsAll, idxRet, seqRet, fkRet, nomalIdxRet, commentRet, viewRet) 176 | } 177 | // 输出配置文件信息 178 | fmt.Println("------------------------------------------------------------------------------------------------------------------------------") 179 | Info() 180 | tblConfig, err := gotable.Create("SourceDb", "DestDb", "MaxParallel", "PageSize", "ExcludeCount") 181 | if err != nil { 182 | fmt.Println("Create tblConfig failed: ", err.Error()) 183 | return 184 | } 185 | ymlConfig := []string{connStr.SrcHost + "-" + connStr.SrcUserName, connStr.DestHost + "-" + connStr.DestDatabase, strconv.Itoa(maxParallel), strconv.Itoa(pageSize), strconv.Itoa(len(excludeTab))} 186 | tblConfig.AddRow(ymlConfig) 187 | fmt.Println(tblConfig) 188 | // 输出迁移摘要 189 | table, err := gotable.Create("Object", "BeginTime", "EndTime", "FailedTotal", "ElapsedTime") 190 | if err != nil { 191 | fmt.Println("Create table failed: ", err.Error()) 192 | return 193 | } 194 | for _, r := range rowsAll { 195 | _ = table.AddRow(r) 196 | } 197 | table.Align("Object", 1) 198 | table.Align("FailedTotal", 1) 199 | table.Align("ElapsedTime", 1) 200 | fmt.Println(table) 201 | // 总耗时 202 | cost := time.Since(start) 203 | log.Info(fmt.Sprintf("All complete totalTime %s The Report Dir %s", cost, logDir)) 204 | } 205 | 206 | // 自动对表分析,然后生成每个表用来迁移查询源库SQL的集合(全表查询或者分页查询) 207 | // 自动分析是否有排除的表名 208 | // 最后返回map结构即 表:[查询SQL] 209 | func fetchTableMap(pageSize int, excludeTable []string) (tableMap map[string][]string) { 210 | var tableNumber int // 表总数 211 | var sqlStr string // 查询源库获取要迁移的表名 212 | log.Info("exclude table ", excludeTable) 213 | // 如果配置文件中exclude存在表名,使用not in排除掉这些表,否则获取到所有表名 214 | if excludeTable != nil { 215 | sqlStr = "select table_name from user_tables where table_name not in (" 216 | buffer := bytes.NewBufferString("") 217 | for index, tabName := range excludeTable { 218 | if index < len(excludeTable)-1 { 219 | buffer.WriteString("'" + tabName + "'" + ",") 220 | } else { 221 | buffer.WriteString("'" + tabName + "'" + ")") 222 | } 223 | } 224 | sqlStr += buffer.String() 225 | } else { 226 | sqlStr = "select table_name from user_tables" // 获取库里全表名称 227 | } 228 | // 查询下源库总共的表,获取到表名 229 | rows, err := srcDb.Query(sqlStr) 230 | defer rows.Close() 231 | if err != nil { 232 | log.Error(fmt.Sprintf("Query "+sqlStr+" failed,\nerr:%v\n", err)) 233 | return 234 | } 235 | var tableName string 236 | //初始化外层的map,键值对,即 表名:[sql语句...] 237 | tableMap = make(map[string][]string) 238 | for rows.Next() { 239 | tableNumber++ 240 | var sqlFullList []string 241 | err = rows.Scan(&tableName) 242 | if err != nil { 243 | log.Error(err) 244 | } 245 | // 单线程调用prepareSqlStr函数获取该表用来执行的sql语句 246 | log.Info(time.Now().Format("2006-01-02 15:04:05.000000"), "ID[", tableNumber, "] ", "prepare ", tableName, " TableMap") 247 | // !tableOnly即没有指定-t选项,调用生成全库的分页查询语句,否则就是指定了-t选项,sqlFullList仅追加空字符串按表一个一个获取 248 | if !tableOnly { 249 | sqlFullList = prepareSqlStr(tableName, pageSize) 250 | if len(sqlFullList) == 0 { // 如果表没有数据,手动append一条1=0的sql语句,否则该表不会被创建,compareDb运行也会不准确 251 | sqlFullList = append(sqlFullList, fmt.Sprintf("select * from \"%s\" where 1=0", tableName)) 252 | } 253 | } else { 254 | sqlFullList = append(sqlFullList, "") 255 | } 256 | // 追加到内层的切片,遍历sql语句切片,一个个追加到map,即sql全表扫描语句或者分页查询语句,例如tableMap[test1]="select * from test1" 257 | for i := 0; i < len(sqlFullList); i++ { 258 | tableMap[tableName] = append(tableMap[tableName], sqlFullList[i]) 259 | } 260 | } 261 | return tableMap 262 | } 263 | 264 | // 迁移数据前先清空目标表数据,并获取每个表查询语句的列名以及列字段类型,表如果不存在返回布尔值true 265 | func preMigData(tableName string, sqlFullSplit []string) (dbCol []string, dbColType []string, tableNotExist bool) { 266 | var sqlCol string 267 | // 在写数据前,先清空下目标表数据 268 | truncateSql := "truncate table " + fmt.Sprintf("`") + tableName + fmt.Sprintf("`") 269 | if _, err := destDb.Exec(truncateSql); err != nil { 270 | log.Error("truncate ", tableName, " failed ", err) 271 | tableNotExist = true 272 | return // 表不存在return布尔值 273 | } 274 | // 获取表的字段名以及类型 275 | // 如果指定了参数-s,就读取yml文件中配置的sql获取"自定义查询sql生成的列名",否则按照select * 查全表获取 276 | if selFromYml { 277 | sqlCol = "select * from (" + sqlFullSplit[0] + " )aa where 1=0" // 在自定义sql外层套一个select * from (自定义sql) where 1=0 278 | } else { 279 | sqlCol = "select * from " + "\"" + tableName + "\"" + " where 1=0" 280 | } 281 | rows, err := srcDb.Query(sqlCol) //源库 SQL查询语句 282 | //defer rows.Close() 283 | if err != nil { 284 | log.Error(fmt.Sprintf("Query "+sqlCol+" failed,\nerr:%v\n", err)) 285 | tableNotExist = true 286 | return 287 | } 288 | defer rows.Close() 289 | //获取列名,这是字符串切片 290 | columns, err := rows.Columns() 291 | if err != nil { 292 | log.Fatal(err.Error()) 293 | } 294 | //获取字段类型,看下是varchar等还是blob 295 | colType, err := rows.ColumnTypes() 296 | if err != nil { 297 | log.Fatal(err.Error()) 298 | } 299 | // 循环遍历列名,把列名全部转为小写 300 | for i, value := range columns { 301 | dbCol = append(dbCol, strings.ToLower(value)) //由于CopyIn方法每个列都会使用双引号包围,这里把列名全部转为小写(pg库默认都是小写的列名),这样即便加上双引号也能正确查询到列 302 | dbColType = append(dbColType, strings.ToUpper(colType[i].DatabaseTypeName())) 303 | } 304 | return dbCol, dbColType, tableNotExist 305 | } 306 | 307 | // 根据表是否有主键,自动生成每个表查询sql,有主键就生成分页查询组成的切片,没主键就拼成全表查询sql,最后返回sql切片 308 | func prepareSqlStr(tableName string, pageSize int) (sqlList []string) { 309 | var colNameFull string 310 | var totalPageNum int // 每个表的分页查询记录总数,即总共有多少页记录 311 | var sqlStr string // 分页查询或者全表扫描sql 312 | // 获取每个表各个列的名称 313 | sql1 := fmt.Sprintf("select trim(',' from (xmlagg(xmlparse(content '\"'||column_name||'\"'||',') order by COLUMN_ID).getclobval())) from user_tab_columns where table_name='%s'", tableName) 314 | err := srcDb.QueryRow(sql1).Scan(&colNameFull) 315 | if err != nil { 316 | log.Fatal(sql1, " exec failed ", err) 317 | return 318 | } 319 | // 根据当前表总数以及每页的页记录大小pageSize,自动计算需要多少页记录数,即总共循环多少次,如果表没有数据,后面判断下切片长度再做处理 320 | sql2 := "/* goapp */" + "select ceil(count(*)/" + strconv.Itoa(pageSize) + ") as total_page_num from " + "\"" + tableName + "\"" 321 | //以下是直接使用QueryRow 322 | err = srcDb.QueryRow(sql2).Scan(&totalPageNum) 323 | if err != nil { 324 | log.Fatal(sql2, " exec failed ", err) 325 | return 326 | } 327 | // 以下生成分页查询语句 328 | for i := 0; i < totalPageNum; i++ { // 使用小于而不是小于等于,否则会多生成一条分页查询边界外的sql,即此sql查询源表没有数据,也会导致后面迁移数据有多个无用的goroutine 329 | curStartPage := i + 1 330 | //以下计算分页查询起始的页数 331 | startNum := curStartPage * pageSize 332 | if curStartPage > 0 { 333 | startNum = ((curStartPage - 1) * pageSize) + 1 334 | } 335 | endNum := startNum + pageSize - 1 336 | sqlStr = fmt.Sprintf("SELECT %s FROM (SELECT A.*, ROWNUM RNcolumn FROM (SELECT * FROM \"%s\") A WHERE ROWNUM <= %s) WHERE RNcolumn >=%s", colNameFull, tableName, strconv.Itoa(endNum), strconv.Itoa(startNum)) 337 | sqlList = append(sqlList, sqlStr) 338 | } 339 | return sqlList 340 | } 341 | 342 | // 使用占位符,目前测下来,在大数据量下相比较不使用占位符的方式,效率较高,遇到blob类型可直接使用go的byte类型数据 343 | func runMigration(logDir string, startPage int, tableName string, sqlStr string, ch chan struct{}, columns []string, colType []string) { 344 | defer wg.Done() 345 | log.Info(fmt.Sprintf("%v Taskid[%d] Processing TableData %v ", time.Now().Format("2006-01-02 15:04:05.000000"), startPage, tableName)) 346 | start := time.Now() 347 | // 直接查询,即查询全表或者分页查询(SELECT t.* FROM (SELECT id FROM test ORDER BY id LIMIT ?, ?) temp LEFT JOIN test t ON temp.id = t.id;) 348 | sqlStr = "/* goapp */" + sqlStr 349 | // 查询源库的sql 350 | rows, err := srcDb.Query(sqlStr) //传入参数之后执行 351 | defer rows.Close() 352 | if err != nil { 353 | log.Error(fmt.Sprintf("[exec %v failed ] ", sqlStr), err) 354 | return 355 | } 356 | //values := make([]sql.RawBytes, len(columns)) // 列的值切片,包含多个列,即单行数据的值 357 | //scanArgs := make([]interface{}, len(values)) // 用来做scan的参数,将上面的列值value保存到scan 358 | //for i := range values { // 这里也是取决于有几列,就循环多少次 359 | // scanArgs[i] = &values[i] // 这里scanArgs是指向列值的指针,scanArgs里每个元素存放的都是地址 360 | //} 361 | // 生成单行数据的占位符,如(?,?),表有几列就有几个问号 362 | singleRowCol := fmt.Sprintf("(%s)", strings.Join(strings.Split(strings.Repeat("?", len(columns)), ""), ",")) 363 | // 用于insert语句拼接的列名,即括号包围的列名(`id`,`name`,`sex`,`sex2`,`sex3`) 364 | colName := "(`" + strings.Join(columns, "`,`") + "`)" 365 | // 批量插入时values后面总的占位符,批量有多少行数据,就有多少个(?,?),例如(?,?),(?,?) 366 | var totalInsertCol string 367 | // 表总行数 368 | var totalRow int 369 | // 用于给批量插入存储的切片数据,如果batchRowSize是100行,这个切片长度就是100 370 | var totalPrepareValues []interface{} 371 | // 单个字段的列值 372 | var value interface{} 373 | // 批量插入的sql带有多个占位符,用于给Prepare方法传入参数 374 | var insertSql string 375 | // 批量插入批次大小行数,MySQL限制insert语句最多使用65535个占位符,下面计算挑选出最小值(自动计算的结果与yml配置文件设定值),防止自己设定的批量batchRowSize超过限制 376 | batchRowSize := int(math.Min(float64(65535/len(columns)-10), float64(viper.GetInt("batchRowSize")))) 377 | //fmt.Println("batchRowSize:", batchRowSize) 378 | txn, err := destDb.Begin() //开始一个事务 379 | if err != nil { 380 | log.Error(err) 381 | } 382 | for rows.Next() { // 从查询结果获取一行行数据 383 | values := make([]sql.RawBytes, len(columns)) // 列的值切片,包含多个列,即单行数据的值 384 | scanArgs := make([]interface{}, len(values)) // 用来做scan的参数,将上面的列值value保存到scan 385 | for i := range values { // 这里也是取决于有几列,就循环多少次 386 | scanArgs[i] = &values[i] // 这里scanArgs是指向列值的指针,scanArgs里每个元素存放的都是地址 387 | } 388 | totalRow++ // 源表行数+1 389 | err = rows.Scan(scanArgs...) //scanArgs切片里的元素是指向values的指针,通过rows.Scan方法将获取游标结果集的各个列值复制到变量scanArgs各个切片元素(指针)指向的对象即values切片里,这里是一行完整的值 390 | if err != nil { 391 | log.Error("ScanArgs Failed ", err.Error()) 392 | } 393 | // 以下for将单行的byte数据循环转换成string类型(大字段就是用byte类型,剩余非大字段类型获取的值再使用string函数转为字符串) 394 | for i, colValue := range values { //values是完整的一行所有列值,这里从values遍历,获取每一列的值并赋值到col变量,col是单列的列值 395 | if colValue == nil { 396 | value = nil //空值判断 397 | } else { 398 | if colType[i] == "BLOB" { //大字段类型就无需使用string函数转为字符串类型,即使用sql.RawBytes类型 399 | value = colValue 400 | } else if colType[i] == "DATE" { 401 | timeValue, err := time.Parse(time.RFC3339, string(colValue)) // RFC3339= "2006-01-02T15:04:05Z07:00",先把列值转为标准的时间格式 402 | if err != nil { 403 | fmt.Println("convert date type error :", err) 404 | return 405 | } 406 | timeLayout := "2006-01-02 15:04:05" //date类型转化所需模板 407 | //timeLayout := "2006-01-02T15:04:05Z" //转化所需模板(不使用time.Parse的时候) 408 | loc, _ := time.LoadLocation("Local") //重要:获取时区 409 | //theTime, _ := time.ParseInLocation(timeLayout, string(colValue), loc) //使用模板在对应时区转化为time.time类型 410 | theTime, _ := time.ParseInLocation(timeLayout, timeValue.Format("2006-01-02 15:04:05"), loc) //使用模板在对应时区转化为time.time类型 411 | value = theTime.Format("2006-01-02 15:04:05") //格式化,否则时差相差8小时 412 | } else if colType[i] == "TIMESTAMPDTY" || colType[i] == "TIMESTAMP" { 413 | timeValue, err := time.Parse(time.RFC3339, string(colValue)) // RFC3339= "2006-01-02T15:04:05Z07:00",先把列值转为标准的时间格式 414 | if err != nil { 415 | fmt.Println("convert timestamp error:", err) 416 | return 417 | } 418 | timeLayout := "2006-01-02 15:04:05.000000 +0000 UTC" //timestamp类型转化所需模板 419 | loc, _ := time.LoadLocation("Local") //重要:获取时区 420 | theTime, _ := time.ParseInLocation(timeLayout, timeValue.Format("2006-01-02 15:04:05.000000 +0000 UTC"), loc) //使用模板在对应时区转化为time.time类型 421 | value = theTime.Format("2006-01-02 15:04:05.000000") //格式化,否则时差相差8小时 422 | } else { 423 | value = string(colValue) //非大字段类型,显式使用string函数强制转换为字符串文本,否则都是字节类型文本(即sql.RawBytes) 424 | } 425 | } 426 | // 把一行一行数据追加到这个totalPrepareValues,便于后面给Prepare方法一次性提供批量的实参,比如该表2个字段,数据即1 tom 2 jim 3 kim 427 | totalPrepareValues = append(totalPrepareValues, value) 428 | } 429 | // 多行数据value拼接的占位符用逗号隔开,例如(?,?),(?,?),(?,?), -注意这里结尾有逗号 430 | totalInsertCol += singleRowCol + "," 431 | // 每隔一定行数,批量插入一次 432 | if totalRow%batchRowSize == 0 { 433 | totalInsertCol = strings.TrimRight(totalInsertCol, ",") // 去掉占位符最后括号的逗号 434 | //insertSql = fmt.Sprintf("insert into `%s` values %s", tableName, totalInsertCol) 435 | insertSql = fmt.Sprintf("insert into `%s`%s values %s", tableName, colName, totalInsertCol) 436 | if len(totalPrepareValues) != 0 { // 排除掉多线程遇到空切片的数据 437 | stmt, err := txn.Prepare(insertSql) //prepare里的方法CopyIn只是把copy语句拼接好并返回,并非直接执行copy 438 | if err != nil { 439 | log.Error("txn.Prepare(insertSql) failed table[", tableName, "] ", err) 440 | LogError(logDir, "errorTableData ", tableName, err) 441 | //responseChannel <- fmt.Sprintf("data error %s", tableName) 442 | <-ch // 通道向外发送数据 443 | return 444 | } 445 | // 下面是把实参的值传到插入语句的占位符,真正开始批量写入数据 446 | _, err = stmt.Exec(totalPrepareValues...) //这里Exec只传入实参,即上面prepare的CopyIn所需的参数,这里理解为把stmt所有数据先存放到buffer里面 447 | if err != nil { 448 | log.Error(tableName, " stmt.Exec(totalPrepareValues...) Failed: ", err) //注意这里不能使用Fatal,否则会直接退出程序,也就没法遇到错误继续了 449 | LogError(logDir, "errorTableData ", tableName, err) 450 | err := txn.Rollback() 451 | if err != nil { 452 | log.Error(tableName, " Rollback failed table ", err) 453 | } 454 | <-ch // 通道向外发送数据 455 | return 456 | } 457 | err = stmt.Close() //关闭stmt 458 | if err != nil { 459 | log.Error(err) 460 | } 461 | log.Info(time.Now(), " ID[", startPage, "] insert ", tableName, " ", totalRow, " rows") 462 | totalPrepareValues = nil 463 | totalInsertCol = "" 464 | } 465 | } 466 | } 467 | err = txn.Commit() // 提交事务 468 | if err != nil { 469 | err := txn.Rollback() 470 | if err != nil { 471 | return 472 | } 473 | log.Error("Commit failed ", err) 474 | } 475 | // rows.Next方法最后一部分数据的插入 476 | //insertSql = fmt.Sprintf("insert into `%s` values %s", tableName, totalInsertCol) 477 | insertSql = fmt.Sprintf("insert into `%s`%s values %s", tableName, colName, totalInsertCol) // 目标数据库表结构是源数据库表结构超集即可,表结构无需完全一致即可迁移数据 478 | insertSql = strings.TrimRight(insertSql, ",") 479 | txn, err = destDb.Begin() //开始一个事务 480 | if err != nil { 481 | log.Error(err) 482 | } 483 | if len(totalPrepareValues) != 0 { 484 | stmt, err := txn.Prepare(insertSql) //prepare里的方法CopyIn只是把copy语句拼接好并返回,并非直接执行copy 485 | if err != nil { 486 | log.Error("txn.Prepare(insertSql) failed table[", tableName, "] ", err) 487 | LogError(logDir, "errorTableData ", tableName, err) 488 | //responseChannel <- fmt.Sprintf("data error %s", tableName) 489 | <-ch // 通道向外发送数据 490 | return 491 | } 492 | // 下面是把实参的值传到插入语句的占位符,真正开始批量写入数据 493 | _, err = stmt.Exec(totalPrepareValues...) //这里Exec只传入实参,即上面prepare的CopyIn所需的参数,这里理解为把stmt所有数据先存放到buffer里面 494 | if err != nil { 495 | log.Error(tableName, " last part stmt.Exec(totalPrepareValues...) Failed: ", err) //注意这里不能使用Fatal,否则会直接退出程序,也就没法遇到错误继续了 496 | LogError(logDir, "errorTableData ", tableName, err) 497 | err := txn.Rollback() 498 | if err != nil { 499 | log.Error(tableName, " Rollback last part failed table ", err) 500 | } 501 | <-ch // 通道向外发送数据 502 | return 503 | } 504 | err = stmt.Close() //关闭stmt 505 | if err != nil { 506 | log.Error(err) 507 | } 508 | //err = txn.Commit() // 提交事务,这里注意Commit在上面Close之后 509 | //if err != nil { 510 | // err := txn.Rollback() 511 | // if err != nil { 512 | // log.Error("rollback failed ", err) 513 | // } 514 | // log.Error("Commit failed ", err) 515 | //} 516 | log.Info(time.Now().Format("2006-01-02 15:04:05.000"), " ID[", startPage, "] insert ", tableName, " ", totalRow, " rows") 517 | } 518 | cost := time.Since(start) //计算时间差 519 | log.Info(fmt.Sprintf("%v Taskid[%d] table %v complete,processed %d rows,execTime %s", time.Now().Format("2006-01-02 15:04:05.000000"), startPage, tableName, totalRow, cost)) 520 | // 剩下显式的提交一下,不然目标库会有很多sleep的线程导致超出最大连接数 521 | err = txn.Commit() // 提交事务,这里注意Commit在上面Close之后 522 | if err != nil { 523 | log.Error("Commit failed ", err) 524 | } 525 | <-ch // 通道向外发送数据 526 | } 527 | 528 | func Execute() { // init 函数初始化之后再运行此Execute函数 529 | if err := rootCmd.Execute(); err != nil { 530 | fmt.Println(err) 531 | os.Exit(1) 532 | } 533 | } 534 | 535 | // 程序中第一个调用的函数,先初始化config 536 | func init() { 537 | cobra.OnInitialize(initConfig) 538 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.example.yaml)") 539 | rootCmd.PersistentFlags().BoolVarP(&selFromYml, "selFromYml", "s", false, "select from yml true") 540 | rootCmd.PersistentFlags().BoolVarP(&metaData, "metaData", "m", false, "only output create meta script true") 541 | //rootCmd.PersistentFlags().BoolVarP(&tableOnly, "tableOnly", "t", false, "only create table true") 542 | } 543 | 544 | // initConfig reads in config file and ENV variables if set. 545 | func initConfig() { 546 | if cfgFile != "" { 547 | // Use config file from the flag. 548 | viper.SetConfigFile(cfgFile) 549 | } else { 550 | // Find home directory. 551 | home, err := homedir.Dir() 552 | if err != nil { 553 | fmt.Println(err) 554 | os.Exit(1) 555 | } 556 | 557 | // Search config in home directory with name "yml" (without extension). 558 | viper.AddConfigPath(home) 559 | viper.SetConfigName(".example") 560 | } 561 | 562 | viper.AutomaticEnv() // read in environment variables that match 563 | 564 | // 通过viper读取配置文件进行加载 565 | if err := viper.ReadInConfig(); err == nil { 566 | log.Info("Using config file:", viper.ConfigFileUsed()) 567 | } else { 568 | log.Fatal(viper.ConfigFileUsed(), " has some error please check your yml file ! ", "Detail-> ", err) 569 | } 570 | log.Info("Using selFromYml:", selFromYml) 571 | } 572 | -------------------------------------------------------------------------------- /cmd/tablemeta.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | var tabRet []string 13 | var tableCount int 14 | var failedCount int 15 | 16 | type Database interface { 17 | // TableCreate (logDir string, tableMap map[string][]string) (result []string) 单线程 18 | TableCreate(logDir string, tblName string, ch chan struct{}) 19 | IdxCreate(logDir string, tableName string, ch chan struct{}, id int) 20 | SeqCreate(logDir string) (ret []string) 21 | FkCreate(logDir string) (ret []string) 22 | NormalIdx(logDir string) (ret []string) 23 | CommentCreate(logDir string) (ret []string) 24 | ViewCreate(logDir string) (ret []string) 25 | PrintDbFunc(logDir string) 26 | } 27 | 28 | type Table struct { 29 | columnName string 30 | dataType string 31 | characterMaximumLength string 32 | isNullable string 33 | columnDefault string 34 | numericPrecision int 35 | numericScale int 36 | datetimePrecision string 37 | columnKey string 38 | columnComment string 39 | ordinalPosition int 40 | avgColLen int 41 | destType string 42 | destNullable string 43 | destDefault string 44 | autoIncrement int 45 | destSeqSql string 46 | destDefaultSeq string 47 | dropSeqSql string 48 | destIdxSql string 49 | viewSql string 50 | } 51 | 52 | func (tb *Table) TableCreate(logDir string, tblName string, ch chan struct{}) { 53 | defer wg2.Done() 54 | var newTable Table 55 | var colDefaultValue sql.NullString 56 | tableCount += 1 57 | // 使用goroutine并发的创建多个表 58 | var colTotal int 59 | if selFromYml { //-s自定义迁移表的时候,统一把yml文件的表名转为大写(否则查询语句的表名都是小写),原因是map键值对(key:value),viper这个库始终把key的值转为小写的值 60 | tblName = strings.ToUpper(tblName) 61 | } 62 | createTblSql := "create table " + fmt.Sprintf("`") + tblName + fmt.Sprintf("`") + "(" 63 | // 查询当前表总共有多少个列字段 64 | colTotalSql := fmt.Sprintf("select count(*) from user_tab_columns where table_name='%s'", tblName) 65 | err := srcDb.QueryRow(colTotalSql).Scan(&colTotal) 66 | if err != nil { 67 | log.Error(err) 68 | } 69 | if colTotal == 0 { 70 | log.Error("Table ", tblName, " not exist and not create table") 71 | return 72 | } 73 | // 查询源库表结构 74 | sqlStr := fmt.Sprintf("SELECT A.COLUMN_NAME,A.DATA_TYPE,A.CHAR_LENGTH,case when A.NULLABLE ='Y' THEN 'YES' ELSE 'NO' END as isnull,A.DATA_DEFAULT,case when A.DATA_PRECISION is null then -1 else A.DATA_PRECISION end DATA_PRECISION,case when A.DATA_SCALE is null then -1 when A.DATA_SCALE >30 then least(A.DATA_PRECISION,30)-1 else A.DATA_SCALE end DATA_SCALE, nvl(B.COMMENTS,'null') COMMENTS,case when a.AVG_COL_LEN is null then -1 else a.AVG_COL_LEN end AVG_COL_LEN,COLUMN_ID FROM USER_TAB_COLUMNS A LEFT JOIN USER_COL_COMMENTS B ON A.TABLE_NAME=B.TABLE_NAME AND A.COLUMN_NAME=B.COLUMN_NAME WHERE A.TABLE_NAME='%s' ORDER BY COLUMN_ID", tblName) 75 | //fmt.Println(sqlStr) 76 | rows, err := srcDb.Query(sqlStr) 77 | if err != nil { 78 | log.Error(err) 79 | } 80 | // 遍历MySQL表字段,一行就是一个字段的基本信息 81 | for rows.Next() { 82 | if err := rows.Scan(&newTable.columnName, &newTable.dataType, &newTable.characterMaximumLength, &newTable.isNullable, &colDefaultValue, &newTable.numericPrecision, &newTable.numericScale, &newTable.columnComment, &newTable.avgColLen, &newTable.ordinalPosition); err != nil { 83 | log.Error(err) 84 | } 85 | // 判断colDefaultValue的长度,如果len大于0说明就是有非null的默认值,如果len为0说明在源库的默认值就是null 86 | if len([]rune(colDefaultValue.String)) > 0 { 87 | newTable.columnDefault = strings.ReplaceAll(colDefaultValue.String, "\n", "") 88 | } else { 89 | newTable.columnDefault = "null" 90 | } 91 | //fmt.Println(columnName,dataType,characterMaximumLength,isNullable,columnDefault,numericPrecision,numericScale,datetimePrecision,columnKey,columnComment,ordinalPosition) 92 | // 列字段是否允许null 93 | switch newTable.isNullable { 94 | case "NO": 95 | newTable.destNullable = "not null" 96 | default: 97 | newTable.destNullable = "" // 原先是"null" 98 | } 99 | // 列字段default默认值的处理 100 | switch { 101 | case newTable.columnDefault != "null": // 默认值不为空 102 | if newTable.dataType == "VARCHAR2" || newTable.dataType == "NVARCHAR2" || newTable.dataType == "CHAR" { 103 | if strings.ToUpper(newTable.columnDefault) == "SYS_GUID()" || strings.ToUpper(newTable.columnDefault) == "USER" { // mysql默认值不支持函数,统一设为null 104 | newTable.destDefault = "default null" 105 | } else { 106 | newTable.destDefault = "default " + strings.ReplaceAll(strings.ReplaceAll(newTable.columnDefault, "(", ""), ")", "") //去掉默认值中的左右括号,如( 'user' ) 107 | } 108 | } else if newTable.dataType == "NUMBER" { 109 | // 创建正则表达式,number类型默认值包含括号的情况,如default (1.7),仅提取括号内数字部分 110 | re := regexp.MustCompile(`[\d.]+`) 111 | // 提取匹配的字符串 112 | matches := re.FindAllString(newTable.columnDefault, -1) 113 | if len(matches) > 0 { //如果能匹配提取数字部分,否则就给null 114 | newTable.destDefault = "default " + matches[0] 115 | } else { 116 | newTable.destDefault = "default null" 117 | } 118 | } else if strings.ToUpper(newTable.columnDefault) == "SYSDATE" || strings.ToUpper(newTable.columnDefault) == "CURRENT_TIMESTAMP" { 119 | re := regexp.MustCompile(`\w+`) 120 | matches := re.FindAllString(newTable.dataType, -1) 121 | if matches[0] == "TIMESTAMP" { //timestamp类型,才需要加精度值 122 | newTable.destDefault = "default current_timestamp(" + strconv.Itoa(newTable.numericScale) + ")" 123 | } else { 124 | newTable.destDefault = "default current_timestamp" 125 | } 126 | } else { // 其余默认值类型无需使用单引号包围 127 | newTable.destDefault = fmt.Sprintf("default %s", newTable.columnDefault) 128 | } 129 | default: 130 | newTable.destDefault = "" // 如果没有默认值,默认值就是空字符串 131 | } 132 | // 列字段类型的处理 133 | switch newTable.dataType { 134 | case "NUMBER": 135 | if newTable.columnDefault == "NULL" { 136 | newTable.destDefault = "null" 137 | } 138 | if newTable.numericPrecision > 0 && newTable.numericScale > 0 { //场景1 Oracle number(m,n) -> MySQL decimal(m,n) 139 | newTable.destType = "decimal(" + strconv.Itoa(newTable.numericPrecision) + "," + strconv.Itoa(newTable.numericScale) + ")" 140 | } else if newTable.avgColLen >= 6 { // 场景2 avg_col_len >= 6 ,Oracle number(m,0) -> MySQL bigint 141 | newTable.destType = "bigint" 142 | } else if newTable.avgColLen < 6 { 143 | newTable.destType = "int" 144 | } 145 | case "VARCHAR2", "NVARCHAR2", "UROWID": 146 | newTable.destType = "varchar(" + newTable.characterMaximumLength + ")" 147 | case "CHAR", "NCHAR": 148 | newTable.destType = "char(" + newTable.characterMaximumLength + ")" 149 | case "DATE": 150 | newTable.destType = "datetime" 151 | case "CLOB", "NCLOB", "LONG": 152 | newTable.destType = "longtext" 153 | case "BLOB", "RAW", "LONG RAW": 154 | newTable.destType = "longblob" 155 | // 其余类型,源库使用什么类型,目标库就使用什么类型 156 | default: 157 | newTable.destType = newTable.dataType 158 | } 159 | // 列注释,每个列字段的注释使用comment 注释的文字进行拼接 160 | colComment := "" 161 | if newTable.columnComment != "null" { 162 | colComment = fmt.Sprintf(" comment '%s'", newTable.columnComment) 163 | } 164 | // 在目标库创建的语句 165 | createTblSql += fmt.Sprintf("`%s` %s %s %s %s,", newTable.columnName, newTable.destType, newTable.destNullable, newTable.destDefault, colComment) 166 | if newTable.ordinalPosition == colTotal { 167 | createTblSql = createTblSql[:len(createTblSql)-1] + ")" // 最后一个列字段结尾去掉逗号,并且加上语句的右括号 168 | } 169 | } 170 | //fmt.Println(createTblSql) // 打印创建表语句 171 | LogOutput(logDir, "createSql", createTblSql+";") 172 | if !metaData { 173 | // 创建前先删除目标表 174 | dropDestTbl := "drop table if exists " + fmt.Sprintf("`") + tblName + fmt.Sprintf("`") + " cascade" 175 | if _, err = destDb.Exec(dropDestTbl); err != nil { 176 | log.Error("drop table ", tblName, " failed ", err) 177 | } 178 | // 创建表结构 179 | log.Info(fmt.Sprintf("%v Table total %s create table %s", time.Now().Format("2006-01-02 15:04:05.000000"), strconv.Itoa(tableCount), tblName)) 180 | 181 | if _, err = destDb.Exec(createTblSql); err != nil { 182 | log.Error("table ", tblName, " create failed ", err) 183 | LogError(logDir, "tableCreateFailed", createTblSql, err) 184 | failedCount += 1 185 | } 186 | } 187 | <-ch 188 | } 189 | 190 | func (tb *Table) IdxCreate(logDir string, tableName string, ch chan struct{}, id int) { 191 | defer wg2.Done() 192 | destIdxSql := "" 193 | // 查询索引、主键、唯一约束等信息,批量生成创建语句 194 | sqlStr := fmt.Sprintf("SELECT (CASE WHEN C.CONSTRAINT_TYPE = 'P' OR C.CONSTRAINT_TYPE = 'R' THEN 'ALTER TABLE `' || T.TABLE_NAME || '` ADD CONSTRAINT ' ||'`'||T.INDEX_NAME||'`' || (CASE WHEN C.CONSTRAINT_TYPE = 'P' THEN ' PRIMARY KEY (' ELSE ' FOREIGN KEY (' END) || listagg(T.COLUMN_NAME,',') within group(order by T.COLUMN_position) || ');' ELSE 'CREATE ' || (CASE WHEN I.UNIQUENESS = 'UNIQUE' THEN I.UNIQUENESS || ' ' ELSE CASE WHEN I.INDEX_TYPE = 'NORMAL' THEN '' ELSE I.INDEX_TYPE || ' ' END END) || 'INDEX ' || '`'||T.INDEX_NAME||'`' || ' ON ' || T.TABLE_NAME || '(' ||listagg(T.COLUMN_NAME,',') within group(order by T.COLUMN_position) || ');' END) SQL_CMD FROM USER_IND_COLUMNS T, USER_INDEXES I, USER_CONSTRAINTS C WHERE T.INDEX_NAME = I.INDEX_NAME AND T.INDEX_NAME = C.CONSTRAINT_NAME(+) and i.index_type != 'FUNCTION-BASED NORMAL' and i.table_name='%s' GROUP BY T.TABLE_NAME, T.INDEX_NAME, I.UNIQUENESS, I.INDEX_TYPE,C.CONSTRAINT_TYPE", tableName) 195 | //fmt.Println(sql) 196 | rows, err := srcDb.Query(sqlStr) 197 | if err != nil { 198 | log.Error(err) 199 | } 200 | defer rows.Close() 201 | // 从sql结果集遍历,获取到创建语句 202 | for rows.Next() { 203 | if err := rows.Scan(&destIdxSql); err != nil { 204 | log.Error(err) 205 | } 206 | LogOutput(logDir, "createSql", destIdxSql) 207 | destIdxSql = "/* goapp */" + destIdxSql 208 | // 创建目标索引,主键、其余约束 209 | if !metaData { 210 | if _, err = destDb.Exec(destIdxSql); err != nil { 211 | log.Error("index ", destIdxSql, " create index failed ", err) 212 | LogError(logDir, "idxCreateFailed", destIdxSql, err) 213 | failedCount += 1 214 | } 215 | } 216 | } 217 | if destIdxSql != "" { 218 | log.Info("[", id, "] Table ", tableName, " create index finish ") 219 | } 220 | <-ch 221 | } 222 | 223 | func (tb *Table) SeqCreate(logDir string) (ret []string) { 224 | startTime := time.Now() 225 | failedCount = 0 226 | var dbRet, tableName string 227 | rows, err := srcDb.Query("select table_name,trigger_body from user_triggers where upper(trigger_type) ='BEFORE EACH ROW'") 228 | if err != nil { 229 | log.Error(err) 230 | } 231 | defer rows.Close() 232 | idx := 0 233 | for rows.Next() { 234 | idx += 1 235 | err := rows.Scan(&tableName, &dbRet) 236 | if err != nil { 237 | log.Error(err) 238 | } 239 | dbRet = strings.ToUpper(dbRet) 240 | dbRet = strings.ReplaceAll(dbRet, "INTO:", "INTO :") 241 | dbRet = strings.ReplaceAll(dbRet, "SYS.DUAL ", "DUAL") 242 | dbRet = strings.ReplaceAll(dbRet, "SYS.DUAL", "DUAL") 243 | dbRet = strings.ReplaceAll(dbRet, "\n", "") 244 | pattern := `SELECT\s+(.*?)\.NEXTVAL\s+INTO\s+:NEW\.` 245 | re := regexp.MustCompile(pattern) 246 | match := re.FindStringSubmatch(dbRet) 247 | if len(match) > 0 { // 第一层,先正则匹配SELECT .NEXTVAL INTO :NEW包含的字符窜,主要是要匹配到自增列性质的触发器 248 | //如果符合第一层正则的条件,再匹配第二层,第二层主要是获取:NEW.后面的名称,即自增列名称 249 | re := regexp.MustCompile(`:NEW\.(\w+)`) // 正则表达式,匹配以 ":NEW." 开头的字符串,并提取后面的单词字符(包括字母、数字和下划线) 250 | match := re.FindStringSubmatch(dbRet) // 查找匹配项 251 | if len(match) == 2 { 252 | autoColName := match[1] 253 | // 创建目标数据库该表表的自增列索引 254 | sqlAutoColIdx := "/* goapp */" + "create index ids_" + tableName + "_" + autoColName + "_" + strconv.Itoa(idx) + " on " + tableName + "(" + autoColName + ")" 255 | log.Info("[", idx, "] create auto_increment for table ", tableName) 256 | LogOutput(logDir, "createSql", sqlAutoColIdx+";") 257 | if !metaData { 258 | if _, err = destDb.Exec(sqlAutoColIdx); err != nil { 259 | log.Error(sqlAutoColIdx, " create index autoCol failed ", err) 260 | LogError(logDir, "AutoIdxCreateFailed", sqlAutoColIdx, err) 261 | failedCount += 1 262 | } 263 | } 264 | 265 | // 更改目标数据库该表的列属性为自增列 266 | sqlModifyAuto := "/* goapp */" + "alter table " + tableName + " modify " + autoColName + " bigint auto_increment" 267 | LogOutput(logDir, "createSql", sqlModifyAuto+";") 268 | if !metaData { 269 | if _, err = destDb.Exec(sqlModifyAuto); err != nil { 270 | log.Error(sqlModifyAuto, " failed ", err) 271 | LogError(logDir, "alterTableFailed", sqlModifyAuto, err) 272 | failedCount += 1 273 | } 274 | } 275 | 276 | } 277 | } 278 | } 279 | endTime := time.Now() 280 | cost := time.Since(startTime) 281 | ret = append(ret, "AutoIncrement", startTime.Format("2006-01-02 15:04:05.000000"), endTime.Format("2006-01-02 15:04:05.000000"), strconv.Itoa(failedCount), cost.String()) 282 | return ret 283 | } 284 | 285 | func (tb *Table) FkCreate(logDir string) (ret []string) { 286 | startTime := time.Now() 287 | failedCount = 0 288 | var tableName, sqlStr string 289 | rows, err := srcDb.Query("SELECT B.TABLE_NAME,'ALTER TABLE ' || B.TABLE_NAME || ' ADD CONSTRAINT ' ||\n B.CONSTRAINT_NAME || ' FOREIGN KEY (' ||\n (SELECT listagg(A.COLUMN_NAME,',') within group(order by a.position)\n FROM USER_CONS_COLUMNS A\n WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME) || ') REFERENCES ' ||\n (SELECT B1.table_name FROM USER_CONSTRAINTS B1\n WHERE B1.CONSTRAINT_NAME = B.R_CONSTRAINT_NAME) || '(' ||\n (SELECT listagg(A.COLUMN_NAME,',') within group(order by a.position)\n FROM USER_CONS_COLUMNS A\n WHERE A.CONSTRAINT_NAME = B.R_CONSTRAINT_NAME) || ');'\nFROM USER_CONSTRAINTS B\nWHERE B.CONSTRAINT_TYPE = 'R' ") 290 | if err != nil { 291 | log.Error(err) 292 | } 293 | defer rows.Close() 294 | idx := 0 295 | for rows.Next() { 296 | idx += 1 297 | err := rows.Scan(&tableName, &sqlStr) 298 | if err != nil { 299 | log.Error(err) 300 | } 301 | log.Info("[", idx, "] create foreign key for table ", tableName) 302 | sqlStr = "/* goapp */" + sqlStr 303 | LogOutput(logDir, "createSql", sqlStr) 304 | if !metaData { 305 | if _, err = destDb.Exec(sqlStr); err != nil { 306 | log.Error(sqlStr, " create foreign key failed ", err) 307 | LogError(logDir, "FKCreateFailed", sqlStr, err) 308 | failedCount += 1 309 | } 310 | } 311 | } 312 | endTime := time.Now() 313 | cost := time.Since(startTime) 314 | ret = append(ret, "ForeignKey", startTime.Format("2006-01-02 15:04:05.000000"), endTime.Format("2006-01-02 15:04:05.000000"), strconv.Itoa(failedCount), cost.String()) 315 | return ret 316 | } 317 | 318 | func (tb *Table) NormalIdx(logDir string) (ret []string) { 319 | startTime := time.Now() 320 | failedCount = 0 321 | var idxName, tableName, sqlStr, userName, createSql string 322 | err := srcDb.QueryRow("select user from dual").Scan(&userName) 323 | if err != nil { 324 | log.Error(err) 325 | } 326 | rows, err := srcDb.Query("Select index_name,table_name from user_indexes where index_type='FUNCTION-BASED NORMAL'") 327 | if err != nil { 328 | log.Error(err) 329 | } 330 | defer rows.Close() 331 | idx := 0 332 | for rows.Next() { 333 | idx += 1 334 | err := rows.Scan(&idxName, &tableName) // 先获取normal-index的索引名称和表名 335 | if err != nil { 336 | log.Error(err) 337 | } 338 | if len(idxName) > 0 { // 如果有normal-index,就通过dbms_metadata获取该normal-index的DDL语句 339 | sqlStr = fmt.Sprintf("select trim(replace(regexp_replace(regexp_replace(SUBSTR(upper(to_char(dbms_metadata.get_ddl('INDEX','%s','%s'))), 1, INSTR(upper(to_char(dbms_metadata.get_ddl('INDEX','%s','%s'))), ' PCTFREE')-1),'\"','',1,0,'i'),'%s'||'.','',1,0,'i'),chr(10),'')) from dual", idxName, userName, idxName, userName, userName) 340 | err := srcDb.QueryRow(sqlStr).Scan(&createSql) // 获取到创建normal-index的sql语句 341 | if err != nil { 342 | log.Error(err) 343 | } 344 | log.Info("[", idx, "] create normal index for table ", tableName) 345 | LogOutput(logDir, "createSql", createSql+";") 346 | createSql = "/* goapp */" + createSql 347 | if !metaData { 348 | if _, err = destDb.Exec(createSql); err != nil { 349 | log.Error(createSql, " create normal index failed ", err) 350 | LogError(logDir, "NormalIdxCreateFailed", createSql, err) 351 | failedCount += 1 352 | } 353 | } 354 | } 355 | 356 | } 357 | endTime := time.Now() 358 | cost := time.Since(startTime) 359 | ret = append(ret, "NormalIndex", startTime.Format("2006-01-02 15:04:05.000000"), endTime.Format("2006-01-02 15:04:05.000000"), strconv.Itoa(failedCount), cost.String()) 360 | return ret 361 | } 362 | 363 | func (tb *Table) CommentCreate(logDir string) (ret []string) { 364 | startTime := time.Now() 365 | failedCount = 0 366 | var tableName, createSql string 367 | rows, err := srcDb.Query("select TABLE_NAME,'alter table '||TABLE_NAME||' comment '||''''||COMMENTS||'''' as create_comment from USER_TAB_COMMENTS where COMMENTS is not null") 368 | if err != nil { 369 | log.Error(err) 370 | } 371 | defer rows.Close() 372 | idx := 0 373 | for rows.Next() { 374 | idx += 1 375 | err := rows.Scan(&tableName, &createSql) // 先获取normal-index的索引名称和表名 376 | if err != nil { 377 | log.Error(err) 378 | } 379 | if len(createSql) > 0 { // 如果有normal-index,就通过dbms_metadata获取该normal-index的DDL语句 380 | log.Info("[", idx, "] create comment for table ", tableName) 381 | LogOutput(logDir, "createSql", createSql+";") 382 | if !metaData { 383 | if _, err = destDb.Exec(createSql); err != nil { 384 | log.Error(createSql, " create comment failed ", err) 385 | LogError(logDir, "CommentCreateFailed", createSql, err) 386 | failedCount += 1 387 | } 388 | } 389 | } 390 | 391 | } 392 | endTime := time.Now() 393 | cost := time.Since(startTime) 394 | ret = append(ret, "Comment", startTime.Format("2006-01-02 15:04:05.000000"), endTime.Format("2006-01-02 15:04:05.000000"), strconv.Itoa(failedCount), cost.String()) 395 | return ret 396 | } 397 | 398 | func (tb *Table) ViewCreate(logDir string) (ret []string) { 399 | startTime := time.Now() 400 | failedCount = 0 401 | var dbRet, viewName string 402 | rows, err := srcDb.Query("select view_name,text from user_views") 403 | if err != nil { 404 | log.Error(err) 405 | } 406 | defer rows.Close() 407 | idx := 0 408 | for rows.Next() { 409 | idx += 1 410 | err := rows.Scan(&viewName, &dbRet) 411 | if err != nil { 412 | log.Error(err) 413 | } 414 | if _, err = srcDb.Exec("alter view " + viewName + " compile"); err != nil { // 先编译下源数据库的视图 415 | log.Error(" alter view ", viewName, " compile failed", err) 416 | } 417 | dbRet = strings.ToUpper(dbRet) 418 | dbRet = strings.ReplaceAll(dbRet, "--", "-- -- ") 419 | dbRet = strings.ReplaceAll(dbRet, "\"", "`") 420 | dbRet = strings.ReplaceAll(dbRet, "NVL(", "IFNULL(") 421 | dbRet = strings.ReplaceAll(dbRet, "UNISTR('\0030')", "0") 422 | dbRet = strings.ReplaceAll(dbRet, "UNISTR('\0031')", "1") 423 | dbRet = strings.ReplaceAll(dbRet, "UNISTR('\0033')", "3") 424 | if len(viewName) > 0 { 425 | sqlStr := "create or replace view " + viewName + " as " + dbRet 426 | log.Info("[", idx, "] create view ", viewName) 427 | LogOutput(logDir, "createSql", sqlStr+";") 428 | if !metaData { 429 | if _, err = destDb.Exec(sqlStr); err != nil { 430 | //log.Error(sqlStr, " create view failed ", err) 431 | LogError(logDir, "ViewCreateFailed", sqlStr, err) 432 | failedCount += 1 433 | } 434 | } 435 | 436 | } 437 | } 438 | endTime := time.Now() 439 | cost := time.Since(startTime) 440 | ret = append(ret, "View", startTime.Format("2006-01-02 15:04:05.000000"), endTime.Format("2006-01-02 15:04:05.000000"), strconv.Itoa(failedCount), cost.String()) 441 | return ret 442 | } 443 | 444 | func (tb *Table) PrintDbFunc(logDir string) { //转储源数据库的函数、存储过程、包等对象到平面文件 445 | var dbRet string 446 | rows, err := srcDb.Query("SELECT DBMS_METADATA.GET_DDL(U.OBJECT_TYPE, u.object_name) ddl_sql FROM USER_OBJECTS u where U.OBJECT_TYPE IN ('FUNCTION','PROCEDURE','PACKAGE') order by OBJECT_TYPE") 447 | if err != nil { 448 | log.Error(err) 449 | } 450 | defer rows.Close() 451 | for rows.Next() { 452 | err := rows.Scan(&dbRet) 453 | if err != nil { 454 | log.Error(err) 455 | } 456 | LogError(logDir, "FuncObject", dbRet, err) 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "os" 7 | "time" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var ver = "0.1.3" 13 | 14 | func init() { 15 | rootCmd.AddCommand(versionCmd) 16 | } 17 | 18 | var versionCmd = &cobra.Command{ 19 | Use: "version", 20 | Short: "Print the version number of mysqlDataSyncTool", 21 | Long: ``, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | fmt.Println("\n\nyour version v" + ver) 24 | os.Exit(0) 25 | }, 26 | } 27 | 28 | func Info() { 29 | color.Red("DDDDDDDDDDDDD BBBBBBBBBBBBBBBBB AAA GGGGGGGGGGGGG OOOOOOOOO DDDDDDDDDDDDD ") 30 | color.Red("D::::::::::::DDD B::::::::::::::::B A:::A GGG::::::::::::G OO:::::::::OO D::::::::::::DDD ") 31 | color.Red("D:::::::::::::::DD B::::::BBBBBB:::::B A:::::A GG:::::::::::::::G OO:::::::::::::OO D:::::::::::::::DD ") 32 | color.Red("DDD:::::DDDDD:::::DBB:::::B B:::::B A:::::::A G:::::GGGGGGGG::::GO:::::::OOO:::::::ODDD:::::DDDDD:::::D ") 33 | color.Red(" D:::::D D:::::D B::::B B:::::B A:::::::::A G:::::G GGGGGGO::::::O O::::::O D:::::D D:::::D ") 34 | color.Red(" D:::::D D:::::DB::::B B:::::B A:::::A:::::A G:::::G O:::::O O:::::O D:::::D D:::::D") 35 | color.Red(" D:::::D D:::::DB::::BBBBBB:::::B A:::::A A:::::A G:::::G O:::::O O:::::O D:::::D D:::::D") 36 | color.Red(" D:::::D D:::::DB:::::::::::::BB A:::::A A:::::A G:::::G GGGGGGGGGGO:::::O O:::::O D:::::D D:::::D") 37 | color.Red(" D:::::D D:::::DB::::BBBBBB:::::B A:::::A A:::::A G:::::G G::::::::GO:::::O O:::::O D:::::D D:::::D") 38 | color.Red(" D:::::D D:::::DB::::B B:::::B A:::::AAAAAAAAA:::::A G:::::G GGGGG::::GO:::::O O:::::O D:::::D D:::::D") 39 | color.Red(" D:::::D D:::::DB::::B B:::::B A:::::::::::::::::::::AG:::::G G::::GO:::::O O:::::O D:::::D D:::::D") 40 | color.Red(" D:::::D D:::::D B::::B B:::::B A:::::AAAAAAAAAAAAA:::::AG:::::G G::::GO::::::O O::::::O D:::::D D:::::D ") 41 | color.Red("DDD:::::DDDDD:::::DBB:::::BBBBBB::::::BA:::::A A:::::AG:::::GGGGGGGG::::GO:::::::OOO:::::::ODDD:::::DDDDD:::::D ") 42 | color.Red("D:::::::::::::::DD B:::::::::::::::::BA:::::A A:::::AGG:::::::::::::::G OO:::::::::::::OO D:::::::::::::::DD ") 43 | color.Red("D::::::::::::DDD B::::::::::::::::BA:::::A A:::::A GGG::::::GGG:::G OO:::::::::OO D::::::::::::DDD ") 44 | color.Red("DDDDDDDDDDDDD BBBBBBBBBBBBBBBBBAAAAAAA AAAAAAA GGGGGG GGGG OOOOOOOOO DDDDDDDDDDDDD ") 45 | colorStr := color.New() 46 | colorStr.Add(color.FgHiGreen) 47 | colorStr.Printf("OracleSync2MySQL\n") 48 | colorStr.Printf("Powered By: DBA Team Of Infrastructure Research Center \nRelease version v" + ver) 49 | time.Sleep(5 * 100 * time.Millisecond) 50 | fmt.Printf("\n") 51 | } 52 | -------------------------------------------------------------------------------- /connect/connect.go: -------------------------------------------------------------------------------- 1 | package connect 2 | 3 | // DbConnStr related with config.yml 4 | type DbConnStr struct { 5 | SrcHost string 6 | SrcUserName string 7 | SrcPassword string 8 | SrcDatabase string 9 | SrcPort int 10 | DestHost string 11 | DestPort int 12 | DestUserName string 13 | DestPassword string 14 | DestDatabase string 15 | DestParams map[string]string 16 | } 17 | -------------------------------------------------------------------------------- /example.yml: -------------------------------------------------------------------------------- 1 | src: 2 | host: 192.168.1.200 3 | port: 1521 4 | database: orcl 5 | username: admin 6 | password: oracle 7 | dest: 8 | host: 192.168.1.37 9 | port: 3306 10 | database: test 11 | username: root 12 | password: 11111 13 | pageSize: 100000 14 | maxParallel: 100 15 | batchRowSize: 1000 16 | tables: 17 | test: 18 | - select * from test 19 | exclude: 20 | operationlog 21 | params: { charset:utf8, maxAllowedPacket:0 } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module OracleSync2MySQL 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/fatih/color v1.15.0 // indirect 7 | github.com/fsnotify/fsnotify v1.6.0 // indirect 8 | github.com/go-logfmt/logfmt v0.5.1 // indirect 9 | github.com/go-logr/logr v1.2.3 // indirect 10 | github.com/go-sql-driver/mysql v1.7.1 // indirect 11 | github.com/godror/godror v0.37.0 // indirect 12 | github.com/godror/knownpb v0.1.0 // indirect 13 | github.com/hashicorp/hcl v1.0.0 // indirect 14 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 15 | github.com/liushuochen/gotable v0.0.0-20221119160816-1113793e7092 // indirect 16 | github.com/magiconair/properties v1.8.7 // indirect 17 | github.com/mattn/go-colorable v0.1.13 // indirect 18 | github.com/mattn/go-isatty v0.0.17 // indirect 19 | github.com/mitchellh/go-homedir v1.1.0 // indirect 20 | github.com/mitchellh/mapstructure v1.5.0 // indirect 21 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 22 | github.com/sijms/go-ora/v2 v2.7.9 // indirect 23 | github.com/sirupsen/logrus v1.9.3 // indirect 24 | github.com/spf13/afero v1.9.5 // indirect 25 | github.com/spf13/cast v1.5.1 // indirect 26 | github.com/spf13/cobra v1.7.0 // indirect 27 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 28 | github.com/spf13/pflag v1.0.5 // indirect 29 | github.com/spf13/viper v1.16.0 // indirect 30 | github.com/subosito/gotenv v1.4.2 // indirect 31 | golang.org/x/sys v0.8.0 // indirect 32 | golang.org/x/text v0.9.0 // indirect 33 | google.golang.org/protobuf v1.30.0 // indirect 34 | gopkg.in/ini.v1 v1.67.0 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 41 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 42 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 43 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 44 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 45 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 46 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 47 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 48 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 49 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 50 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 51 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 52 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 53 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 54 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 55 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 56 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 57 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 58 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 59 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 60 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 61 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 62 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 63 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 64 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 65 | github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= 66 | github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 67 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 68 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 69 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= 70 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 71 | github.com/godror/godror v0.37.0 h1:3wR3/1msywDE49PzuXh9UUiwWOBNri0RVQQcu3HU4UY= 72 | github.com/godror/godror v0.37.0/go.mod h1:jW1+pN+z/V0h28p9XZXVNtEvfZP/2EBfaSjKJLp3E4g= 73 | github.com/godror/knownpb v0.1.0 h1:dJPK8s/I3PQzGGaGcUStL2zIaaICNzKKAK8BzP1uLio= 74 | github.com/godror/knownpb v0.1.0/go.mod h1:4nRFbQo1dDuwKnblRXDxrfCFYeT4hjg3GjMqef58eRE= 75 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 76 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 77 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 78 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 79 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 80 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 81 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 82 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 83 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 84 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 85 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 86 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 87 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 88 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 89 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 90 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 91 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 92 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 93 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 94 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 95 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 96 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 97 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 98 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 99 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 100 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 101 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 102 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 103 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 104 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 105 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 106 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 107 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 108 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 109 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 110 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 111 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 112 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 113 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 114 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 115 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 116 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 117 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 118 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 119 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 120 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 121 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 122 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 123 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 124 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 125 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 126 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 127 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 128 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 129 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 130 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 131 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 132 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 133 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 134 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 135 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 136 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 137 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 138 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 139 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 140 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 141 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 142 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 143 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 144 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 145 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 146 | github.com/liushuochen/gotable v0.0.0-20221119160816-1113793e7092 h1:u9I3sJ+uTakxnRrvuYJGsEi4SvEMN+yB47WWGDHHxIk= 147 | github.com/liushuochen/gotable v0.0.0-20221119160816-1113793e7092/go.mod h1:CxUy8nDvutaC1pOfaG9TRoYwdHHqoNstSPPKhomC9k8= 148 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 149 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 150 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 151 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 152 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 153 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 154 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 155 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 156 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 157 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 158 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 159 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 160 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 161 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 162 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 163 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 164 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 165 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 166 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 167 | github.com/sijms/go-ora/v2 v2.7.9 h1:FvPwsyNtAOywDKlgjrgCpGkL0s49ZA/ShTBgEAfYKE0= 168 | github.com/sijms/go-ora/v2 v2.7.9/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk= 169 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 170 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 171 | github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= 172 | github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= 173 | github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= 174 | github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= 175 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 176 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 177 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 178 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 179 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 180 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 181 | github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= 182 | github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= 183 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 184 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 185 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 186 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 187 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 188 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 189 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 190 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 191 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 192 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 193 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= 194 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 195 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 196 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 197 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 198 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 199 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 200 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 201 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 202 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 203 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 204 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 205 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 206 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 207 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 208 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 209 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 210 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 211 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 212 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 213 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 214 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 215 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 216 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 217 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 218 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 219 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 220 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 221 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 222 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 223 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 224 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 225 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 226 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 227 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 228 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 229 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 230 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 231 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 232 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 233 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 234 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 235 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 236 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 237 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 238 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 239 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 240 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 241 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 242 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 243 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 244 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 245 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 246 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 247 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 248 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 249 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 250 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 251 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 252 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 253 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 254 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 255 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 256 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 257 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 258 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 259 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 260 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 261 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 262 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 263 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 264 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 265 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 266 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 267 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 268 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 269 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 270 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 271 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 272 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 273 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 274 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 275 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 276 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 277 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 278 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 279 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 280 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 281 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 282 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 283 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 284 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 285 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 286 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 287 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 288 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 289 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 290 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 291 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 292 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 293 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 294 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 295 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 296 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 297 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 298 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 321 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 324 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 326 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 327 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 328 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 329 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 330 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 331 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 332 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 333 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 334 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 335 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 336 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 337 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 338 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 339 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 340 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 341 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 342 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 343 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 344 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 345 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 346 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 347 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 348 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 349 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 350 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 351 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 352 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 353 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 354 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 355 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 356 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 357 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 358 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 359 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 360 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 361 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 362 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 363 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 364 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 365 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 366 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 367 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 368 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 369 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 370 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 371 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 372 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 373 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 374 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 375 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 376 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 377 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 378 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 379 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 380 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 381 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 382 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 383 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 384 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 385 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 386 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 387 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 388 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 389 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 390 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 391 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 392 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 393 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 394 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 395 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 396 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 397 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 398 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 399 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 400 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 401 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 402 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 403 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 404 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 405 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 406 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 407 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 408 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 409 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 410 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 411 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 412 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 413 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 414 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 415 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 416 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 417 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 418 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 419 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 420 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 421 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 422 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 423 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 424 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 425 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 426 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 427 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 428 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 429 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 430 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 431 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 432 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 433 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 434 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 435 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 436 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 437 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 438 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 439 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 440 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 441 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 442 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 443 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 444 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 445 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 446 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 447 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 448 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 449 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 450 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 451 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 452 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 453 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 454 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 455 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 456 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 457 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 458 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 459 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 460 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 461 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 462 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 463 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 464 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 465 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 466 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 467 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 468 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 469 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 470 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 471 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 472 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 473 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 474 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 475 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 476 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 477 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 478 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 479 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 480 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 481 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 482 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 483 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 484 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 485 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 486 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 487 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 488 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 489 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 490 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 491 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 492 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 493 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 494 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 495 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 496 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 497 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 498 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 499 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 500 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 501 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 502 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 503 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 504 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 505 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 506 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 507 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 508 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 509 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 510 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 511 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 512 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 513 | -------------------------------------------------------------------------------- /image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iverycd/OracleSync2MySQL/891399257974bd5eec41ec79cd9a51f2bc40523d/image/logo.png -------------------------------------------------------------------------------- /macos_compile_remote.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export LC_ALL=en_US.UTF-8 3 | export LANG=en_US.UTF-8 4 | version=$1 5 | 6 | # clean old binary zip file 7 | cd /Users/kay/Documents/GoProj/OracleSync2MySQL/binary 8 | rm -rf OracleSync2MySQL.exe 9 | rm -rf example.yml 10 | rm -rf OracleSync2MySQL-MacOS-x64-v$version.zip 11 | rm -rf OracleSync2MySQL-win-x64-v$version.zip 12 | rm -rf OracleSync2MySQL-linux-x64-v$version.zip 13 | 14 | # MacOS macos compile 15 | cd /Users/kay/Documents/GoProj/OracleSync2MySQL 16 | /Users/kay/sdk/go1.22.0/bin/go clean 17 | /Users/kay/sdk/go1.22.0/bin/go build -o OracleSync2MySQL OracleSync2MySQL 18 | zip -r OracleSync2MySQL-MacOS-x64-v$version.zip OracleSync2MySQL example.yml instantclient 19 | mv OracleSync2MySQL-MacOS-x64-v$version.zip binary 20 | 21 | 22 | # Linux ssh remote linux server and compile 23 | ssh root@192.168.74.129 "rm -rf /root/go/src/OracleSync2MySQL/cmd" 24 | ssh root@192.168.74.129 "rm -rf /root/go/src/OracleSync2MySQL/connect" 25 | ssh root@192.168.74.129 "rm -rf /root/go/src/OracleSync2MySQL/go.mod" 26 | ssh root@192.168.74.129 "rm -rf /root/go/src/OracleSync2MySQL/go.sum" 27 | ssh root@192.168.74.129 "rm -rf /root/go/src/OracleSync2MySQL/main.go" 28 | ssh root@192.168.74.129 "rm -rf /root/go/src/OracleSync2MySQL/*.yml" 29 | ssh root@192.168.74.129 "rm -rf /root/go/src/OracleSync2MySQL/*.zip" 30 | scp -r /Users/kay/Documents/GoProj/OracleSync2MySQL/cmd/ root@192.168.74.129:/root/go/src/OracleSync2MySQL 31 | scp -r /Users/kay/Documents/GoProj/OracleSync2MySQL/connect/ root@192.168.74.129:/root/go/src/OracleSync2MySQL 32 | scp /Users/kay/Documents/GoProj/OracleSync2MySQL/go.mod root@192.168.74.129:/root/go/src/OracleSync2MySQL 33 | scp /Users/kay/Documents/GoProj/OracleSync2MySQL/go.sum root@192.168.74.129:/root/go/src/OracleSync2MySQL 34 | scp /Users/kay/Documents/GoProj/OracleSync2MySQL/main.go root@192.168.74.129:/root/go/src/OracleSync2MySQL 35 | scp /Users/kay/Documents/GoProj/OracleSync2MySQL/*.yml root@192.168.74.129:/root/go/src/OracleSync2MySQL 36 | ssh root@192.168.74.129 "rm -rf /root/go/src/OracleSync2MySQL/*.zip" 37 | ssh root@192.168.74.129 "cd /root/go/src/OracleSync2MySQL && /usr/local/go/bin/go clean" 38 | ssh root@192.168.74.129 "cd /root/go/src/OracleSync2MySQL && /usr/local/go/bin/go build -o OracleSync2MySQL OracleSync2MySQL" 39 | ssh root@192.168.74.129 "cd /root/go/src/OracleSync2MySQL && zip -r OracleSync2MySQL-linux-x64-v$version.zip OracleSync2MySQL example.yml instantclient" 40 | scp root@192.168.74.129:/root/go/src/OracleSync2MySQL/OracleSync2MySQL-linux-x64-v$version.zip /Users/kay/Documents/GoProj/OracleSync2MySQL/binary 41 | 42 | 43 | # Windows ssh remote Windows server and compile 44 | ssh administrator@192.168.149.80 "cd C:\go\src\OracleSync2MySQL && C:\Users\Administrator\sdk\go1.20.6\bin\go clean" 45 | ssh administrator@192.168.149.80 "cd C:\go\src\OracleSync2MySQL && del /f /s /q *.zip *.yml go* main.go cmd connect" 46 | scp -r /Users/kay/Documents/GoProj/OracleSync2MySQL/cmd/ administrator@192.168.149.80:"C:\go\src\OracleSync2MySQL" 47 | scp -r /Users/kay/Documents/GoProj/OracleSync2MySQL/connect/ administrator@192.168.149.80:"C:\go\src\OracleSync2MySQL" 48 | scp -r /Users/kay/Documents/GoProj/OracleSync2MySQL/test/ administrator@192.168.149.80:"C:\go\src\OracleSync2MySQL" 49 | scp /Users/kay/Documents/GoProj/OracleSync2MySQL/go.mod administrator@192.168.149.80:"C:\go\src\OracleSync2MySQL" 50 | scp /Users/kay/Documents/GoProj/OracleSync2MySQL/go.sum administrator@192.168.149.80:"C:\go\src\OracleSync2MySQL" 51 | scp /Users/kay/Documents/GoProj/OracleSync2MySQL/main.go administrator@192.168.149.80:"C:\go\src\OracleSync2MySQL" 52 | scp /Users/kay/Documents/GoProj/OracleSync2MySQL/*.yml administrator@192.168.149.80:"C:\go\src\OracleSync2MySQL" 53 | ssh administrator@192.168.149.80 "cd C:\go\src\OracleSync2MySQL && C:\Users\Administrator\sdk\go1.20.6\bin\go build -o OracleSync2MySQL.exe OracleSync2MySQL" 54 | # pack zip file from windows to local mac 55 | cd /Users/kay/Documents/GoProj/OracleSync2MySQL/binary 56 | scp administrator@192.168.149.80:"C:/go/src/OracleSync2MySQL/OracleSync2MySQL.exe" /Users/kay/Documents/GoProj/OracleSync2MySQL/binary 57 | cp /Users/kay/Documents/GoProj/OracleSync2MySQL/example.yml . 58 | zip -r OracleSync2MySQL-win-x64-v$version.zip OracleSync2MySQL.exe example.yml instantclient -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "OracleSync2MySQL/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # OracleSync2MySQL 2 | 3 | ![logo.png](image/logo.png) 4 | 5 | ([CN](https://github.com/iverycd/OracleSync2MySQL/blob/master/readme_cn.md)) 6 | 7 | ## Features 8 | 9 | 10 | Online migration of Oracle databases to target MySQL kernel databases, such as MySQL, PolarDB, Percona Server MySQL, MariaDB, OceanBase, TiDB, GaussDB for MySQL 11 | 12 | - Migrate the entire database table structure and table row data to the target database 13 | - The target database table structure is a superset of the source database that can migrate row data 14 | - Multi thread batch migration of table row data 15 | - Data comparison between source and target databases 16 | 17 | 18 | ## Pre-requirement 19 | The running client PC needs to be able to connect to both the source database and the target database simultaneously 20 | 21 | run on Windows,Linux,macOS 22 | 23 | ## Installation 24 | 25 | unzip and run 26 | 27 | e.g. 28 | 29 | `[root@localhost opt]# unzip OracleSync2MySQL-linux64-0.0.1.zip` 30 | 31 | ## How to use 32 | 33 | The following is an example of a Windows platform, with the same command-line parameters as other operating systems 34 | 35 | `Note`: Please run this tool in `CMD` on `Windows` system, or in a directory with read and write permissions on `MacOS` or `Linux` 36 | 37 | 38 | `If you run on linux please first set LD_LIBRARY_PATH=./instantclient` 39 | 40 | ```bash 41 | example below 42 | 43 | [root@uatenv OracleSync2MySQL]# pwd 44 | /opt/OracleSync2MySQL 45 | 46 | [root@uatenv OracleSync2MySQL]# ls 47 | example.yml instantclient OracleSync2MySQL 48 | 49 | [root@uatenv OracleSync2MySQL]# export LD_LIBRARY_PATH=./instantclient 50 | 51 | ``` 52 | 53 | 54 | ### 1 Edit yml configuration file 55 | 56 | Edit the `example.cfg` file and input the source(src) and target(dest) database information separately 57 | 58 | ```yaml 59 | src: 60 | host: 192.168.1.200 61 | port: 1521 62 | database: orcl 63 | username: admin 64 | password: oracle 65 | dest: 66 | host: 192.168.1.37 67 | port: 3306 68 | database: test_polar 69 | username: root 70 | password: 11111 71 | pageSize: 100000 72 | maxParallel: 100 73 | batchRowSize: 1000 74 | tables: 75 | test: 76 | - select * from test 77 | exclude: 78 | operationlog 79 | ``` 80 | 81 | database: `src` is oracle service_name,`dest` is database name 82 | 83 | pageSize: Number of records per page for pagination query 84 | 85 | maxParallel: The maximum number of concurrency that can run goroutine simultaneously 86 | 87 | tables: Customized migrated tables and customized query source tables, indented in yml format 88 | 89 | exclude: Tables that do not migrate to target database, indented in yml format 90 | 91 | batchRowSize: Number of rows in batch insert target table 92 | 93 | ### 2 Full database migration 94 | 95 | Migrate entire database table structure, row data, views, index constraints, and self increasing columns to target database 96 | 97 | OracleSync2MySQL.exe --config file.yml 98 | ``` 99 | e.g. 100 | OracleSync2MySQL.exe --config example.yml 101 | 102 | on Linux and MacOS you can run 103 | Note: If running on Linux, please first set the environment variable in the directory where the tool is located to specify the instantclient used by the current tool directory 104 | 105 | [root@uatenv OracleSync2MySQL]# pwd 106 | /opt/OracleSync2MySQL 107 | 108 | [root@uatenv OracleSync2MySQL]# ls 109 | example.yml instantclient OracleSync2MySQL 110 | 111 | [root@uatenv OracleSync2MySQL]# export LD_LIBRARY_PATH=./instantclient 112 | 113 | [root@uatenv OracleSync2MySQL]#./OracleSync2MySQL --config example.yml 114 | ``` 115 | 116 | ### 3 View Migration Summary 117 | 118 | After the entire database migration is completed, a migration summary will be generated to observe if there are any failed objects. By querying the migration log, the failed objects can be analyzed 119 | 120 | ```bash 121 | +-------------------------+---------------------+-------------+----------+ 122 | | SourceDb | DestDb | MaxParallel | PageSize | 123 | +-------------------------+---------------------+-------------+----------+ 124 | | 192.168.149.37-sourcedb | 192.168.149.33-test | 30 | 100000 | 125 | +-------------------------+---------------------+-------------+----------+ 126 | 127 | +-----------+----------------------------+----------------------------+-------------+--------------+ 128 | |Object | BeginTime | EndTime |FailedTotal |ElapsedTime | 129 | +-----------+----------------------------+----------------------------+-------------+--------------+ 130 | |Table | 2023-07-21 17:12:51.680525 | 2023-07-21 17:12:52.477100 |0 |796.579837ms | 131 | |TableData | 2023-07-21 17:12:52.477166 | 2023-07-21 17:12:59.704021 |0 |7.226889553s | 132 | +-----------+----------------------------+----------------------------+-------------+--------------+ 133 | 134 | Table Create finish elapsed time 5.0256021s 135 | 136 | ``` 137 | 138 | ### 4 Compare Source and Target database 139 | 140 | After migration finish you can compare source table and target database table rows,displayed failed table only 141 | 142 | `OracleSync2MySQL.exe --config your_file.yml compareDb` 143 | 144 | ``` 145 | e.g. 146 | OracleSync2MySQL.exe --config example.yml compareDb 147 | 148 | on Linux and MacOS you can run 149 | ./OracleSync2MySQL --config example.yml compareDb 150 | ``` 151 | 152 | ```bash 153 | Table Compare Result (Only Not Ok Displayed) 154 | +-----------------------+------------+----------+-------------+------+ 155 | |Table |SourceRows |DestRows |DestIsExist |isOk | 156 | +-----------------------+------------+----------+-------------+------+ 157 | |abc_testinfo |7458 |0 |YES |NO | 158 | |log1_qweharddiskweqaz |0 |0 |NO |NO | 159 | |abcdef_jkiu_button |4 |0 |YES |NO | 160 | |abcdrf_yuio |5 |0 |YES |NO | 161 | |zzz_ss_idcard |56639 |0 |YES |NO | 162 | |asdxz_uiop |290497 |190497 |YES |NO | 163 | |abcd_info |1052258 |700000 |YES |NO | 164 | +-----------------------+------------+----------+-------------+------+ 165 | INFO[0040] Table Compare finish elapsed time 11.307881434s 166 | ``` 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | ## Other migration modes 175 | 176 | 177 | ### 1 Full database migration 178 | 179 | Migrate entire database table structure, row data, views, index constraints, and self increasing columns to target database 180 | 181 | OracleSync2MySQL.exe --config file.yml 182 | 183 | ``` 184 | e.g. 185 | OracleSync2MySQL.exe --config example.yml 186 | 187 | Note: If running on Linux, please first set the environment variable in the directory where the tool is located to specify the instantclient used by the current tool directory 188 | 189 | [root@uatenv OracleSync2MySQL]# pwd 190 | /opt/OracleSync2MySQL 191 | 192 | [root@uatenv OracleSync2MySQL]# ls 193 | example.yml instantclient OracleSync2MySQL 194 | 195 | [root@uatenv OracleSync2MySQL]# export LD_LIBRARY_PATH=./instantclient 196 | 197 | [root@uatenv OracleSync2MySQL]#./OracleSync2MySQL --config example.yml 198 | ``` 199 | 200 | ### 2 Custom SQL Query Migration 201 | 202 | only migrate some tables not entire database, and migrate the table structure and table data to the target database according to the custom query statement in file.yml 203 | 204 | OracleSync2MySQL.exe --config file.yml -s 205 | 206 | ``` 207 | e.g. 208 | OracleSync2MySQL.exe --config example.yml -s 209 | ``` 210 | 211 | ### 3 Migrate all table structures in the entire database 212 | 213 | Create all table structure(only table metadata not row data) to target database 214 | 215 | OracleSync2MySQL.exe --config file.yml createTable -t 216 | 217 | ``` 218 | e.g. 219 | OracleSync2MySQL.exe --config example.yml createTable -t 220 | ``` 221 | 222 | ### 4 Migrate the table structure of custom tables 223 | 224 | Read custom tables from yml file and create target table 225 | 226 | OracleSync2MySQL.exe --config file.yml createTable -s -t 227 | 228 | ``` 229 | e.g. 230 | OracleSync2MySQL.exe --config example.yml createTable -s -t 231 | ``` 232 | 233 | ### 5 Migrate row data across the entire database 234 | 235 | Only migrate all row data from the source database to the target database, excluding table structures 236 | 237 | OracleSync2MySQL.exe --config file.yml onlyData 238 | 239 | ``` 240 | e.g. 241 | OracleSync2MySQL.exe --config example.yml onlyData 242 | ``` 243 | 244 | ### 6 Migrate custom table row data 245 | 246 | only migrate file.yml custom sql query table row data exclude table struct 247 | 248 | OracleSync2MySQL.exe --config file.yml onlyData -s 249 | 250 | ``` 251 | e.g. 252 | OracleSync2MySQL.exe --config example.yml onlyData -s 253 | ``` 254 | 255 | ### 7 output create ddl 256 | 257 | Do not migrate any data, only dump DDL statements. Output DDL statements for table creation, indexes, auto increment columns, views, and other objects to createSql.log in the log file 258 | 259 | OracleSync2MySQL.exe --config file.yml -m 260 | 261 | ``` 262 | e.g. 263 | OracleSync2MySQL.exe --config example.yml -m 264 | ``` 265 | 266 | ## change history 267 | ### v0.1.3 268 | 2024-06-04 269 | fix tablemeta view problem,add -m mode only output ddl script to plat file 270 | 271 | 272 | ### v0.1.2 273 | 2024-05-30 274 | fix missing default value 275 | 276 | ### v0.1.1 277 | 2024-05-14 278 | add column comment 279 | 280 | ### v0.1.0 281 | 2024-03-29 282 | fix some error 283 | 284 | 285 | ### v0.0.9 286 | 2023-11-27 287 | fix -s mode with upper table name 288 | 289 | ### v0.0.8 290 | 2023-10-24 291 | modify fetchTableMap 292 | 293 | ### v0.0.7 294 | 2023-08-31 295 | When modifying the insert method for migrating data, the insert statement has been changed from the previous insert into tableName values to insert into tableName (col1, col2) values. Fix the issue of incorrect timestamp type conversion text 296 | 297 | 298 | ### v0.0.6 299 | 2023-08-23 300 | New add triggers & sequence Oracle autoincrement migration to target database autoincrement columns, migrate foreign keys, indexes of normal index type, comment comments, views, dump source database functions, stored procedures, and other objects to flat files 301 | 302 | 303 | ### v0.0.5 304 | 2023-08-14 305 | Add Oracle instantclient 306 | 307 | ### v0.0.4 308 | 2023-08-04 309 | Fix the issue of tables without data not being created in the target database, add new indexes, and migrate constraints 310 | 311 | 312 | ### v0.0.3 313 | 2023-08-01 314 | Modify the number of connection pools for the source and target databases to unlimited, and use Godror to connect to Oracle 315 | 316 | ### v0.0.2 317 | 2023-07-28 318 | Paging query to obtain bug fixes and increase timestamp type adaptation 319 | 320 | 321 | ### v0.0.1 322 | 2023-07-27 323 | Oracle full database migration of tables and table data to the target MySQL database -------------------------------------------------------------------------------- /readme_cn.md: -------------------------------------------------------------------------------- 1 | # OracleSync2MySQL 2 | ![logo.png](image/logo.png) 3 | 4 | ## 一、工具特性以及环境要求 5 | ### 1.1 功能特性 6 | 7 | 在线迁移Oracle到目标MySQL内核的数据库,如MySQL,PolarDB,Percona Server MySQL,MariaDB,OceanBase,TiDB,GaussDB for MySQL 8 | 9 | - 迁移全库表结构以及表行数据到目标数据库 10 | - 目标数据库表结构是源库超集即可迁移行数据 11 | - 多线程批量方式迁移表行数据 12 | - 数据比对源库以及目标库 13 | 14 | ### 1.2 环境要求 15 | 在运行的客户端PC需要同时能连通源端数据库以及目标数据库 16 | 17 | 支持Windows、Linux、MacOS 18 | 19 | ### 1.3 如何安装 20 | 21 | 解压之后即可运行此工具 22 | 23 | 若在Linux环境下请使用unzip解压,例如: 24 | 25 | 26 | `[root@localhost opt]# unzip OracleSync2MySQL-linux64-0.0.1.zip` 27 | 28 | ## 二、使用方法 29 | 30 | 以下为Windows平台示例,其余操作系统命令行参数一样 31 | 32 | `注意:`在`Windows`系统请在`CMD`运行此工具,如果是在`MacOS`或者`Linux`系统,请在有读写权限的目录运行 33 | 34 | 备注:如果在linux下运行,请先在工具所在目录设定下环境变量LD_LIBRARY_PATH中指定当前工具目录使用的instantclient,如下: 35 | 36 | ```bash 37 | [root@uatenv OracleSync2MySQL]# pwd 38 | /opt/OracleSync2MySQL 39 | 40 | [root@uatenv OracleSync2MySQL]# ls 41 | example.yml instantclient OracleSync2MySQL 42 | 43 | [root@uatenv OracleSync2MySQL]# export LD_LIBRARY_PATH=./instantclient 44 | ``` 45 | 46 | 47 | ### 2.1 编辑yml配置文件 48 | 49 | 编辑`example.cfg`文件,分别输入源库跟目标数据库信息 50 | 51 | ```yaml 52 | src: 53 | host: 192.168.1.200 54 | port: 1521 55 | database: orcl 56 | username: admin 57 | password: oracle 58 | dest: 59 | host: 192.168.1.37 60 | port: 3306 61 | database: test_polar 62 | username: root 63 | password: 11111 64 | pageSize: 100000 65 | maxParallel: 100 66 | batchRowSize: 1000 67 | tables: 68 | test: 69 | - select * from test 70 | exclude: 71 | operationlog 72 | ``` 73 | database: `源端src`是oracle服务名,`目标端dest`是数据库名称 74 | 75 | pageSize: 分页查询每页的记录数 76 | 77 | maxParallel: 最大能同时运行goroutine的并发数 78 | 79 | tables: 自定义迁移的表以及自定义查询源表,按yml格式缩进 80 | 81 | exclude: 不需要迁移的表,按yml格式缩进 82 | 83 | batchRowSize: 批量insert目标表的行数 84 | 85 | ### 2.2 全库迁移 86 | 87 | 迁移全库表结构、行数据,索引约束、自增列等对象 88 | 89 | OracleSync2MySQL.exe --config 配置文件 90 | ``` 91 | 示例 92 | OracleSync2MySQL.exe --config example.yml 93 | 94 | 如果是Linux或者macOS请在终端运行 95 | 备注:如果在linux下运行,请先在工具所在目录设定下环境变量LD_LIBRARY_PATH中指定当前工具目录使用的instantclient 96 | [root@uatenv OracleSync2MySQL]# pwd 97 | /opt/OracleSync2MySQL 98 | 99 | [root@uatenv OracleSync2MySQL]# ls 100 | example.yml instantclient OracleSync2MySQL 101 | 102 | [root@uatenv OracleSync2MySQL]# export LD_LIBRARY_PATH=./instantclient 103 | 104 | [root@uatenv OracleSync2MySQL]#./OracleSync2MySQL --config example.yml 105 | ``` 106 | 107 | ### 2.3 查看迁移摘要 108 | 109 | 全库迁移完成之后会生成迁移摘要,观察下是否有失败的对象,通过查询迁移日志可对迁移失败的对象进行分析 110 | 111 | ```bash 112 | +-------------------------+---------------------+-------------+----------+ 113 | | SourceDb | DestDb | MaxParallel | PageSize | 114 | +-------------------------+---------------------+-------------+----------+ 115 | | 192.168.149.37-sourcedb | 192.168.149.33-test | 30 | 100000 | 116 | +-------------------------+---------------------+-------------+----------+ 117 | 118 | +-----------+----------------------------+----------------------------+-------------+--------------+ 119 | |Object | BeginTime | EndTime |FailedTotal |ElapsedTime | 120 | +-----------+----------------------------+----------------------------+-------------+--------------+ 121 | |Table | 2023-07-21 17:12:51.680525 | 2023-07-21 17:12:52.477100 |0 |796.579837ms | 122 | |TableData | 2023-07-21 17:12:52.477166 | 2023-07-21 17:12:59.704021 |0 |7.226889553s | 123 | +-----------+----------------------------+----------------------------+-------------+--------------+ 124 | 125 | 126 | Table Create finish elapsed time 5.0256021s 127 | 128 | ``` 129 | 130 | ### 2.4 比对数据库 131 | 132 | 迁移完之后比对源库和目标库,查看是否有迁移数据失败的表 133 | 134 | `windows使用:OracleSync2MySQL.exe --config your_file.yml compareDb` 135 | 136 | ``` 137 | e.g. 138 | OracleSync2MySQL.exe --config example.yml compareDb 139 | 140 | 在Linux,MacOS使用示例如下 141 | ./OracleSync2MySQL --config example.yml compareDb 142 | ``` 143 | 144 | ```bash 145 | Table Compare Result (Only Not Ok Displayed) 146 | +-----------------------+------------+----------+-------------+------+ 147 | |Table |SourceRows |DestRows |DestIsExist |isOk | 148 | +-----------------------+------------+----------+-------------+------+ 149 | |abc_testinfo |7458 |0 |YES |NO | 150 | |log1_qweharddiskweqaz |0 |0 |NO |NO | 151 | |abcdef_jkiu_button |4 |0 |YES |NO | 152 | |abcdrf_yuio |5 |0 |YES |NO | 153 | |zzz_ss_idcard |56639 |0 |YES |NO | 154 | |asdxz_uiop |290497 |190497 |YES |NO | 155 | |abcd_info |1052258 |700000 |YES |NO | 156 | +-----------------------+------------+----------+-------------+------+ 157 | INFO[0040] Table Compare finish elapsed time 11.307881434s 158 | ``` 159 | 160 | 161 | 162 | 163 | ## 三、其他迁移模式 164 | 165 | ### 3.1 全库迁移 166 | 167 | 迁移全库表结构、行数据,视图、索引约束、自增列等对象 168 | 169 | OracleSync2MySQL.exe --config 配置文件 170 | 171 | ``` 172 | 示例 173 | OracleSync2MySQL.exe --config example.yml 174 | 175 | 如果是Linux或者macOS请在终端运行 176 | 备注:如果在linux下运行,请先在工具所在目录设定下环境变量LD_LIBRARY_PATH中指定当前工具目录使用的instantclient 177 | [root@uatenv OracleSync2MySQL]# pwd 178 | /opt/OracleSync2MySQL 179 | 180 | [root@uatenv OracleSync2MySQL]# ls 181 | example.yml instantclient OracleSync2MySQL 182 | 183 | [root@uatenv OracleSync2MySQL]# export LD_LIBRARY_PATH=./instantclient 184 | 185 | [root@uatenv OracleSync2MySQL]#./OracleSync2MySQL --config example.yml 186 | ``` 187 | 188 | ### 3.2 自定义SQL查询迁移 189 | 190 | 不迁移全库数据,只迁移部分表,根据配置文件中自定义查询语句迁移表结构和表行数据到目标库 191 | 192 | OracleSync2MySQL.exe --config 配置文件 -s 193 | 194 | ``` 195 | 示例 196 | OracleSync2MySQL.exe --config example.yml -s 197 | ``` 198 | 199 | ### 3.3 迁移全库所有表结构 200 | 201 | 仅在目标库创建所有表的表结构,不包括行数据 202 | 203 | OracleSync2MySQL.exe --config 配置文件 createTable -t 204 | 205 | ``` 206 | 示例 207 | OracleSync2MySQL.exe --config example.yml createTable -t 208 | ``` 209 | 210 | ### 3.4 迁移自定义表的表结构 211 | 212 | 仅在目标库创建自定义的表结构,不包括行数据 213 | 214 | OracleSync2MySQL.exe --config 配置文件 createTable -s -t 215 | 216 | ``` 217 | 示例 218 | OracleSync2MySQL.exe --config example.yml createTable -s -t 219 | ``` 220 | 221 | ### 3.5 迁移全库的行数据 222 | 223 | 仅迁移源库的所有行数据到目标库,不包括表结构 224 | 225 | OracleSync2MySQL.exe --config 配置文件 onlyData 226 | 227 | ``` 228 | 示例 229 | OracleSync2MySQL.exe --config example.yml onlyData 230 | ``` 231 | 232 | ### 3.6 迁移自定义表的行数据 233 | 234 | 仅迁移yml配置文件中自定义查询sql的所有行数据到目标库,不包括表结构 235 | 236 | OracleSync2MySQL.exe --config 配置文件 onlyData -s 237 | 238 | ``` 239 | 示例 240 | OracleSync2MySQL.exe --config example.yml onlyData -s 241 | ``` 242 | 243 | ### 3.7 输出建库脚本 244 | 245 | 不迁移任何数据,仅转储DDL语句,表创建、索引、自增列、视图等对象ddl语句输出到log文件下的createSql.log 246 | 247 | OracleSync2MySQL.exe --config 配置文件 -m 248 | 249 | ``` 250 | 示例 251 | OracleSync2MySQL.exe --config example.yml -m 252 | ``` 253 | 254 | ## change history 255 | ### v0.1.3 256 | 2024-06-04 257 | 修复tablemeta中视图批量替换的问题,新增-m模式,仅输出创建数据库对象的脚本文件到createSql.log文件,不会迁移任何对象 258 | 259 | 260 | ### v0.1.2 261 | 2024-05-30 262 | 修复默认值缺失的问题 263 | 264 | ### v0.1.1 265 | 2024-05-14 266 | 修复注释没有迁移的问题 267 | 268 | 269 | ### v0.1.0 270 | 2024-03-29 271 | 修复分页查询遇到别名冲突问题 272 | 273 | 274 | ### v0.0.9 275 | 2023-11-27 276 | -s自定义迁移的时候,读取配置文件中的表名并统一转为大小 277 | 278 | ### v0.0.8 279 | 2023-10-24 280 | 优化fetchTableMap的方法 281 | 282 | ### v0.0.7 283 | 2023-08-31 284 | 修改迁移数据时的insert方法,insert语句由之前的insert into tableName values改成了insert into tableName(col1,col2) values。修复timestamp类型转换文本不正确的问题 285 | 286 | 287 | ### v0.0.6 288 | 2023-08-23 289 | 新增触发器+序列形式的Oracle自增迁移到目标库自增列,迁移外键,normal-index类型的索引,comment注释,视图,转储源数据库的函数、存储过程等对象到平面文件 290 | 291 | ### v0.0.5 292 | 2023-08-14 293 | 工具新增Oracle instantclient在工具同一目录 294 | 295 | 296 | ### v0.0.4 297 | 2023-08-04 298 | 修复没有数据的表没有在目标库创建的问题,新增索引以及约束迁移 299 | 300 | 301 | ### v0.0.3 302 | 2023-08-01 303 | 修改连接源库以及目标库连接池数量为不限制,使用godror连接Oracle 304 | 305 | ### v0.0.2 306 | 2023-07-28 307 | 分页查询获取bug修复,增加timestamp类型适配 308 | 309 | 310 | ### v0.0.1 311 | 2023-07-27 312 | Oracle全库迁移表和表数据到目标MySQL数据库 -------------------------------------------------------------------------------- /test/_cmd/app.go_: -------------------------------------------------------------------------------- 1 | package _cmd 2 | 3 | import ( 4 | "OracleSync2MySQL/connect" 5 | "database/sql" 6 | "fmt" 7 | "github.com/godror/godror" 8 | "strconv" 9 | ) 10 | 11 | var oracleConnStr godror.ConnectionParams 12 | 13 | type DB struct { 14 | *sql.DB 15 | } 16 | 17 | func NewDB(connStr *connect.DbConnStr) *DB { 18 | srcHost := connStr.SrcHost 19 | srcUserName := connStr.SrcUserName 20 | srcPassword := connStr.SrcPassword 21 | srcDatabase := connStr.SrcDatabase 22 | srcPort := connStr.SrcPort 23 | oracleConnStr.LibDir = "instantclient" 24 | oracleConnStr.Username = srcUserName 25 | oracleConnStr.Password = godror.NewPassword(srcPassword) 26 | oracleConnStr.ConnectString = fmt.Sprintf("%s:%s/%s", srcHost, strconv.Itoa(srcPort), srcDatabase) 27 | db := sql.OpenDB(godror.NewConnector(oracleConnStr)) 28 | return &DB{db} 29 | } -------------------------------------------------------------------------------- /test/_cmd/root.go_: -------------------------------------------------------------------------------- 1 | package _cmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // V0.0.7 macOS和centos多协程调用prepareSqlStr能正确关闭数据库连接,Windows多协程下无法关闭数据库连接 11 | // 自动对表分析,然后生成每个表用来迁移查询源库SQL的集合(全表查询或者分页查询) 12 | // 自动分析是否有排除的表名 13 | // 最后返回map结构即 表:[查询SQL] 14 | func fetchTableMap(pageSize int, excludeTable []string) (tableMap map[string][]string) { 15 | var tableNumber int // 表总数 16 | var sqlStr string // 查询源库获取要迁移的表名 17 | // 声明一个等待组 18 | var wg sync.WaitGroup 19 | // 使用互斥锁 sync.Mutex才能使用并发的goroutine 20 | mutex := &sync.Mutex{} 21 | log.Info("exclude table ", excludeTable) 22 | // 如果配置文件中exclude存在表名,使用not in排除掉这些表,否则获取到所有表名 23 | if excludeTable != nil { 24 | sqlStr = "select table_name from user_tables where table_name not in (" 25 | buffer := bytes.NewBufferString("") 26 | for index, tabName := range excludeTable { 27 | if index < len(excludeTable)-1 { 28 | buffer.WriteString("'" + tabName + "'" + ",") 29 | } else { 30 | buffer.WriteString("'" + tabName + "'" + ")") 31 | } 32 | } 33 | sqlStr += buffer.String() 34 | } else { 35 | sqlStr = "select table_name from user_tables" // 获取库里全表名称 36 | } 37 | // 查询下源库总共的表,获取到表名 38 | rows, err := srcDb.Query(sqlStr) 39 | defer rows.Close() 40 | if err != nil { 41 | log.Error(fmt.Sprintf("Query "+sqlStr+" failed,\nerr:%v\n", err)) 42 | return 43 | } 44 | var tableName string 45 | //初始化外层的map,键值对,即 表名:[sql语句...] 46 | tableMap = make(map[string][]string) 47 | for rows.Next() { 48 | tableNumber++ 49 | // 每一个任务开始时, 将等待组增加1 50 | wg.Add(1) 51 | var sqlFullList []string 52 | err = rows.Scan(&tableName) 53 | if err != nil { 54 | log.Error(err) 55 | } 56 | // 使用多个并发的goroutine调用函数获取该表用来执行的sql语句 57 | log.Info(time.Now().Format("2006-01-02 15:04:05.000000"), "ID[", tableNumber, "] ", "prepare ", tableName, " TableMap") 58 | go func(tableName string, sqlFullList []string) { 59 | // 使用defer, 表示函数完成时将等待组值减1 60 | defer wg.Done() 61 | // !tableOnly即没有指定-t选项,生成全库的分页查询语句,否则就是指定了-t选项,sqlFullList仅追加空字符串 62 | if !tableOnly { 63 | sqlFullList = prepareSqlStr(tableName, pageSize) 64 | if len(sqlFullList) == 0 { // 如果表没有数据,手动append一条1=0的sql语句,否则该表不会被创建,compareDb运行也会不准确 65 | sqlFullList = append(sqlFullList, fmt.Sprintf("select * from \"%s\" where 1=0", tableName)) 66 | } 67 | } else { 68 | sqlFullList = append(sqlFullList, "") 69 | } 70 | // 追加到内层的切片,sql全表扫描语句或者分页查询语句,例如tableMap[test1]="select * from test1" 71 | for i := 0; i < len(sqlFullList); i++ { 72 | mutex.Lock() 73 | tableMap[tableName] = append(tableMap[tableName], sqlFullList[i]) 74 | mutex.Unlock() 75 | } 76 | }(tableName, sqlFullList) 77 | } 78 | // 等待所有的任务完成 79 | wg.Wait() 80 | return tableMap 81 | } 82 | -------------------------------------------------------------------------------- /test/_cmd/tablemeta.go_: -------------------------------------------------------------------------------- 1 | package _cmd 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | // IdxCreate 单线程,不使用goroutine创建索引 10 | func (tb *Table) IdxCreate(logDir string) (result []string) { 11 | startTime := time.Now() 12 | failedCount := 0 13 | id := 0 14 | // 查询索引、主键、唯一约束等信息,批量生成创建语句 15 | sqlStr := fmt.Sprintf("SELECT (CASE WHEN C.CONSTRAINT_TYPE = 'P' OR C.CONSTRAINT_TYPE = 'R' THEN 'ALTER TABLE `' || T.TABLE_NAME || '` ADD CONSTRAINT ' ||'`'||T.INDEX_NAME||'`' || (CASE WHEN C.CONSTRAINT_TYPE = 'P' THEN ' PRIMARY KEY (' ELSE ' FOREIGN KEY (' END) || listagg(T.COLUMN_NAME,',') within group(order by T.COLUMN_position) || ');' ELSE 'CREATE ' || (CASE WHEN I.UNIQUENESS = 'UNIQUE' THEN I.UNIQUENESS || ' ' ELSE CASE WHEN I.INDEX_TYPE = 'NORMAL' THEN '' ELSE I.INDEX_TYPE || ' ' END END) || 'INDEX ' || '`'||T.INDEX_NAME||'`' || ' ON ' || T.TABLE_NAME || '(' ||listagg(T.COLUMN_NAME,',') within group(order by T.COLUMN_position) || ');' END) SQL_CMD FROM USER_IND_COLUMNS T, USER_INDEXES I, USER_CONSTRAINTS C WHERE T.INDEX_NAME = I.INDEX_NAME AND T.INDEX_NAME = C.CONSTRAINT_NAME(+) and i.index_type != 'FUNCTION-BASED NORMAL' GROUP BY T.TABLE_NAME, T.INDEX_NAME, I.UNIQUENESS, I.INDEX_TYPE,C.CONSTRAINT_TYPE",tableName) 16 | //fmt.Println(sql) 17 | rows, err := srcDb.Query(sqlStr) 18 | if err != nil { 19 | log.Error(err) 20 | } 21 | defer rows.Close() 22 | // 从sql结果集遍历,获取到创建语句 23 | for rows.Next() { 24 | id += 1 25 | if err := rows.Scan(&tb.destIdxSql); err != nil { 26 | log.Error(err) 27 | } 28 | // 创建目标索引,主键、其余约束 29 | log.Info(fmt.Sprintf("%v ProcessingID %s %s", time.Now().Format("2006-01-02 15:04:05.000000"), strconv.Itoa(id), tb.destIdxSql)) 30 | if _, err = destDb.Exec(tb.destIdxSql); err != nil { 31 | log.Error("index ", tb.destIdxSql, " create index failed ", err) 32 | LogError(logDir, "idxCreateFailed", tb.destIdxSql, err) 33 | failedCount += 1 34 | } 35 | } 36 | endTime := time.Now() 37 | cost := time.Since(startTime) 38 | log.Info("index count ", id) 39 | result = append(result, "Index", startTime.Format("2006-01-02 15:04:05.000000"), endTime.Format("2006-01-02 15:04:05.000000"), strconv.Itoa(failedCount), cost.String()) 40 | return result 41 | } 42 | --------------------------------------------------------------------------------