RunShell starts an interactive SQL shell with a fully initialized DuckDB connection. It uses the same CreateDBConnection path as the PostgreSQL server, so extensions, DuckLake, and pg_catalog views are all available.
(cfg Config)
| 18 | // It uses the same CreateDBConnection path as the PostgreSQL server, so extensions, |
| 19 | // DuckLake, and pg_catalog views are all available. |
| 20 | func RunShell(cfg Config) { |
| 21 | sem := make(chan struct{}, 1) |
| 22 | if err := bootstrapBundledExtensions(cfg.DataDir); err != nil { |
| 23 | slog.Error("Failed to bootstrap bundled DuckDB extensions.", "source", bundledDuckDBExtensionsDir, "extension_directory", filepath.Join(cfg.DataDir, "extensions"), "error", err) |
| 24 | os.Exit(1) |
| 25 | } |
| 26 | db, err := CreateDBConnection(cfg, sem, "shell", processStartTime, processVersion) |
| 27 | if err != nil { |
| 28 | slog.Error("Failed to create database connection.", "error", err) |
| 29 | os.Exit(1) |
| 30 | } |
| 31 | defer func() { _ = db.Close() }() |
| 32 | |
| 33 | stopRefresh := StartCredentialRefresh(db, cfg.DuckLake) |
| 34 | defer stopRefresh() |
| 35 | |
| 36 | fmt.Fprintln(os.Stderr, "Duckgres shell (type \\q to exit)") |
| 37 | |
| 38 | scanner := bufio.NewScanner(os.Stdin) |
| 39 | scanner.Buffer(make([]byte, bufio.MaxScanTokenSize), 1024*1024) // 1MB max line |
| 40 | |
| 41 | var buf strings.Builder |
| 42 | promptPrimary := "duckgres> " |
| 43 | promptContinuation := " -> " |
| 44 | |
| 45 | // mu protects queryCancel and buf from concurrent access |
| 46 | // between the main goroutine and the signal goroutine. |
| 47 | var mu sync.Mutex |
| 48 | var queryCancel context.CancelFunc |
| 49 | |
| 50 | // Channel for query cancellation via Ctrl+C |
| 51 | cancelCh := make(chan os.Signal, 1) |
| 52 | signal.Notify(cancelCh, syscall.SIGINT) |
| 53 | |
| 54 | done := make(chan struct{}) |
| 55 | go func() { |
| 56 | for { |
| 57 | select { |
| 58 | case <-cancelCh: |
| 59 | mu.Lock() |
| 60 | cancel := queryCancel |
| 61 | if cancel != nil { |
| 62 | mu.Unlock() |
| 63 | cancel() |
| 64 | } else { |
| 65 | // Not in a query -- clear the current input buffer. |
| 66 | buf.Reset() |
| 67 | mu.Unlock() |
| 68 | fmt.Fprint(os.Stderr, "\n"+promptPrimary) |
| 69 | } |
| 70 | case <-done: |
| 71 | return |
| 72 | } |
| 73 | } |
| 74 | }() |
| 75 | |
| 76 | defer func() { |
| 77 | signal.Stop(cancelCh) |
no test coverage detected