main.go 21 KB

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