2025-08-31 20:16:45 -07:00
|
|
|
|
package command
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"context"
|
2025-08-31 23:10:50 -07:00
|
|
|
|
"encoding/csv"
|
|
|
|
|
"encoding/json"
|
2025-08-31 20:16:45 -07:00
|
|
|
|
"fmt"
|
2025-08-31 23:10:50 -07:00
|
|
|
|
"io/ioutil"
|
2025-08-31 20:16:45 -07:00
|
|
|
|
"os"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/query/engine"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
cmdSql.Run = runSql
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cmdSql = &Command{
|
2025-09-01 01:08:11 -07:00
|
|
|
|
UsageLine: "sql [-master=localhost:9333] [-interactive] [-file=query.sql] [-output=table|json|csv] [-database=dbname] [-query=\"SQL\"]",
|
2025-08-31 23:10:50 -07:00
|
|
|
|
Short: "advanced SQL query interface for SeaweedFS MQ topics with multiple execution modes",
|
|
|
|
|
Long: `Enhanced SQL interface for SeaweedFS Message Queue topics with multiple execution modes.
|
2025-08-31 20:16:45 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
Execution Modes:
|
2025-09-01 01:08:11 -07:00
|
|
|
|
- Interactive shell (default): weed sql -interactive
|
|
|
|
|
- Single query: weed sql -query "SELECT * FROM user_events"
|
|
|
|
|
- Batch from file: weed sql -file queries.sql
|
|
|
|
|
- Context switching: weed sql -database analytics -interactive
|
2025-08-31 23:10:50 -07:00
|
|
|
|
|
|
|
|
|
Output Formats:
|
|
|
|
|
- table: ASCII table format (default for interactive)
|
|
|
|
|
- json: JSON format (default for non-interactive)
|
|
|
|
|
- csv: Comma-separated values
|
2025-08-31 20:16:45 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
Features:
|
|
|
|
|
- Full WHERE clause support (=, <, >, <=, >=, !=, LIKE, IN)
|
|
|
|
|
- Advanced pattern matching with LIKE wildcards (%, _)
|
|
|
|
|
- Multi-value filtering with IN operator
|
|
|
|
|
- Real MQ namespace and topic discovery
|
|
|
|
|
- Database context switching
|
2025-08-31 20:16:45 -07:00
|
|
|
|
|
|
|
|
|
Examples:
|
2025-09-01 01:08:11 -07:00
|
|
|
|
weed sql -interactive
|
|
|
|
|
weed sql -query "SHOW DATABASES" -output json
|
|
|
|
|
weed sql -file batch_queries.sql -output csv
|
|
|
|
|
weed sql -database analytics -query "SELECT COUNT(*) FROM metrics"
|
|
|
|
|
weed sql -master broker1:9333 -interactive
|
2025-08-31 20:16:45 -07:00
|
|
|
|
`,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
2025-09-01 01:08:11 -07:00
|
|
|
|
sqlMaster = cmdSql.Flag.String("master", "localhost:9333", "SeaweedFS master server HTTP address")
|
2025-08-31 23:10:50 -07:00
|
|
|
|
sqlInteractive = cmdSql.Flag.Bool("interactive", false, "start interactive shell mode")
|
2025-08-31 23:16:34 -07:00
|
|
|
|
sqlFile = cmdSql.Flag.String("file", "", "execute SQL queries from file")
|
|
|
|
|
sqlOutput = cmdSql.Flag.String("output", "", "output format: table, json, csv (auto-detected if not specified)")
|
|
|
|
|
sqlDatabase = cmdSql.Flag.String("database", "", "default database context")
|
|
|
|
|
sqlQuery = cmdSql.Flag.String("query", "", "execute single SQL query")
|
2025-08-31 23:10:50 -07:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// OutputFormat represents different output formatting options
|
|
|
|
|
type OutputFormat string
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
OutputTable OutputFormat = "table"
|
2025-08-31 23:16:34 -07:00
|
|
|
|
OutputJSON OutputFormat = "json"
|
2025-08-31 23:10:50 -07:00
|
|
|
|
OutputCSV OutputFormat = "csv"
|
2025-08-31 20:16:45 -07:00
|
|
|
|
)
|
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// SQLContext holds the execution context for SQL operations
|
|
|
|
|
type SQLContext struct {
|
|
|
|
|
engine *engine.SQLEngine
|
|
|
|
|
currentDatabase string
|
|
|
|
|
outputFormat OutputFormat
|
|
|
|
|
interactive bool
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
func runSql(command *Command, args []string) bool {
|
2025-09-01 01:08:11 -07:00
|
|
|
|
// Initialize SQL engine with master address for service discovery
|
|
|
|
|
sqlEngine := engine.NewSQLEngine(*sqlMaster)
|
2025-08-31 23:16:34 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Determine execution mode and output format
|
|
|
|
|
interactive := *sqlInteractive || (*sqlQuery == "" && *sqlFile == "")
|
|
|
|
|
outputFormat := determineOutputFormat(*sqlOutput, interactive)
|
2025-08-31 23:16:34 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Create SQL context
|
|
|
|
|
ctx := &SQLContext{
|
|
|
|
|
engine: sqlEngine,
|
|
|
|
|
currentDatabase: *sqlDatabase,
|
|
|
|
|
outputFormat: outputFormat,
|
|
|
|
|
interactive: interactive,
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 01:25:54 -07:00
|
|
|
|
// Set current database in SQL engine if specified via command line
|
|
|
|
|
if *sqlDatabase != "" {
|
|
|
|
|
ctx.engine.GetCatalog().SetCurrentDatabase(*sqlDatabase)
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Execute based on mode
|
|
|
|
|
switch {
|
|
|
|
|
case *sqlQuery != "":
|
|
|
|
|
// Single query mode
|
|
|
|
|
return executeSingleQuery(ctx, *sqlQuery)
|
|
|
|
|
case *sqlFile != "":
|
|
|
|
|
// Batch file mode
|
|
|
|
|
return executeFileQueries(ctx, *sqlFile)
|
|
|
|
|
default:
|
|
|
|
|
// Interactive mode
|
|
|
|
|
return runInteractiveShell(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// determineOutputFormat selects the appropriate output format
|
|
|
|
|
func determineOutputFormat(specified string, interactive bool) OutputFormat {
|
|
|
|
|
switch strings.ToLower(specified) {
|
|
|
|
|
case "table":
|
|
|
|
|
return OutputTable
|
|
|
|
|
case "json":
|
|
|
|
|
return OutputJSON
|
|
|
|
|
case "csv":
|
|
|
|
|
return OutputCSV
|
|
|
|
|
default:
|
|
|
|
|
// Auto-detect based on mode
|
|
|
|
|
if interactive {
|
|
|
|
|
return OutputTable
|
|
|
|
|
}
|
|
|
|
|
return OutputJSON
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// executeSingleQuery executes a single query and outputs the result
|
|
|
|
|
func executeSingleQuery(ctx *SQLContext, query string) bool {
|
|
|
|
|
if ctx.outputFormat != OutputTable {
|
|
|
|
|
// Suppress banner for non-interactive output
|
|
|
|
|
return executeAndDisplay(ctx, query, false)
|
|
|
|
|
}
|
2025-08-31 23:16:34 -07:00
|
|
|
|
|
2025-09-01 01:08:11 -07:00
|
|
|
|
fmt.Printf("Executing query against %s...\n", *sqlMaster)
|
2025-08-31 23:10:50 -07:00
|
|
|
|
return executeAndDisplay(ctx, query, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// executeFileQueries processes SQL queries from a file
|
|
|
|
|
func executeFileQueries(ctx *SQLContext, filename string) bool {
|
|
|
|
|
content, err := ioutil.ReadFile(filename)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Printf("Error reading file %s: %v\n", filename, err)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ctx.outputFormat == OutputTable && ctx.interactive {
|
2025-09-01 01:08:11 -07:00
|
|
|
|
fmt.Printf("Executing queries from %s against %s...\n", filename, *sqlMaster)
|
2025-08-31 23:10:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Split file content into individual queries (simple approach)
|
|
|
|
|
queries := strings.Split(string(content), ";")
|
2025-08-31 23:16:34 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
for i, query := range queries {
|
|
|
|
|
query = strings.TrimSpace(query)
|
|
|
|
|
if query == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ctx.outputFormat == OutputTable && len(queries) > 1 {
|
|
|
|
|
fmt.Printf("\n--- Query %d ---\n", i+1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !executeAndDisplay(ctx, query, ctx.outputFormat == OutputTable) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// runInteractiveShell starts the enhanced interactive shell
|
|
|
|
|
func runInteractiveShell(ctx *SQLContext) bool {
|
|
|
|
|
fmt.Println("🚀 SeaweedFS Enhanced SQL Interface")
|
2025-08-31 20:16:45 -07:00
|
|
|
|
fmt.Println("Type 'help;' for help, 'exit;' to quit")
|
2025-09-01 01:08:11 -07:00
|
|
|
|
fmt.Printf("Connected to master: %s\n", *sqlMaster)
|
2025-08-31 23:10:50 -07:00
|
|
|
|
if ctx.currentDatabase != "" {
|
|
|
|
|
fmt.Printf("Current database: %s\n", ctx.currentDatabase)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println("✨ Advanced WHERE operators supported: <=, >=, !=, LIKE, IN")
|
2025-08-31 20:16:45 -07:00
|
|
|
|
fmt.Println()
|
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Interactive shell with command history (basic implementation)
|
2025-08-31 20:16:45 -07:00
|
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
|
var queryBuffer strings.Builder
|
2025-08-31 23:10:50 -07:00
|
|
|
|
queryHistory := make([]string, 0)
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
for {
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Show prompt with current database context
|
2025-08-31 20:16:45 -07:00
|
|
|
|
if queryBuffer.Len() == 0 {
|
2025-08-31 23:10:50 -07:00
|
|
|
|
if ctx.currentDatabase != "" {
|
|
|
|
|
fmt.Printf("seaweedfs:%s> ", ctx.currentDatabase)
|
|
|
|
|
} else {
|
|
|
|
|
fmt.Print("seaweedfs> ")
|
|
|
|
|
}
|
2025-08-31 20:16:45 -07:00
|
|
|
|
} else {
|
2025-08-31 23:03:22 -07:00
|
|
|
|
fmt.Print(" -> ") // Continuation prompt
|
2025-08-31 20:16:45 -07:00
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Read line
|
|
|
|
|
if !scanner.Scan() {
|
|
|
|
|
break
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
line := strings.TrimSpace(scanner.Text())
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Handle special commands
|
|
|
|
|
if line == "exit;" || line == "quit;" || line == "\\q" {
|
2025-08-31 23:10:50 -07:00
|
|
|
|
fmt.Println("Goodbye! 👋")
|
2025-08-31 20:16:45 -07:00
|
|
|
|
break
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
if line == "help;" {
|
2025-08-31 23:10:50 -07:00
|
|
|
|
showEnhancedHelp()
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle database switching
|
2025-09-01 01:25:54 -07:00
|
|
|
|
upperLine := strings.ToUpper(line)
|
|
|
|
|
if strings.HasPrefix(upperLine, "USE ") {
|
|
|
|
|
// Extract database name preserving original case
|
|
|
|
|
parts := strings.SplitN(line, " ", 2)
|
|
|
|
|
if len(parts) >= 2 {
|
|
|
|
|
dbName := strings.TrimSpace(parts[1])
|
|
|
|
|
dbName = strings.TrimSuffix(dbName, ";")
|
|
|
|
|
ctx.currentDatabase = dbName
|
|
|
|
|
// Also update the SQL engine's catalog current database
|
|
|
|
|
ctx.engine.GetCatalog().SetCurrentDatabase(dbName)
|
|
|
|
|
fmt.Printf("Database changed to: %s\n\n", strings.ToUpper(dbName))
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-08-31 23:10:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle output format switching
|
|
|
|
|
if strings.HasPrefix(strings.ToUpper(line), "\\FORMAT ") {
|
|
|
|
|
format := strings.TrimSpace(strings.TrimPrefix(strings.ToUpper(line), "\\FORMAT "))
|
|
|
|
|
switch format {
|
|
|
|
|
case "TABLE":
|
|
|
|
|
ctx.outputFormat = OutputTable
|
|
|
|
|
fmt.Println("Output format set to: table")
|
|
|
|
|
case "JSON":
|
|
|
|
|
ctx.outputFormat = OutputJSON
|
|
|
|
|
fmt.Println("Output format set to: json")
|
|
|
|
|
case "CSV":
|
|
|
|
|
ctx.outputFormat = OutputCSV
|
|
|
|
|
fmt.Println("Output format set to: csv")
|
|
|
|
|
default:
|
|
|
|
|
fmt.Printf("Invalid format: %s. Supported: table, json, csv\n", format)
|
|
|
|
|
}
|
2025-08-31 20:16:45 -07:00
|
|
|
|
continue
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
if line == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Accumulate multi-line queries
|
|
|
|
|
queryBuffer.WriteString(line)
|
|
|
|
|
queryBuffer.WriteString(" ")
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Execute when query ends with semicolon
|
|
|
|
|
if strings.HasSuffix(line, ";") {
|
|
|
|
|
query := strings.TrimSpace(queryBuffer.String())
|
|
|
|
|
query = strings.TrimSuffix(query, ";") // Remove trailing semicolon
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Add to history
|
|
|
|
|
queryHistory = append(queryHistory, query)
|
|
|
|
|
|
|
|
|
|
// Execute query
|
|
|
|
|
executeAndDisplay(ctx, query, true)
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Reset buffer for next query
|
|
|
|
|
queryBuffer.Reset()
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// executeAndDisplay executes a query and displays the result in the specified format
|
|
|
|
|
func executeAndDisplay(ctx *SQLContext, query string, showTiming bool) bool {
|
2025-08-31 20:16:45 -07:00
|
|
|
|
startTime := time.Now()
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Execute the query
|
2025-08-31 23:10:50 -07:00
|
|
|
|
execCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
2025-08-31 20:16:45 -07:00
|
|
|
|
defer cancel()
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
result, err := ctx.engine.ExecuteSQL(execCtx, query)
|
2025-08-31 20:16:45 -07:00
|
|
|
|
if err != nil {
|
2025-08-31 23:10:50 -07:00
|
|
|
|
if ctx.outputFormat == OutputJSON {
|
|
|
|
|
errorResult := map[string]interface{}{
|
|
|
|
|
"error": err.Error(),
|
|
|
|
|
"query": query,
|
|
|
|
|
}
|
|
|
|
|
jsonBytes, _ := json.MarshalIndent(errorResult, "", " ")
|
|
|
|
|
fmt.Println(string(jsonBytes))
|
|
|
|
|
} else {
|
|
|
|
|
fmt.Printf("Error: %v\n", err)
|
|
|
|
|
}
|
|
|
|
|
return false
|
2025-08-31 20:16:45 -07:00
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
if result.Error != nil {
|
2025-08-31 23:10:50 -07:00
|
|
|
|
if ctx.outputFormat == OutputJSON {
|
|
|
|
|
errorResult := map[string]interface{}{
|
|
|
|
|
"error": result.Error.Error(),
|
|
|
|
|
"query": query,
|
|
|
|
|
}
|
|
|
|
|
jsonBytes, _ := json.MarshalIndent(errorResult, "", " ")
|
|
|
|
|
fmt.Println(string(jsonBytes))
|
|
|
|
|
} else {
|
|
|
|
|
fmt.Printf("Query Error: %v\n", result.Error)
|
|
|
|
|
}
|
|
|
|
|
return false
|
2025-08-31 20:16:45 -07:00
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Display results in the specified format
|
|
|
|
|
switch ctx.outputFormat {
|
|
|
|
|
case OutputTable:
|
|
|
|
|
displayTableResult(result)
|
|
|
|
|
case OutputJSON:
|
|
|
|
|
displayJSONResult(result)
|
|
|
|
|
case OutputCSV:
|
|
|
|
|
displayCSVResult(result)
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Show execution time for interactive/table mode
|
|
|
|
|
if showTiming && ctx.outputFormat == OutputTable {
|
|
|
|
|
elapsed := time.Since(startTime)
|
|
|
|
|
fmt.Printf("\n(%d rows in set, %.3f sec)\n\n", len(result.Rows), elapsed.Seconds())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
2025-08-31 20:16:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// displayTableResult formats and displays query results in ASCII table format
|
|
|
|
|
func displayTableResult(result *engine.QueryResult) {
|
2025-08-31 20:16:45 -07:00
|
|
|
|
if len(result.Columns) == 0 {
|
|
|
|
|
fmt.Println("Empty result set")
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Calculate column widths for formatting
|
|
|
|
|
colWidths := make([]int, len(result.Columns))
|
|
|
|
|
for i, col := range result.Columns {
|
|
|
|
|
colWidths[i] = len(col)
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Check data for wider columns
|
|
|
|
|
for _, row := range result.Rows {
|
|
|
|
|
for i, val := range row {
|
|
|
|
|
if i < len(colWidths) {
|
|
|
|
|
valStr := val.ToString()
|
|
|
|
|
if len(valStr) > colWidths[i] {
|
|
|
|
|
colWidths[i] = len(valStr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Print header separator
|
|
|
|
|
fmt.Print("+")
|
|
|
|
|
for _, width := range colWidths {
|
|
|
|
|
fmt.Print(strings.Repeat("-", width+2) + "+")
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Print column headers
|
|
|
|
|
fmt.Print("|")
|
|
|
|
|
for i, col := range result.Columns {
|
|
|
|
|
fmt.Printf(" %-*s |", colWidths[i], col)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Print separator
|
|
|
|
|
fmt.Print("+")
|
|
|
|
|
for _, width := range colWidths {
|
|
|
|
|
fmt.Print(strings.Repeat("-", width+2) + "+")
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Print data rows
|
|
|
|
|
for _, row := range result.Rows {
|
|
|
|
|
fmt.Print("|")
|
|
|
|
|
for i, val := range row {
|
|
|
|
|
if i < len(colWidths) {
|
|
|
|
|
fmt.Printf(" %-*s |", colWidths[i], val.ToString())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
|
|
|
|
}
|
2025-08-31 23:03:22 -07:00
|
|
|
|
|
2025-08-31 20:16:45 -07:00
|
|
|
|
// Print bottom separator
|
|
|
|
|
fmt.Print("+")
|
|
|
|
|
for _, width := range colWidths {
|
|
|
|
|
fmt.Print(strings.Repeat("-", width+2) + "+")
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// displayJSONResult outputs query results in JSON format
|
|
|
|
|
func displayJSONResult(result *engine.QueryResult) {
|
|
|
|
|
// Convert result to JSON-friendly format
|
|
|
|
|
jsonResult := map[string]interface{}{
|
|
|
|
|
"columns": result.Columns,
|
|
|
|
|
"rows": make([]map[string]interface{}, len(result.Rows)),
|
|
|
|
|
"count": len(result.Rows),
|
|
|
|
|
}
|
2025-08-31 20:16:45 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
// Convert rows to JSON objects
|
|
|
|
|
for i, row := range result.Rows {
|
|
|
|
|
rowObj := make(map[string]interface{})
|
|
|
|
|
for j, val := range row {
|
|
|
|
|
if j < len(result.Columns) {
|
|
|
|
|
rowObj[result.Columns[j]] = val.ToString()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
jsonResult["rows"].([]map[string]interface{})[i] = rowObj
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Marshal and print JSON
|
|
|
|
|
jsonBytes, err := json.MarshalIndent(jsonResult, "", " ")
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Printf("Error formatting JSON: %v\n", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println(string(jsonBytes))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// displayCSVResult outputs query results in CSV format
|
|
|
|
|
func displayCSVResult(result *engine.QueryResult) {
|
|
|
|
|
writer := csv.NewWriter(os.Stdout)
|
|
|
|
|
defer writer.Flush()
|
|
|
|
|
|
|
|
|
|
// Write headers
|
|
|
|
|
if err := writer.Write(result.Columns); err != nil {
|
|
|
|
|
fmt.Printf("Error writing CSV headers: %v\n", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write data rows
|
|
|
|
|
for _, row := range result.Rows {
|
|
|
|
|
csvRow := make([]string, len(row))
|
|
|
|
|
for i, val := range row {
|
|
|
|
|
csvRow[i] = val.ToString()
|
|
|
|
|
}
|
|
|
|
|
if err := writer.Write(csvRow); err != nil {
|
|
|
|
|
fmt.Printf("Error writing CSV row: %v\n", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func showEnhancedHelp() {
|
|
|
|
|
fmt.Println(`🚀 SeaweedFS Enhanced SQL Interface Help:
|
|
|
|
|
|
|
|
|
|
📊 METADATA OPERATIONS:
|
2025-08-31 20:16:45 -07:00
|
|
|
|
SHOW DATABASES; - List all MQ namespaces
|
|
|
|
|
SHOW TABLES; - List all topics in current namespace
|
|
|
|
|
SHOW TABLES FROM database; - List topics in specific namespace
|
2025-08-31 23:10:50 -07:00
|
|
|
|
DESCRIBE table_name; - Show table schema
|
|
|
|
|
|
|
|
|
|
🔍 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:
|
|
|
|
|
CREATE TABLE topic (field1 INT, field2 STRING); - Create topic
|
|
|
|
|
DROP TABLE table_name; - Delete topic
|
|
|
|
|
|
|
|
|
|
⚙️ SPECIAL COMMANDS:
|
|
|
|
|
USE database_name; - Switch database context
|
|
|
|
|
\format table|json|csv - Change output format
|
2025-08-31 20:16:45 -07:00
|
|
|
|
help; - Show this help
|
2025-08-31 23:10:50 -07:00
|
|
|
|
exit; or quit; or \q - Exit interface
|
|
|
|
|
|
|
|
|
|
🎯 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
|
2025-08-31 20:16:45 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
💡 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';
|
2025-08-31 20:16:45 -07:00
|
|
|
|
|
2025-08-31 23:10:50 -07:00
|
|
|
|
🌟 Current Status: Full WHERE clause support + Real MQ integration`)
|
2025-08-31 20:16:45 -07:00
|
|
|
|
}
|