Skip to content
This repository was archived by the owner on Feb 21, 2023. It is now read-only.

Commit 0d38dff

Browse files
author
Shlomi Noach
authored
Merge pull request #48 from github/multi-schema-query
Support for multiple-schemas
2 parents 2b02539 + 59799c0 commit 0d38dff

5 files changed

Lines changed: 87 additions & 21 deletions

File tree

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Usage of ccql:
3232
MySQL password
3333
-q string
3434
Query/queries to execute
35+
-s string
36+
List of databases to query from; overrides -d, prints schema name to output
3537
-t float
3638
Connect timeout seconds
3739
-u string
@@ -58,6 +60,16 @@ You may provide a query or a list of queries in the following ways:
5860
Queries are delimited by a semicolon (`;`). The last query may, but does not have to, be terminated by a semicolon.
5961
Quotes are respected, up to a reasonable level. It is valid to include a semicolon in a quoted text, as in `select 'single;query'`. However `ccql` does not employ a full blown parser, so please don't overdo it. For example, the following may not be parsed correctly: `select '\';\''`. You get it.
6062

63+
#### Schemas
64+
65+
You may either provide:
66+
67+
- An implicit, default schema via `-d schema_name`
68+
- Schema name is not visible on output.
69+
- Or explicit list of schemas via `-s "schema_1,schema_2[,schema_3...]"` (overrides `-d`)
70+
- Queries are executed per host, per schema.
71+
- Schema name printed as output column.
72+
6173
#### Credentials input
6274

6375
You may provide credentials in the following ways:
@@ -144,6 +156,20 @@ Set `sync_binlog=0` on all intermediate masters:
144156
cat /tmp/hosts.txt | ccql -q "show slave status;" | awk -F $'\t' '{print $3 ":" $5}' | sort | uniq | ccql -q "show slave status" | awk '{print $1}' | ccql -q "set global sync_binlog=0"
145157
```
146158

159+
Multiple schemas:
160+
161+
```shell
162+
$ cat /tmp/hosts.txt | ccql -t 0.5 -s "test,meta" -q "select uuid() from dual" | column -t
163+
host3:3306 test d0d95311-b8ad-11e7-81e7-008cfa542442
164+
host2:3306 meta d0d95311-b8ad-11e7-a16c-a0369fb3dc94
165+
host2:3306 test d0d95fd6-b8ad-11e7-9a23-008cfa544064
166+
host1:3306 meta d0d95311-b8ad-11e7-9a15-a0369fb5fdd0
167+
host3:3306 meta d0d95311-b8ad-11e7-bd26-a0369fb5f3d8
168+
host4:3306 meta d0d95311-b8ad-11e7-a16c-a0369fb3dc94
169+
host1:3306 test d0d96924-b8ad-11e7-9bde-008cfa5440e4
170+
host4:3306 test d0d99a9d-b8ad-11e7-a680-008cfa542c9e
171+
```
172+
147173
## LICENSE
148174

149175
See [LICENSE](LICENSE). _ccql_ imports and includes 3rd party libraries, which have their own license. These are found under [vendor](vendor).

