From 58d877c3f5bc14bf808c1b78481ac4aa2fc6118a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Llor=C3=A0?= Date: Wed, 2 Mar 2016 16:49:36 -0800 Subject: [PATCH] Rudimentary REPL implementation to allow you to run BQL statements. --- storage/memory/memory.go | 2 +- tools/vcli/bw/common/common.go | 2 + tools/vcli/bw/io/io.go | 9 ++ tools/vcli/bw/repl/repl.go | 161 +++++++++++++++++++++++++++++++ tools/vcli/bw/run/run.go | 11 +-- tools/vcli/bw/version/version.go | 5 - 6 files changed, 174 insertions(+), 16 deletions(-) create mode 100644 tools/vcli/bw/repl/repl.go diff --git a/storage/memory/memory.go b/storage/memory/memory.go index 58634ed0..8b844e95 100644 --- a/storage/memory/memory.go +++ b/storage/memory/memory.go @@ -50,7 +50,7 @@ func NewStore() storage.Store { // Name returns the ID of the backend being used. func (s *memoryStore) Name(ctx context.Context) string { - return "MEMORY_STORE" + return "VOLATILE" } // Version returns the version of the driver implementation. diff --git a/tools/vcli/bw/common/common.go b/tools/vcli/bw/common/common.go index 0c5a953b..c2289863 100644 --- a/tools/vcli/bw/common/common.go +++ b/tools/vcli/bw/common/common.go @@ -28,6 +28,7 @@ import ( "github.com/google/badwolf/storage" "github.com/google/badwolf/tools/vcli/bw/assert" "github.com/google/badwolf/tools/vcli/bw/command" + "github.com/google/badwolf/tools/vcli/bw/repl" "github.com/google/badwolf/tools/vcli/bw/run" "github.com/google/badwolf/tools/vcli/bw/version" "github.com/google/badwolf/triple/literal" @@ -98,6 +99,7 @@ func InitializeCommands(driver storage.Store, chanSize int) []*command.Command { return []*command.Command{ assert.New(driver, literal.DefaultBuilder(), chanSize), run.New(driver, chanSize), + repl.New(driver, chanSize), version.New(), } } diff --git a/tools/vcli/bw/io/io.go b/tools/vcli/bw/io/io.go index 6349b811..318bb8e5 100644 --- a/tools/vcli/bw/io/io.go +++ b/tools/vcli/bw/io/io.go @@ -22,6 +22,15 @@ import ( "strings" ) +// GetStatementsFromFile returns the statements found in the provided file. +func GetStatementsFromFile(path string) ([]string, error) { + stms, err := ReadLines(path) + if err != nil { + return nil, err + } + return stms, nil +} + // ReadLines from a file into a string array. func ReadLines(path string) ([]string, error) { f, err := os.Open(path) diff --git a/tools/vcli/bw/repl/repl.go b/tools/vcli/bw/repl/repl.go new file mode 100644 index 00000000..db6111e0 --- /dev/null +++ b/tools/vcli/bw/repl/repl.go @@ -0,0 +1,161 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package repl contains the implementation of the command that prints the +// BQL version. +package repl + +import ( + "bufio" + "fmt" + "os" + "strings" + "time" + + "golang.org/x/net/context" + + "github.com/google/badwolf/bql/grammar" + "github.com/google/badwolf/bql/planner" + "github.com/google/badwolf/bql/semantic" + "github.com/google/badwolf/bql/table" + "github.com/google/badwolf/bql/version" + "github.com/google/badwolf/storage" + "github.com/google/badwolf/tools/vcli/bw/command" + "github.com/google/badwolf/tools/vcli/bw/io" +) + +const prompt = "bql> " + +// New create the version command. +func New(driver storage.Store, chanSize int) *command.Command { + return &command.Command{ + Run: func(ctx context.Context, args []string) int { + REPL(driver, os.Stdin, simpleReadLine, chanSize) + return 0 + }, + UsageLine: "bql", + Short: "starts a REPL to run BQL statements.", + Long: "Starts a REPL from the command line to accept BQL statements. Type quit; to leave the REPL.", + } +} + +type readLiner func(*os.File) <-chan string + +// simpleReadline reads a line from the provided file. This does not support +// any advanced terminal functionalities. +// +// TODO(xllora): Replace simple reader for function that supports advanced +// teminal input. +func simpleReadLine(f *os.File) <-chan string { + c := make(chan string) + go func() { + defer close(c) + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + c <- strings.TrimSpace(scanner.Text()) + } + }() + return c +} + +// REPL starts a read-evaluation-print-loop to run BQL commands. +func REPL(driver storage.Store, input *os.File, rl readLiner, chanSize int) int { + ctx := context.Background() + fmt.Printf("Welcome to BadWolf vCli (%d.%d.%d-%s)\n", version.Major, version.Minor, version.Patch, version.Release) + fmt.Printf("Using driver %q. Type quit; to exit\n", driver.Name(ctx)) + fmt.Printf("Session started at %v\n\n", time.Now()) + defer func() { + fmt.Printf("\nThanks for all those BQL queries!\n\n") + }() + fmt.Print(prompt) + for l := range rl(input) { + if strings.HasPrefix(l, "quit") { + break + } + if strings.HasPrefix(l, "help") { + printHelp() + fmt.Print(prompt) + continue + } + if strings.HasPrefix(l, "run") { + path, cmds, err := loadData(ctx, driver, chanSize, l) + if err != nil { + fmt.Printf("[ERROR] %s\n\n", err) + } else { + fmt.Printf("Loaded %q and run %d BQL commands successfully\n\n", path, cmds) + } + fmt.Print(prompt) + continue + } + table, err := runBQL(ctx, l, driver, chanSize) + if err != nil { + fmt.Printf("[ERROR] %s\n\n", err) + } else { + fmt.Println(table.String()) + } + fmt.Print(prompt) + } + return 0 +} + +// printHelp prints help for the console commands. +func printHelp() { + fmt.Println("help - prints help for the bw console.") + fmt.Println("run - quits the console.") + fmt.Println("quit - quits the console.") + fmt.Println() +} + +// loadData loads all the triples in the file into the current driver. +func loadData(ctx context.Context, driver storage.Store, chanSize int, line string) (string, int, error) { + ss := strings.Split(strings.TrimSpace(line), " ") + if len(ss) != 2 { + return "", 0, fmt.Errorf("wrong syntax: run ") + } + path := ss[1] + lines, err := io.GetStatementsFromFile(path) + if err != nil { + return "", 0, fmt.Errorf("failed to read file %q with error %v on\n", path, err) + } + for idx, stm := range lines { + fmt.Printf("Processing statement (%d/%d)\n", idx+1, len(lines)) + _, err := runBQL(ctx, stm, driver, chanSize) + if err != nil { + return "", 0, fmt.Errorf("%v on\n%s\n", err, stm) + } + } + fmt.Println() + return path, len(lines), nil +} + +// runBQL attemps to excecute the provided query against the given store. +func runBQL(ctx context.Context, bql string, s storage.Store, chanSize int) (*table.Table, error) { + p, err := grammar.NewParser(grammar.SemanticBQL()) + if err != nil { + return nil, fmt.Errorf("failed to initilize a valid BQL parser") + } + stm := &semantic.Statement{} + if err := p.Parse(grammar.NewLLk(bql, 1), stm); err != nil { + return nil, fmt.Errorf("failed to parse BQL statement with error %v", err) + } + pln, err := planner.New(ctx, s, stm, chanSize) + if err != nil { + return nil, fmt.Errorf("should have not failed to create a plan using memory.DefaultStorage for statement %v with error %v", stm, err) + } + res, err := pln.Excecute(ctx) + if err != nil { + return nil, fmt.Errorf("planner.Execute: failed to execute insert plan with error %v", err) + } + return res, nil +} diff --git a/tools/vcli/bw/run/run.go b/tools/vcli/bw/run/run.go index cf61dc84..5ece00dd 100644 --- a/tools/vcli/bw/run/run.go +++ b/tools/vcli/bw/run/run.go @@ -56,7 +56,7 @@ func runCommand(ctx context.Context, cmd *command.Command, args []string, store return 2 } file := strings.TrimSpace(args[len(args)-1]) - lines, err := getStatementsFromFile(file) + lines, err := io.GetStatementsFromFile(file) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read file %s\n\n\t%v\n\n", file, err) return 2 @@ -98,12 +98,3 @@ func runBQL(ctx context.Context, bql string, s storage.Store, chanSize int) (*ta } return res, nil } - -// getStatementsFromFile returns the statements found in the provided file. -func getStatementsFromFile(path string) ([]string, error) { - stms, err := io.ReadLines(path) - if err != nil { - return nil, err - } - return stms, nil -} diff --git a/tools/vcli/bw/version/version.go b/tools/vcli/bw/version/version.go index 7512738a..9bec38dd 100644 --- a/tools/vcli/bw/version/version.go +++ b/tools/vcli/bw/version/version.go @@ -26,11 +26,6 @@ import ( "github.com/google/badwolf/tools/vcli/bw/command" ) -var stage = "alpha" -var major = 0 -var minor = 1 -var patch = "dev" - // New create the version command. func New() *command.Command { return &command.Command{