Skip to content

Conversation

@juanpabloxk
Copy link
Contributor

@juanpabloxk juanpabloxk commented Jul 17, 2025

Closes #147

Previous implementation

sequenceDiagram
    participant User as User/Code
    participant SS as SharedStorage::Postgres
    participant Req as Utils::Postgres::Request
    participant PG as Postgres Server

    User->>SS: new(read_options, write_options)
    activate SS
    Note over SS: Instance created with options

    User->>SS: read()
    activate SS
    SS->>Req: execute({connection: read_options[:connection], query: read_query})
    activate Req
    Req->>PG: PG::Connection.new(connection params)
    activate PG
    Note over Req,PG: Connection established
    Req->>PG: exec_params(sentence, params) or exec(query)
    PG->>Req: Query results
    Req->>Req: map results to symbols
    deactivate PG
    Note over Req,PG: Connection not explicitly closed (auto-closed on GC)
    Req->>SS: Return mapped results
    deactivate Req
    SS->>SS: Build Read response
    SS->>User: Return Read object
    deactivate SS

    User->>SS: set_in_process()
    activate SS
    alt avoid_process == true or id nil
        SS->>User: Return nil
    else
        SS->>Req: execute({connection: read_options[:connection], query: update_query(id, "in process")})
        activate Req
        Req->>PG: PG::Connection.new(connection params)
        activate PG
        Note over Req,PG: Connection established
        Req->>PG: exec_params(sentence, params)
        PG->>Req: Update result
        deactivate PG
        Note over Req,PG: Connection not explicitly closed
        Req->>SS: Return result
        deactivate Req
        SS->>User: Return result
    end
    deactivate SS

    User->>SS: write(data)
    activate SS
    SS->>Req: execute({connection: write_options[:connection], query: write_query(data)})
    activate Req
    Req->>PG: PG::Connection.new(connection params)
    activate PG
    Note over Req,PG: Connection established
    Req->>PG: exec_params(sentence, params)
    PG->>Req: Insert result
    deactivate PG
    Note over Req,PG: Connection not explicitly closed
    Req->>SS: Return result
    deactivate Req
    SS->>User: Return result
    deactivate SS

    User->>SS: set_processed()
    activate SS
    alt avoid_process == true or id nil
        SS->>User: Return nil
    else
        SS->>Req: execute({connection: read_options[:connection], query: update_query(id, "processed")})
        activate Req
        Req->>PG: PG::Connection.new(connection params)
        activate PG
        Note over Req,PG: Connection established
        Req->>PG: exec_params(sentence, params)
        PG->>Req: Update result
        deactivate PG
        Note over Req,PG: Connection not explicitly closed
        Req->>SS: Return result
        deactivate Req
        SS->>User: Return result
    end
    deactivate SS

    deactivate SS
Loading

New Implementation

sequenceDiagram
    participant User as User/Code
    participant SS as SharedStorage::Postgres
    participant RConn as Read Connection (Utils::Postgres::Connection)
    participant WConn as Write Connection (Utils::Postgres::Connection)
    participant PG as Postgres Server

    User->>SS: new(options)
    activate SS
    Note over SS: Instance created with @read_options and @write_options

    User->>SS: read()
    activate SS
    alt @read_connection nil
        SS->>RConn: new(read_options[:connection])
        activate RConn
        RConn->>PG: PG::Connection.new(params)
        activate PG
        Note over RConn,PG: Read connection established
    end
    SS->>RConn: query(read_query)  # [sentence, params]
    RConn->>PG: exec_params(sentence, params)
    PG->>RConn: Query results
    RConn->>RConn: map results to symbols
    RConn->>SS: Return mapped results
    SS->>SS: Build Read response
    SS->>User: Return Read object
    deactivate SS

    User->>SS: set_in_process()
    activate SS
    alt avoid_process == true or read_response.id nil
        SS->>User: Return nil
    else
        alt @read_connection nil
            SS->>RConn: new(read_options[:connection])
            activate RConn
            RConn->>PG: PG::Connection.new(params)
            activate PG
        end
        SS->>RConn: query(update_query(id, "in process"))
        RConn->>PG: exec_params(sentence, params)
        PG->>RConn: Update result
        RConn->>SS: Return result
        SS->>User: Return result
    end
    deactivate SS

    User->>SS: write(data)
    activate SS
    alt @write_connection nil
        SS->>WConn: new(write_options[:connection])
        activate WConn
        WConn->>PG: PG::Connection.new(params)
        activate PG
        Note over WConn,PG: Write connection established (may be same or different PG)
    end
    SS->>WConn: query(write_query(data))
    WConn->>PG: exec_params(sentence, params)
    PG->>WConn: Insert result
    WConn->>SS: Return result
    SS->>User: Return result
    deactivate SS

    User->>SS: set_processed()
    activate SS
    alt avoid_process == true or read_response.id nil
        SS->>User: Return nil
    else
        SS->>RConn: query(update_query(id, "processed"))
        RConn->>PG: exec_params(sentence, params)
        PG->>RConn: Update result
        RConn->>SS: Return result
        SS->>User: Return result
    end
    deactivate SS

    User->>SS: close_connections()
    activate SS
    alt @read_connection exists
        SS->>RConn: finish()
        RConn->>PG: finish
        deactivate PG
        deactivate RConn
    end
    alt @write_connection exists
        SS->>WConn: finish()
        WConn->>PG: finish
        deactivate PG
        deactivate WConn
    end
    deactivate SS

    deactivate SS
