Skip to content

Add PostgreSQL Client, CLI Configuration, and Bubbletea Query UI - #44

Open
rahulc0dy wants to merge 2 commits into
feat/sql-gatewayfrom
feat/cli-tui
Open

rahulc0dy wants to merge 2 commits into
feat/sql-gatewayfrom
feat/cli-tui

Conversation

@rahulc0dy

@rahulc0dy rahulc0dy commented Sep 12, 2026 •

Copy link
Copy Markdown
Member

Issue Reference

  • Fixes #

Summary by CodeRabbit

  • New Features
    • Added an interactive command-line SQL client for connecting to the PenguinDB Gateway.
    • Added query execution, database switching, table information, multi-line SQL, history, and CSV export.
    • Added confirmation prompts for potentially destructive statements.
    • Added styled, responsive result tables with row numbers, timing, NULL values, errors, and command status.
    • Added loading indicators, window-aware layouts, help shortcuts, startup connection details, and status feedback.
  • Configuration
    • Added host, port, user, and database options with configuration-file defaults and command-line overrides.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

Walkthrough

The PR adds a PostgreSQL wire-protocol client, CLI startup and configuration handling, responsive terminal rendering, and a Bubbletea v2 interactive query interface.

Changes

PenguinDB CLI and interactive terminal

Layer / File(s) Summary
PostgreSQL wire-protocol client
cmd/cli/client.go
Adds PGClient, query result types, startup and SSL negotiation, authentication handling, query execution, response parsing, database switching, timeouts, error parsing, and connection shutdown.
CLI configuration and startup
cmd/cli/main.go, go.mod
Loads gateway settings from configs/config.json, applies command-line flags, connects the client, starts Bubbletea v2, and updates the Charm dependencies.
Responsive terminal presentation
cmd/cli/styles.go
Adds width-aware banners, prompts, result tables, errors, confirmations, history, help panels, numeric alignment, truncation, and row numbering.
Interactive query controls
cmd/cli/ui.go
Adds Bubbletea v2 APIs, destructive-query confirmation, query history, \history, \export, loading indicators, responsive sizing, result storage, scrolling, and CSV export.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Bubbletea CLI
  participant PGClient
  participant PenguinDB Gateway
  User->>Bubbletea CLI: Enter SQL statement
  Bubbletea CLI->>PGClient: ExecuteQuery(sql)
  PGClient->>PenguinDB Gateway: Send PostgreSQL query frame
  PenguinDB Gateway-->>PGClient: Return result frames
  PGClient-->>Bubbletea CLI: Return query result
  Bubbletea CLI-->>User: Render formatted result or confirmation
Loading

Priority: ➖ Normal

Merge Risk: 🟠 High · up to 5a1d2

The CLI can mis-handle concurrent queries, hide connection failures, and allow destructive statements to bypass confirmation; the server also uses a gRPC version affected by a denial-of-service vulnerability. These issues should be fixed before merge.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot changed the title @coderabbitai Add PostgreSQL Client, CLI Configuration, and Bubbletea Query UI Sep 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 0400c31b-8084-4da2-9c4e-fdf9fc21d700

📥 Commits

Reviewing files that changed from the base of the PR and between 09bccc1 and 6d0dcbf.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • cmd/cli/client.go
  • cmd/cli/main.go
  • cmd/cli/styles.go
  • cmd/cli/ui.go
  • go.mod

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread cmd/cli/client.go
Comment on lines +113 to +114
c.conn.Write(lenBuf[:])
c.conn.Write(payload.Bytes())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Propagate startup packet write failures.

Connect sets a 30-second deadline before the two unchecked startup writes. net.Conn.Write can return a partial count and an error. If either write fails, Connect ignores the failure and may wait for the handshake read until the deadline before Close runs. The CLI then exits with a misleading connection error.

Write the complete startup packet with a checked helper. Close the connection immediately on failure.

Comment thread cmd/cli/client.go
c.Close()
return err
}
pData := make([]byte, pLen-4)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Validate server-controlled frame lengths before allocation.

Connect and ExecuteQuery pass pLen-4 directly to make; values such as pLen == 0 cause a negative-size panic, and no application maximum limits excessive allocations. Negative row and column counts also reach make. Any value length other than -1 can be negative, and a positive value length larger than r.Len() causes an oversized allocation before bytes.Reader.Read returns a short read.

Reject invalid outer lengths and negative counts. Enforce a maximum packet size. Accept -1 only for NULL values, and require other value lengths to be nonnegative and no greater than the remaining payload. The current code does not perform an out-of-bounds read because bytes.Reader.Read truncates the read, but it ignores that short-read result.

Comment thread cmd/cli/client.go
Comment on lines +168 to +170
if err := c.Connect(); err != nil {
return nil, err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return a non-nil result when reconnection fails.

This path returns nil, err, but cmd/cli/ui.go discards the error and forwards only the result. FormatResultTable then converts the nil result to empty output.

Return a QueryResult with Error set, as the later network failure paths do. Alternatively, update execQueryCmd to preserve the separate error.

Comment thread cmd/cli/styles.go Outdated
}

