diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3f88597..c55696c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,30 +4,25 @@ on: branches: - "!**/*" tags: - - "v*" + - "v*.*.*" jobs: release: name: Release runs-on: ubuntu-latest steps: - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.16 + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: 1.17 - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + - name: Check out code into the Go module directory + uses: actions/checkout@v2 - - name: setup tools - run: | - mkdir ~/bin - curl -sL https://github.com/Songmu/goxz/releases/download/v0.4.1/goxz_v0.4.1_linux_amd64.tar.gz | tar zxvf - && install goxz_v0.4.1_linux_amd64/goxz ~/bin/ - curl -sL https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz | tar zxvf - && install ghr_v0.13.0_linux_amd64/ghr ~/bin/ - - name: dist - run: PATH=~/bin:$PATH make dist - - - name: release - run: PATH=~/bin:$PATH make release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v1 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f5140a8..5521f10 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,7 +5,7 @@ jobs: strategy: matrix: go: - - 1.16 + - 1.17 name: Build runs-on: ubuntu-latest steps: diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..b0fd9ea --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,34 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + - go mod download +builds: + - env: + - CGO_ENABLED=0 + main: ./cmd/mysqlbatch + binary: mysqlbatch + ldflags: + - -s -w + - -X main.Version=v{{.Version}} + - -X main.BuildDate=v{{.Date}} + goos: + - darwin + - linux + - windows + goarch: + - amd64 + - arm64 +release: + prerelease: true +archives: +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ .Env.NIGHTLY_VERSION }}" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/Makefile b/Makefile index 8dd2160..2efd6bd 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,8 @@ -GIT_VER := $(shell git describe --tags) -DATE := $(shell date +%Y-%m-%dT%H:%M:%S%z) -GO_VER := $(shell go version) - export GO111MODULE := on -.PHONY: test binary install clean dist +.PHONY: test clean cmd/mysqlbatch/mysqlbatch: *.go cmd/mysqlbatch/*.go - cd cmd/mysqlbatch && go build -ldflags "-s -w -X 'main.Version=${GIT_VER}' -X 'main.BuildDate=${DATE}' -X 'main.GoVersion=${GO_VER}'" -gcflags="-trimpath=${PWD}" + cd cmd/mysqlbatch && go build . test: go test -race . @@ -15,12 +11,3 @@ test: clean: rm -f cmd/mysqlbatch/mysqlbatch rm -fr dist/ - -dist: - CGO_ENABLED=0 \ - goxz -pv=$(GIT_VER) \ - -build-ldflags="-s -w -X 'main.Version=${GIT_VER}' -X 'main.BuildDate=${DATE}' -X 'main.GoVersion=${GO_VER}'" \ - -os=darwin,linux -arch=amd64 -d=dist ./cmd/mysqlbatch - -release: - ghr -u mashiike -r mysqlbatch -n "$(GIT_VER)" $(GIT_VER) dist/ diff --git a/README.md b/README.md index eea00ee..a78fa34 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,23 @@ $ mysqlbatch -u root -p ${password} -h localhost < batch.sql ``` +## Usage as a library + + +```go +executer, err := mysqlbatch.Open("root:password@tcp(localhost:3306)/testdb?parseTime=true") +if err != nil { + //... +} +defer executer.Close() +if err := executer.Execute(strings.NewReader("UPDATE users SET name = 'hoge';")); err != nil { + //... +} +``` + +more infomation see [go doc](https://godoc.org/github.com/mashiike/mysqlbatch). + +## License + +see [LICENSE](https://github.com/mashiike/mysqlbatch/blob/master/LICENSE) file. + diff --git a/cmd/mysqlbatch/main.go b/cmd/mysqlbatch/main.go index 3198415..e45fd88 100644 --- a/cmd/mysqlbatch/main.go +++ b/cmd/mysqlbatch/main.go @@ -7,6 +7,7 @@ import ( "log" "os" "os/signal" + "runtime" "syscall" "github.com/mashiike/mysqlbatch" @@ -15,7 +16,6 @@ import ( var ( Version = "current" BuildDate = "(no data)" - GoVersion = "(no data)" ) func main() { @@ -34,7 +34,7 @@ func main() { if *versionFlag { fmt.Printf("version : %s\n", Version) - fmt.Printf("go version: %s\n", GoVersion) + fmt.Printf("go version: %s\n", runtime.Version()) fmt.Printf("build date: %s\n", BuildDate) return } diff --git a/executer.go b/executer.go index 8750955..b8dbafa 100644 --- a/executer.go +++ b/executer.go @@ -15,6 +15,8 @@ import ( "github.com/pkg/errors" ) +// Config is a connection setting to MySQL. +// Exists to generate a Golang connection DSN to MySQL type Config struct { DSN string User string @@ -24,6 +26,7 @@ type Config struct { Database string } +// NewDefaultConfig returns the config for connecting to the local MySQL server func NewDefaultConfig() *Config { return &Config{ User: "root", @@ -32,6 +35,7 @@ func NewDefaultConfig() *Config { } } +//GetDSN returns a DSN dedicated to connecting to MySQL. func (c *Config) GetDSN() string { if c.DSN == "" { return fmt.Sprintf( @@ -46,32 +50,43 @@ func (c *Config) GetDSN() string { return strings.TrimPrefix(c.DSN, "mysql://") } +//Executer queries the DB. There is no parallelism type Executer struct { mu sync.Mutex - dsn string db *sql.DB lastExecuteTime time.Time selectHook func(query string, columns []string, rows [][]string) executeHook func(query string, rowsAffected int64, lastInsertId int64) + isSelectFunc func(query string) bool + timeCheckQuery string } +//New return Executer with config func New(config *Config) (*Executer, error) { return Open(config.GetDSN()) } +//Open with dsn func Open(dsn string) (*Executer, error) { db, err := sql.Open("mysql", dsn) if err != nil { return nil, errors.Wrap(err, "mysql connect failed") } + return NewWithDB(db), nil +} + +// NewWithDB returns Executer with *sql.DB +// Note: Since it is made assuming MySQL, it may be inconvenient for other DBs. +func NewWithDB(db *sql.DB) *Executer { db.SetMaxIdleConns(1) db.SetMaxOpenConns(1) return &Executer{ - dsn: dsn, - db: db, - }, nil + db: db, + timeCheckQuery: "SELECT NOW()", + } } +// Close DB func (e *Executer) Close() error { e.mu.Lock() defer e.mu.Unlock() @@ -81,10 +96,12 @@ func (e *Executer) Close() error { return e.db.Close() } +//Execute SQL func (e *Executer) Execute(queryReader io.Reader) error { return e.ExecuteContext(context.Background(), queryReader) } +//ExecuteContext SQL execute with context.Context func (e *Executer) ExecuteContext(ctx context.Context, queryReader io.Reader) error { e.mu.Lock() defer e.mu.Unlock() @@ -95,7 +112,7 @@ func (e *Executer) ExecuteContext(ctx context.Context, queryReader io.Reader) er } func (e *Executer) updateLastExecuteTime(ctx context.Context) error { - row := e.db.QueryRowContext(ctx, "SELECT NOW()") + row := e.db.QueryRowContext(ctx, e.timeCheckQuery) if err := row.Err(); err != nil { return errors.Wrap(err, "get db time") } @@ -116,7 +133,15 @@ func (e *Executer) executeContext(ctx context.Context, queryReader io.Reader) er } if e.selectHook != nil { upperedQuery := strings.ToUpper(query) - if strings.HasPrefix(upperedQuery, "SELECT") || strings.HasPrefix(upperedQuery, "SHOW") { + var isSelect bool + if e.isSelectFunc == nil { + if strings.HasPrefix(upperedQuery, "SELECT") || strings.HasPrefix(upperedQuery, "SHOW") || strings.HasPrefix(upperedQuery, `\`) { + isSelect = true + } + } else { + isSelect = e.isSelectFunc(upperedQuery) + } + if isSelect { if err := e.queryContext(ctx, query); err != nil { return errors.Wrap(err, "query rows failed") } @@ -175,18 +200,32 @@ func (e *Executer) queryContext(ctx context.Context, query string) error { return nil } +//LastExecuteTime returns last execute time on DB func (e *Executer) LastExecuteTime() time.Time { return e.lastExecuteTime } +//SetExecuteHook set non select query hook func (e *Executer) SetExecuteHook(hook func(query string, rowsAffected, lastInsertId int64)) { e.executeHook = hook } +//SetSelectHook set select query hook func (e *Executer) SetSelectHook(hook func(query string, columns []string, rows [][]string)) { e.selectHook = hook } +//SetIsSelectFunc :Set the function to decide whether to execute in QueryContext +func (e *Executer) SetIsSelectFunc(f func(query string) bool) { + e.isSelectFunc = f +} + +//SetTimeCheckQuery set time check query for non mysql db +func (e *Executer) SetTimeCheckQuery(query string) { + e.timeCheckQuery = query +} + +//SetTimeCheckQuery set select query hook, but result is table string func (e *Executer) SetTableSelectHook(hook func(query, table string)) { e.selectHook = func(query string, columns []string, rows [][]string) { var buf strings.Builder @@ -198,10 +237,12 @@ func (e *Executer) SetTableSelectHook(hook func(query, table string)) { } } +// QueryScanner separate string by ; and delete newline type QueryScanner struct { *bufio.Scanner } +//NewQueryScanner returns QueryScanner func NewQueryScanner(queryReader io.Reader) *QueryScanner { scanner := bufio.NewScanner(queryReader) onSplit := func(data []byte, atEOF bool) (advance int, token []byte, err error) { @@ -224,6 +265,7 @@ func NewQueryScanner(queryReader io.Reader) *QueryScanner { } } +//Query return func (s *QueryScanner) Query() string { return strings.Trim(strings.NewReplacer( "\r\n", " ", diff --git a/go.mod b/go.mod index f1a4839..02502f9 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,15 @@ module github.com/mashiike/mysqlbatch -go 1.16 +go 1.17 require ( github.com/go-sql-driver/mysql v1.6.0 - github.com/mattn/go-runewidth v0.0.13 // indirect github.com/olekukonko/tablewriter v0.0.5 github.com/pkg/errors v0.9.1 github.com/sergi/go-diff v1.1.0 ) + +require ( + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/rivo/uniseg v0.2.0 // indirect +)