main.go 24 KB

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