2
0

main.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. package main
  2. import (
  3. "bufio"
  4. "context"
  5. "flag"
  6. "fmt"
  7. "net/http"
  8. "os"
  9. "os/signal"
  10. "strings"
  11. "syscall"
  12. "time"
  13. "github.com/danfragoso/pizzasql-next/pkg/csvexport"
  14. "github.com/danfragoso/pizzasql-next/pkg/csvimport"
  15. "github.com/danfragoso/pizzasql-next/pkg/executor"
  16. "github.com/danfragoso/pizzasql-next/pkg/httpserver"
  17. "github.com/danfragoso/pizzasql-next/pkg/kvmanager"
  18. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  19. "github.com/danfragoso/pizzasql-next/pkg/parser"
  20. "github.com/danfragoso/pizzasql-next/pkg/sqlexport"
  21. "github.com/danfragoso/pizzasql-next/pkg/sqlimport"
  22. "github.com/danfragoso/pizzasql-next/pkg/storage"
  23. )
  24. var (
  25. kvAddr = flag.String("kvaddr", "localhost:8085", "PizzaKV server address (ignored if -kv is set)")
  26. kvLaunch = flag.Bool("kv", false, "Launch PizzaKV automatically")
  27. kvFlags = flag.String("kvflags", "", "Flags to pass to PizzaKV (e.g., \"-iwal -port=9090\")")
  28. kvInfoFile = flag.String("kvinfo", ".pizzakv.json", "Path to PizzaKV info file")
  29. database = flag.String("db", "pizzasql", "Database name")
  30. poolSize = flag.Int("pool", 5, "Connection pool size")
  31. timeout = flag.Duration("timeout", 30*time.Second, "Query timeout")
  32. httpEnable = flag.Bool("http", false, "Enable HTTP server")
  33. httpHost = flag.String("http-host", "localhost", "HTTP server host")
  34. httpPort = flag.Int("http-port", 8080, "HTTP server port")
  35. httpCORS = flag.Bool("http-cors", true, "Enable CORS")
  36. httpAuth = flag.Bool("http-auth", false, "Enable authentication")
  37. httpCompression = flag.Bool("http-compression", true, "Enable HTTP response compression")
  38. httpQuiet = flag.Bool("quiet", false, "Disable request logging")
  39. apiKeys = flag.String("api-keys", "", "Comma-separated API keys")
  40. // Export/Import flags
  41. exportFile = flag.String("o", "", "Output file for export")
  42. importFile = flag.String("i", "", "Input file for import")
  43. exportTable = flag.String("table", "", "Specific table to export (empty = all)")
  44. exportDrop = flag.Bool("drop", false, "Include DROP TABLE statements in export")
  45. ignoreErrors = flag.Bool("ignore-errors", false, "Continue import on errors")
  46. exportFormat = flag.String("format", "", "Export/import format: sql, csv (auto-detect from extension)")
  47. createTable = flag.Bool("create-table", false, "Create table if not exists (CSV import)")
  48. )
  49. var kvManager *kvmanager.Manager
  50. var startPprofServerHook func() *http.Server
  51. func main() {
  52. flag.Parse()
  53. // If -kv flag is set, launch PizzaKV
  54. if *kvLaunch {
  55. if err := launchPizzaKV(); err != nil {
  56. fmt.Fprintf(os.Stderr, "Failed to launch PizzaKV: %v\n", err)
  57. os.Exit(1)
  58. }
  59. defer stopPizzaKV()
  60. // Set up signal handling for graceful shutdown
  61. sigChan := make(chan os.Signal, 1)
  62. signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
  63. go func() {
  64. <-sigChan
  65. fmt.Println("\nShutting down...")
  66. stopPizzaKV()
  67. os.Exit(0)
  68. }()
  69. }
  70. // Check if HTTP server mode is enabled
  71. if *httpEnable {
  72. runHTTPServer()
  73. return
  74. }
  75. // Check for export command
  76. if *exportFile != "" {
  77. runExport()
  78. return
  79. }
  80. // Check for import command
  81. if *importFile != "" {
  82. runImport()
  83. return
  84. }
  85. // Check for command-line SQL
  86. args := flag.Args()
  87. if len(args) > 0 {
  88. // Execute single SQL statement
  89. sql := strings.Join(args, " ")
  90. executeSingle(sql)
  91. return
  92. }
  93. // Check for piped input
  94. stat, _ := os.Stdin.Stat()
  95. if (stat.Mode() & os.ModeCharDevice) == 0 {
  96. // Input is from pipe
  97. executePipe()
  98. return
  99. }
  100. // Interactive REPL mode
  101. runREPL()
  102. }
  103. func executeSingle(sql string) {
  104. // Try to connect to PizzaKV
  105. pool, err := storage.NewKVPool(*kvAddr, *poolSize, *timeout)
  106. if err != nil {
  107. // Fall back to expression-only mode
  108. executeExpressionOnly(sql)
  109. return
  110. }
  111. defer pool.Close()
  112. schema := storage.NewSchemaManager(pool, *database)
  113. table := storage.NewTableManager(pool, schema, *database)
  114. exec := executor.New(schema, table)
  115. exec.SyncCatalog()
  116. result, err := executeSQL(exec, sql)
  117. if err != nil {
  118. fmt.Fprintf(os.Stderr, "Error: %v\n", err)
  119. os.Exit(1)
  120. }
  121. fmt.Print(result.String())
  122. }
  123. func executePipe() {
  124. // Try to connect to PizzaKV
  125. pool, err := storage.NewKVPool(*kvAddr, *poolSize, *timeout)
  126. if err != nil {
  127. // Fall back to expression-only mode
  128. scanner := bufio.NewScanner(os.Stdin)
  129. for scanner.Scan() {
  130. sql := strings.TrimSpace(scanner.Text())
  131. if sql == "" || strings.HasPrefix(sql, "--") {
  132. continue
  133. }
  134. executeExpressionOnly(sql)
  135. }
  136. return
  137. }
  138. defer pool.Close()
  139. schema := storage.NewSchemaManager(pool, *database)
  140. table := storage.NewTableManager(pool, schema, *database)
  141. exec := executor.New(schema, table)
  142. exec.SyncCatalog()
  143. scanner := bufio.NewScanner(os.Stdin)
  144. for scanner.Scan() {
  145. sql := strings.TrimSpace(scanner.Text())
  146. if sql == "" || strings.HasPrefix(sql, "--") {
  147. continue
  148. }
  149. result, err := executeSQL(exec, sql)
  150. if err != nil {
  151. fmt.Fprintf(os.Stderr, "Error: %v\n", err)
  152. continue
  153. }
  154. fmt.Print(result.String())
  155. }
  156. }
  157. func runREPL() {
  158. fmt.Println("PizzaSQL - SQL-92 compatible database")
  159. fmt.Println("Type 'help' for usage, 'quit' to exit")
  160. fmt.Println()
  161. // Try to connect to PizzaKV
  162. var pool *storage.KVPool
  163. var schema *storage.SchemaManager
  164. var table *storage.TableManager
  165. var exec *executor.Executor
  166. pool, err := storage.NewKVPool(*kvAddr, *poolSize, *timeout)
  167. if err != nil {
  168. fmt.Printf("Warning: Cannot connect to PizzaKV at %s\n", *kvAddr)
  169. fmt.Println("Running in expression-only mode (no table storage)")
  170. fmt.Println()
  171. } else {
  172. schema = storage.NewSchemaManager(pool, *database)
  173. table = storage.NewTableManager(pool, schema, *database)
  174. exec = executor.New(schema, table)
  175. exec.SyncCatalog()
  176. fmt.Printf("Connected to PizzaKV at %s (database: %s)\n\n", *kvAddr, *database)
  177. }
  178. reader := bufio.NewReader(os.Stdin)
  179. var sqlBuffer strings.Builder
  180. for {
  181. if sqlBuffer.Len() == 0 {
  182. fmt.Print("pizzasql> ")
  183. } else {
  184. fmt.Print(" -> ")
  185. }
  186. line, err := reader.ReadString('\n')
  187. if err != nil {
  188. fmt.Println()
  189. break
  190. }
  191. line = strings.TrimSpace(line)
  192. // Handle special commands
  193. switch strings.ToLower(line) {
  194. case "quit", "exit", "\\q":
  195. fmt.Println("Goodbye!")
  196. if pool != nil {
  197. pool.Close()
  198. }
  199. return
  200. case "help", "\\h":
  201. printHelp()
  202. continue
  203. case "tables", "\\dt":
  204. if schema != nil {
  205. listTables(schema)
  206. } else {
  207. fmt.Println("Not connected to database")
  208. }
  209. continue
  210. case "clear", "\\c":
  211. sqlBuffer.Reset()
  212. fmt.Println("Buffer cleared")
  213. continue
  214. }
  215. // Skip empty lines and comments
  216. if line == "" || strings.HasPrefix(line, "--") {
  217. continue
  218. }
  219. // Accumulate SQL
  220. if sqlBuffer.Len() > 0 {
  221. sqlBuffer.WriteString(" ")
  222. }
  223. sqlBuffer.WriteString(line)
  224. // Check if statement is complete (ends with semicolon)
  225. sql := sqlBuffer.String()
  226. if !strings.HasSuffix(sql, ";") {
  227. continue
  228. }
  229. // Remove semicolon and execute
  230. sql = strings.TrimSuffix(sql, ";")
  231. sqlBuffer.Reset()
  232. if exec != nil {
  233. result, err := executeSQL(exec, sql)
  234. if err != nil {
  235. fmt.Fprintf(os.Stderr, "Error: %v\n", err)
  236. continue
  237. }
  238. fmt.Print(result.String())
  239. } else {
  240. executeExpressionOnly(sql)
  241. }
  242. }
  243. }
  244. func executeSQL(exec *executor.Executor, sql string) (*executor.Result, error) {
  245. l := lexer.New(sql)
  246. p := parser.New(l)
  247. stmt, err := p.Parse()
  248. if err != nil {
  249. return nil, fmt.Errorf("parse error: %w", err)
  250. }
  251. return exec.Execute(stmt)
  252. }
  253. func executeExpressionOnly(sql string) {
  254. l := lexer.New(sql)
  255. p := parser.New(l)
  256. stmt, err := p.Parse()
  257. if err != nil {
  258. fmt.Fprintf(os.Stderr, "Parse error: %v\n", err)
  259. return
  260. }
  261. // For SELECT statements without FROM, we can evaluate expressions
  262. if sel, ok := stmt.(*parser.SelectStmt); ok && len(sel.From) == 0 {
  263. exec := &executor.Executor{}
  264. result, err := executeSelectExpr(exec, sel)
  265. if err != nil {
  266. fmt.Fprintf(os.Stderr, "Error: %v\n", err)
  267. return
  268. }
  269. fmt.Print(result.String())
  270. return
  271. }
  272. // For other statements, just print what was parsed
  273. switch s := stmt.(type) {
  274. case *parser.SelectStmt:
  275. fmt.Printf("SELECT statement with %d columns\n", len(s.Columns))
  276. if len(s.From) > 0 {
  277. fmt.Printf(" FROM: %s\n", s.From[0].Name)
  278. }
  279. if s.Where != nil {
  280. fmt.Println(" WHERE: <condition>")
  281. }
  282. fmt.Println("(Not connected to database - cannot execute)")
  283. case *parser.InsertStmt:
  284. fmt.Printf("INSERT into %s (%d rows)\n", s.Table.Name, len(s.Values))
  285. fmt.Println("(Not connected to database - cannot execute)")
  286. case *parser.UpdateStmt:
  287. fmt.Printf("UPDATE %s (%d assignments)\n", s.Table.Name, len(s.Set))
  288. fmt.Println("(Not connected to database - cannot execute)")
  289. case *parser.DeleteStmt:
  290. fmt.Printf("DELETE from %s\n", s.Table.Name)
  291. fmt.Println("(Not connected to database - cannot execute)")
  292. case *parser.CreateTableStmt:
  293. fmt.Printf("CREATE TABLE %s (%d columns)\n", s.Table.Name, len(s.Columns))
  294. fmt.Println("(Not connected to database - cannot execute)")
  295. case *parser.DropTableStmt:
  296. fmt.Printf("DROP TABLE %s\n", s.Tables[0].Name)
  297. fmt.Println("(Not connected to database - cannot execute)")
  298. default:
  299. fmt.Printf("Parsed: %T\n", stmt)
  300. }
  301. }
  302. // executeSelectExpr handles SELECT without FROM (expression evaluation)
  303. func executeSelectExpr(exec *executor.Executor, stmt *parser.SelectStmt) (*executor.Result, error) {
  304. result := executor.NewResult("SELECT")
  305. // Determine columns
  306. for i, col := range stmt.Columns {
  307. if col.Alias != "" {
  308. result.AddColumn(col.Alias)
  309. } else {
  310. result.AddColumn(fmt.Sprintf("column%d", i+1))
  311. }
  312. }
  313. // Evaluate expressions using reflection to access private method
  314. // For simplicity, we'll use a minimal evaluator here
  315. values := make([]interface{}, len(stmt.Columns))
  316. for i, col := range stmt.Columns {
  317. val, err := evalExprSimple(col.Expr)
  318. if err != nil {
  319. return nil, err
  320. }
  321. values[i] = val
  322. }
  323. result.AddRow(values...)
  324. return result, nil
  325. }
  326. // evalExprSimple is a simplified expression evaluator for standalone expressions
  327. func evalExprSimple(expr parser.Expr) (interface{}, error) {
  328. switch e := expr.(type) {
  329. case *parser.LiteralExpr:
  330. switch e.Type {
  331. case lexer.TokenNumber:
  332. if strings.Contains(e.Value, ".") {
  333. var f float64
  334. fmt.Sscanf(e.Value, "%f", &f)
  335. return f, nil
  336. }
  337. var i int64
  338. fmt.Sscanf(e.Value, "%d", &i)
  339. return i, nil
  340. case lexer.TokenString:
  341. return e.Value, nil
  342. case lexer.TokenNULL:
  343. return nil, nil
  344. case lexer.TokenTRUE:
  345. return true, nil
  346. case lexer.TokenFALSE:
  347. return false, nil
  348. }
  349. case *parser.BinaryExpr:
  350. left, err := evalExprSimple(e.Left)
  351. if err != nil {
  352. return nil, err
  353. }
  354. right, err := evalExprSimple(e.Right)
  355. if err != nil {
  356. return nil, err
  357. }
  358. return evalBinarySimple(e.Op, left, right)
  359. case *parser.UnaryExpr:
  360. val, err := evalExprSimple(e.Operand)
  361. if err != nil {
  362. return nil, err
  363. }
  364. switch e.Op {
  365. case lexer.TokenMinus:
  366. return -toFloatSimple(val), nil
  367. case lexer.TokenNOT:
  368. return !toBoolSimple(val), nil
  369. }
  370. return val, nil
  371. case *parser.ParenExpr:
  372. return evalExprSimple(e.Expr)
  373. }
  374. return nil, fmt.Errorf("unsupported expression type: %T", expr)
  375. }
  376. func evalBinarySimple(op lexer.TokenType, left, right interface{}) (interface{}, error) {
  377. switch op {
  378. case lexer.TokenPlus:
  379. return toFloatSimple(left) + toFloatSimple(right), nil
  380. case lexer.TokenMinus:
  381. return toFloatSimple(left) - toFloatSimple(right), nil
  382. case lexer.TokenStar:
  383. return toFloatSimple(left) * toFloatSimple(right), nil
  384. case lexer.TokenSlash:
  385. r := toFloatSimple(right)
  386. if r == 0 {
  387. return nil, nil
  388. }
  389. return toFloatSimple(left) / r, nil
  390. case lexer.TokenEq:
  391. return compareSimple(left, right) == 0, nil
  392. case lexer.TokenNeq:
  393. return compareSimple(left, right) != 0, nil
  394. case lexer.TokenLt:
  395. return compareSimple(left, right) < 0, nil
  396. case lexer.TokenGt:
  397. return compareSimple(left, right) > 0, nil
  398. case lexer.TokenLte:
  399. return compareSimple(left, right) <= 0, nil
  400. case lexer.TokenGte:
  401. return compareSimple(left, right) >= 0, nil
  402. case lexer.TokenAND:
  403. return toBoolSimple(left) && toBoolSimple(right), nil
  404. case lexer.TokenOR:
  405. return toBoolSimple(left) || toBoolSimple(right), nil
  406. }
  407. return nil, fmt.Errorf("unsupported operator: %v", op)
  408. }
  409. func toFloatSimple(v interface{}) float64 {
  410. switch val := v.(type) {
  411. case int64:
  412. return float64(val)
  413. case float64:
  414. return val
  415. case bool:
  416. if val {
  417. return 1
  418. }
  419. return 0
  420. }
  421. return 0
  422. }
  423. func toBoolSimple(v interface{}) bool {
  424. switch val := v.(type) {
  425. case bool:
  426. return val
  427. case int64:
  428. return val != 0
  429. case float64:
  430. return val != 0
  431. }
  432. return false
  433. }
  434. func compareSimple(a, b interface{}) int {
  435. fa := toFloatSimple(a)
  436. fb := toFloatSimple(b)
  437. if fa < fb {
  438. return -1
  439. }
  440. if fa > fb {
  441. return 1
  442. }
  443. return 0
  444. }
  445. func printHelp() {
  446. fmt.Println("PizzaSQL Commands:")
  447. fmt.Println(" help, \\h Show this help")
  448. fmt.Println(" quit, \\q Exit the program")
  449. fmt.Println(" tables, \\dt List all tables")
  450. fmt.Println(" clear, \\c Clear the input buffer")
  451. fmt.Println()
  452. fmt.Println("SQL Statements (end with semicolon):")
  453. fmt.Println(" SELECT ... FROM ... WHERE ...")
  454. fmt.Println(" INSERT INTO table (cols) VALUES (...)")
  455. fmt.Println(" UPDATE table SET col = val WHERE ...")
  456. fmt.Println(" DELETE FROM table WHERE ...")
  457. fmt.Println(" CREATE TABLE table (col TYPE, ...)")
  458. fmt.Println(" DROP TABLE table")
  459. fmt.Println()
  460. fmt.Println("Expression Mode (SELECT without FROM):")
  461. fmt.Println(" SELECT 1 + 2 * 3;")
  462. fmt.Println(" SELECT UPPER('hello');")
  463. fmt.Println()
  464. fmt.Println("Export/Import:")
  465. fmt.Println(" pizzasql -db mydb -o backup.sql Export database to SQL file")
  466. fmt.Println(" pizzasql -db mydb -table users -o t.sql Export single table")
  467. fmt.Println(" pizzasql -db mydb -o backup.sql -drop Include DROP TABLE statements")
  468. fmt.Println(" pizzasql -db mydb -i backup.sql Import SQL file")
  469. fmt.Println(" pizzasql -db mydb -i backup.sql -ignore-errors Continue on errors")
  470. fmt.Println()
  471. fmt.Println("CSV Format:")
  472. fmt.Println(" pizzasql -db mydb -table users -o users.csv Export table to CSV")
  473. fmt.Println(" pizzasql -db mydb -table users -i users.csv Import CSV to table")
  474. fmt.Println(" pizzasql -db mydb -table new -i data.csv -create-table Create table from CSV")
  475. }
  476. func listTables(schema *storage.SchemaManager) {
  477. tables, err := schema.ListTables()
  478. if err != nil {
  479. fmt.Fprintf(os.Stderr, "Error: %v\n", err)
  480. return
  481. }
  482. if len(tables) == 0 {
  483. fmt.Println("No tables found")
  484. return
  485. }
  486. fmt.Println("Tables:")
  487. for _, t := range tables {
  488. fmt.Printf(" %s\n", t)
  489. }
  490. }
  491. func runExport() {
  492. // Connect to PizzaKV
  493. pool, err := storage.NewKVPool(*kvAddr, *poolSize, *timeout)
  494. if err != nil {
  495. fmt.Fprintf(os.Stderr, "Failed to connect to PizzaKV at %s: %v\n", *kvAddr, err)
  496. os.Exit(1)
  497. }
  498. defer pool.Close()
  499. schema := storage.NewSchemaManager(pool, *database)
  500. table := storage.NewTableManager(pool, schema, *database)
  501. // Determine format from flag or file extension
  502. format := strings.ToLower(*exportFormat)
  503. if format == "" {
  504. format = detectFileFormat(*exportFile)
  505. }
  506. switch format {
  507. case "csv":
  508. // CSV export requires a table name
  509. if *exportTable == "" {
  510. fmt.Fprintf(os.Stderr, "CSV export requires -table flag\n")
  511. os.Exit(1)
  512. }
  513. csvOpts := csvexport.DefaultExportOptions()
  514. csvOpts.Table = *exportTable
  515. data, err := csvexport.ExportTableToBytes(schema, table, csvOpts)
  516. if err != nil {
  517. fmt.Fprintf(os.Stderr, "Export failed: %v\n", err)
  518. os.Exit(1)
  519. }
  520. err = os.WriteFile(*exportFile, data, 0644)
  521. if err != nil {
  522. fmt.Fprintf(os.Stderr, "Failed to write file: %v\n", err)
  523. os.Exit(1)
  524. }
  525. fmt.Printf("Exported table '%s' to %s (CSV)\n", *exportTable, *exportFile)
  526. default: // sql, sqlite
  527. // Configure export options
  528. opts := sqlexport.ExportOptions{
  529. IncludeData: true,
  530. DropTables: *exportDrop,
  531. }
  532. if *exportTable != "" {
  533. opts.Tables = []string{*exportTable}
  534. }
  535. // Export database
  536. sql, err := sqlexport.ExportDatabase(schema, table, opts)
  537. if err != nil {
  538. fmt.Fprintf(os.Stderr, "Export failed: %v\n", err)
  539. os.Exit(1)
  540. }
  541. // Write to file
  542. err = os.WriteFile(*exportFile, []byte(sql), 0644)
  543. if err != nil {
  544. fmt.Fprintf(os.Stderr, "Failed to write file: %v\n", err)
  545. os.Exit(1)
  546. }
  547. fmt.Printf("Exported database '%s' to %s\n", *database, *exportFile)
  548. }
  549. }
  550. func runImport() {
  551. // Connect to PizzaKV
  552. pool, err := storage.NewKVPool(*kvAddr, *poolSize, *timeout)
  553. if err != nil {
  554. fmt.Fprintf(os.Stderr, "Failed to connect to PizzaKV at %s: %v\n", *kvAddr, err)
  555. os.Exit(1)
  556. }
  557. defer pool.Close()
  558. schema := storage.NewSchemaManager(pool, *database)
  559. table := storage.NewTableManager(pool, schema, *database)
  560. exec := executor.New(schema, table)
  561. exec.SyncCatalog()
  562. // Read file
  563. data, err := os.ReadFile(*importFile)
  564. if err != nil {
  565. fmt.Fprintf(os.Stderr, "Failed to read file: %v\n", err)
  566. os.Exit(1)
  567. }
  568. // Determine format from flag or file extension
  569. format := strings.ToLower(*exportFormat)
  570. if format == "" {
  571. format = detectFileFormat(*importFile)
  572. }
  573. switch format {
  574. case "csv":
  575. // CSV import requires a table name
  576. if *exportTable == "" {
  577. fmt.Fprintf(os.Stderr, "CSV import requires -table flag\n")
  578. os.Exit(1)
  579. }
  580. csvOpts := csvimport.DefaultImportOptions()
  581. csvOpts.TableName = *exportTable
  582. csvOpts.IgnoreErrors = *ignoreErrors
  583. csvOpts.CreateTable = *createTable
  584. result, err := csvimport.ImportCSV(strings.NewReader(string(data)), schema, table, csvOpts)
  585. if err != nil {
  586. fmt.Fprintf(os.Stderr, "Import failed: %v\n", err)
  587. if len(result.Errors) > 0 {
  588. fmt.Fprintf(os.Stderr, "Errors:\n")
  589. for _, e := range result.Errors {
  590. fmt.Fprintf(os.Stderr, " - %s\n", e)
  591. }
  592. }
  593. os.Exit(1)
  594. }
  595. fmt.Printf("CSV import completed successfully\n")
  596. fmt.Printf(" Rows imported: %d\n", result.RowsImported)
  597. if result.RowsSkipped > 0 {
  598. fmt.Printf(" Rows skipped: %d\n", result.RowsSkipped)
  599. }
  600. if result.TableCreated {
  601. fmt.Printf(" Table created: %s\n", *exportTable)
  602. }
  603. if len(result.Errors) > 0 {
  604. fmt.Printf(" Warnings/Errors: %d\n", len(result.Errors))
  605. for _, e := range result.Errors {
  606. fmt.Printf(" - %s\n", e)
  607. }
  608. }
  609. default: // sql, sqlite
  610. // Configure import options
  611. opts := sqlimport.ImportOptions{
  612. IgnoreErrors: *ignoreErrors,
  613. }
  614. // Import SQL
  615. result, err := sqlimport.ImportSQL(exec, string(data), opts)
  616. if err != nil {
  617. fmt.Fprintf(os.Stderr, "Import failed: %v\n", err)
  618. if len(result.Errors) > 0 {
  619. fmt.Fprintf(os.Stderr, "Errors:\n")
  620. for _, e := range result.Errors {
  621. fmt.Fprintf(os.Stderr, " - %s\n", e)
  622. }
  623. }
  624. os.Exit(1)
  625. }
  626. fmt.Printf("Import completed successfully\n")
  627. fmt.Printf(" Statements executed: %d\n", result.StatementsExecuted)
  628. if len(result.TablesCreated) > 0 {
  629. fmt.Printf(" Tables created: %s\n", strings.Join(result.TablesCreated, ", "))
  630. }
  631. if len(result.TablesDropped) > 0 {
  632. fmt.Printf(" Tables dropped: %s\n", strings.Join(result.TablesDropped, ", "))
  633. }
  634. fmt.Printf(" Rows inserted: %d\n", result.RowsInserted)
  635. if len(result.Errors) > 0 {
  636. fmt.Printf(" Warnings/Errors: %d\n", len(result.Errors))
  637. for _, e := range result.Errors {
  638. fmt.Printf(" - %s\n", e)
  639. }
  640. }
  641. }
  642. }
  643. func detectFileFormat(filename string) string {
  644. lower := strings.ToLower(filename)
  645. if strings.HasSuffix(lower, ".csv") {
  646. return "csv"
  647. }
  648. if strings.HasSuffix(lower, ".db") || strings.HasSuffix(lower, ".sqlite") || strings.HasSuffix(lower, ".sqlite3") {
  649. return "sqlite"
  650. }
  651. return "sql"
  652. }
  653. func runHTTPServer() {
  654. // Connect to PizzaKV
  655. pool, err := storage.NewKVPool(*kvAddr, *poolSize, *timeout)
  656. if err != nil {
  657. fmt.Fprintf(os.Stderr, "Failed to connect to PizzaKV at %s: %v\n", *kvAddr, err)
  658. fmt.Fprintf(os.Stderr, "Make sure PizzaKV is running: pizzakv\n")
  659. os.Exit(1)
  660. }
  661. defer pool.Close()
  662. // Create database manager for multi-database support
  663. dbManagerConfig := &storage.DatabaseManagerConfig{
  664. DefaultDatabase: *database,
  665. AutoCreate: true, // Auto-create databases on first access
  666. }
  667. dbManager := storage.NewDatabaseManager(pool, dbManagerConfig)
  668. // Configure HTTP server
  669. config := httpserver.DefaultConfig()
  670. config.Host = *httpHost
  671. config.Port = *httpPort
  672. config.EnableCORS = *httpCORS
  673. config.EnableAuth = *httpAuth
  674. config.EnableCompression = *httpCompression
  675. config.EnableLogging = !*httpQuiet
  676. if *apiKeys != "" {
  677. config.APIKeys = strings.Split(*apiKeys, ",")
  678. }
  679. // Create and start server with multi-database support
  680. server := httpserver.NewWithDatabaseManager(config, dbManager)
  681. var pprofServer *http.Server
  682. if startPprofServerHook != nil {
  683. pprofServer = startPprofServerHook()
  684. }
  685. // Handle graceful shutdown
  686. stop := make(chan os.Signal, 1)
  687. signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
  688. // Start server in goroutine
  689. go func() {
  690. if err := server.Start(); err != nil && err != http.ErrServerClosed {
  691. fmt.Fprintf(os.Stderr, "HTTP server error: %v\n", err)
  692. os.Exit(1)
  693. }
  694. }()
  695. fmt.Printf("PizzaSQL HTTP server started on http://%s:%d\n", *httpHost, *httpPort)
  696. if pprofServer != nil {
  697. fmt.Printf("pprof debug server started on http://%s/debug/pprof/\n", pprofServer.Addr)
  698. }
  699. fmt.Printf("Default database: %s\n", *database)
  700. fmt.Printf("PizzaKV: %s\n", *kvAddr)
  701. fmt.Println()
  702. fmt.Println("Multi-database support enabled!")
  703. fmt.Println("Use the X-Database header to select a database per request.")
  704. fmt.Println()
  705. fmt.Println("Endpoints:")
  706. fmt.Println(" POST /query - Execute SQL query")
  707. fmt.Println(" POST /execute - Batch execution")
  708. fmt.Println(" GET /schema/tables - List tables")
  709. fmt.Println(" GET /schema/tables/{name} - Table schema")
  710. fmt.Println(" GET /health - Health check")
  711. fmt.Println(" GET /stats - Statistics")
  712. fmt.Println(" GET /metrics - Prometheus metrics")
  713. fmt.Println(" POST /transaction/begin - Begin transaction")
  714. fmt.Println(" POST /transaction/commit - Commit transaction")
  715. fmt.Println(" POST /transaction/rollback - Rollback transaction")
  716. fmt.Println()
  717. fmt.Println("Examples:")
  718. fmt.Printf(" # Query default database\n")
  719. fmt.Printf(" curl -X POST http://%s:%d/query -H 'Content-Type: application/json' -d '{\"sql\":\"SELECT 1+1\"}'\n", *httpHost, *httpPort)
  720. fmt.Println()
  721. fmt.Printf(" # Query specific database using X-Database header\n")
  722. fmt.Printf(" curl -X POST http://%s:%d/query -H 'Content-Type: application/json' -H 'X-Database: tenant_db' -d '{\"sql\":\"SELECT * FROM users\"}'\n", *httpHost, *httpPort)
  723. fmt.Println()
  724. fmt.Println("Press Ctrl+C to stop")
  725. <-stop
  726. fmt.Println("\nShutting down server...")
  727. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  728. defer cancel()
  729. if err := server.Shutdown(ctx); err != nil {
  730. fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
  731. }
  732. if pprofServer != nil {
  733. if err := pprofServer.Shutdown(ctx); err != nil {
  734. fmt.Fprintf(os.Stderr, "Error during pprof shutdown: %v\n", err)
  735. }
  736. }
  737. fmt.Println("Server stopped")
  738. }
  739. // launchPizzaKV starts a PizzaKV instance and updates kvAddr
  740. func launchPizzaKV() error {
  741. kvManager = kvmanager.NewManager()
  742. kvManager.SetInfoFile(*kvInfoFile)
  743. // Clean up any stale process info
  744. if err := kvmanager.CleanupStaleProcess(*kvInfoFile); err != nil {
  745. // Check if it's an "already running" error
  746. if strings.Contains(err.Error(), "already running") {
  747. fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
  748. fmt.Fprintf(os.Stderr, "\nOptions:\n")
  749. fmt.Fprintf(os.Stderr, " 1. Use the existing instance: remove -kv flag and use -kvaddr=localhost:<port>\n")
  750. fmt.Fprintf(os.Stderr, " 2. Stop it: kill %d\n", getPIDFromFile(*kvInfoFile))
  751. fmt.Fprintf(os.Stderr, " 3. Delete the info file: rm %s\n\n", *kvInfoFile)
  752. return err
  753. }
  754. return fmt.Errorf("failed to cleanup stale process: %w", err)
  755. }
  756. fmt.Println("Starting PizzaKV...")
  757. info, err := kvManager.Start(*kvFlags)
  758. if err != nil {
  759. return err
  760. }
  761. fmt.Printf("\nPizzaKV started on %s (PID: %d)\n", info.Addr, info.PID)
  762. fmt.Printf("Info written to: %s\n", *kvInfoFile)
  763. fmt.Println("PizzaKV is ready!")
  764. // Update kvAddr to use the launched instance
  765. *kvAddr = info.Addr
  766. return nil
  767. }
  768. // stopPizzaKV stops the managed PizzaKV instance
  769. func stopPizzaKV() {
  770. if kvManager != nil {
  771. fmt.Println("Stopping PizzaKV...")
  772. if err := kvManager.Stop(); err != nil {
  773. fmt.Fprintf(os.Stderr, "Error stopping PizzaKV: %v\n", err)
  774. } else {
  775. fmt.Println("PizzaKV stopped")
  776. }
  777. }
  778. }
  779. // getPIDFromFile reads the PID from the info file
  780. func getPIDFromFile(path string) int {
  781. mgr := kvmanager.NewManager()
  782. mgr.SetInfoFile(path)
  783. info, err := mgr.LoadInfo()
  784. if err != nil {
  785. return 0
  786. }
  787. return info.PID
  788. }