main.go 24 KB

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