github.com/runner-mei/ql@v1.1.0/ql/main.go (about)

     1  // Copyright 2014 The ql Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Command ql is a utility to explore a database, prototype a schema or test
     6  // drive a query, etc.
     7  //
     8  // Installation:
     9  //
    10  //	$ go get github.com/cznic/ql/ql
    11  //
    12  // Usage:
    13  //
    14  //	ql [-db name] [-schema regexp] [-tables regexp] [-fld] statement_list
    15  //
    16  // Options:
    17  //
    18  //	-db name	Name of the database to use. Defaults to "ql.db".
    19  //			If the DB file does not exists it is created automatically.
    20  //
    21  //	-schema re	If re != "" show the CREATE statements of matching tables and exit.
    22  //
    23  //	-tables re	If re != "" show the matching table names and exit.
    24  //
    25  //	-fld		First row of a query result set will show field names.
    26  //
    27  //	statement_list	QL statements to execute.
    28  //			If no non flag arguments are present, ql reads from stdin.
    29  //			The list is wrapped into an automatic transaction.
    30  //
    31  //	-t		Report and measure time to execute, including creating/opening and closing the DB.
    32  //
    33  // Example:
    34  //
    35  //	$ ql 'create table t (i int, s string)'
    36  //	$ ql << EOF
    37  //	> insert into t values
    38  //	> (1, "a"),
    39  //	> (2, "b"),
    40  //	> (3, "c"),
    41  //	> EOF
    42  //	$ ql 'select * from t'
    43  //	3, "c"
    44  //	2, "b"
    45  //	1, "a"
    46  //	$ ql -fld 'select * from t where i != 2 order by s'
    47  //	"i", "s"
    48  //	1, "a"
    49  //	3, "c"
    50  //	$
    51  package main
    52  
    53  import (
    54  	"bufio"
    55  	"flag"
    56  	"fmt"
    57  	"io/ioutil"
    58  	"log"
    59  	"os"
    60  	"regexp"
    61  	"sort"
    62  	"strings"
    63  	"time"
    64  
    65  	"github.com/cznic/ql"
    66  )
    67  
    68  func str(data []interface{}) string {
    69  	a := make([]string, len(data))
    70  	for i, v := range data {
    71  		switch x := v.(type) {
    72  		case string:
    73  			a[i] = fmt.Sprintf("%q", x)
    74  		default:
    75  			a[i] = fmt.Sprint(x)
    76  		}
    77  	}
    78  	return strings.Join(a, ", ")
    79  }
    80  
    81  func main() {
    82  	if err := do(); err != nil {
    83  		log.Fatal(err)
    84  	}
    85  }
    86  
    87  func do() (err error) {
    88  	oDB := flag.String("db", "ql.db", "The DB file to open. It'll be created if missing.")
    89  	oFlds := flag.Bool("fld", false, "Show recordset's field names.")
    90  	oSchema := flag.String("schema", "", "If non empty, show the CREATE statements of matching tables and exit.")
    91  	oTables := flag.String("tables", "", "If non empty, list matching table names and exit.")
    92  	oTime := flag.Bool("t", false, "Measure and report time to execute the statement(s) including DB create/open/close.")
    93  	flag.Parse()
    94  
    95  	t0 := time.Now()
    96  	if *oTime {
    97  		defer func() {
    98  			fmt.Fprintf(os.Stderr, "%s\n", time.Since(t0))
    99  		}()
   100  	}
   101  
   102  	db, err := ql.OpenFile(*oDB, &ql.Options{CanCreate: true})
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	defer func() {
   108  		ec := db.Close()
   109  		switch {
   110  		case ec != nil && err != nil:
   111  			log.Println(ec)
   112  		case ec != nil:
   113  			err = ec
   114  		}
   115  	}()
   116  
   117  	if pat := *oSchema; pat != "" {
   118  		re, err := regexp.Compile(pat)
   119  		if err != nil {
   120  			return err
   121  		}
   122  
   123  		nfo, err := db.Info()
   124  		if err != nil {
   125  			return err
   126  		}
   127  
   128  		r := []string{}
   129  		for _, ti := range nfo.Tables {
   130  			if !re.MatchString(ti.Name) {
   131  				continue
   132  			}
   133  
   134  			a := []string{}
   135  			for _, ci := range ti.Columns {
   136  				a = append(a, fmt.Sprintf("%s %s", ci.Name, ci.Type))
   137  			}
   138  			r = append(r, fmt.Sprintf("CREATE TABLE %s (%s);", ti.Name, strings.Join(a, ", ")))
   139  		}
   140  		sort.Strings(r)
   141  		if len(r) != 0 {
   142  			fmt.Println(strings.Join(r, "\n"))
   143  		}
   144  		return nil
   145  	}
   146  
   147  	if pat := *oTables; pat != "" {
   148  		re, err := regexp.Compile(pat)
   149  		if err != nil {
   150  			return err
   151  		}
   152  
   153  		nfo, err := db.Info()
   154  		if err != nil {
   155  			return err
   156  		}
   157  
   158  		r := []string{}
   159  		for _, ti := range nfo.Tables {
   160  			if !re.MatchString(ti.Name) {
   161  				continue
   162  			}
   163  
   164  			r = append(r, ti.Name)
   165  		}
   166  		sort.Strings(r)
   167  		if len(r) != 0 {
   168  			fmt.Println(strings.Join(r, "\n"))
   169  		}
   170  		return nil
   171  	}
   172  
   173  	var src string
   174  	switch n := flag.NArg(); n {
   175  	case 0:
   176  		b, err := ioutil.ReadAll(bufio.NewReader(os.Stdin))
   177  		if err != nil {
   178  			return err
   179  		}
   180  
   181  		src = string(b)
   182  	default:
   183  		a := make([]string, n)
   184  		for i := range a {
   185  			a[i] = flag.Arg(i)
   186  		}
   187  		src = strings.Join(a, " ")
   188  	}
   189  
   190  	src = "BEGIN TRANSACTION; " + src + "; COMMIT;"
   191  	l, err := ql.Compile(src)
   192  	if err != nil {
   193  		log.Println(src)
   194  		return err
   195  	}
   196  
   197  	rs, i, err := db.Execute(ql.NewRWCtx(), l)
   198  	if err != nil {
   199  		a := strings.Split(strings.TrimSpace(fmt.Sprint(l)), "\n")
   200  		return fmt.Errorf("%v: %s", err, a[i])
   201  	}
   202  
   203  	if len(rs) == 0 {
   204  		return
   205  	}
   206  
   207  	switch {
   208  	case l.IsExplainStmt():
   209  		return rs[len(rs)-1].Do(*oFlds, func(data []interface{}) (bool, error) {
   210  			fmt.Println(data[0])
   211  			return true, nil
   212  		})
   213  	default:
   214  		return rs[len(rs)-1].Do(*oFlds, func(data []interface{}) (bool, error) {
   215  			fmt.Println(str(data))
   216  			return true, nil
   217  		})
   218  	}
   219  }