Loading

Testing

I tested this new implementation with two use cases from bas_use_cases

  • Birthdays (single connection)
  • OSPO Maintenance (Multiple connections, one per GitHub issue)

Adding some logging to know when connections are instantiated:

# lib/bas/shared_storage/postgres.rb:55

def establish_connection(action)
  case action
  when :read
    puts "\nEstablishing connection for read" if @read_connection.nil?
    @read_connection ||= Utils::Postgres::Connection.new(read_options[:connection])
  when :write
    puts "\nEstablishing connection for write" if @write_connection.nil?
    @write_connection ||= Utils::Postgres::Connection.new(write_options[:connection])
  end
end
# lib/bas/shared_storage/base.rb

class Connection
  def initialize(params)
    puts "Instantiating connection with params: #{params}"
    @connection = PG::Connection.new(params)
    puts "Conntection instantiated\n\n"
  end

  # ...
end

Only one connection is instantiated no matter how many items are processed:

format create_or_update

Summary by CodeRabbit

  • New Features

    • Improved connection management for PostgreSQL operations, including automatic closing of connections after processing.
    • Added configurable option to control whether connections are closed after processing.
  • Bug Fixes

    • Enhanced handling of database connection lifecycle to prevent resource leaks.
  • Tests

    • Expanded and improved test coverage for connection reuse, closing behavior, and edge cases in PostgreSQL operations.
    • Added new tests for the PostgreSQL connection utility to ensure robust behavior and error handling.

@coderabbitai
Copy link

coderabbitai bot commented Jul 17, 2025