if res.Error != nil {
return FormatErrorBox(res.Error.Error())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize database text before terminal rendering.

The renderer passes errors, column names, and cell values directly to the terminal. A stored value or server response can contain ANSI, OSC, or other control sequences. These sequences can spoof output or modify terminal state.

Remove terminal control sequences before Lipgloss renders untrusted text. Preserve only explicitly supported characters such as newline and tab where required.

Also applies to: 193-193, 204-204

Comment thread cmd/cli/ui.go Outdated
Comment on lines +85 to +96
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c":
if len(m.multiBuffer) > 0 {
m.multiBuffer = nil
m.textInput.SetValue("")
m.textInput.Prompt = FormatPrompt(m.activeDb)
m.appendOutput("^C")
return m, nil
}
m.client.Close()
return m, tea.Quit

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Serialize PGClient.Close with in-flight ExecuteQuery.

execQueryCmd runs ExecuteQuery asynchronously. If Ctrl-C arrives during execution, Update calls m.client.Close concurrently. Although net.Conn supports concurrent method calls, PGClient does not synchronize its conn field. Close sets c.conn = nil while ExecuteQuery reads it for deadlines, writes, reads, and deferred cleanup. This can cause a data race or a nil-connection panic. Closing the socket can also interrupt the query.

Protect the connection lifecycle with synchronization, or cancel and wait for ExecuteQuery before Close clears c.conn. Ensure error cleanup inside ExecuteQuery does not re-enter a lock held for the execution. This is separate from preventing multiple query submissions.

Comment thread cmd/cli/ui.go Outdated
Comment on lines +193 to +198
if strings.HasSuffix(fullSql, ";") {
m.history = append(m.history, fullSql)
m.multiBuffer = nil
m.textInput.Prompt = FormatPrompt(m.activeDb)
m.loading = true
return m, execQueryCmd(m.client, fullSql)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Prevent concurrent queries on the shared PostgreSQL connection.

The Enter handler does not check m.loading, so it can return another execQueryCmd before the first QueryResultMsg arrives. Bubble Tea v1.3.10 runs commands asynchronously. Each command calls PGClient.ExecuteQuery, which directly reads from and writes to c.conn. PGClient has no mutex or other serialization. Serialize or queue query submissions until the current query completes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: f9e2435f-64bd-4b7a-adc4-1739bf16489d

📥 Commits

Reviewing files that changed from the base of the PR and between 6d0dcbf and 5a1d201.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • cmd/cli/main.go
  • cmd/cli/styles.go
  • cmd/cli/ui.go
  • go.mod

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread cmd/cli/styles.go
Comment on lines +193 to +195
if contentWidth < 20 {
contentWidth = bannerArtWidth
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep narrow terminal layouts within availWidth.

When availWidth is positive and narrow, RenderBanner can set contentWidth to 78. FormatHistory can leave long entries unwrapped when availWidth <= 14. FormatHelpPanel can reserve 40 columns for commands and leave descriptions unconstrained. These paths can overflow narrow terminals.

Apply one consistent width policy across the three formatters. Account for borders and padding, and reduce the help command column when necessary. The impact is localized visual overflow in these panels, not a major workflow failure.

Comment thread cmd/cli/styles.go
Comment on lines +388 to +400
if total <= budget || budget <= numCols*minColWidth {
return natural, false
}

scaled := make([]int, numCols)
for c, w := range natural {
nw := int(float64(w) / float64(total) * float64(budget))
if nw < minColWidth {
nw = minColWidth
}
scaled[c] = nw
}
return scaled, true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep result-table widths within the available terminal width.

computeColumnWidths can return widths whose sum exceeds budget. The minimum-width clamp can increase scaled columns after proportional allocation. The budget <= numCols*minColWidth branch can also return natural widths above the budget. FormatResultTable uses these widths when rendering the table, so narrow terminals can overflow. Apply a narrow-budget allocation policy that keeps the returned widths within the budget, including when the budget is below the minimum-width total.

Comment thread cmd/cli/styles.go
Comment on lines +431 to +435
r := []rune(s)
if len(r) <= width-1 {
return s
}
return string(r[:width-1]) + "…"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Truncate by terminal display width.

This code slices by rune count after measuring with lipgloss.Width. Wide characters can still exceed width. For example, three double-width characters remain unchanged when width is four.

Build the prefix using display width, then append the ellipsis.

Comment thread cmd/cli/ui.go
Comment on lines +24 to +26
reDropOrTruncate = regexp.MustCompile(`(?i)^\s*(DROP\s+(TABLE|DATABASE)|TRUNCATE)\b`)
reDeleteOrUpdate = regexp.MustCompile(`(?i)^\s*(DELETE\s+FROM|UPDATE)\b`)
reHasWhere = regexp.MustCompile(`(?i)\bWHERE\b`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not classify destructive SQL with raw regular expressions.

reHasWhere matches text inside literals and comments. For example, UPDATE users SET note = 'WHERE'; bypasses confirmation but updates every row. Leading comments also prevent the anchored DROP and TRUNCATE expressions from matching.

Tokenize or parse the SQL before checking statement type and top-level WHERE clauses.

Comment thread cmd/cli/ui.go
if err != nil {
return FormatErrorBox(fmt.Sprintf("could not create file: %v", err), m.termWidth())
}
defer f.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Report file-close failures before confirming export success.

The deferred f.Close() error is discarded. A filesystem can accept writes but fail during final close, while this function still reports a successful export.

Close the file explicitly after w.Flush(). Return an error if Close fails.

Comment thread go.mod
charm.land/lipgloss/v2 v2.0.6
github.com/google/uuid v1.6.0
google.golang.org/grpc v1.82.0
google.golang.org/grpc v1.82.1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Upgrade gRPC beyond the affected version range.

google.golang.org/grpc v1.82.1 is affected by GHSA-vp52-pcj8-j9qc. An unauthenticated HTTP/2 attacker can exhaust heap memory through fragmented DATA frames and cause a denial of service. The advisory lists v1.83.1 as patched. Upgrade to v1.83.1 or a later patched release, then refresh the module sums. (github.com)

🧰 Tools
🪛 OSV Scanner (2.5.1)

[HIGH] 10-10: google.golang.org/grpc 1.82.1: gRPC-Go: Heap Memory Exhaustion (OOM) via HTTP/2 DATA Frame Fragmentation

(GHSA-vp52-pcj8-j9qc)

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant