mssqlbulkloader/app.go

210 lines
4.6 KiB
Go
Raw Normal View History

2023-04-03 19:12:30 +05:00
package main
import (
"database/sql"
"fmt"
"io"
"os"
"time"
_ "time/tzdata"
_ "github.com/denisenkom/go-mssqldb"
"github.com/urfave/cli"
)
var app = &cli.App{
Version: "2023.03.27",
Usage: "bulk loader into Microsoft SQL Server",
HideHelp: true,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "filepath",
Usage: "input file path",
Required: true,
TakesFile: true},
&cli.StringFlag{
Name: "type",
Usage: "input file type",
Required: false,
Value: "auto",
},
&cli.StringFlag{
Name: "encoding",
Usage: "input file encoding",
Required: false,
Value: "utf8",
},
&cli.StringFlag{
Name: "sheetname",
Usage: "Excel file sheet name",
Required: false},
&cli.StringFlag{
Name: "server",
Usage: "database server address",
Value: "127.0.0.1"},
&cli.StringFlag{
Name: "database",
Usage: "database name",
Required: true},
&cli.StringFlag{
Name: "table",
Usage: "table name in schema.name format",
Required: true},
&cli.StringFlag{
Name: "fields",
Usage: "list of field types in [sifdt ]+ format",
Required: true},
&cli.BoolFlag{
Name: "create",
Usage: "create table"},
&cli.BoolFlag{
Name: "overwrite",
Usage: "overwrite existing table"},
&cli.IntFlag{
Name: "skiprows",
Usage: "number of rows to skip before read header"},
&cli.BoolFlag{
Name: "unknowncolumnnames",
Usage: "insert to table with unknown column names",
},
&cli.StringFlag{
Name: "timezone",
Usage: "Time zone (IANA Time Zone database format)",
Value: "Local",
},
&cli.StringFlag{
Name: "comma",
Usage: "CSV file delimiter",
Value: ",",
},
&cli.StringFlag{
Name: "dateformat",
Usage: "date format (Go style)",
Value: "02.01.2006"},
&cli.StringFlag{
Name: "timestampformat",
Usage: "timestamp format (Go style)",
Value: "02.01.2006 15:04:05"},
&cli.StringFlag{
Name: "decompress",
Usage: "decompressor name for archived files",
},
&cli.BoolFlag{
Name: "silent",
Usage: "disable output",
},
},
Action: func(c *cli.Context) error {
initLogger(c.Bool("silent"))
var comma rune
if c.String("comma") == "\\t" {
comma = rune("\t"[0])
} else {
comma = rune(c.String("comma")[0])
}
location, err := time.LoadLocation(c.String("timezone"))
if err != nil {
return fmt.Errorf("parse timezone: %w", err)
}
options := &Options{
filePath: c.String("filepath"),
fileType: c.String("type"),
sheetName: c.String("sheetname"),
server: c.String("server"),
database: c.String("database"),
tableName: c.String("table"),
fieldsTypes: c.String("fields"),
create: c.Bool("create"),
overwrite: c.Bool("overwrite"),
skipRows: c.Int("skiprows"),
encoding: c.String("encoding"),
dateFormat: c.String("dateformat"),
timestampFormat: c.String("timestampformat"),
timezone: location,
decompress: c.String("decompress"),
unknownColumnNames: c.Bool("unknowncolumnnames"),
silent: c.Bool("silent"),
comma: comma,
}
if options.decompress != "" {
var archiveType ArchiveType
err = archiveType.UnmarshalText([]byte(options.decompress))
if err != nil {
return err
}
ar, err := archiveType.Open()
if err != nil {
return err
}
err = ar.Process(options)
if err != nil {
return err
}
} else {
f, err := os.Open(options.filePath)
if err != nil {
return err
}
defer f.Close()
err = process(f, options)
if err != nil {
return err
}
}
logger.Print("Complete.")
return nil
}}
func process(r io.Reader, options *Options) error {
var fileType FileType
err := fileType.UnmarshalText([]byte(options.fileType))
if err != nil {
return err
}
reader, err := fileType.Open(r, options)
if err != nil {
return err
}
defer reader.Close()
db, err := sql.Open("sqlserver", fmt.Sprintf("sqlserver://%s?database=%s", options.server, options.database))
if err != nil {
return fmt.Errorf("open database: %w", err)
}
defer db.Close()
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("begin transaction: %w", err)
}
err = prepareTable(reader, tx)
if err != nil {
tx.Rollback()
return fmt.Errorf("prepare table: %w", err)
}
err = insertData(reader, tx)
if err != nil {
tx.Rollback()
return fmt.Errorf("insert data: %w", err)
}
err = tx.Commit()
if err != nil {
return fmt.Errorf("commit transaction: %w", err)
}
return nil
}