Browse Source

show databases works

pull/7185/head
chrislu 1 month ago
parent
commit
aa883472a5
  1. 32
      weed/command/sql.go
  2. 87
      weed/query/engine/broker_client.go
  3. 40
      weed/query/engine/catalog.go
  4. 6
      weed/query/engine/engine.go

32
weed/command/sql.go

@ -19,15 +19,15 @@ func init() {
}
var cmdSql = &Command{
UsageLine: "sql [-server=localhost:8888] [-interactive] [-file=query.sql] [-output=table|json|csv] [-database=dbname] [-query=\"SQL\"]",
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.
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:
- table: ASCII table format (default for interactive)
@ -42,16 +42,16 @@ Features:
- Database context switching
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 --server broker1:8888 --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
`,
}
var (
sqlServer = cmdSql.Flag.String("server", "localhost:8888", "SeaweedFS server address")
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)")
@ -77,8 +77,8 @@ type SQLContext struct {
}
func runSql(command *Command, args []string) bool {
// Initialize SQL engine
sqlEngine := engine.NewSQLEngine(*sqlServer)
// Initialize SQL engine with master address for service discovery
sqlEngine := engine.NewSQLEngine(*sqlMaster)
// Determine execution mode and output format
interactive := *sqlInteractive || (*sqlQuery == "" && *sqlFile == "")
@ -131,7 +131,7 @@ func executeSingleQuery(ctx *SQLContext, query string) bool {
return executeAndDisplay(ctx, query, false)
}
fmt.Printf("Executing query against %s...\n", *sqlServer)
fmt.Printf("Executing query against %s...\n", *sqlMaster)
return executeAndDisplay(ctx, query, true)
}
@ -144,7 +144,7 @@ func executeFileQueries(ctx *SQLContext, filename string) bool {
}
if ctx.outputFormat == OutputTable && ctx.interactive {
fmt.Printf("Executing queries from %s against %s...\n", filename, *sqlServer)
fmt.Printf("Executing queries from %s against %s...\n", filename, *sqlMaster)
}
// Split file content into individual queries (simple approach)
@ -172,7 +172,7 @@ func executeFileQueries(ctx *SQLContext, filename string) bool {
func runInteractiveShell(ctx *SQLContext) bool {
fmt.Println("🚀 SeaweedFS Enhanced SQL Interface")
fmt.Println("Type 'help;' for help, 'exit;' to quit")
fmt.Printf("Connected to: %s\n", *sqlServer)
fmt.Printf("Connected to master: %s\n", *sqlMaster)
if ctx.currentDatabase != "" {
fmt.Printf("Current database: %s\n", ctx.currentDatabase)
}

87
weed/query/engine/broker_client.go

@ -4,11 +4,15 @@ import (
"context"
"fmt"
"io"
"strconv"
"strings"
"time"
"github.com/seaweedfs/seaweedfs/weed/cluster"
"github.com/seaweedfs/seaweedfs/weed/mq/pub_balancer"
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
"google.golang.org/grpc"
@ -17,32 +21,90 @@ import (
// BrokerClient handles communication with SeaweedFS MQ broker
// Assumptions:
// 1. Broker discovery via filer lock mechanism (same as shell commands)
// 1. Service discovery via master server (discovers filers and brokers)
// 2. gRPC connection with default timeout of 30 seconds
// 3. Topics and namespaces are managed via SeaweedMessaging service
type BrokerClient struct {
masterAddress string
filerAddress string
brokerAddress string
grpcDialOption grpc.DialOption
}
// NewBrokerClient creates a new MQ broker client
// Assumption: Filer address is used to discover broker balancer
func NewBrokerClient(filerAddress string) *BrokerClient {
// Uses master HTTP address and converts it to gRPC address for service discovery
func NewBrokerClient(masterHTTPAddress string) *BrokerClient {
// Convert HTTP address to gRPC address (typically HTTP port + 10000)
masterGRPCAddress := convertHTTPToGRPC(masterHTTPAddress)
return &BrokerClient{
filerAddress: filerAddress,
masterAddress: masterGRPCAddress,
grpcDialOption: grpc.WithTransportCredentials(insecure.NewCredentials()),
}
}
// convertHTTPToGRPC converts HTTP address to gRPC address
// Follows SeaweedFS convention: gRPC port = HTTP port + 10000
func convertHTTPToGRPC(httpAddress string) string {
if strings.Contains(httpAddress, ":") {
parts := strings.Split(httpAddress, ":")
if len(parts) == 2 {
if port, err := strconv.Atoi(parts[1]); err == nil {
return fmt.Sprintf("%s:%d", parts[0], port+10000)
}
}
}
// Fallback: return original address if conversion fails
return httpAddress
}
// discoverFiler finds a filer from the master server
func (c *BrokerClient) discoverFiler() error {
if c.filerAddress != "" {
return nil // already discovered
}
conn, err := grpc.Dial(c.masterAddress, c.grpcDialOption)
if err != nil {
return fmt.Errorf("failed to connect to master at %s: %v", c.masterAddress, err)
}
defer conn.Close()
client := master_pb.NewSeaweedClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := client.ListClusterNodes(ctx, &master_pb.ListClusterNodesRequest{
ClientType: cluster.FilerType,
})
if err != nil {
return fmt.Errorf("failed to list filers from master: %v", err)
}
if len(resp.ClusterNodes) == 0 {
return fmt.Errorf("no filers found in cluster")
}
// Use the first available filer and convert HTTP address to gRPC
filerHTTPAddress := resp.ClusterNodes[0].Address
c.filerAddress = convertHTTPToGRPC(filerHTTPAddress)
return nil
}
// findBrokerBalancer discovers the broker balancer using filer lock mechanism
// Assumption: Uses same pattern as existing shell commands
// First discovers filer from master, then uses filer to find broker balancer
func (c *BrokerClient) findBrokerBalancer() error {
if c.brokerAddress != "" {
return nil // already found
}
conn, err := grpc.Dial(c.filerAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
// First discover filer from master
if err := c.discoverFiler(); err != nil {
return fmt.Errorf("failed to discover filer: %v", err)
}
conn, err := grpc.Dial(c.filerAddress, c.grpcDialOption)
if err != nil {
return fmt.Errorf("failed to connect to filer at %s: %v", c.filerAddress, err)
}
@ -65,10 +127,11 @@ func (c *BrokerClient) findBrokerBalancer() error {
}
// GetFilerClient creates a filer client for accessing MQ data files
// This resolves TODO: Get real filerClient from broker connection
// Discovers filer from master if not already known
func (c *BrokerClient) GetFilerClient() (filer_pb.FilerClient, error) {
if c.filerAddress == "" {
return nil, fmt.Errorf("filer address not specified")
// Ensure filer is discovered
if err := c.discoverFiler(); err != nil {
return nil, fmt.Errorf("failed to discover filer: %v", err)
}
return &filerClientImpl{
@ -113,8 +176,7 @@ func (c *BrokerClient) ListNamespaces(ctx context.Context) ([]string, error) {
// Get filer client to list directories under /topics
filerClient, err := c.GetFilerClient()
if err != nil {
// Return empty list if filer unavailable - no fallback sample data
return []string{}, nil
return []string{}, fmt.Errorf("failed to get filer client: %v", err)
}
var namespaces []string
@ -148,8 +210,7 @@ func (c *BrokerClient) ListNamespaces(ctx context.Context) ([]string, error) {
})
if err != nil {
// Return empty list if directory listing fails - no fallback sample data
return []string{}, nil
return []string{}, fmt.Errorf("failed to list namespaces from /topics: %v", err)
}
// Return actual namespaces found (may be empty if no topics exist)

40
weed/query/engine/catalog.go

@ -43,26 +43,26 @@ type DatabaseInfo struct {
// 2. Schema evolution maintains backward compatibility
// 3. Primary key is implicitly the message timestamp/offset
type TableInfo struct {
Name string
Namespace string
Schema *schema.Schema
Columns []ColumnInfo
RevisionId uint32
Name string
Namespace string
Schema *schema.Schema
Columns []ColumnInfo
RevisionId uint32
}
// ColumnInfo represents a SQL column (MQ schema field)
type ColumnInfo struct {
Name string
Type string // SQL type representation
Nullable bool // Assumption: MQ fields are nullable by default
Type string // SQL type representation
Nullable bool // Assumption: MQ fields are nullable by default
}
// NewSchemaCatalog creates a new schema catalog
// Assumption: Catalog starts empty and is populated on-demand
func NewSchemaCatalog(filerAddress string) *SchemaCatalog {
// Uses master address for service discovery of filers and brokers
func NewSchemaCatalog(masterAddress string) *SchemaCatalog {
return &SchemaCatalog{
databases: make(map[string]*DatabaseInfo),
brokerClient: NewBrokerClient(filerAddress),
brokerClient: NewBrokerClient(masterAddress),
}
}
@ -78,16 +78,15 @@ func (c *SchemaCatalog) ListDatabases() []string {
namespaces, err := c.brokerClient.ListNamespaces(ctx)
if err != nil {
// Silently handle broker connection errors
// Fallback to cached databases if broker unavailable
databases := make([]string, 0, len(c.databases))
for name := range c.databases {
databases = append(databases, name)
}
// If no cached data, return sample data for testing
if len(databases) == 0 {
return []string{"default", "analytics", "logs"}
}
// Return empty list if no cached data (no more sample data)
return databases
}
@ -108,17 +107,8 @@ func (c *SchemaCatalog) ListTables(database string) ([]string, error) {
// Fallback to cached data if broker unavailable
db, exists := c.databases[database]
if !exists {
// Return sample data if no cache
switch database {
case "default":
return []string{"user_events", "system_logs"}, nil
case "analytics":
return []string{"page_views", "click_events"}, nil
case "logs":
return []string{"error_logs", "access_logs"}, nil
default:
return nil, fmt.Errorf("database '%s' not found", database)
}
// Return empty list if database not found (no more sample data)
return []string{}, nil
}
tables := make([]string, 0, len(db.Tables))

6
weed/query/engine/engine.go

@ -34,10 +34,10 @@ type QueryResult struct {
}
// NewSQLEngine creates a new SQL execution engine
// Assumption: Schema catalog is initialized with current MQ state
func NewSQLEngine(filerAddress string) *SQLEngine {
// Uses master address for service discovery and initialization
func NewSQLEngine(masterAddress string) *SQLEngine {
return &SQLEngine{
catalog: NewSchemaCatalog(filerAddress),
catalog: NewSchemaCatalog(masterAddress),
}
}

Loading…
Cancel
Save