2016-06-02 13:53:30 +02:00

220 lines
4.4 KiB
Go

// Copyright 2014 The ql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Command ql is a utility to explore a database, prototype a schema or test
// drive a query, etc.
//
// Installation:
//
// $ go get github.com/cznic/ql/ql
//
// Usage:
//
// ql [-db name] [-schema regexp] [-tables regexp] [-fld] statement_list
//
// Options:
//
// -db name Name of the database to use. Defaults to "ql.db".
// If the DB file does not exists it is created automatically.
//
// -schema re If re != "" show the CREATE statements of matching tables and exit.
//
// -tables re If re != "" show the matching table names and exit.
//
// -fld First row of a query result set will show field names.
//
// statement_list QL statements to execute.
// If no non flag arguments are present, ql reads from stdin.
// The list is wrapped into an automatic transaction.
//
// -t Report and measure time to execute, including creating/opening and closing the DB.
//
// Example:
//
// $ ql 'create table t (i int, s string)'
// $ ql << EOF
// > insert into t values
// > (1, "a"),
// > (2, "b"),
// > (3, "c"),
// > EOF
// $ ql 'select * from t'
// 3, "c"
// 2, "b"
// 1, "a"
// $ ql -fld 'select * from t where i != 2 order by s'
// "i", "s"
// 1, "a"
// 3, "c"
// $
package main
import (
"bufio"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
"sort"
"strings"
"time"
"github.com/cznic/ql"
)
func str(data []interface{}) string {
a := make([]string, len(data))
for i, v := range data {
switch x := v.(type) {
case string:
a[i] = fmt.Sprintf("%q", x)
default:
a[i] = fmt.Sprint(x)
}
}
return strings.Join(a, ", ")
}
func main() {
if err := do(); err != nil {
log.Fatal(err)
}
}
func do() (err error) {
oDB := flag.String("db", "ql.db", "The DB file to open. It'll be created if missing.")
oFlds := flag.Bool("fld", false, "Show recordset's field names.")
oSchema := flag.String("schema", "", "If non empty, show the CREATE statements of matching tables and exit.")
oTables := flag.String("tables", "", "If non empty, list matching table names and exit.")
oTime := flag.Bool("t", false, "Measure and report time to execute the statement(s) including DB create/open/close.")
flag.Parse()
t0 := time.Now()
if *oTime {
defer func() {
fmt.Fprintf(os.Stderr, "%s\n", time.Since(t0))
}()
}
db, err := ql.OpenFile(*oDB, &ql.Options{CanCreate: true})
if err != nil {
return err
}
defer func() {
ec := db.Close()
switch {
case ec != nil && err != nil:
log.Println(ec)
case ec != nil:
err = ec
}
}()
if pat := *oSchema; pat != "" {
re, err := regexp.Compile(pat)
if err != nil {
return err
}
nfo, err := db.Info()
if err != nil {
return err
}
r := []string{}
for _, ti := range nfo.Tables {
if !re.MatchString(ti.Name) {
continue
}
a := []string{}
for _, ci := range ti.Columns {
a = append(a, fmt.Sprintf("%s %s", ci.Name, ci.Type))
}
r = append(r, fmt.Sprintf("CREATE TABLE %s (%s);", ti.Name, strings.Join(a, ", ")))
}
sort.Strings(r)
if len(r) != 0 {
fmt.Println(strings.Join(r, "\n"))
}
return nil
}
if pat := *oTables; pat != "" {
re, err := regexp.Compile(pat)
if err != nil {
return err
}
nfo, err := db.Info()
if err != nil {
return err
}
r := []string{}
for _, ti := range nfo.Tables {
if !re.MatchString(ti.Name) {
continue
}
r = append(r, ti.Name)
}
sort.Strings(r)
if len(r) != 0 {
fmt.Println(strings.Join(r, "\n"))
}
return nil
}
var src string
switch n := flag.NArg(); n {
case 0:
b, err := ioutil.ReadAll(bufio.NewReader(os.Stdin))
if err != nil {
return err
}
src = string(b)
default:
a := make([]string, n)
for i := range a {
a[i] = flag.Arg(i)
}
src = strings.Join(a, " ")
}
src = "BEGIN TRANSACTION; " + src + "; COMMIT;"
l, err := ql.Compile(src)
if err != nil {
log.Println(src)
return err
}
rs, i, err := db.Execute(ql.NewRWCtx(), l)
if err != nil {
a := strings.Split(strings.TrimSpace(fmt.Sprint(l)), "\n")
return fmt.Errorf("%v: %s", err, a[i])
}
if len(rs) == 0 {
return
}
switch {
case l.IsExplainStmt():
return rs[len(rs)-1].Do(*oFlds, func(data []interface{}) (bool, error) {
fmt.Println(data[0])
return true, nil
})
default:
return rs[len(rs)-1].Do(*oFlds, func(data []interface{}) (bool, error) {
fmt.Println(str(data))
return true, nil
})
}
}