main.go 25 KB

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