go/cmd/ccql/main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ func main() {
4040
askPassword := flag.Bool("ask-pass", false, "prompt for MySQL password")
4141
credentialsFile := flag.String("C", "", "Credentials file, expecting [client] scope, with 'user', 'password' fields. Overrides -u and -p")
4242
defaultSchema := flag.String("d", "information_schema", "Default schema to use")
43+
schemasList := flag.String("s", "", "List of databases to query from; overrides -d, prints schema name to output")
4344
hostsList := flag.String("h", "", "Comma or space delimited list of hosts in hostname[:port] format. If not given, hosts read from stdin")
4445
hostsFile := flag.String("H", "", "Hosts file, hostname[:port] comma or space or newline delimited format. If not given, hosts read from stdin")
4546
queriesText := flag.String("q", "", "Query/queries to execute")
4647
queriesFile := flag.String("Q", "", "Query/queries input file")
4748
timeout := flag.Float64("t", 0, "Connect timeout seconds")
4849
maxConcurrency := flag.Uint("m", 32, "Max concurrent connections")
50+
4951
flag.Parse()
5052

5153
if AppVersion == "" {
@@ -117,7 +119,9 @@ func main() {
117119
*password = string(passwd)
118120
}
119121

120-
if err := logic.QueryHosts(hosts, *user, *password, *defaultSchema, queries, *maxConcurrency, *timeout); err != nil {
122+
schemas := text.SplitNonEmpty(*schemasList, ",")
123+
124+
if err := logic.QueryHosts(hosts, *user, *password, *defaultSchema, schemas, queries, *maxConcurrency, *timeout); err != nil {
121125
os.Exit(1)
122126
}
123127
}

go/logic/ccql.go

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,29 @@ import (
44
"fmt"
55
"log"
66
"strings"
7+
"sync"
78

89
"github.com/outbrain/golib/sqlutils"
910
)
1011

1112
// queryHost connects to a given host, issues the given set of queries, and outputs the results
1213
// line per row in tab delimited format
13-
func queryHost(host string, user string, password string, defaultSchema string, queries []string, timeout float64) error {
14-
mysqlURI := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=%fs", user, password, host, defaultSchema, timeout)
14+
func queryHost(host string, user string, password string, schema string, queries []string, timeout float64, printSchema bool) error {
15+
mysqlURI := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=%fs", user, password, host, schema, timeout)
1516
db, _, err := sqlutils.GetDB(mysqlURI)
1617
if err != nil {
1718
return err
1819
}
19-
2020
for _, query := range queries {
2121
resultData, err := sqlutils.QueryResultData(db, query)
2222
if err != nil {
2323
return err
2424
}
2525
for _, row := range resultData {
2626
output := []string{host}
27+
if printSchema {
28+
output = append(output, schema)
29+
}
2730
for _, rowCell := range row {
2831
output = append(output, rowCell.String)
2932
}
@@ -35,25 +38,34 @@ func queryHost(host string, user string, password string, defaultSchema string,
3538
}
3639

3740
// QueryHosts will issue concurrent queries on given list of hosts
38-
func QueryHosts(hosts []string, user string, password string, defaultSchema string, queries []string, maxConcurrency uint, timeout float64) (anyError error) {
39-
concurrentHosts := make(chan bool, maxConcurrency)
40-
completedHosts := make(chan bool)
41-
41+
func QueryHosts(hosts []string, user string, password string,
42+
defaultSchema string, schemas []string, queries []string,
43+
maxConcurrency uint, timeout float64,
44+
) (anyError error) {
45+
concurrentQueries := make(chan bool, maxConcurrency)
46+
printSchema := len(schemas) > 0
47+
if len(schemas) == 0 {
48+
schemas = []string{defaultSchema}
49+
}
50+
var wg sync.WaitGroup
4251
for _, host := range hosts {
43-
go func(host string) {
44-
concurrentHosts <- true
45-
if err := queryHost(host, user, password, defaultSchema, queries, timeout); err != nil {
46-
anyError = err
47-
log.Printf("%s %s", host, err.Error())
48-
}
49-
<-concurrentHosts
50-
51-
completedHosts <- true
52-
}(host)
52+
// For each host, run all queries for the respective schema
53+
for _, schema := range schemas {
54+
wg.Add(1)
55+
go func(host, schema string) {
56+
concurrentQueries <- true
57+
defer func() { <-concurrentQueries }()
58+
defer wg.Done()
59+
if err := queryHost(host, user, password, schema, queries, timeout, printSchema); err != nil {
60+
anyError = err
61+
log.Printf("%s %s", host, err.Error())
62+
}
63+
}(host, schema)
64+
}
5365
}
66+
5467
// Barrier. Wait for all to complete
55-
for range hosts {
56-
<-completedHosts
57-
}
68+
wg.Wait()
69+
5870
return anyError
5971
}

go/text/hosts.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,13 @@ func ParseHosts(hostsList string, hostsFile string) (hosts []string, err error)
4343

4444
return hosts, err
4545
}
46+
47+
func SplitNonEmpty(s string, sep string) (result []string) {
48+
tokens := strings.Split(s, sep)
49+
for _, token := range tokens {
50+
if token != "" {
51+
result = append(result, strings.TrimSpace(token))
52+
}
53+
}
54+
return result
55+
}

go/text/hosts_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,17 @@ func TestParseHostsMulti(t *testing.T) {
4848
}
4949
}
5050
}
51+
52+
func TestSplitNonEmpty(t *testing.T) {
53+
s := "the, quick,, brown,fox ,,"
54+
splits := SplitNonEmpty(s, ",")
55+
56+
if len(splits) != 4 {
57+
t.Errorf("expected 4 tokens; got %+v", len(splits))
58+
}
59+
join := strings.Join(splits, ";")
60+
expected := "the;quick;brown;fox"
61+
if join != expected {
62+
t.Errorf("expected tokens: `%+v`. Got: `%+v`", expected, join)
63+
}
64+
}

0 commit comments

Comments
 (0)