Move SQL engine and PostgreSQL server to their own binaries (#8417)
* Drop SQL engine and PostgreSQL server * Split SQL tooling into weed-db and weed-sql * move * fix building
This commit is contained in:
5
Makefile
5
Makefile
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: test admin-generate admin-build admin-clean admin-dev admin-run admin-test admin-fmt admin-help
|
.PHONY: test admin-generate admin-build admin-clean admin-dev admin-run admin-test admin-fmt admin-help weed-commands
|
||||||
|
|
||||||
BINARY = weed
|
BINARY = weed
|
||||||
ADMIN_DIR = weed/admin
|
ADMIN_DIR = weed/admin
|
||||||
@@ -11,6 +11,9 @@ all: install
|
|||||||
install: admin-generate
|
install: admin-generate
|
||||||
cd weed; go install
|
cd weed; go install
|
||||||
|
|
||||||
|
weed-commands:
|
||||||
|
cd weed && $(MAKE) weed-db weed-sql
|
||||||
|
|
||||||
warp_install:
|
warp_install:
|
||||||
go install github.com/minio/warp@v0.7.6
|
go install github.com/minio/warp@v0.7.6
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package command
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -13,43 +14,12 @@ import (
|
|||||||
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/server/postgres"
|
"github.com/seaweedfs/seaweedfs/weed/server/postgres"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||||
|
flag "github.com/seaweedfs/seaweedfs/weed/util/fla9"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const usageLine = "weed-db -port=5432 -master=<master_server>"
|
||||||
dbOptions DBOptions
|
|
||||||
)
|
|
||||||
|
|
||||||
type DBOptions struct {
|
const longHelp = `Start a PostgreSQL wire protocol compatible database server that provides SQL query access to SeaweedFS.
|
||||||
host *string
|
|
||||||
port *int
|
|
||||||
masterAddr *string
|
|
||||||
authMethod *string
|
|
||||||
users *string
|
|
||||||
database *string
|
|
||||||
maxConns *int
|
|
||||||
idleTimeout *string
|
|
||||||
tlsCert *string
|
|
||||||
tlsKey *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cmdDB.Run = runDB // break init cycle
|
|
||||||
dbOptions.host = cmdDB.Flag.String("host", "localhost", "Database server host")
|
|
||||||
dbOptions.port = cmdDB.Flag.Int("port", 5432, "Database server port")
|
|
||||||
dbOptions.masterAddr = cmdDB.Flag.String("master", "localhost:9333", "SeaweedFS master server address")
|
|
||||||
dbOptions.authMethod = cmdDB.Flag.String("auth", "trust", "Authentication method: trust, password, md5")
|
|
||||||
dbOptions.users = cmdDB.Flag.String("users", "", "User credentials for auth (JSON format '{\"user1\":\"pass1\",\"user2\":\"pass2\"}' or file '@/path/to/users.json')")
|
|
||||||
dbOptions.database = cmdDB.Flag.String("database", "default", "Default database name")
|
|
||||||
dbOptions.maxConns = cmdDB.Flag.Int("max-connections", 100, "Maximum concurrent connections per server")
|
|
||||||
dbOptions.idleTimeout = cmdDB.Flag.String("idle-timeout", "1h", "Connection idle timeout")
|
|
||||||
dbOptions.tlsCert = cmdDB.Flag.String("tls-cert", "", "TLS certificate file path")
|
|
||||||
dbOptions.tlsKey = cmdDB.Flag.String("tls-key", "", "TLS private key file path")
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdDB = &Command{
|
|
||||||
UsageLine: "db -port=5432 -master=<master_server>",
|
|
||||||
Short: "start a PostgreSQL-compatible database server for SQL queries",
|
|
||||||
Long: `Start a PostgreSQL wire protocol compatible database server that provides SQL query access to SeaweedFS.
|
|
||||||
|
|
||||||
This database server enables any PostgreSQL client, tool, or application to connect to SeaweedFS
|
This database server enables any PostgreSQL client, tool, or application to connect to SeaweedFS
|
||||||
and execute SQL queries against MQ topics. It implements the PostgreSQL wire protocol for maximum
|
and execute SQL queries against MQ topics. It implements the PostgreSQL wire protocol for maximum
|
||||||
@@ -58,25 +28,25 @@ compatibility with the existing PostgreSQL ecosystem.
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
# Start database server on default port 5432
|
# Start database server on default port 5432
|
||||||
weed db
|
weed-db
|
||||||
|
|
||||||
# Start with MD5 authentication using JSON format (recommended)
|
# Start with MD5 authentication using JSON format (recommended)
|
||||||
weed db -auth=md5 -users='{"admin":"secret","readonly":"view123"}'
|
weed-db -auth=md5 -users='{"admin":"secret","readonly":"view123"}'
|
||||||
|
|
||||||
# Start with complex passwords using JSON format
|
# Start with complex passwords using JSON format
|
||||||
weed db -auth=md5 -users='{"admin":"pass;with;semicolons","user":"password:with:colons"}'
|
weed-db -auth=md5 -users='{"admin":"pass;with;semicolons","user":"password:with:colons"}'
|
||||||
|
|
||||||
# Start with credentials from JSON file (most secure)
|
# Start with credentials from JSON file (most secure)
|
||||||
weed db -auth=md5 -users="@/etc/seaweedfs/users.json"
|
weed-db -auth=md5 -users="@/etc/seaweedfs/users.json"
|
||||||
|
|
||||||
# Start with custom port and master
|
# Start with custom port and master
|
||||||
weed db -port=5433 -master=master1:9333
|
weed-db -port=5433 -master=master1:9333
|
||||||
|
|
||||||
# Allow connections from any host
|
# Allow connections from any host
|
||||||
weed db -host=0.0.0.0 -port=5432
|
weed-db -host=0.0.0.0 -port=5432
|
||||||
|
|
||||||
# Start with TLS encryption
|
# Start with TLS encryption
|
||||||
weed db -tls-cert=server.crt -tls-key=server.key
|
weed-db -tls-cert=server.crt -tls-key=server.key
|
||||||
|
|
||||||
Client Connection Examples:
|
Client Connection Examples:
|
||||||
|
|
||||||
@@ -95,7 +65,7 @@ Programming Language Examples:
|
|||||||
# Python (psycopg2)
|
# Python (psycopg2)
|
||||||
import psycopg2
|
import psycopg2
|
||||||
conn = psycopg2.connect(
|
conn = psycopg2.connect(
|
||||||
host="localhost", port=5432,
|
host="localhost", port=5432,
|
||||||
user="seaweedfs", database="default"
|
user="seaweedfs", database="default"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -116,7 +86,7 @@ Supported SQL Operations:
|
|||||||
- SELECT queries on MQ topics
|
- SELECT queries on MQ topics
|
||||||
- DESCRIBE/DESC table_name commands
|
- DESCRIBE/DESC table_name commands
|
||||||
- EXPLAIN query execution plans
|
- EXPLAIN query execution plans
|
||||||
- SHOW DATABASES/TABLES commands
|
- SHOW DATABASES/TABLES commands
|
||||||
- Aggregation functions (COUNT, SUM, AVG, MIN, MAX)
|
- Aggregation functions (COUNT, SUM, AVG, MIN, MAX)
|
||||||
- WHERE clauses with filtering
|
- WHERE clauses with filtering
|
||||||
- System columns (_timestamp_ns, _key, _source)
|
- System columns (_timestamp_ns, _key, _source)
|
||||||
@@ -149,50 +119,95 @@ Performance Features:
|
|||||||
- PostgreSQL wire protocol
|
- PostgreSQL wire protocol
|
||||||
- Query result streaming
|
- Query result streaming
|
||||||
|
|
||||||
`,
|
`
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
MasterAddr string
|
||||||
|
AuthMethod string
|
||||||
|
Users string
|
||||||
|
Database string
|
||||||
|
MaxConns int
|
||||||
|
IdleTimeout string
|
||||||
|
TLSCert string
|
||||||
|
TLSKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDB(cmd *Command, args []string) bool {
|
// Run executes the weed-db CLI.
|
||||||
|
func Run(args []string) int {
|
||||||
|
fs := flag.NewFlagSet("weed-db", flag.ContinueOnError)
|
||||||
|
usageWriter := io.Writer(os.Stderr)
|
||||||
|
fs.SetOutput(usageWriter)
|
||||||
|
|
||||||
|
var opts Options
|
||||||
|
fs.StringVar(&opts.Host, "host", "localhost", "Database server host")
|
||||||
|
fs.IntVar(&opts.Port, "port", 5432, "Database server port")
|
||||||
|
fs.StringVar(&opts.MasterAddr, "master", "localhost:9333", "SeaweedFS master server address")
|
||||||
|
fs.StringVar(&opts.AuthMethod, "auth", "trust", "Authentication method: trust, password, md5")
|
||||||
|
fs.StringVar(&opts.Users, "users", "", "User credentials for auth (JSON format '{\"user1\":\"pass1\",\"user2\":\"pass2\"}' or file '@/path/to/users.json')")
|
||||||
|
fs.StringVar(&opts.Database, "database", "default", "Default database name")
|
||||||
|
fs.IntVar(&opts.MaxConns, "max-connections", 100, "Maximum concurrent connections per server")
|
||||||
|
fs.StringVar(&opts.IdleTimeout, "idle-timeout", "1h", "Connection idle timeout")
|
||||||
|
fs.StringVar(&opts.TLSCert, "tls-cert", "", "TLS certificate file path")
|
||||||
|
fs.StringVar(&opts.TLSKey, "tls-key", "", "TLS private key file path")
|
||||||
|
|
||||||
|
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 runWithOptions(opts *Options) bool {
|
||||||
util.LoadConfiguration("security", false)
|
util.LoadConfiguration("security", false)
|
||||||
|
|
||||||
// Validate options
|
// Validate options.
|
||||||
if *dbOptions.masterAddr == "" {
|
if opts.MasterAddr == "" {
|
||||||
fmt.Fprintf(os.Stderr, "Error: master address is required\n")
|
fmt.Fprintf(os.Stderr, "Error: master address is required\n")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse authentication method
|
// Parse authentication method.
|
||||||
authMethod, err := parseAuthMethod(*dbOptions.authMethod)
|
authMethod, err := parseAuthMethod(opts.AuthMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse user credentials
|
// Parse user credentials.
|
||||||
users, err := parseUsers(*dbOptions.users, authMethod)
|
users, err := parseUsers(opts.Users, authMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse idle timeout
|
// Parse idle timeout.
|
||||||
idleTimeout, err := time.ParseDuration(*dbOptions.idleTimeout)
|
idleTimeout, err := time.ParseDuration(opts.IdleTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error parsing idle timeout: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error parsing idle timeout: %v\n", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate port number
|
// Validate port number.
|
||||||
if err := validatePortNumber(*dbOptions.port); err != nil {
|
if err := validatePortNumber(opts.Port); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup TLS if requested
|
// Setup TLS if requested.
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if *dbOptions.tlsCert != "" && *dbOptions.tlsKey != "" {
|
if opts.TLSCert != "" && opts.TLSKey != "" {
|
||||||
cert, err := tls.LoadX509KeyPair(*dbOptions.tlsCert, *dbOptions.tlsKey)
|
cert, err := tls.LoadX509KeyPair(opts.TLSCert, opts.TLSKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error loading TLS certificates: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error loading TLS certificates: %v\n", err)
|
||||||
return false
|
return false
|
||||||
@@ -202,34 +217,34 @@ func runDB(cmd *Command, args []string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create server configuration
|
// Create server configuration.
|
||||||
config := &postgres.PostgreSQLServerConfig{
|
config := &postgres.PostgreSQLServerConfig{
|
||||||
Host: *dbOptions.host,
|
Host: opts.Host,
|
||||||
Port: *dbOptions.port,
|
Port: opts.Port,
|
||||||
AuthMethod: authMethod,
|
AuthMethod: authMethod,
|
||||||
Users: users,
|
Users: users,
|
||||||
Database: *dbOptions.database,
|
Database: opts.Database,
|
||||||
MaxConns: *dbOptions.maxConns,
|
MaxConns: opts.MaxConns,
|
||||||
IdleTimeout: idleTimeout,
|
IdleTimeout: idleTimeout,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create database server
|
// Create database server.
|
||||||
dbServer, err := postgres.NewPostgreSQLServer(config, *dbOptions.masterAddr)
|
dbServer, err := postgres.NewPostgreSQLServer(config, opts.MasterAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error creating database server: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error creating database server: %v\n", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print startup information
|
// Print startup information.
|
||||||
fmt.Printf("Starting SeaweedFS Database Server...\n")
|
fmt.Printf("Starting SeaweedFS Database Server...\n")
|
||||||
fmt.Printf("Host: %s\n", *dbOptions.host)
|
fmt.Printf("Host: %s\n", opts.Host)
|
||||||
fmt.Printf("Port: %d\n", *dbOptions.port)
|
fmt.Printf("Port: %d\n", opts.Port)
|
||||||
fmt.Printf("Master: %s\n", *dbOptions.masterAddr)
|
fmt.Printf("Master: %s\n", opts.MasterAddr)
|
||||||
fmt.Printf("Database: %s\n", *dbOptions.database)
|
fmt.Printf("Database: %s\n", opts.Database)
|
||||||
fmt.Printf("Auth Method: %s\n", *dbOptions.authMethod)
|
fmt.Printf("Auth Method: %s\n", opts.AuthMethod)
|
||||||
fmt.Printf("Max Connections: %d\n", *dbOptions.maxConns)
|
fmt.Printf("Max Connections: %d\n", opts.MaxConns)
|
||||||
fmt.Printf("Idle Timeout: %s\n", *dbOptions.idleTimeout)
|
fmt.Printf("Idle Timeout: %s\n", opts.IdleTimeout)
|
||||||
if tlsConfig != nil {
|
if tlsConfig != nil {
|
||||||
fmt.Printf("TLS: Enabled\n")
|
fmt.Printf("TLS: Enabled\n")
|
||||||
} else {
|
} else {
|
||||||
@@ -240,15 +255,15 @@ func runDB(cmd *Command, args []string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\nDatabase Connection Examples:\n")
|
fmt.Printf("\nDatabase Connection Examples:\n")
|
||||||
fmt.Printf(" psql -h %s -p %d -U seaweedfs -d %s\n", *dbOptions.host, *dbOptions.port, *dbOptions.database)
|
fmt.Printf(" psql -h %s -p %d -U seaweedfs -d %s\n", opts.Host, opts.Port, opts.Database)
|
||||||
if len(users) > 0 {
|
if len(users) > 0 {
|
||||||
// Show first user as example
|
// Show first user as example.
|
||||||
for username := range users {
|
for username := range users {
|
||||||
fmt.Printf(" psql -h %s -p %d -U %s -d %s\n", *dbOptions.host, *dbOptions.port, username, *dbOptions.database)
|
fmt.Printf(" psql -h %s -p %d -U %s -d %s\n", opts.Host, opts.Port, username, opts.Database)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf(" postgresql://%s:%d/%s\n", *dbOptions.host, *dbOptions.port, *dbOptions.database)
|
fmt.Printf(" postgresql://%s:%d/%s\n", opts.Host, opts.Port, opts.Database)
|
||||||
|
|
||||||
fmt.Printf("\nSupported Operations:\n")
|
fmt.Printf("\nSupported Operations:\n")
|
||||||
fmt.Printf(" - SELECT queries on MQ topics\n")
|
fmt.Printf(" - SELECT queries on MQ topics\n")
|
||||||
@@ -261,26 +276,26 @@ func runDB(cmd *Command, args []string) bool {
|
|||||||
|
|
||||||
fmt.Printf("\nReady for database connections!\n\n")
|
fmt.Printf("\nReady for database connections!\n\n")
|
||||||
|
|
||||||
// Start the server
|
// Start the server.
|
||||||
err = dbServer.Start()
|
err = dbServer.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error starting database server: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error starting database server: %v\n", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up signal handling for graceful shutdown
|
// Set up signal handling for graceful shutdown.
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
// Wait for shutdown signal
|
// Wait for shutdown signal.
|
||||||
<-sigChan
|
<-sigChan
|
||||||
fmt.Printf("\nReceived shutdown signal, stopping database server...\n")
|
fmt.Printf("\nReceived shutdown signal, stopping database server...\n")
|
||||||
|
|
||||||
// Create context with timeout for graceful shutdown
|
// Create context with timeout for graceful shutdown.
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Stop the server with timeout
|
// Stop the server with timeout.
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
done <- dbServer.Stop()
|
done <- dbServer.Stop()
|
||||||
@@ -301,7 +316,7 @@ func runDB(cmd *Command, args []string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAuthMethod parses the authentication method string
|
// parseAuthMethod parses the authentication method string.
|
||||||
func parseAuthMethod(method string) (postgres.AuthMethod, error) {
|
func parseAuthMethod(method string) (postgres.AuthMethod, error) {
|
||||||
switch strings.ToLower(method) {
|
switch strings.ToLower(method) {
|
||||||
case "trust":
|
case "trust":
|
||||||
@@ -315,7 +330,7 @@ func parseAuthMethod(method string) (postgres.AuthMethod, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseUsers parses the user credentials string with support for secure formats only
|
// parseUsers parses the user credentials string with support for secure formats only.
|
||||||
// Supported formats:
|
// Supported formats:
|
||||||
// 1. JSON format: {"username":"password","username2":"password2"}
|
// 1. JSON format: {"username":"password","username2":"password2"}
|
||||||
// 2. File format: /path/to/users.json or @/path/to/users.json
|
// 2. File format: /path/to/users.json or @/path/to/users.json
|
||||||
@@ -323,41 +338,41 @@ func parseUsers(usersStr string, authMethod postgres.AuthMethod) (map[string]str
|
|||||||
users := make(map[string]string)
|
users := make(map[string]string)
|
||||||
|
|
||||||
if usersStr == "" {
|
if usersStr == "" {
|
||||||
// No users specified
|
// No users specified.
|
||||||
if authMethod != postgres.AuthTrust {
|
if authMethod != postgres.AuthTrust {
|
||||||
return nil, fmt.Errorf("users must be specified when auth method is not 'trust'")
|
return nil, fmt.Errorf("users must be specified when auth method is not 'trust'")
|
||||||
}
|
}
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim whitespace
|
// Trim whitespace.
|
||||||
usersStr = strings.TrimSpace(usersStr)
|
usersStr = strings.TrimSpace(usersStr)
|
||||||
|
|
||||||
// Determine format and parse accordingly
|
// Determine format and parse accordingly.
|
||||||
if strings.HasPrefix(usersStr, "{") && strings.HasSuffix(usersStr, "}") {
|
if strings.HasPrefix(usersStr, "{") && strings.HasSuffix(usersStr, "}") {
|
||||||
// JSON format
|
// JSON format.
|
||||||
return parseUsersJSON(usersStr, authMethod)
|
return parseUsersJSON(usersStr, authMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a file path (with or without @ prefix) before declaring invalid format
|
// Check if it's a file path (with or without @ prefix) before declaring invalid format.
|
||||||
filePath := strings.TrimPrefix(usersStr, "@")
|
filePath := strings.TrimPrefix(usersStr, "@")
|
||||||
if _, err := os.Stat(filePath); err == nil {
|
if _, err := os.Stat(filePath); err == nil {
|
||||||
// File format
|
// File format.
|
||||||
return parseUsersFile(usersStr, authMethod) // Pass original string to preserve @ handling
|
return parseUsersFile(usersStr, authMethod) // Pass original string to preserve @ handling.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid format
|
// Invalid format.
|
||||||
return nil, fmt.Errorf("invalid user credentials format. Use JSON format '{\"user\":\"pass\"}' or file format '@/path/to/users.json' or 'path/to/users.json'. Legacy semicolon-separated format is no longer supported")
|
return nil, fmt.Errorf("invalid user credentials format. Use JSON format '{\"user\":\"pass\"}' or file format '@/path/to/users.json' or 'path/to/users.json'. Legacy semicolon-separated format is no longer supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseUsersJSON parses user credentials from JSON format
|
// parseUsersJSON parses user credentials from JSON format.
|
||||||
func parseUsersJSON(jsonStr string, authMethod postgres.AuthMethod) (map[string]string, error) {
|
func parseUsersJSON(jsonStr string, authMethod postgres.AuthMethod) (map[string]string, error) {
|
||||||
var users map[string]string
|
var users map[string]string
|
||||||
if err := json.Unmarshal([]byte(jsonStr), &users); err != nil {
|
if err := json.Unmarshal([]byte(jsonStr), &users); err != nil {
|
||||||
return nil, fmt.Errorf("invalid JSON format for users: %v", err)
|
return nil, fmt.Errorf("invalid JSON format for users: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate users
|
// Validate users.
|
||||||
for username, password := range users {
|
for username, password := range users {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return nil, fmt.Errorf("empty username in JSON user specification")
|
return nil, fmt.Errorf("empty username in JSON user specification")
|
||||||
@@ -370,12 +385,12 @@ func parseUsersJSON(jsonStr string, authMethod postgres.AuthMethod) (map[string]
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseUsersFile parses user credentials from a JSON file
|
// parseUsersFile parses user credentials from a JSON file.
|
||||||
func parseUsersFile(filePath string, authMethod postgres.AuthMethod) (map[string]string, error) {
|
func parseUsersFile(filePath string, authMethod postgres.AuthMethod) (map[string]string, error) {
|
||||||
// Remove @ prefix if present
|
// Remove @ prefix if present.
|
||||||
filePath = strings.TrimPrefix(filePath, "@")
|
filePath = strings.TrimPrefix(filePath, "@")
|
||||||
|
|
||||||
// Read file content
|
// Read file content.
|
||||||
content, err := os.ReadFile(filePath)
|
content, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read users file '%s': %v", filePath, err)
|
return nil, fmt.Errorf("failed to read users file '%s': %v", filePath, err)
|
||||||
@@ -383,16 +398,16 @@ func parseUsersFile(filePath string, authMethod postgres.AuthMethod) (map[string
|
|||||||
|
|
||||||
contentStr := strings.TrimSpace(string(content))
|
contentStr := strings.TrimSpace(string(content))
|
||||||
|
|
||||||
// File must contain JSON format
|
// File must contain JSON format.
|
||||||
if !strings.HasPrefix(contentStr, "{") || !strings.HasSuffix(contentStr, "}") {
|
if !strings.HasPrefix(contentStr, "{") || !strings.HasSuffix(contentStr, "}") {
|
||||||
return nil, fmt.Errorf("users file '%s' must contain JSON format: {\"user\":\"pass\"}. Legacy formats are no longer supported", filePath)
|
return nil, fmt.Errorf("users file '%s' must contain JSON format: {\"user\":\"pass\"}. Legacy formats are no longer supported", filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse as JSON
|
// Parse as JSON.
|
||||||
return parseUsersJSON(contentStr, authMethod)
|
return parseUsersJSON(contentStr, authMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePortNumber validates that the port number is reasonable
|
// validatePortNumber validates that the port number is reasonable.
|
||||||
func validatePortNumber(port int) error {
|
func validatePortNumber(port int) error {
|
||||||
if port < 1 || port > 65535 {
|
if port < 1 || port > 65535 {
|
||||||
return fmt.Errorf("port number must be between 1 and 65535, got %d", port)
|
return fmt.Errorf("port number must be between 1 and 65535, got %d", port)
|
||||||
7
cmd/weed-db/main.go
Normal file
7
cmd/weed-db/main.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
os.Exit(Run(os.Args[1:]))
|
||||||
|
}
|
||||||
7
cmd/weed-sql/main.go
Normal file
7
cmd/weed-sql/main.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
os.Exit(Run(os.Args[1:]))
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package command
|
package main
|
||||||
|
|
||||||
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() {
|
const usageLine = "weed-sql [-master=localhost:9333] [-interactive] [-file=query.sql] [-output=table|json|csv] [-database=dbname] [-query=\"SQL\"]"
|
||||||
cmdSql.Run = runSql
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdSql = &Command{
|
const longHelp = `Enhanced SQL interface for SeaweedFS Message Queue topics with multiple execution modes.
|
||||||
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:
|
Execution Modes:
|
||||||
- Interactive shell (default): weed sql -interactive
|
- Interactive shell (default): weed-sql -interactive
|
||||||
- Single query: weed sql -query "SELECT * FROM user_events"
|
- Single query: weed-sql -query "SELECT * FROM user_events"
|
||||||
- Batch from file: weed sql -file queries.sql
|
- Batch from file: weed-sql -file queries.sql
|
||||||
- Context switching: weed sql -database analytics -interactive
|
- 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 -interactive
|
||||||
weed sql -query "SHOW DATABASES" -output json
|
weed-sql -query "SHOW DATABASES" -output json
|
||||||
weed sql -file batch_queries.sql -output csv
|
weed-sql -file batch_queries.sql -output csv
|
||||||
weed sql -database analytics -query "SELECT COUNT(*) FROM metrics"
|
weed-sql -database analytics -query "SELECT COUNT(*) FROM metrics"
|
||||||
weed sql -master broker1:9333 -interactive
|
weed-sql -master broker1:9333 -interactive
|
||||||
`,
|
`
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Master string
|
||||||
|
Interactive bool
|
||||||
|
File string
|
||||||
|
Output string
|
||||||
|
Database string
|
||||||
|
Query string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// OutputFormat represents different output formatting options.
|
||||||
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
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSql(command *Command, args []string) bool {
|
// Run executes the weed-sql CLI.
|
||||||
// Initialize SQL engine with master address for service discovery
|
func Run(args []string) int {
|
||||||
sqlEngine := engine.NewSQLEngine(*sqlMaster)
|
fs := flag.NewFlagSet("weed-sql", flag.ContinueOnError)
|
||||||
|
usageWriter := io.Writer(os.Stderr)
|
||||||
|
fs.SetOutput(usageWriter)
|
||||||
|
|
||||||
// Determine execution mode and output format
|
var opts Options
|
||||||
interactive := *sqlInteractive || (*sqlQuery == "" && *sqlFile == "")
|
fs.StringVar(&opts.Master, "master", "localhost:9333", "SeaweedFS master server HTTP address")
|
||||||
outputFormat := determineOutputFormat(*sqlOutput, interactive)
|
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")
|
||||||
|
|
||||||
// Create SQL context
|
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 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 := opts.Interactive || (opts.Query == "" && opts.File == "")
|
||||||
|
outputFormat := determineOutputFormat(opts.Output, interactive)
|
||||||
|
|
||||||
|
// 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
|
// Set current database in SQL engine if specified via command line.
|
||||||
if *sqlDatabase != "" {
|
if opts.Database != "" {
|
||||||
ctx.engine.GetCatalog().SetCurrentDatabase(*sqlDatabase)
|
ctx.engine.GetCatalog().SetCurrentDatabase(opts.Database)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute based on mode
|
// Execute based on mode.
|
||||||
switch {
|
switch {
|
||||||
case *sqlQuery != "":
|
case opts.Query != "":
|
||||||
// Single query mode
|
// Single query mode.
|
||||||
return executeSingleQuery(ctx, *sqlQuery)
|
return executeSingleQuery(ctx, opts.Query)
|
||||||
case *sqlFile != "":
|
case opts.File != "":
|
||||||
// Batch file mode
|
// Batch file mode.
|
||||||
return executeFileQueries(ctx, *sqlFile)
|
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", *sqlMaster)
|
fmt.Printf("Executing query against %s...\n", ctx.master)
|
||||||
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, *sqlMaster)
|
fmt.Printf("Executing queries from %s against %s...\n", filename, ctx.master)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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", *sqlMaster)
|
fmt.Printf("Connected to master: %s\n", ctx.master)
|
||||||
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
|
// Show execution time for interactive/table mode.
|
||||||
// Only show timing if there are columns or if result is truly empty
|
// 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
|
||||||
@@ -16,8 +16,9 @@ RUN go mod download
|
|||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the weed binary with Kafka gateway support
|
# Build the weed binaries with Kafka gateway support
|
||||||
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o weed ./weed
|
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o weed ./weed
|
||||||
|
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o weed-sql ./cmd/weed-sql
|
||||||
|
|
||||||
# Final stage
|
# Final stage
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
@@ -32,8 +33,9 @@ RUN addgroup -g 1000 seaweedfs && \
|
|||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /usr/bin
|
WORKDIR /usr/bin
|
||||||
|
|
||||||
# Copy binary from builder
|
# Copy binaries from builder
|
||||||
COPY --from=builder /app/weed .
|
COPY --from=builder /app/weed .
|
||||||
|
COPY --from=builder /app/weed-sql .
|
||||||
|
|
||||||
# Create data directory
|
# Create data directory
|
||||||
RUN mkdir -p /data && chown seaweedfs:seaweedfs /data
|
RUN mkdir -p /data && chown seaweedfs:seaweedfs /data
|
||||||
|
|||||||
3
test/kafka/test_json_timestamp.sh
Executable file → Normal file
3
test/kafka/test_json_timestamp.sh
Executable file → Normal file
@@ -14,8 +14,7 @@ sleep 2
|
|||||||
|
|
||||||
echo "Querying messages..."
|
echo "Querying messages..."
|
||||||
cd /Users/chrislu/go/src/github.com/seaweedfs/seaweedfs/test/kafka/kafka-client-loadtest
|
cd /Users/chrislu/go/src/github.com/seaweedfs/seaweedfs/test/kafka/kafka-client-loadtest
|
||||||
docker compose exec kafka-gateway /usr/local/bin/weed sql \
|
docker compose exec kafka-gateway /usr/bin/weed-sql \
|
||||||
-master=seaweedfs-master:9333 \
|
-master=seaweedfs-master:9333 \
|
||||||
-database=kafka \
|
-database=kafka \
|
||||||
-query="SELECT id, timestamp, producer_id, counter, user_id, event_type FROM \"test-json-topic\" LIMIT 5;"
|
-query="SELECT id, timestamp, producer_id, counter, user_id, event_type FROM \"test-json-topic\" LIMIT 5;"
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ RUN go mod download
|
|||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the weed binary without CGO
|
# Build the weed binaries without CGO
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o weed ./weed/
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o weed ./weed/
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o weed-db ./cmd/weed-db
|
||||||
|
|
||||||
# Final stage - minimal runtime image
|
# Final stage - minimal runtime image
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
@@ -24,11 +25,12 @@ RUN apk --no-cache add ca-certificates netcat-openbsd curl
|
|||||||
|
|
||||||
WORKDIR /root/
|
WORKDIR /root/
|
||||||
|
|
||||||
# Copy the weed binary from builder stage
|
# Copy the binaries from builder stage
|
||||||
COPY --from=builder /app/weed .
|
COPY --from=builder /app/weed .
|
||||||
|
COPY --from=builder /app/weed-db .
|
||||||
|
|
||||||
# Make it executable
|
# Make it executable
|
||||||
RUN chmod +x ./weed
|
RUN chmod +x ./weed ./weed-db
|
||||||
|
|
||||||
# Expose ports
|
# Expose ports
|
||||||
EXPOSE 9333 8888 8333 8085 9533 5432
|
EXPOSE 9333 8888 8333 8085 9533 5432
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ This test setup proves:
|
|||||||
- Comprehensive error handling
|
- Comprehensive error handling
|
||||||
|
|
||||||
### ✅ Performance and Scalability
|
### ✅ Performance and Scalability
|
||||||
- Direct SQL engine integration (same as `weed sql`)
|
- Direct SQL engine integration (same as `weed-sql`)
|
||||||
- No translation overhead for real queries
|
- No translation overhead for real queries
|
||||||
- Efficient data access from stored formats
|
- Efficient data access from stored formats
|
||||||
- Scalable architecture with service discovery
|
- Scalable architecture with service discovery
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ services:
|
|||||||
seaweedfs:
|
seaweedfs:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
command: >
|
command: >
|
||||||
./weed db
|
./weed-db
|
||||||
-host=0.0.0.0
|
-host=0.0.0.0
|
||||||
-port=5432
|
-port=5432
|
||||||
-master=seaweedfs:9333
|
-master=seaweedfs:9333
|
||||||
|
|||||||
0
test/postgres/run-tests.sh
Executable file → Normal file
0
test/postgres/run-tests.sh
Executable file → Normal file
0
test/postgres/validate-setup.sh
Executable file → Normal file
0
test/postgres/validate-setup.sh
Executable file → Normal file
@@ -9,6 +9,14 @@ all: install
|
|||||||
install:
|
install:
|
||||||
go install -ldflags="-s -w"
|
go install -ldflags="-s -w"
|
||||||
|
|
||||||
|
.PHONY: weed-db weed-sql
|
||||||
|
|
||||||
|
weed-db:
|
||||||
|
go build -ldflags="-s -w" -o weed-db ./cmd/weed-db
|
||||||
|
|
||||||
|
weed-sql:
|
||||||
|
go build -ldflags="-s -w" -o weed-sql ./cmd/weed-sql
|
||||||
|
|
||||||
build_docker:
|
build_docker:
|
||||||
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w"
|
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w"
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,10 @@ var Commands = []*Command{
|
|||||||
cmdMqAgent,
|
cmdMqAgent,
|
||||||
cmdMqBroker,
|
cmdMqBroker,
|
||||||
cmdMqKafkaGateway,
|
cmdMqKafkaGateway,
|
||||||
cmdDB,
|
|
||||||
cmdS3,
|
cmdS3,
|
||||||
cmdScaffold,
|
cmdScaffold,
|
||||||
cmdServer,
|
cmdServer,
|
||||||
cmdShell,
|
cmdShell,
|
||||||
cmdSql,
|
|
||||||
cmdUpdate,
|
cmdUpdate,
|
||||||
cmdUpload,
|
cmdUpload,
|
||||||
cmdVersion,
|
cmdVersion,
|
||||||
|
|||||||
@@ -296,9 +296,9 @@ jdbc:postgresql://localhost:5432/default?user=seaweedfs&password=secret
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start PostgreSQL protocol server
|
# Start PostgreSQL protocol server
|
||||||
weed db -port=5432 -auth=trust
|
weed-db -port=5432 -auth=trust
|
||||||
weed db -port=5432 -auth=password -users="admin:secret;readonly:pass"
|
weed-db -port=5432 -auth=password -users="admin:secret;readonly:pass"
|
||||||
weed db -port=5432 -tls-cert=server.crt -tls-key=server.key
|
weed-db -port=5432 -tls-cert=server.crt -tls-key=server.key
|
||||||
|
|
||||||
# Configuration options
|
# Configuration options
|
||||||
-host=localhost # Listen host
|
-host=localhost # Listen host
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ The PostgreSQL server now directly integrates with SeaweedFS Message Queue topic
|
|||||||
- **Real Schema Information**: Reads actual topic schemas from broker configuration
|
- **Real Schema Information**: Reads actual topic schemas from broker configuration
|
||||||
- **Actual Data Access**: Queries real MQ data stored in Parquet and log files
|
- **Actual Data Access**: Queries real MQ data stored in Parquet and log files
|
||||||
- **Dynamic Updates**: Reflects topic additions and schema changes automatically
|
- **Dynamic Updates**: Reflects topic additions and schema changes automatically
|
||||||
- **Consistent SQL Engine**: Uses the same SQL engine as `weed sql` command
|
- **Consistent SQL Engine**: Uses the same SQL engine as `weed-sql` command
|
||||||
|
|
||||||
### Database Context Management
|
### Database Context Management
|
||||||
- **Session Isolation**: Each PostgreSQL connection has its own database context
|
- **Session Isolation**: Each PostgreSQL connection has its own database context
|
||||||
@@ -232,7 +232,7 @@ psql -h localhost -p 5432 -U seaweedfs -d default
|
|||||||
- **DESIGN.md**: Complete architecture and design overview
|
- **DESIGN.md**: Complete architecture and design overview
|
||||||
- **IMPLEMENTATION.md**: Detailed implementation guide
|
- **IMPLEMENTATION.md**: Detailed implementation guide
|
||||||
- **postgres-examples/**: Client examples and test scripts
|
- **postgres-examples/**: Client examples and test scripts
|
||||||
- **Command Documentation**: `weed db -help`
|
- **Command Documentation**: `weed-db -help`
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user