mirror of
https://github.com/nxshock/mssqlbulkloader.git
synced 2024-11-28 00:21:03 +05:00
210 lines
4.6 KiB
Go
210 lines
4.6 KiB
Go
|
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
|
||
|
}
|