@ -1,4 +1,4 @@
package com mand
package mai n
import (
import (
"context"
"context"
@ -13,28 +13,24 @@ import (
"github.com/peterh/liner"
"github.com/peterh/liner"
"github.com/seaweedfs/seaweedfs/weed/query/engine"
"github.com/seaweedfs/seaweedfs/weed/query/engine"
flag "github.com/seaweedfs/seaweedfs/weed/util/fla9"
"github.com/seaweedfs/seaweedfs/weed/util/grace"
"github.com/seaweedfs/seaweedfs/weed/util/grace"
"github.com/seaweedfs/seaweedfs/weed/util/sqlutil"
"github.com/seaweedfs/seaweedfs/weed/util/sqlutil"
)
)
func init ( ) {
cmdSql . Run = runSql
}
const usageLine = "weed-sql [-master=localhost:9333] [-interactive] [-file=query.sql] [-output=table|json|csv] [-database=dbname] [-query=\"SQL\"]"
var cmdSql = & Command {
UsageLine : "sql [-master=localhost:9333] [-interactive] [-file=query.sql] [-output=table|json|csv] [-database=dbname] [-query=\"SQL\"]" ,
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 .
const longHelp = ` Enhanced SQL interface for SeaweedFS Message Queue topics with multiple execution modes .
Execution Modes :
Execution Modes :
- 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
- 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
Output Formats :
Output Formats :
- table : ASCII table format ( default for interactive )
- table : ASCII table format ( default for interactive )
- json : JSON format ( default for non - interactive )
- json : JSON format ( default for non - interactive )
- csv : Comma - separated values
- csv : Comma - separated values
Features :
Features :
@ -45,24 +41,23 @@ Features:
- Database context switching
- Database context switching
Examples :
Examples :
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
` ,
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
`
type Options struct {
Master string
Interactive bool
File string
Output string
Database string
Query string
}
}
var (
sqlMaster = cmdSql . Flag . String ( "master" , "localhost:9333" , "SeaweedFS master server HTTP address" )
sqlInteractive = cmdSql . Flag . Bool ( "interactive" , false , "start interactive shell mode" )
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" )
)
// OutputFormat represents different output formatting options
// OutputFormat represents different output formatting options.
type OutputFormat string
type OutputFormat string
const (
const (
@ -71,50 +66,82 @@ const (
OutputCSV OutputFormat = "csv"
OutputCSV OutputFormat = "csv"
)
)
// SQLContext holds the execution context for SQL operations
// SQLContext holds the execution context for SQL operations.
type SQLContext struct {
type SQLContext struct {
engine * engine . SQLEngine
engine * engine . SQLEngine
currentDatabase string
currentDatabase string
outputFormat OutputFormat
outputFormat OutputFormat
interactive bool
interactive bool
master string
}
// Run executes the weed-sql CLI.
func Run ( args [ ] string ) int {
fs := flag . NewFlagSet ( "weed-sql" , flag . ContinueOnError )
usageWriter := io . Writer ( os . Stderr )
fs . SetOutput ( usageWriter )
var opts Options
fs . StringVar ( & opts . Master , "master" , "localhost:9333" , "SeaweedFS master server HTTP address" )
fs . BoolVar ( & opts . Interactive , "interactive" , false , "start interactive shell mode" )
fs . StringVar ( & opts . File , "file" , "" , "execute SQL queries from file" )
fs . StringVar ( & opts . Output , "output" , "" , "output format: table, json, csv (auto-detected if not specified)" )
fs . StringVar ( & opts . Database , "database" , "" , "default database context" )
fs . StringVar ( & opts . Query , "query" , "" , "execute single SQL query" )
fs . Usage = func ( ) {
fmt . Fprintf ( usageWriter , "Usage: %s\n\n%s\n" , usageLine , longHelp )
fmt . Fprintln ( usageWriter , "Default Parameters:" )
fs . PrintDefaults ( )
}
if err := fs . Parse ( args ) ; err != nil {
return 2
}
if ! runWithOptions ( & opts ) {
return 1
}
return 0
}
}
func runSql ( command * Command , args [ ] string ) bool {
// Initialize SQL engine with master address for service discovery
sqlEngine := engine . NewSQLEngine ( * sqlMaster )
func runWithOptions ( opts * Options ) bool {
// Initialize SQL engine with master address for service discovery.
sqlEngine := engine . NewSQLEngine ( opts . Master )
// Determine execution mode and output format
interactive := * sqlInteractive || ( * sqlQuery == "" && * sqlFile == "" )
outputFormat := determineOutputFormat ( * sqlOutput , interactive )
// Determine execution mode and output format.
interactive := opts . Interactive || ( opts . Query == "" && opts . File == "" )
outputFormat := determineOutputFormat ( opts . Output , interactive )
// Create SQL context
// Create SQL context.
ctx := & SQLContext {
ctx := & SQLContext {
engine : sqlEngine ,
engine : sqlEngine ,
currentDatabase : * sqlDatabase ,
currentDatabase : opts . Database ,
outputFormat : outputFormat ,
outputFormat : outputFormat ,
interactive : interactive ,
interactive : interactive ,
master : opts . Master ,
}
}
// Set current database in SQL engine if specified via command line
if * sqlDatabase != "" {
ctx . engine . GetCatalog ( ) . SetCurrentDatabase ( * sqlDatabase )
// Set current database in SQL engine if specified via command line.
if opts . Database != "" {
ctx . engine . GetCatalog ( ) . SetCurrentDatabase ( opts . Database )
}
}
// Execute based on mode
// Execute based on mode.
switch {
switch {
case * sqlQuery != "" :
// Single query mode
return executeSingleQuery ( ctx , * sqlQuery )
case * sqlFile != "" :
// Batch file mode
return executeFileQueries ( ctx , * sqlFile )
case opts . Query != "" :
// Single query mode.
return executeSingleQuery ( ctx , opts . Query )
case opts . File != "" :
// Batch file mode.
return executeFileQueries ( ctx , opts . File )
default :
default :
// Interactive mode
// Interactive mode.
return runInteractiveShell ( ctx )
return runInteractiveShell ( ctx )
}
}
}
}
// determineOutputFormat selects the appropriate output format
// determineOutputFormat selects the appropriate output format.
func determineOutputFormat ( specified string , interactive bool ) OutputFormat {
func determineOutputFormat ( specified string , interactive bool ) OutputFormat {
switch strings . ToLower ( specified ) {
switch strings . ToLower ( specified ) {
case "table" :
case "table" :
@ -124,7 +151,7 @@ func determineOutputFormat(specified string, interactive bool) OutputFormat {
case "csv" :
case "csv" :
return OutputCSV
return OutputCSV
default :
default :
// Auto-detect based on mode
// Auto-detect based on mode.
if interactive {
if interactive {
return OutputTable
return OutputTable
}
}
@ -132,18 +159,18 @@ func determineOutputFormat(specified string, interactive bool) OutputFormat {
}
}
}
}
// executeSingleQuery executes a single query and outputs the result
// executeSingleQuery executes a single query and outputs the result.
func executeSingleQuery ( ctx * SQLContext , query string ) bool {
func executeSingleQuery ( ctx * SQLContext , query string ) bool {
if ctx . outputFormat != OutputTable {
if ctx . outputFormat != OutputTable {
// Suppress banner for non-interactive output
// Suppress banner for non-interactive output.
return executeAndDisplay ( ctx , query , false )
return executeAndDisplay ( ctx , query , false )
}
}
fmt . Printf ( "Executing query against %s...\n" , * sqlM aster)
fmt . Printf ( "Executing query against %s...\n" , ctx . m aster)
return executeAndDisplay ( ctx , query , true )
return executeAndDisplay ( ctx , query , true )
}
}
// executeFileQueries processes SQL queries from a file
// executeFileQueries processes SQL queries from a file.
func executeFileQueries ( ctx * SQLContext , filename string ) bool {
func executeFileQueries ( ctx * SQLContext , filename string ) bool {
content , err := os . ReadFile ( filename )
content , err := os . ReadFile ( filename )
if err != nil {
if err != nil {
@ -152,10 +179,10 @@ func executeFileQueries(ctx *SQLContext, filename string) bool {
}
}
if ctx . outputFormat == OutputTable && ctx . interactive {
if ctx . outputFormat == OutputTable && ctx . interactive {
fmt . Printf ( "Executing queries from %s against %s...\n" , filename , * sqlM aster)
fmt . Printf ( "Executing queries from %s against %s...\n" , filename , ctx . m aster)
}
}
// Split file content into individual queries (robust approach)
// Split file content into individual queries (robust approach).
queries := sqlutil . SplitStatements ( string ( content ) )
queries := sqlutil . SplitStatements ( string ( content ) )
for i , query := range queries {
for i , query := range queries {
@ -176,11 +203,11 @@ func executeFileQueries(ctx *SQLContext, filename string) bool {
return true
return true
}
}
// runInteractiveShell starts the enhanced interactive shell with readline support
// runInteractiveShell starts the enhanced interactive shell with readline support.
func runInteractiveShell ( ctx * SQLContext ) bool {
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 . Println ( "Type 'help;' for help, 'exit;' to quit" )
fmt . Printf ( "Connected to master: %s\n" , * sqlM aster)
fmt . Printf ( "Connected to master: %s\n" , ctx . m aster)
if ctx . currentDatabase != "" {
if ctx . currentDatabase != "" {
fmt . Printf ( "Current database: %s\n" , ctx . currentDatabase )
fmt . Printf ( "Current database: %s\n" , ctx . currentDatabase )
}
}
@ -188,24 +215,24 @@ func runInteractiveShell(ctx *SQLContext) bool {
fmt . Println ( "Use up/down arrows for command history" )
fmt . Println ( "Use up/down arrows for command history" )
fmt . Println ( )
fmt . Println ( )
// Initialize liner for readline functionality
// Initialize liner for readline functionality.
line := liner . NewLiner ( )
line := liner . NewLiner ( )
defer line . Close ( )
defer line . Close ( )
// Handle Ctrl+C gracefully
// Handle Ctrl+C gracefully.
line . SetCtrlCAborts ( true )
line . SetCtrlCAborts ( true )
grace . OnInterrupt ( func ( ) {
grace . OnInterrupt ( func ( ) {
line . Close ( )
line . Close ( )
} )
} )
// Load command history
// Load command history.
historyPath := path . Join ( os . TempDir ( ) , "weed-sql-history" )
historyPath := path . Join ( os . TempDir ( ) , "weed-sql-history" )
if f , err := os . Open ( historyPath ) ; err == nil {
if f , err := os . Open ( historyPath ) ; err == nil {
line . ReadHistory ( f )
line . ReadHistory ( f )
f . Close ( )
f . Close ( )
}
}
// Save history on exit
// Save history on exit.
defer func ( ) {
defer func ( ) {
if f , err := os . Create ( historyPath ) ; err == nil {
if f , err := os . Create ( historyPath ) ; err == nil {
line . WriteHistory ( f )
line . WriteHistory ( f )
@ -216,7 +243,7 @@ func runInteractiveShell(ctx *SQLContext) bool {
var queryBuffer strings . Builder
var queryBuffer strings . Builder
for {
for {
// Show prompt with current database context
// Show prompt with current database context.
var prompt string
var prompt string
if queryBuffer . Len ( ) == 0 {
if queryBuffer . Len ( ) == 0 {
if ctx . currentDatabase != "" {
if ctx . currentDatabase != "" {
@ -225,10 +252,10 @@ func runInteractiveShell(ctx *SQLContext) bool {
prompt = "seaweedfs> "
prompt = "seaweedfs> "
}
}
} else {
} else {
prompt = " -> " // Continuation prompt
prompt = " -> " // Continuation prompt.
}
}
// Read line with readline support
// Read line with readline support.
input , err := line . Prompt ( prompt )
input , err := line . Prompt ( prompt )
if err != nil {
if err != nil {
if err == liner . ErrPromptAborted {
if err == liner . ErrPromptAborted {
@ -244,30 +271,30 @@ func runInteractiveShell(ctx *SQLContext) bool {
lineStr := strings . TrimSpace ( input )
lineStr := strings . TrimSpace ( input )
// Handle empty lines
// Handle empty lines.
if lineStr == "" {
if lineStr == "" {
continue
continue
}
}
// Accumulate lines in query buffer
// Accumulate lines in query buffer.
if queryBuffer . Len ( ) > 0 {
if queryBuffer . Len ( ) > 0 {
queryBuffer . WriteString ( " " )
queryBuffer . WriteString ( " " )
}
}
queryBuffer . WriteString ( lineStr )
queryBuffer . WriteString ( lineStr )
// Check if we have a complete statement (ends with semicolon or special command)
// Check if we have a complete statement (ends with semicolon or special command).
fullQuery := strings . TrimSpace ( queryBuffer . String ( ) )
fullQuery := strings . TrimSpace ( queryBuffer . String ( ) )
isComplete := strings . HasSuffix ( lineStr , ";" ) ||
isComplete := strings . HasSuffix ( lineStr , ";" ) ||
isSpecialCommand ( fullQuery )
isSpecialCommand ( fullQuery )
if ! isComplete {
if ! isComplete {
continue // Continue reading more lines
continue // Continue reading more lines.
}
}
// Add completed command to history
// Add completed command to history.
line . AppendHistory ( fullQuery )
line . AppendHistory ( fullQuery )
// Handle special commands (with or without semicolon)
// Handle special commands (with or without semicolon).
cleanQuery := strings . TrimSuffix ( fullQuery , ";" )
cleanQuery := strings . TrimSuffix ( fullQuery , ";" )
cleanQuery = strings . TrimSpace ( cleanQuery )
cleanQuery = strings . TrimSpace ( cleanQuery )
@ -282,19 +309,19 @@ func runInteractiveShell(ctx *SQLContext) bool {
continue
continue
}
}
// Handle database switching - use proper SQL parser instead of manual parsing
// Handle database switching - use proper SQL parser instead of manual parsing.
if strings . HasPrefix ( strings . ToUpper ( cleanQuery ) , "USE " ) {
if strings . HasPrefix ( strings . ToUpper ( cleanQuery ) , "USE " ) {
// Execute USE statement through the SQL engine for proper parsing
// Execute USE statement through the SQL engine for proper parsing.
result , err := ctx . engine . ExecuteSQL ( context . Background ( ) , cleanQuery )
result , err := ctx . engine . ExecuteSQL ( context . Background ( ) , cleanQuery )
if err != nil {
if err != nil {
fmt . Printf ( "Error: %v\n\n" , err )
fmt . Printf ( "Error: %v\n\n" , err )
} else if result . Error != nil {
} else if result . Error != nil {
fmt . Printf ( "Error: %v\n\n" , result . Error )
fmt . Printf ( "Error: %v\n\n" , result . Error )
} else {
} else {
// Extract the database name from the result message for CLI context
// Extract the database name from the result message for CLI context.
if len ( result . Rows ) > 0 && len ( result . Rows [ 0 ] ) > 0 {
if len ( result . Rows ) > 0 && len ( result . Rows [ 0 ] ) > 0 {
message := result . Rows [ 0 ] [ 0 ] . ToString ( )
message := result . Rows [ 0 ] [ 0 ] . ToString ( )
// Extract database name from "Database changed to: dbname"
// Extract database name from "Database changed to: dbname".
if strings . HasPrefix ( message , "Database changed to: " ) {
if strings . HasPrefix ( message , "Database changed to: " ) {
ctx . currentDatabase = strings . TrimPrefix ( message , "Database changed to: " )
ctx . currentDatabase = strings . TrimPrefix ( message , "Database changed to: " )
}
}
@ -305,7 +332,7 @@ func runInteractiveShell(ctx *SQLContext) bool {
continue
continue
}
}
// Handle output format switching
// Handle output format switching.
if strings . HasPrefix ( strings . ToUpper ( cleanQuery ) , "\\FORMAT " ) {
if strings . HasPrefix ( strings . ToUpper ( cleanQuery ) , "\\FORMAT " ) {
format := strings . TrimSpace ( strings . TrimPrefix ( strings . ToUpper ( cleanQuery ) , "\\FORMAT " ) )
format := strings . TrimSpace ( strings . TrimPrefix ( strings . ToUpper ( cleanQuery ) , "\\FORMAT " ) )
switch format {
switch format {
@ -325,22 +352,22 @@ func runInteractiveShell(ctx *SQLContext) bool {
continue
continue
}
}
// Execute SQL query (without semicolon)
// Execute SQL query (without semicolon).
executeAndDisplay ( ctx , cleanQuery , true )
executeAndDisplay ( ctx , cleanQuery , true )
// Reset buffer for next query
// Reset buffer for next query.
queryBuffer . Reset ( )
queryBuffer . Reset ( )
}
}
return true
return true
}
}
// isSpecialCommand checks if a command is a special command that doesn't require semicolon
// isSpecialCommand checks if a command is a special command that doesn't require semicolon.
func isSpecialCommand ( query string ) bool {
func isSpecialCommand ( query string ) bool {
cleanQuery := strings . TrimSuffix ( strings . TrimSpace ( query ) , ";" )
cleanQuery := strings . TrimSuffix ( strings . TrimSpace ( query ) , ";" )
cleanQuery = strings . ToLower ( cleanQuery )
cleanQuery = strings . ToLower ( cleanQuery )
// Special commands that work with or without semicolon
// Special commands that work with or without semicolon.
specialCommands := [ ] string {
specialCommands := [ ] string {
"exit" , "quit" , "\\q" , "help" ,
"exit" , "quit" , "\\q" , "help" ,
}
}
@ -351,7 +378,7 @@ func isSpecialCommand(query string) bool {
}
}
}
}
// Commands that are exactly specific commands (not just prefixes)
// Commands that are exactly specific commands (not just prefixes).
parts := strings . Fields ( strings . ToUpper ( cleanQuery ) )
parts := strings . Fields ( strings . ToUpper ( cleanQuery ) )
if len ( parts ) == 0 {
if len ( parts ) == 0 {
return false
return false
@ -360,11 +387,11 @@ func isSpecialCommand(query string) bool {
strings . HasPrefix ( strings . ToUpper ( cleanQuery ) , "\\FORMAT " )
strings . HasPrefix ( strings . ToUpper ( cleanQuery ) , "\\FORMAT " )
}
}
// executeAndDisplay executes a query and displays the result in the specified format
// executeAndDisplay executes a query and displays the result in the specified format.
func executeAndDisplay ( ctx * SQLContext , query string , showTiming bool ) bool {
func executeAndDisplay ( ctx * SQLContext , query string , showTiming bool ) bool {
startTime := time . Now ( )
startTime := time . Now ( )
// Execute the query
// Execute the query.
execCtx , cancel := context . WithTimeout ( context . Background ( ) , 30 * time . Second )
execCtx , cancel := context . WithTimeout ( context . Background ( ) , 30 * time . Second )
defer cancel ( )
defer cancel ( )
@ -397,7 +424,7 @@ func executeAndDisplay(ctx *SQLContext, query string, showTiming bool) bool {
return false
return false
}
}
// Display results in the specified format
// Display results in the specified format.
switch ctx . outputFormat {
switch ctx . outputFormat {
case OutputTable :
case OutputTable :
displayTableResult ( result )
displayTableResult ( result )
@ -407,8 +434,8 @@ func executeAndDisplay(ctx *SQLContext, query string, showTiming bool) bool {
displayCSVResult ( result )
displayCSVResult ( result )
}
}
// Show execution time for interactive/table mode
// Only show timing if there are columns or if result is truly empty
// Show execution time for interactive/table mode.
// Only show timing if there are columns or if result is truly empty.
if showTiming && ctx . outputFormat == OutputTable && ( len ( result . Columns ) > 0 || len ( result . Rows ) == 0 ) {
if showTiming && ctx . outputFormat == OutputTable && ( len ( result . Columns ) > 0 || len ( result . Rows ) == 0 ) {
elapsed := time . Since ( startTime )
elapsed := time . Since ( startTime )
fmt . Printf ( "\n(%d rows in set, %.3f sec)\n\n" , len ( result . Rows ) , elapsed . Seconds ( ) )
fmt . Printf ( "\n(%d rows in set, %.3f sec)\n\n" , len ( result . Rows ) , elapsed . Seconds ( ) )
@ -417,20 +444,20 @@ func executeAndDisplay(ctx *SQLContext, query string, showTiming bool) bool {
return true
return true
}
}
// displayTableResult formats and displays query results in ASCII table format
// displayTableResult formats and displays query results in ASCII table format.
func displayTableResult ( result * engine . QueryResult ) {
func displayTableResult ( result * engine . QueryResult ) {
if len ( result . Columns ) == 0 {
if len ( result . Columns ) == 0 {
fmt . Println ( "Empty result set" )
fmt . Println ( "Empty result set" )
return
return
}
}
// Calculate column widths for formatting
// Calculate column widths for formatting.
colWidths := make ( [ ] int , len ( result . Columns ) )
colWidths := make ( [ ] int , len ( result . Columns ) )
for i , col := range result . Columns {
for i , col := range result . Columns {
colWidths [ i ] = len ( col )
colWidths [ i ] = len ( col )
}
}
// Check data for wider columns
// Check data for wider columns.
for _ , row := range result . Rows {
for _ , row := range result . Rows {
for i , val := range row {
for i , val := range row {
if i < len ( colWidths ) {
if i < len ( colWidths ) {
@ -442,28 +469,28 @@ func displayTableResult(result *engine.QueryResult) {
}
}
}
}
// Print header separator
// Print header separator.
fmt . Print ( "+" )
fmt . Print ( "+" )
for _ , width := range colWidths {
for _ , width := range colWidths {
fmt . Print ( strings . Repeat ( "-" , width + 2 ) + "+" )
fmt . Print ( strings . Repeat ( "-" , width + 2 ) + "+" )
}
}
fmt . Println ( )
fmt . Println ( )
// Print column headers
// Print column headers.
fmt . Print ( "|" )
fmt . Print ( "|" )
for i , col := range result . Columns {
for i , col := range result . Columns {
fmt . Printf ( " %-*s |" , colWidths [ i ] , col )
fmt . Printf ( " %-*s |" , colWidths [ i ] , col )
}
}
fmt . Println ( )
fmt . Println ( )
// Print separator
// Print separator.
fmt . Print ( "+" )
fmt . Print ( "+" )
for _ , width := range colWidths {
for _ , width := range colWidths {
fmt . Print ( strings . Repeat ( "-" , width + 2 ) + "+" )
fmt . Print ( strings . Repeat ( "-" , width + 2 ) + "+" )
}
}
fmt . Println ( )
fmt . Println ( )
// Print data rows
// Print data rows.
for _ , row := range result . Rows {
for _ , row := range result . Rows {
fmt . Print ( "|" )
fmt . Print ( "|" )
for i , val := range row {
for i , val := range row {
@ -474,7 +501,7 @@ func displayTableResult(result *engine.QueryResult) {
fmt . Println ( )
fmt . Println ( )
}
}
// Print bottom separator
// Print bottom separator.
fmt . Print ( "+" )
fmt . Print ( "+" )
for _ , width := range colWidths {
for _ , width := range colWidths {
fmt . Print ( strings . Repeat ( "-" , width + 2 ) + "+" )
fmt . Print ( strings . Repeat ( "-" , width + 2 ) + "+" )
@ -482,16 +509,16 @@ func displayTableResult(result *engine.QueryResult) {
fmt . Println ( )
fmt . Println ( )
}
}
// displayJSONResult outputs query results in JSON format
// displayJSONResult outputs query results in JSON format.
func displayJSONResult ( result * engine . QueryResult ) {
func displayJSONResult ( result * engine . QueryResult ) {
// Convert result to JSON-friendly format
// Convert result to JSON-friendly format.
jsonResult := map [ string ] interface { } {
jsonResult := map [ string ] interface { } {
"columns" : result . Columns ,
"columns" : result . Columns ,
"rows" : make ( [ ] map [ string ] interface { } , len ( result . Rows ) ) ,
"rows" : make ( [ ] map [ string ] interface { } , len ( result . Rows ) ) ,
"count" : len ( result . Rows ) ,
"count" : len ( result . Rows ) ,
}
}
// Convert rows to JSON objects
// Convert rows to JSON objects.
for i , row := range result . Rows {
for i , row := range result . Rows {
rowObj := make ( map [ string ] interface { } )
rowObj := make ( map [ string ] interface { } )
for j , val := range row {
for j , val := range row {
@ -502,7 +529,7 @@ func displayJSONResult(result *engine.QueryResult) {
jsonResult [ "rows" ] . ( [ ] map [ string ] interface { } ) [ i ] = rowObj
jsonResult [ "rows" ] . ( [ ] map [ string ] interface { } ) [ i ] = rowObj
}
}
// Marshal and print JSON
// Marshal and print JSON.
jsonBytes , err := json . MarshalIndent ( jsonResult , "" , " " )
jsonBytes , err := json . MarshalIndent ( jsonResult , "" , " " )
if err != nil {
if err != nil {
fmt . Printf ( "Error formatting JSON: %v\n" , err )
fmt . Printf ( "Error formatting JSON: %v\n" , err )
@ -512,11 +539,11 @@ func displayJSONResult(result *engine.QueryResult) {
fmt . Println ( string ( jsonBytes ) )
fmt . Println ( string ( jsonBytes ) )
}
}
// displayCSVResult outputs query results in CSV format
// displayCSVResult outputs query results in CSV format.
func displayCSVResult ( result * engine . QueryResult ) {
func displayCSVResult ( result * engine . QueryResult ) {
// Handle execution plan results specially to avoid CSV quoting issues
// Handle execution plan results specially to avoid CSV quoting issues.
if len ( result . Columns ) == 1 && result . Columns [ 0 ] == "Query Execution Plan" {
if len ( result . Columns ) == 1 && result . Columns [ 0 ] == "Query Execution Plan" {
// For execution plans, output directly without CSV encoding to avoid quotes
// For execution plans, output directly without CSV encoding to avoid quotes.
for _ , row := range result . Rows {
for _ , row := range result . Rows {
if len ( row ) > 0 {
if len ( row ) > 0 {
fmt . Println ( row [ 0 ] . ToString ( ) )
fmt . Println ( row [ 0 ] . ToString ( ) )
@ -525,17 +552,17 @@ func displayCSVResult(result *engine.QueryResult) {
return
return
}
}
// Standard CSV output for regular query results
// Standard CSV output for regular query results.
writer := csv . NewWriter ( os . Stdout )
writer := csv . NewWriter ( os . Stdout )
defer writer . Flush ( )
defer writer . Flush ( )
// Write headers
// Write headers.
if err := writer . Write ( result . Columns ) ; err != nil {
if err := writer . Write ( result . Columns ) ; err != nil {
fmt . Printf ( "Error writing CSV headers: %v\n" , err )
fmt . Printf ( "Error writing CSV headers: %v\n" , err )
return
return
}
}
// Write data rows
// Write data rows.
for _ , row := range result . Rows {
for _ , row := range result . Rows {
csvRow := make ( [ ] string , len ( row ) )
csvRow := make ( [ ] string , len ( row ) )
for i , val := range row {
for i , val := range row {
@ -553,7 +580,7 @@ func showEnhancedHelp() {
METADATA OPERATIONS :
METADATA OPERATIONS :
SHOW DATABASES ; - List all MQ namespaces
SHOW DATABASES ; - List all MQ namespaces
SHOW TABLES ; - List all topics in current namespace
SHOW TABLES ; - List all topics in current namespace
SHOW TABLES FROM database ; - List topics in specific namespace
SHOW TABLES FROM database ; - List topics in specific namespace
DESCRIBE table_name ; - Show table schema
DESCRIBE table_name ; - Show table schema
@ -581,7 +608,7 @@ SPECIAL COMMANDS:
EXTENDED WHERE OPERATORS :
EXTENDED WHERE OPERATORS :
= , < , > , <= , >= - Comparison operators
= , < , > , <= , >= - Comparison operators
!= , < > - Not equal operators
!= , < > - Not equal operators
LIKE ' pattern % ' - Pattern matching ( % = any chars , _ = single char )
LIKE ' pattern % ' - Pattern matching ( % = any chars , _ = single char )
IN ( value1 , value2 , ... ) - Multi - value matching
IN ( value1 , value2 , ... ) - Multi - value matching
AND , OR - Logical operators
AND , OR - Logical operators