Skip to content

Commit

Permalink
Merge pull request #18 from jackyzhen/postgres-support
Browse files Browse the repository at this point in the history
Postgres support
  • Loading branch information
Mariano Gappa authored Oct 25, 2018
2 parents 99d526e + 5bd877a commit 05758b0
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 146 deletions.
11 changes: 10 additions & 1 deletion .databases.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
"dbName": "prod_database",
"dbServer": "prod.db.server.com",
"user": "user",
"pass": "pass"
"pass": "pass",
"sqlType": "mysql"
},
"postgresdb": {
"appServer": "pg.server.com",
"dbName": "pg_database",
"dbServer": "pg.db.server.com",
"user": "postgres",
"pass": "postgres",
"sqlType": "postgres"
}
}
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM golang:1.11

RUN apt-get update && apt-get install -y --no-install-recommends mysql-client && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends mysql-client postgresql-client && rm -rf /var/lib/apt/lists/*

ENTRYPOINT [ "go", "test", "-v", "." ]
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ MySQL pipe

## What does it do?

- `sql` allows you to pipe STDIN (hopefully containing SQL) to one or more pre-configured MySQL databases
- `sql` allows you to pipe STDIN (hopefully containing SQL) to one or more pre-configured MySQL or PostgreSQL databases
- output comes out in `\t`-separated format, allowing further piping (e.g. works really well with [chart](https://github.com/MarianoGappa/chart))
- when more than one database is queried, the requests are made in parallel
- `sql` can either run `mysql` locally, run `mysql` locally but connecting to a remote host (by configuring a `dbServer`), or `ssh` to a remote host and from there run `mysql` to either a local or remote host (by configuring an `appServer` and a `dbServer`)
- `sql` can either run `mysql/psql` locally, run `mysql/psql` locally but connecting to a remote host (by configuring a `dbServer`), or `ssh` to a remote host and from there run `mysql/psql` to either a local or remote host (by configuring an `appServer` and a `dbServer`)

## Installation

Expand All @@ -37,6 +37,8 @@ $ compinit

Create a `.databases.json` dotfile in your home folder or in any [XDG-compliant](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) directory. [This](.databases.json.example) is an example file.

`sql` decides to execute with MySQL or PostgreSQL depending on the `sqlType` property set for a database, *defaulting to to MySQL if not set.*

## Example usages

```
Expand All @@ -50,8 +52,9 @@ sql all "SELECT * FROM users WHERE name = 'John'"
## Notes

- when more than one database is queried, the resulting rows are prefixed with the database identifier
- the `all` special keyword means "sql to all configured databases"
- the `all` special keyword means "sql to all configured databases".
- `sql` assumes that you have correctly configured SSH keys on all servers you `ssh` to
- `sql` will error if all targeted databases do not have the same sql type.

## Beware!

Expand All @@ -61,7 +64,7 @@ sql all "SELECT * FROM users WHERE name = 'John'"

## Dependencies

- mysql
- mysql-client and/or postgresql-client
- ssh (only if you configure an "appServer")

## Contribute
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type database struct {
DbName string
User string
Pass string
SQLType string
}

func mustReadDatabasesConfigFile() map[string]database {
Expand Down
82 changes: 3 additions & 79 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package main

import (
"bufio"
"context"
"flag"
"fmt"
"log"
"os"
"os/exec"
"strings"
"sync"
)

Expand Down Expand Up @@ -84,11 +81,13 @@ func _main(databases map[string]database, databasesArgs []string, query string,
var wg sync.WaitGroup
wg.Add(len(targetDatabases))

sqlRunner := mustNewSQLRunner(quitContext, println, query, len(targetDatabases) > 1)

returnCode := 0
for _, k := range targetDatabases {
go func(db database, k string) {
defer wg.Done()
if r := runSQL(quitContext, db, query, k, len(targetDatabases) > 1, println); !r {
if r := sqlRunner.runSQL(db, k); !r {
returnCode = 1
}
}(databases[k], k)
Expand All @@ -97,78 +96,3 @@ func _main(databases map[string]database, databasesArgs []string, query string,
wg.Wait()
return returnCode
}

func runSQL(quitContext context.Context, db database, query string, key string, prependKey bool, println func(string)) bool {
userOption := ""
if db.User != "" {
userOption = fmt.Sprintf("-u %v ", db.User)
}

passOption := ""
if db.Pass != "" {
passOption = fmt.Sprintf("-p%v ", db.Pass)
}

hostOption := ""
if db.DbServer != "" {
hostOption = fmt.Sprintf("-h %v ", db.DbServer)
}

prepend := ""
if prependKey {
prepend = key + "\t"
}

mysql := "mysql"
options := fmt.Sprintf(" -Nsr %v%v%v%v -e ", userOption, passOption, hostOption, db.DbName)

var cmd *exec.Cmd
if db.AppServer != "" {
escapedQuery := fmt.Sprintf(`'%v'`, strings.Replace(query, `'`, `'"'"'`, -1))
cmd = exec.CommandContext(quitContext, "ssh", db.AppServer, mysql+options+escapedQuery)
} else {
args := append(trimEmpty(strings.Split(options, " ")), query)
cmd = exec.CommandContext(quitContext, mysql, args...)
}

stdout, err := cmd.StdoutPipe()
if err != nil {
log.Printf("Cannot create pipe for STDOUT of running command on %v; not running. err=%v\n", key, err)
return false
}

stderr, err := cmd.StderrPipe()
if err != nil {
log.Printf("Cannot create pipe for STDERR of running command on %v; not running. err=%v\n", key, err)
return false
}

if err := cmd.Start(); err != nil {
log.Printf("Cannot start command on %v; not running. err=%v\n", key, err)
return false
}

scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
println(prepend + scanner.Text())
}

stderrLines := []string{}
scanner = bufio.NewScanner(stderr)
for scanner.Scan() {
stderrLines = append(stderrLines, scanner.Text())
}

cmd.Wait()

result := true
if len(stderrLines) > 0 {
result = false
log.Println(key + " had errors:")
for _, v := range stderrLines {
log.Println(key + " [ERROR] " + v)
}
}

return result
}
Loading

0 comments on commit 05758b0

Please sign in to comment.