"""

Walkthrough

The changes introduce persistent PostgreSQL connection management to replace per-operation connection creation. A new connection utility class is added, and shared storage logic is refactored to reuse connections for read and write actions, with explicit lifecycle management. Conditional connection closure is implemented in the bot base class, and comprehensive tests are provided for the new connection logic and its integration.

Changes

File(s) Change Summary
lib/bas/bot/base.rb Adds close_connections method, merges default options, and conditionally closes connections post-process.
lib/bas/shared_storage/base.rb Adds empty close_connections method as a placeholder.
lib/bas/shared_storage/postgres.rb Refactors to use persistent connections via new utility, adds connection lifecycle methods.
lib/bas/utils/postgres/connection.rb Introduces a new connection utility class for PostgreSQL with query and finish methods.
spec/bas/bot/base_spec.rb Updates and adds tests for conditional connection closure in the bot base.
spec/bas/shared_storage/postgres_spec.rb Expands tests for connection reuse, closure, and edge cases in shared storage Postgres logic.
spec/bas/utils/postgres/connection_spec.rb Adds comprehensive tests for the new PostgreSQL connection utility class.

Sequence Diagram(s)

sequenceDiagram
    participant Bot as Bas::Bot::Base
    participant Reader as SharedStorageReader
    participant Writer as SharedStorageWriter
    participant ConnUtil as Utils::Postgres::Connection

    Bot->>Reader: read(...)
    activate Reader
    Reader->>ConnUtil: establish_connection(:read)
    ConnUtil-->>Reader: returns connection
    Reader->>ConnUtil: query(...)
    ConnUtil-->>Reader: returns results
    deactivate Reader

    Bot->>Writer: write(...)
    activate Writer
    Writer->>ConnUtil: establish_connection(:write)
    ConnUtil-->>Writer: returns connection
    Writer->>ConnUtil: query(...)
    ConnUtil-->>Writer: returns results
    deactivate Writer

    alt Option: close_connections_after_process == true
        Bot->>Reader: close_connections
        Reader->>ConnUtil: finish
        Bot->>Writer: close_connections
        Writer->>ConnUtil: finish
    end
Loading

Estimated code review effort

3 (~45 minutes)

Poem

A hop, a skip, a database leap—
Connections now steady, no longer heap!
Persistent and tidy, the queries all flow,
Closing when needed, as rabbits well know.
With tests all around and code shining bright,
This bunny declares: the sync’s now just right!
🐇✨
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a03d3db and 8e0ec85.

📒 Files selected for processing (1)
  • spec/bas/utils/postgres/connection_spec.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • spec/bas/utils/postgres/connection_spec.rb
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (5)
lib/bas/utils/postgres/connection.rb (1)

15-25: Consider adding connection health checks and error handling

The query method should handle potential connection errors and provide meaningful error messages. Also, consider checking connection health before executing queries.

 def query(query)
+  raise "Connection is closed" if @connection.nil?
+  
   results = if query.is_a? String
               @connection.exec(query)
             else
               sentence, params = query
 
               @connection.exec_params(sentence, params)
             end
 
   results.map { |result| result.transform_keys(&:to_sym) }
+rescue PG::Error => e
+  raise "Query execution failed: #{e.message}"
 end
spec/bas/bot/base_spec.rb (1)

77-92: Consider extracting common test setup to reduce duplication

Both connection closing tests have similar setup code. Consider using a shared context or helper method to reduce duplication.

+shared_context "bot execution setup" do
+  before do
+    allow(@shared_storage_reader).to receive(:read).and_return(read_response)
+    allow(@shared_storage_writer).to receive(:write).and_return({})
+    allow(@shared_storage_reader).to receive(:close_connections).and_return(true)
+    allow(@shared_storage_writer).to receive(:close_connections).and_return(true)
+    allow_any_instance_of(described_class).to receive(:process).and_return({ success: "ok" })
+  end
+end
+
 it "closes the connections when close_connections_after_process is true" do
-  allow(@shared_storage_reader).to receive(:read).and_return(read_response)
-  allow(@shared_storage_writer).to receive(:write).and_return({})
-  allow(@shared_storage_reader).to receive(:close_connections).and_return(true)
-  allow(@shared_storage_writer).to receive(:close_connections).and_return(true)
-  allow_any_instance_of(described_class).to receive(:process).and_return({ success: "ok" })
+  include_context "bot execution setup"
   allow(@shared_storage_reader).to receive(:respond_to?).with(:close_connections).and_return(true)
   allow(@shared_storage_writer).to receive(:respond_to?).with(:close_connections).and_return(true)
lib/bas/bot/base.rb (1)

35-35: Use truthy check instead of strict equality

The strict equality check with true might miss truthy values. Consider using a more flexible check.

-close_connections if @process_options[:close_connections_after_process].eql?(true)
+close_connections if @process_options[:close_connections_after_process]
lib/bas/shared_storage/postgres.rb (1)

20-26: Consider handling connection errors gracefully

While the connection is established lazily, query execution might fail if the connection is lost. Based on the codebase's error handling pattern (from learnings), errors should bubble up, but you might want to add connection health checks for long-running processes.

Add a comment to document the error handling behavior:

 def read
   establish_connection(:read)
 
+  # Errors from query execution are intentionally not caught here
+  # to allow explicit error handling at higher levels
   first_result = @read_connection.query(read_query).first || {}
spec/bas/utils/postgres/connection_spec.rb (1)

26-33: Consider making the mock data more flexible

The mock setup hardcodes specific test data. Consider parameterizing the mock data or using a factory pattern to make tests more maintainable and flexible for different test scenarios.

-    allow(pg_result).to receive(:map) do
-      # Simulate the actual behavior where each result hash gets its keys transformed to symbols
-      raw_data = [
-        { "id" => "1", "name" => "John Doe", "email" => "john@example.com" },
-        { "id" => "2", "name" => "Jane Smith", "email" => "jane@example.com" }
-      ]
-      raw_data.map { |result| result.transform_keys(&:to_sym) }
-    end
+    # Default mock data that can be overridden in specific tests
+    default_raw_data = [
+      { "id" => "1", "name" => "John Doe", "email" => "john@example.com" },
+      { "id" => "2", "name" => "Jane Smith", "email" => "jane@example.com" }
+    ]
+    allow(pg_result).to receive(:map) do
+      # Allow tests to override this behavior if needed
+      (defined?(@custom_raw_data) ? @custom_raw_data : default_raw_data).map { |result| result.transform_keys(&:to_sym) }
+    end
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d457757 and 5da3aab.

📒 Files selected for processing (8)
  • lib/bas/bot/base.rb (3 hunks)
  • lib/bas/shared_storage/POSTGRES.md (1 hunks)
  • lib/bas/shared_storage/base.rb (1 hunks)
  • lib/bas/shared_storage/postgres.rb (4 hunks)
  • lib/bas/utils/postgres/connection.rb (1 hunks)
  • spec/bas/bot/base_spec.rb (2 hunks)
  • spec/bas/shared_storage/postgres_spec.rb (3 hunks)
  • spec/bas/utils/postgres/connection_spec.rb (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
spec/bas/bot/base_spec.rb (1)
Learnt from: juanpabloxk
PR: kommitters/bas#138
File: lib/bas/shared_storage/elasticsearch.rb:25-35
Timestamp: 2025-07-02T19:18:20.943Z
Learning: In the Bas codebase, errors from shared storage operations (like Elasticsearch requests) should be thrown to the caller rather than being caught and handled within the storage methods themselves. This allows for explicit error handling at higher levels in the application.
🧬 Code Graph Analysis (4)
lib/bas/shared_storage/base.rb (2)
lib/bas/bot/base.rb (1)
  • close_connections (66-69)
lib/bas/shared_storage/postgres.rb (1)
  • close_connections (34-39)
lib/bas/bot/base.rb (2)
lib/bas/shared_storage/base.rb (1)
  • close_connections (24-27)
lib/bas/shared_storage/postgres.rb (1)
  • close_connections (34-39)
spec/bas/shared_storage/postgres_spec.rb (1)
lib/bas/shared_storage/postgres.rb (6)
  • read (16-109)
  • read (19-26)
  • write (28-32)
  • close_connections (34-39)
  • set_in_process (41-45)
  • set_processed (47-51)
spec/bas/utils/postgres/connection_spec.rb (1)
lib/bas/utils/postgres/connection.rb (2)
  • query (15-25)
  • finish (27-30)
🔇 Additional comments (3)
lib/bas/shared_storage/postgres.rb (1)

34-39: LGTM! Proper connection cleanup implementation

The close_connections method correctly closes both connections and clears the instance variables to prevent reuse of closed connections.

lib/bas/shared_storage/POSTGRES.md (1)

1-91: Documentation does not reflect the refactored connection management approach

This sequence diagram documents the old request-based architecture where each operation creates a new connection via Utils::Postgres::Request. However, according to the PR objectives, the code has been refactored to use persistent connections with:

  • Memoized @read_connection and @write_connection instance variables
  • Connection reuse across operations
  • Explicit close_connections method
  • Utils::Postgres::Connection instead of Utils::Postgres::Request

The documentation should be updated to reflect the new connection management pattern.

Likely an incorrect or invalid review comment.

spec/bas/shared_storage/postgres_spec.rb (1)

1-227: Excellent test coverage for the refactored connection management

The test suite comprehensively validates the new persistent connection behavior:

  • Connection reuse for multiple operations
  • Proper connection closing and re-establishment
  • Separate connections for read/write operations
  • Edge cases like empty results and nil IDs

The tests effectively verify that the refactoring achieves its goal of reducing connection overhead.

@coveralls
Copy link

coveralls commented Jul 18, 2025

Pull Request Test Coverage Report for Build 16427129026

Details

  • 317 of 317 (100.0%) changed or added relevant lines in 7 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.3%) to 98.413%

Totals Coverage Status
Change from base Build 16307043006: 0.3%
Covered Lines: 1984
Relevant Lines: 2016

💛 - Coveralls

Copy link
Collaborator

@FelipeGuzmanSierra FelipeGuzmanSierra left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀

@juanpabloxk could you check the coveralls and make sure they cover 100%?

@juanpabloxk juanpabloxk force-pushed the fix-postgres-pooling branch from 3523c4a to a03d3db Compare July 21, 2025 19:42
@juanpabloxk
Copy link
Contributor Author

LGTM! 🚀

@juanpabloxk could you check the coveralls and make sure they cover 100%?

@FelipeGuzmanSierra Sure, done ✅

Copy link
Collaborator

@FelipeGuzmanSierra FelipeGuzmanSierra left a comment

Choose a reason for hiding this comment

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

Great!

@juanpabloxk juanpabloxk merged commit dee089d into main Jul 21, 2025
3 checks passed
@juanpabloxk juanpabloxk deleted the fix-postgres-pooling branch July 21, 2025 21:02
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.

Refactor PostgreSQL connections

5 participants