main.go 21 KB

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