|
|
@ -1,17 +1,20 @@ |
|
|
|
package command |
|
|
|
|
|
|
|
import ( |
|
|
|
"bufio" |
|
|
|
"context" |
|
|
|
"encoding/csv" |
|
|
|
"encoding/json" |
|
|
|
"fmt" |
|
|
|
"io" |
|
|
|
"io/ioutil" |
|
|
|
"os" |
|
|
|
"path" |
|
|
|
"strings" |
|
|
|
"time" |
|
|
|
|
|
|
|
"github.com/peterh/liner" |
|
|
|
"github.com/seaweedfs/seaweedfs/weed/query/engine" |
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util/grace" |
|
|
|
) |
|
|
|
|
|
|
|
func init() { |
|
|
@ -173,57 +176,90 @@ func executeFileQueries(ctx *SQLContext, filename string) bool { |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
// runInteractiveShell starts the enhanced interactive shell
|
|
|
|
// runInteractiveShell starts the enhanced interactive shell with readline support
|
|
|
|
func runInteractiveShell(ctx *SQLContext) bool { |
|
|
|
fmt.Println("🚀 SeaweedFS Enhanced SQL Interface") |
|
|
|
fmt.Println("SeaweedFS Enhanced SQL Interface") |
|
|
|
fmt.Println("Type 'help;' for help, 'exit;' to quit") |
|
|
|
fmt.Printf("Connected to master: %s\n", *sqlMaster) |
|
|
|
if ctx.currentDatabase != "" { |
|
|
|
fmt.Printf("Current database: %s\n", ctx.currentDatabase) |
|
|
|
} |
|
|
|
fmt.Println("✨ Advanced WHERE operators supported: <=, >=, !=, LIKE, IN") |
|
|
|
fmt.Println("Advanced WHERE operators supported: <=, >=, !=, LIKE, IN") |
|
|
|
fmt.Println("Use up/down arrows for command history") |
|
|
|
fmt.Println() |
|
|
|
|
|
|
|
// Interactive shell with command history (basic implementation)
|
|
|
|
scanner := bufio.NewScanner(os.Stdin) |
|
|
|
// Initialize liner for readline functionality
|
|
|
|
line := liner.NewLiner() |
|
|
|
defer line.Close() |
|
|
|
|
|
|
|
// Handle Ctrl+C gracefully
|
|
|
|
line.SetCtrlCAborts(true) |
|
|
|
grace.OnInterrupt(func() { |
|
|
|
line.Close() |
|
|
|
}) |
|
|
|
|
|
|
|
// Load command history
|
|
|
|
historyPath := path.Join(os.TempDir(), "weed-sql-history") |
|
|
|
if f, err := os.Open(historyPath); err == nil { |
|
|
|
line.ReadHistory(f) |
|
|
|
f.Close() |
|
|
|
} |
|
|
|
|
|
|
|
// Save history on exit
|
|
|
|
defer func() { |
|
|
|
if f, err := os.Create(historyPath); err == nil { |
|
|
|
line.WriteHistory(f) |
|
|
|
f.Close() |
|
|
|
} |
|
|
|
}() |
|
|
|
|
|
|
|
var queryBuffer strings.Builder |
|
|
|
queryHistory := make([]string, 0) |
|
|
|
|
|
|
|
for { |
|
|
|
// Show prompt with current database context
|
|
|
|
var prompt string |
|
|
|
if queryBuffer.Len() == 0 { |
|
|
|
if ctx.currentDatabase != "" { |
|
|
|
fmt.Printf("seaweedfs:%s> ", ctx.currentDatabase) |
|
|
|
prompt = fmt.Sprintf("seaweedfs:%s> ", ctx.currentDatabase) |
|
|
|
} else { |
|
|
|
fmt.Print("seaweedfs> ") |
|
|
|
prompt = "seaweedfs> " |
|
|
|
} |
|
|
|
} else { |
|
|
|
fmt.Print(" -> ") // Continuation prompt
|
|
|
|
prompt = " -> " // Continuation prompt
|
|
|
|
} |
|
|
|
|
|
|
|
// Read line
|
|
|
|
if !scanner.Scan() { |
|
|
|
// Read line with readline support
|
|
|
|
input, err := line.Prompt(prompt) |
|
|
|
if err != nil { |
|
|
|
if err == liner.ErrPromptAborted { |
|
|
|
fmt.Println("Query cancelled") |
|
|
|
queryBuffer.Reset() |
|
|
|
continue |
|
|
|
} |
|
|
|
if err != io.EOF { |
|
|
|
fmt.Printf("Input error: %v\n", err) |
|
|
|
} |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
line := strings.TrimSpace(scanner.Text()) |
|
|
|
lineStr := strings.TrimSpace(input) |
|
|
|
|
|
|
|
// Handle special commands
|
|
|
|
if line == "exit;" || line == "quit;" || line == "\\q" { |
|
|
|
fmt.Println("Goodbye! 👋") |
|
|
|
if lineStr == "exit;" || lineStr == "quit;" || lineStr == "\\q" { |
|
|
|
fmt.Println("Goodbye!") |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
if line == "help;" { |
|
|
|
if lineStr == "help;" { |
|
|
|
showEnhancedHelp() |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
// Handle database switching
|
|
|
|
upperLine := strings.ToUpper(line) |
|
|
|
upperLine := strings.ToUpper(lineStr) |
|
|
|
if strings.HasPrefix(upperLine, "USE ") { |
|
|
|
// Extract database name preserving original case
|
|
|
|
parts := strings.SplitN(line, " ", 2) |
|
|
|
parts := strings.SplitN(lineStr, " ", 2) |
|
|
|
if len(parts) >= 2 { |
|
|
|
dbName := strings.TrimSpace(parts[1]) |
|
|
|
dbName = strings.TrimSuffix(dbName, ";") |
|
|
@ -236,8 +272,8 @@ func runInteractiveShell(ctx *SQLContext) bool { |
|
|
|
} |
|
|
|
|
|
|
|
// Handle output format switching
|
|
|
|
if strings.HasPrefix(strings.ToUpper(line), "\\FORMAT ") { |
|
|
|
format := strings.TrimSpace(strings.TrimPrefix(strings.ToUpper(line), "\\FORMAT ")) |
|
|
|
if strings.HasPrefix(strings.ToUpper(lineStr), "\\FORMAT ") { |
|
|
|
format := strings.TrimSpace(strings.TrimPrefix(strings.ToUpper(lineStr), "\\FORMAT ")) |
|
|
|
switch format { |
|
|
|
case "TABLE": |
|
|
|
ctx.outputFormat = OutputTable |
|
|
@ -254,23 +290,23 @@ func runInteractiveShell(ctx *SQLContext) bool { |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
if line == "" { |
|
|
|
if lineStr == "" { |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
// Accumulate multi-line queries
|
|
|
|
queryBuffer.WriteString(line) |
|
|
|
queryBuffer.WriteString(lineStr) |
|
|
|
queryBuffer.WriteString(" ") |
|
|
|
|
|
|
|
// Execute when query ends with semicolon
|
|
|
|
if strings.HasSuffix(line, ";") { |
|
|
|
query := strings.TrimSpace(queryBuffer.String()) |
|
|
|
query = strings.TrimSuffix(query, ";") // Remove trailing semicolon
|
|
|
|
if strings.HasSuffix(lineStr, ";") { |
|
|
|
fullQuery := strings.TrimSpace(queryBuffer.String()) |
|
|
|
query := strings.TrimSuffix(fullQuery, ";") // Remove trailing semicolon for execution
|
|
|
|
|
|
|
|
// Add to history
|
|
|
|
queryHistory = append(queryHistory, query) |
|
|
|
// Add to history with semicolon (as user actually typed it)
|
|
|
|
line.AppendHistory(fullQuery) |
|
|
|
|
|
|
|
// Execute query
|
|
|
|
// Execute query (without semicolon)
|
|
|
|
executeAndDisplay(ctx, query, true) |
|
|
|
|
|
|
|
// Reset buffer for next query
|
|
|
@ -457,42 +493,42 @@ func displayCSVResult(result *engine.QueryResult) { |
|
|
|
} |
|
|
|
|
|
|
|
func showEnhancedHelp() { |
|
|
|
fmt.Println(`🚀 SeaweedFS Enhanced SQL Interface Help: |
|
|
|
fmt.Println(`SeaweedFS Enhanced SQL Interface Help: |
|
|
|
|
|
|
|
📊 METADATA OPERATIONS: |
|
|
|
METADATA OPERATIONS: |
|
|
|
SHOW DATABASES; - List all MQ namespaces |
|
|
|
SHOW TABLES; - List all topics in current namespace |
|
|
|
SHOW TABLES FROM database; - List topics in specific namespace |
|
|
|
DESCRIBE table_name; - Show table schema |
|
|
|
|
|
|
|
🔍 ADVANCED QUERYING: |
|
|
|
ADVANCED QUERYING: |
|
|
|
SELECT * FROM table_name; - Query all data |
|
|
|
SELECT col1, col2 FROM table WHERE ...; - Column projection |
|
|
|
SELECT * FROM table WHERE id <= 100; - Range filtering |
|
|
|
SELECT * FROM table WHERE name LIKE 'admin%'; - Pattern matching |
|
|
|
SELECT * FROM table WHERE status IN ('active', 'pending'); - Multi-value |
|
|
|
|
|
|
|
📝 DDL OPERATIONS: |
|
|
|
DDL OPERATIONS: |
|
|
|
CREATE TABLE topic (field1 INT, field2 STRING); - Create topic |
|
|
|
DROP TABLE table_name; - Delete topic |
|
|
|
|
|
|
|
⚙️ SPECIAL COMMANDS: |
|
|
|
SPECIAL COMMANDS: |
|
|
|
USE database_name; - Switch database context |
|
|
|
\format table|json|csv - Change output format |
|
|
|
help; - Show this help |
|
|
|
exit; or quit; or \q - Exit interface |
|
|
|
|
|
|
|
🎯 EXTENDED WHERE OPERATORS: |
|
|
|
EXTENDED WHERE OPERATORS: |
|
|
|
=, <, >, <=, >= - Comparison operators |
|
|
|
!=, <> - Not equal operators |
|
|
|
LIKE 'pattern%' - Pattern matching (% = any chars, _ = single char) |
|
|
|
IN (value1, value2, ...) - Multi-value matching |
|
|
|
AND, OR - Logical operators |
|
|
|
|
|
|
|
💡 EXAMPLES: |
|
|
|
EXAMPLES: |
|
|
|
SELECT * FROM user_events WHERE user_id >= 10 AND status != 'deleted'; |
|
|
|
SELECT username FROM users WHERE email LIKE '%@company.com'; |
|
|
|
SELECT * FROM logs WHERE level IN ('error', 'warning') AND timestamp >= '2023-01-01'; |
|
|
|
|
|
|
|
🌟 Current Status: Full WHERE clause support + Real MQ integration`) |
|
|
|
Current Status: Full WHERE clause support + Real MQ integration`) |
|
|
|
} |