main.go 30 KB

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