Skip to content

Conversation

@MarcusGoldschmidt
Copy link
Contributor

@MarcusGoldschmidt MarcusGoldschmidt commented Apr 17, 2025

  • Add suport to multiple databases, if not selected one in the DSN
  • Add database name on entitlement

BB-450 Postgres Enhancements

Summary by CodeRabbit

  • New Features

    • Added support for managing and syncing resources across multiple PostgreSQL databases from a single connection, enabling multi-database operations.
    • Resource identifiers and display names now include database context for improved clarity and management.
  • Improvements

    • Enhanced resource listing, entitlement, and grant operations to dynamically select the appropriate database connection from a client pool.
    • Updated resource ID parsing and formatting to consistently handle database-scoped identifiers.
    • Added handling to skip databases that do not accept connections with informative logging.
    • Dependency versions updated for improved stability and compatibility.
  • Bug Fixes

    • Improved error handling and logging when connecting to databases that do not accept connections.

@coderabbitai
Copy link

coderabbitai bot commented Apr 17, 2025

Walkthrough

This update introduces multi-database support to the connector by replacing single PostgreSQL client instances with a new ClientDatabasesPool, which manages connections to multiple databases. Resource syncers and related logic are refactored to parse and include database identifiers in resource IDs, dynamically retrieving the appropriate client from the pool for each operation. Corresponding changes are made to resource ID formatting and parsing functions. The PostgreSQL client library is enhanced with the pool implementation and a method to retrieve databases by ID. Additionally, several dependencies are upgraded in go.mod, and the Go toolchain directive is removed.

Changes

File(s) Change Summary
go.mod Upgraded multiple dependencies, added new indirect dependencies, and removed the explicit Go toolchain directive.
pkg/connector/connector.go Replaced single client with ClientDatabasesPool in Postgresql struct; updated resource syncer construction and initialization logic.
pkg/connector/helpers.go Added functions for formatting and parsing resource IDs with database context; updated column ID functions to include database ID.
pkg/connector/database.go Added clientPool to databaseSyncer; updated constructor and logic to use the client pool and filter databases; added logging for skipped databases.
pkg/connector/schema.go, pkg/connector/table.go, pkg/connector/column.go, pkg/connector/view.go, pkg/connector/function.go, pkg/connector/procedure.go, pkg/connector/sequence.go Replaced single client with ClientDatabasesPool in syncers; updated constructors, resource ID parsing/formatting, and all logic to use the pool for multi-database support.
pkg/postgres/client.go Introduced ClientDatabasesPool for managing clients per database; added methods for pool management and updated client creation logic.
pkg/postgres/databases.go Added GetDatabaseById method to retrieve a database by its ID.
pkg/postgres/tables.go Changed ListTables method to accept schema name instead of schema ID and updated query accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Connector
    participant ClientDatabasesPool
    participant Client

    Caller->>Connector: Request resource sync (e.g., list tables)
    Connector->>ClientDatabasesPool: Parse resource ID for db, get client (Get)
    ClientDatabasesPool->>Client: Return client for db (create if needed)
    Connector->>Client: Perform operation (e.g., ListTables)
    Client-->>Connector: Return results
    Connector-->>Caller: Return resources
Loading

Poem

In the warren of databases, connections leap and bound,
Now a clever pool ensures each one is found.
With IDs that whisper which burrow to seek,
Our syncers hop smartly, unique for each creek.
So raise your paws high for this multi-db feat—
The connector’s grown nimble, and rabbits love neat!
🐇✨


📜 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 a6f80b0 and c5a2b49.

📒 Files selected for processing (2)
  • pkg/connector/sequence.go (5 hunks)
  • pkg/connector/view.go (5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
pkg/connector/view.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (13-22)
pkg/connector/sequence.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (13-22)
🔇 Additional comments (6)
pkg/connector/sequence.go (3)

74-98: Good improvement to Entitlements with database context.

Prefixing entitlement display names with the database name improves clarity for users, especially in a multi-database environment. This makes it clear which database the entitlements belong to.


103-125: Thorough implementation of database-aware grants.

The implementation correctly extracts the database ID, retrieves the appropriate client, and uses it for grant-related operations. This maintains database isolation while reusing the existing permissions model.


132-136: Constructor updated to support multi-database architecture.

The constructor correctly adapts to the new multi-database architecture by accepting a client pool instead of a single client.

pkg/connector/view.go (3)

74-98: Effective implementation of database-contextualized entitlements.

The code now correctly extracts the database ID from the resource ID, retrieves the database information, and prefixes the entitlement display names with the database name. This provides clear context in multi-database environments.


103-125: Well-structured grants implementation for multi-database support.

The implementation correctly handles database-specific grants by:

  1. Extracting the database ID from the resource ID
  2. Retrieving the appropriate client from the pool
  3. Using that client for view and role operations

This maintains database isolation while leveraging the existing grant structure.


132-136: Constructor properly updated for multi-database architecture.

The constructor now correctly accepts a client pool instead of a single client, aligning with the new multi-database architecture.

✨ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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: 14

🔭 Outside diff range comments (1)
pkg/connector/table.go (1)

83-108: ⚠️ Potential issue

Possible misuse of the default client for cross‑database lookup

r.clientPool.Default(ctx) is used to fetch a database record by ID.
If the DSN contained a database name, the “default” client will be pinned to that DB and pg_database look‑ups for other DBs could fail (or at best be inconsistent).

Consider fetching against any healthy client instead, or even the same client already used for the DB in question:

-	dbModel, err := r.clientPool.
-		Default(ctx).
-		GetDatabaseById(ctx, dbId)
+	dbClient, _, err := r.clientPool.Get(ctx, dbId)
+	if err != nil {
+		return nil, "", nil, err
+	}
+	dbModel, err := dbClient.GetDatabaseById(ctx, dbId)

This guarantees the query runs in the correct database context.

♻️ Duplicate comments (3)
pkg/connector/view.go (1)

98-109: Same nil‑pointer hazard in Grants – see earlier comment; the fix is identical.

pkg/connector/sequence.go (1)

98-109: Repeats the pToken nil‑check issue – see first comment in this file.

pkg/connector/table.go (1)

118-133: Nil‑pointer concern for pToken again – identical fix as above.

🧹 Nitpick comments (8)
pkg/postgres/databases.go (1)

80-98: Consider type conversion for database ID.

The new method accepts a database ID as a string but uses it directly in the query against the OID column (which is an integer type). While PostgreSQL may handle the type conversion implicitly, it would be safer to parse the string to an integer first to avoid potential type mismatch issues.

Also, consider maintaining naming consistency with existing methods (e.g., dbId vs dbID used elsewhere).

-func (c *Client) GetDatabaseById(ctx context.Context, dbId string) (*DatabaseModel, error) {
+func (c *Client) GetDatabaseById(ctx context.Context, dbID string) (*DatabaseModel, error) {
 	ret := &DatabaseModel{}
 
 	q := `
 SELECT "oid"::int,
        "datname",
        "datdba",
        "datacl"
 from "pg_catalog"."pg_database"
 WHERE "oid"=$1
 `
 
-	err := pgxscan.Get(ctx, c.db, ret, q, dbId)
+	oid, err := strconv.ParseInt(dbID, 10, 64)
+	if err != nil {
+		return nil, fmt.Errorf("invalid database ID: %w", err)
+	}
+	
+	err = pgxscan.Get(ctx, c.db, ret, q, oid)
 	if err != nil {
 		return nil, err
 	}
 
 	return ret, nil
}
pkg/connector/connector.go (1)

64-67: Error handling could be more specific.

While the changes are correct, consider enhancing the error message to provide more context about potential failure scenarios specific to the client pool creation.

-		return nil, fmt.Errorf("failed to create postgres client pool: %w", err)
+		return nil, fmt.Errorf("failed to create postgres client pool (check DSN and connection permissions): %w", err)
pkg/connector/function.go (1)

92-94: Minor: keep entitlement display names stable

Re‑prefixing every entitlement with the database name changes display strings that consumers may already persist for audit logs. If that’s intentional – great. If the intention is disambiguation only when the same entitlement slug exists in multiple DBs, consider keeping the original DisplayName and instead add the DB context to Description or an annotation field.

pkg/connector/database.go (1)

5-10: Run goimports to normalise import grouping

Static analysis flagged the file as not goimports‑formatted. Running:

goimports -w pkg/connector/database.go

will group std‑lib, third‑party and local imports, avoiding CI failures in strict pipelines.

🧰 Tools
🪛 GitHub Check: go-lint

[failure] 6-6:
File is not properly formatted (goimports)

pkg/connector/schema.go (1)

51-54: Remove superfluous blank line to satisfy go vet/golint.

There’s an extra empty line after the early‑return that triggers the “unnecessary trailing newline” lint warning.

-	if dbName == "" {
-		return nil, "", nil, fmt.Errorf("database name not found for ID %d", dbId)
-
-	}
+	if dbName == "" {
+		return nil, "", nil, fmt.Errorf("database name not found for ID %d", dbId)
+	}
🧰 Tools
🪛 GitHub Check: go-lint

[failure] 54-54:
unnecessary trailing newline (whitespace)

pkg/postgres/client.go (1)

3-10: Run goimports to sort imports and remove unused ones.

The file is currently flagged by goimports/golint. Running the tool will:

  1. Group standard‑library imports first.
  2. Remove errors if no longer used after other fixes, or ensure it’s grouped correctly.

Keeping imports tidy prevents CI failures and improves readability.

🧰 Tools
🪛 GitHub Check: go-lint

[failure] 5-5:
File is not properly formatted (goimports)

pkg/connector/view.go (1)

73-81: TODO left unaddressed for entitlements

The comment // TODO: add dat indicates incomplete entitlement handling. This may lead to missing privileges in downstream access‑reviews.
Would you like help fleshing this out?

pkg/connector/sequence.go (1)

79-81: Missing sequence‑level entitlements

// TODO: add entitlements for sequences should be addressed before shipping; otherwise users cannot request access to sequences.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 76f018b and 94e86a0.

⛔ Files ignored due to path filters (171)
  • README.md is excluded by none and included by none
  • go.sum is excluded by !**/*.sum and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/CHANGELOG.md is excluded by !vendor/** and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/api_op_CreateEventSourceMapping.go is excluded by !vendor/** and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/api_op_DeleteEventSourceMapping.go is excluded by !vendor/** and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/api_op_GetEventSourceMapping.go is excluded by !vendor/** and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/api_op_UpdateEventSourceMapping.go is excluded by !vendor/** and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/go_module_metadata.go is excluded by !vendor/** and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/types/enums.go is excluded by !vendor/** and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/types/errors.go is excluded by !vendor/** and included by none
  • vendor/github.com/aws/aws-sdk-go-v2/service/lambda/types/types.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pb/c1/connector/v2/annotation_baton_id.pb.go is excluded by !**/*.pb.go, !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pb/c1/connector/v2/annotation_baton_id.pb.validate.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pb/c1/connector/v2/annotation_trait.pb.go is excluded by !**/*.pb.go, !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pb/c1/connectorapi/baton/v1/config.pb.go is excluded by !**/*.pb.go, !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pb/c1/connectorapi/baton/v1/config.pb.validate.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/annotations/annotations.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/auth/jwt.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/bid/bid.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/bid/parser.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/bid/scanner.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/config/config.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/field/defaults.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/lambda/grpc/config/config.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/lambda/grpc/middleware/auth.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/lambda/grpc/server.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/provisioner/provisioner.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/types/resource/user_trait.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/baton-sdk/pkg/uotel/uotel.go is excluded by !vendor/** and included by none
  • vendor/github.com/conductorone/dpop/integrations/dpop_oauth2/token_client_assertion.go is excluded by !vendor/** and included by none
  • vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md is excluded by !vendor/** and included by none
  • vendor/github.com/go-jose/go-jose/v4/README.md is excluded by !vendor/** and included by none
  • vendor/github.com/go-jose/go-jose/v4/jwe.go is excluded by !vendor/** and included by none
  • vendor/github.com/go-jose/go-jose/v4/jwk.go is excluded by !vendor/** and included by none
  • vendor/github.com/go-jose/go-jose/v4/jws.go is excluded by !vendor/** and included by none
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/query.go is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/.travis.yml is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/LICENSE is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/README.md is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/api.go is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/cacheobject/object.go is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/cachecontrol/doc.go is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/xjwt/xkeyset/LICENSE is excluded by !vendor/** and included by none
  • vendor/github.com/pquerna/xjwt/xkeyset/keyset.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/contrib/bridges/otelzap/LICENSE is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/contrib/bridges/otelzap/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/contrib/bridges/otelzap/convert.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/contrib/bridges/otelzap/core.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/contrib/bridges/otelzap/encoder.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/contrib/bridges/otelzap/gen.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/.gitignore is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/.golangci.yml is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/CHANGELOG.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/CONTRIBUTING.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/Makefile is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/RELEASING.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/dependencies.Dockerfile is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/version.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/DESIGN.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/LICENSE is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/doc.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/embedded/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/embedded/embedded.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/global/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/global/log.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/internal/global/log.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/internal/global/state.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/keyvalue.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/kind_string.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/logger.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/noop/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/noop/noop.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/provider.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/record.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/severity.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/log/severity_string.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/renovate.json is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/requirements.txt is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/DESIGN.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/LICENSE is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/batch.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/doc.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/exporter.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/filter_processor.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/logger.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/processor.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/provider.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/record.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/ring.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/setting.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/log/simple.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/resource/os_release_darwin.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/resource/resource.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/trace/batch_span_processor.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/trace/sampling.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/trace/simple_span_processor.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/sdk/version.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/semconv/v1.27.0/README.md is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/semconv/v1.27.0/attribute_group.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/semconv/v1.27.0/doc.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/semconv/v1.27.0/exception.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/semconv/v1.27.0/metric.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/semconv/v1.27.0/schema.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/auto.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/attr.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/doc.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/id.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/number.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/resource.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/scope.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/span.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/status.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/traces.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/internal/telemetry/value.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/trace/noop.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/version.go is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/otel/versions.yaml is excluded by !vendor/** and included by none
  • vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.go is excluded by !**/*.pb.go, !vendor/** and included by none
  • vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.gw.go is excluded by !**/*.pb.gw.go, !vendor/** and included by none
  • vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service_grpc.pb.go is excluded by !**/*.pb.go, !vendor/** and included by none
  • vendor/go.opentelemetry.io/proto/otlp/logs/v1/logs.pb.go is excluded by !**/*.pb.go, !vendor/** and included by none
  • vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/balancer/balancer.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/balancer/endpointsharding/endpointsharding.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/balancer/roundrobin/roundrobin.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/balancer/subconn.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/balancer_wrapper.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go is excluded by !**/*.pb.go, !vendor/** and included by none
  • vendor/google.golang.org/grpc/clientconn.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/dialoptions.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go is excluded by !**/*.pb.go, !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/gracefulswitch.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/envconfig/xds.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/grpcsync/oncefunc.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/internal.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/proxyattributes/proxyattributes.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/resolver/delegatingresolver/delegatingresolver.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/transport/http2_client.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/transport/proxy.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/internal/transport/transport.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/picker_wrapper.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/resolver/resolver.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/resolver_wrapper.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/rpc_util.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/server.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/stream.go is excluded by !vendor/** and included by none
  • vendor/google.golang.org/grpc/version.go is excluded by !vendor/** and included by none
  • vendor/modules.txt is excluded by !vendor/** and included by none
📒 Files selected for processing (14)
  • go.mod (6 hunks)
  • pkg/connector/column.go (4 hunks)
  • pkg/connector/connector.go (2 hunks)
  • pkg/connector/database.go (4 hunks)
  • pkg/connector/function.go (4 hunks)
  • pkg/connector/helpers.go (3 hunks)
  • pkg/connector/procedure.go (5 hunks)
  • pkg/connector/schema.go (5 hunks)
  • pkg/connector/sequence.go (5 hunks)
  • pkg/connector/table.go (5 hunks)
  • pkg/connector/view.go (5 hunks)
  • pkg/postgres/client.go (1 hunks)
  • pkg/postgres/databases.go (1 hunks)
  • pkg/postgres/tables.go (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (10)
pkg/postgres/databases.go (1)
pkg/postgres/client.go (1)
  • Client (130-134)
pkg/postgres/tables.go (1)
pkg/postgres/client.go (1)
  • Client (130-134)
pkg/connector/function.go (2)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (12-21)
pkg/postgres/privileges.go (1)
  • Execute (106-106)
pkg/connector/column.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (12-21)
pkg/connector/database.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (12-21)
pkg/connector/sequence.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (12-21)
pkg/connector/table.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (12-21)
pkg/connector/procedure.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (12-21)
pkg/connector/view.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (12-21)
pkg/connector/schema.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (12-21)
🪛 GitHub Check: go-lint
pkg/connector/helpers.go

[failure] 18-18:
Comment should end in a period (godot)

pkg/connector/database.go

[failure] 6-6:
File is not properly formatted (goimports)

pkg/postgres/client.go

[failure] 5-5:
File is not properly formatted (goimports)

pkg/connector/schema.go

[failure] 54-54:
unnecessary trailing newline (whitespace)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (10)
pkg/postgres/tables.go (3)

51-51: Method signature updated to support multi-database functionality.

The change from accepting a schema ID (int64) to a schema name (string) aligns with the PR objective of supporting multiple databases. This modification allows for more flexible schema identification across different databases.


65-65: SQL query correctly modified to filter by schema name.

The WHERE clause has been appropriately updated to filter tables by schema name (nspname) rather than by schema ID (OID), which is consistent with the method signature change.


69-69: Query parameter correctly updated to use schema name.

The argument passed to the query has been properly updated to use the schema name parameter.

go.mod (2)

6-6: Dependencies updated to support multi-database functionality.

The updates to baton-sdk (v0.2.93) and PostgreSQL-related dependencies are appropriate for supporting the multi-database functionality introduced in this PR.

Also applies to: 10-10


101-109: New OpenTelemetry dependencies added.

Several OpenTelemetry dependencies have been added, which should enhance the observability of the application. This is a good practice when introducing new functionality like multi-database support.

pkg/connector/connector.go (3)

15-15: Client field updated to use ClientDatabasesPool.

The change from a single client to a client pool correctly implements the multi-database support objective of this PR.


23-32: ResourceSyncers implementation correctly uses client pool.

The ResourceSyncers method now appropriately uses either the default client from the pool or passes the entire pool to syncers as needed. This allows syncers to obtain the correct client for each database operation.


70-70: Struct initialization correctly updated.

The struct initialization properly uses the new clientPool field name.

pkg/connector/column.go (1)

40-48: Same edge‑case as function syncer: ensure DB component exists

If parseWithDatabaseID returns an empty database string we end up defaulting to the implicit client, defeating multi‑DB isolation. Propagate a clear error when db == "" so callers understand the resource ID is malformed or from an earlier connector version.

pkg/postgres/client.go (1)

69-81: Clarify the database parameter semantics.

Get expects the database OID (as string), not the database name, but callers may confuse the two. This is implicit and easy to misuse.

Recommendation:

  1. Rename the parameter to databaseID string.
  2. Add a doc comment explicitly stating it must be the numeric OID.
  3. Consider overloading with GetByName if you foresee name‑based look‑ups.

Clearer naming will prevent accidental bugs when other contributors extend this pool.

@@ -1,14 +1,13 @@
module github.com/conductorone/baton-postgresql

go 1.23.4
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify Go version specification.

The Go version is specified as 1.23.4, which appears to be ahead of the latest released version as of when this review is being conducted. Consider verifying if this is the correct version you intend to use or if it should be updated to the latest stable release.


🌐 Web query:

What is the latest released version of Go programming language?

💡 Result:

The latest released version of the Go programming language is Go 1.24.2, which was released on April 1, 2025[1][3][8]. This release includes security fixes, as well as bug fixes for the compiler, runtime, the go command, and various standard library packages such as crypto/tls, go/types, net/http, and testing[1].

Here’s a summary of recent Go releases:

Version Release Date Notable Notes
1.24.2 2025-04-01 Latest stable release (security and bug fixes)
1.24.1 2025-03-04 Security and bug fixes
1.24.0 2025-02-11 Major release with new features

Go 1.25 is not yet released as of mid-April 2025[7].

For the latest updates and details on new features, you can consult the official Go release history[1][3][8].

Citations:


Update Go version to the latest stable release

The go directive in go.mod is currently set to 1.23.4, which is behind the latest stable release (Go 1.24.2 as of April 1, 2025). Please update it to benefit from the most recent security patches and bug fixes.

• File: go.mod
Line 3:

-go 1.23.4
+go 1.24.2
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
go 1.23.4
go 1.24.2

Comment on lines +40 to +48
db, parentID, err := parseWithDatabaseID(parentResourceID.Resource)
if err != nil {
return nil, "", nil, err
}

functions, nextPageToken, err := r.client.ListFunctions(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
client, _, err := r.clientPool.Get(ctx, db)
if err != nil {
return nil, "", nil, err
}
Copy link

@coderabbitai coderabbitai bot Apr 17, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against resources that don’t embed a database ID

parseWithDatabaseID assumes the incoming resource string always contains a database component. If a caller hands us a schema ID that was produced by an older Baton‑PostgreSQL build (i.e. pre‑multi‑DB), db will be empty and the subsequent clientPool.Get will return the default client, silently re‑introducing single‑DB semantics. That will surface later as confusing “object not found” errors.

Consider short‑circuiting the call when db == "", logging a warning and returning a typed error so the UI can reflect the unsupported ID format.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't need the db name in this scenario

Copy link

Choose a reason for hiding this comment

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

@MarcusGoldschmidt I understand. Thanks for clarifying that the database name isn't needed in this specific scenario. This suggests that either your design already accounts for the case where a database ID might be empty, or that the fallback to the default client is the expected behavior here.

Comment on lines +79 to +83
dbModel, err := r.clientPool.
Default(ctx).
GetDatabaseById(ctx, dbId)

if err != nil {
Copy link

@coderabbitai coderabbitai bot Apr 17, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use a database‑scoped client instead of the default for metadata look‑ups

GetDatabaseById is executed on clientPool.Default(ctx).
When the DSN contains no database, the default client connects to the server without selecting a DB, which means:

  1. search_path may not include the requested DB, producing “undefined table” style errors.
  2. RLS / pg_hba rules might block the query even though connecting directly to that DB would succeed.

Retrieving the database model through the same client that will later be used for entitlement checks avoids those edge cases:

- dbModel, err := r.clientPool.
-     Default(ctx).
-     GetDatabaseById(ctx, dbId)
+ scopedClient, _, err := r.clientPool.Get(ctx, dbId)
+ if err != nil {
+     return nil, "", nil, err
+ }
+ dbModel, err := scopedClient.GetDatabaseById(ctx, dbId)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dbModel, err := r.clientPool.
Default(ctx).
GetDatabaseById(ctx, dbId)
if err != nil {
scopedClient, _, err := r.clientPool.Get(ctx, dbId)
if err != nil {
return nil, "", nil, err
}
dbModel, err := scopedClient.GetDatabaseById(ctx, dbId)
if err != nil {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We check if the database is connectable, if not we don't sync child resources

Copy link

Choose a reason for hiding this comment

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

@MarcusGoldschmidt You're right that you check database connectivity in the databaseSyncer.List method and skip databases that aren't connectable.

However, CodeRabbit's suggestion was actually about a different issue: when accessing metadata for a database that is connectable, using the default client might still cause problems with search_path or permission settings. The database-scoped client (from Get(ctx, dbId)) would have the correct context, while the default client might not.

That said, if you have specific reasons for using the default client in this case or have verified that it works correctly in your environment, feel free to keep the current implementation.


✏️ Learnings added
Learnt from: MarcusGoldschmidt
PR: ConductorOne/baton-postgresql#31
File: pkg/connector/function.go:79-0
Timestamp: 2025-04-17T20:26:27.564Z
Learning: In the baton-postgresql connector, database connectivity is checked in the databaseSyncer.List method. Databases that aren't connectable (error code 55000) are logged and skipped from being synced.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 74 to 85
_, _, err := r.clientPool.Get(ctx, fmt.Sprintf("%d", o.ID))
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
// Database does not accept connections
if pgErr.Code == "55000" {
l.Info("skipping database that does not accept connections", zap.String("database", o.Name), zap.Error(err))
continue
}
}
return nil, "", nil, err
}
Copy link

@coderabbitai coderabbitai bot Apr 17, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Connect‑test the database by name, not by numeric OID

ClientDatabasesPool.Get keys the cache by database name. Passing the numeric OID creates a second, uncached entry, so every run:

  1. Opens a fresh connection (leak).
  2. Fails to hit the already‑validated client, duplicating work.
- _, _, err := r.clientPool.Get(ctx, fmt.Sprintf("%d", o.ID))
+ _, _, err := r.clientPool.Get(ctx, o.Name)

If the intent was to prove the DB accepts connections, the name is sufficient and consistent with other callers (DefaultDatabase, parseWithDatabaseID, etc.).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_, _, err := r.clientPool.Get(ctx, fmt.Sprintf("%d", o.ID))
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
// Database does not accept connections
if pgErr.Code == "55000" {
l.Info("skipping database that does not accept connections", zap.String("database", o.Name), zap.Error(err))
continue
}
}
return nil, "", nil, err
}
_, _, err := r.clientPool.Get(ctx, o.Name)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
// Database does not accept connections
if pgErr.Code == "55000" {
l.Info("skipping database that does not accept connections", zap.String("database", o.Name), zap.Error(err))
continue
}
}
return nil, "", nil, err
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Get method uses the databaseId, in string, not the name.

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the implementation of the ClientDatabasesPool.Get method
ast-grep --pattern 'func (p *ClientDatabasesPool) Get(ctx context.Context, database string)' pkg/postgres/client.go

Length of output: 3046


@MarcusGoldschmidt You're absolutely right. Looking at the implementation of ClientDatabasesPool.Get, it definitely expects a database ID as a string:

func (p *ClientDatabasesPool) Get(ctx context.Context, database string) (*Client, string, error) {
    // ...
    dbModel, err := p.defaultClientDsn.GetDatabaseById(ctx, database)
    // ...
}

The method passes the string parameter directly to GetDatabaseById, confirming it expects an ID rather than a name. The current implementation using fmt.Sprintf("%d", o.ID) is correct.

Thanks for the correction!


🧬 Code Graph Analysis Results

Relevant snippets from pkg/postgres/databases.go (lines 17-22)

DatabaseModel struct {
	ID      int64    `db:"oid"`
	Name    string   `db:"datname"`
	OwnerID int64    `db:"datdba"`
	ACLs    []string `db:"datacl"`
}
  • Defines the DatabaseModel struct representing a database with fields including ID (int64) and Name (string).

Relevant snippets from pkg/postgres/client.go (lines 13-22, 131-135)

ClientDatabasesPool struct {
	databases map[string]*Client
	opts      []ClientOpt
	mutex     *sync.Mutex
	logger    *Logger
	dsn       string
	// Only used if dsn have any database name
	defaultClientWithDatabase *Client
	defaultClientDsn          *Client
}
  • Represents a pool of database clients keyed by string, with synchronization and configuration fields.
Client struct {
	db           *pgxpool.Pool
	cfg          *pgxpool.Config
	schemaFilter []string
}
  • Represents a database client with connection pool and configuration.

Summary of database.go (entire file)

  • The databaseSyncer struct manages database resources using a postgres.ClientDatabasesPool and a default postgres.Client.
  • The Get method is not explicitly defined here, but the Grant and Revoke methods use the database ID parsed from entitlement IDs (as string, then converted to int64) to look up the database via r.client.GetDatabase(ctx, dbID).
  • The List method lists databases and uses the database ID (converted to string) when calling r.clientPool.Get(ctx, fmt.Sprintf("%d", o.ID)).
  • The Create and Delete methods use the database name for creation and deletion operations.
  • The resource IDs for databases are formatted using formatObjectID with the database ID (int64) converted to string.
  • Privileges and grants are managed by parsing entitlement IDs that include the database ID as a string.

[no additional relevant snippets]


✏️ Learnings added
Learnt from: MarcusGoldschmidt
PR: ConductorOne/baton-postgresql#31
File: pkg/connector/database.go:75-0
Timestamp: 2025-04-17T20:26:59.775Z
Learning: The `ClientDatabasesPool.Get` method in the PostgreSQL connector expects a database ID as a string parameter, not a database name. It passes this ID to `GetDatabaseById` to retrieve the database model.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +40 to 51
db, parentID, err := parseWithDatabaseID(parentResourceID.Resource)
if err != nil {
return nil, "", nil, err
}

sequences, nextPageToken, err := r.client.ListSequences(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
client, _, err := r.clientPool.Get(ctx, db)
if err != nil {
return nil, "", nil, err
}

sequences, nextPageToken, err := client.ListSequences(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Nil ‑ pointer risk with pToken

pToken may be nil; dereferencing it here causes a panic.

-	sequences, nextPageToken, err := client.ListSequences(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
+	if pToken == nil {
+		pToken = &pagination.Token{}
+	}
+	sequences, nextPageToken, err := client.ListSequences(ctx, parentID, &postgres.Pager{
+		Token: pToken.Token,
+		Size:  pToken.Size,
+	})

Please propagate this pattern across the file.

Comment on lines 40 to 56
if err != nil {
return nil, "", nil, err
}

client, _, err := r.clientPool.Get(ctx, database)
if err != nil {
return nil, "", nil, err
}

schema, err := client.GetSchema(ctx, parentID)
if err != nil {
return nil, "", nil, err
}

tables, nextPageToken, err := r.client.ListTables(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
tables, nextPageToken, err := client.ListTables(ctx, schema.Name, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
return nil, "", nil, err
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Nil ‑ pointer dereference risk (pToken)

Same pattern as other syncers – add a guard:

-	tables, nextPageToken, err := client.ListTables(ctx, schema.Name, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
+	if pToken == nil {
+		pToken = &pagination.Token{}
+	}
+	tables, nextPageToken, err := client.ListTables(ctx, schema.Name, &postgres.Pager{
+		Token: pToken.Token,
+		Size:  pToken.Size,
+	})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
database, parentID, err := parseWithDatabaseID(parentResourceID.Resource)
if err != nil {
return nil, "", nil, err
}
client, _, err := r.clientPool.Get(ctx, database)
if err != nil {
return nil, "", nil, err
}
schema, err := client.GetSchema(ctx, parentID)
if err != nil {
return nil, "", nil, err
}
tables, nextPageToken, err := r.client.ListTables(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
tables, nextPageToken, err := client.ListTables(ctx, schema.Name, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
database, parentID, err := parseWithDatabaseID(parentResourceID.Resource)
if err != nil {
return nil, "", nil, err
}
client, _, err := r.clientPool.Get(ctx, database)
if err != nil {
return nil, "", nil, err
}
schema, err := client.GetSchema(ctx, parentID)
if err != nil {
return nil, "", nil, err
}
if pToken == nil {
pToken = &pagination.Token{}
}
tables, nextPageToken, err := client.ListTables(ctx, schema.Name, &postgres.Pager{
Token: pToken.Token,
Size: pToken.Size,
})
if err != nil {

Comment on lines 74 to 81
_, _, err := parseWithDatabaseID(resource.Id.Resource)
if err != nil {
return nil, "", nil, err
}

// TODO: add db on entitlement
ens, err := entitlementsForPrivs(ctx, resource, postgres.Execute)
if err != nil {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Entitlements lose the database context

The TODO highlights a real functional gap: entitlements generated for procedures from different databases will share the same ResourceId, leading to collisions and ambiguous grants when the connector syncs multiple DBs. Extend the entitlement construction helpers (and formatWithDatabaseID) so that the database component is embedded in the entitlement ID or slug, mirroring the resource ID strategy adopted elsewhere.

Comment on lines +45 to 52
client, _, err := r.clientPool.Get(ctx, db)
if err != nil {
return nil, "", nil, err
}

procedures, nextPageToken, err := client.ListProcedures(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
return nil, "", nil, err
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle nil pagination token to avoid panics

pToken can be nil when the Baton SDK calls the first page of a collection.
Dereferencing it (pToken.Token, pToken.Size) will raise a panic, crashing the syncer.

-	procedures, nextPageToken, err := client.ListProcedures(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
+	var pager *postgres.Pager
+	if pToken != nil {
+		pager = &postgres.Pager{Token: pToken.Token, Size: pToken.Size}
+	} else {
+		pager = &postgres.Pager{}
+	}
+
+	procedures, nextPageToken, err := client.ListProcedures(ctx, parentID, pager)

This mirrors the defensive pattern used in several other syncers and eliminates a crash path.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
client, _, err := r.clientPool.Get(ctx, db)
if err != nil {
return nil, "", nil, err
}
procedures, nextPageToken, err := client.ListProcedures(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
return nil, "", nil, err
client, _, err := r.clientPool.Get(ctx, db)
if err != nil {
return nil, "", nil, err
}
var pager *postgres.Pager
if pToken != nil {
pager = &postgres.Pager{Token: pToken.Token, Size: pToken.Size}
} else {
pager = &postgres.Pager{}
}
procedures, nextPageToken, err := client.ListProcedures(ctx, parentID, pager)
if err != nil {
return nil, "", nil, err
}

Comment on lines +104 to 107
roles, nextPageToken, err := client.ListRoles(ctx, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
return nil, "", nil, err
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Repeat of nil‑token issue in ListRoles call

The same pToken dereference occurs here. Please patch similarly:

-	roles, nextPageToken, err := client.ListRoles(ctx, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
+	var pager *postgres.Pager
+	if pToken != nil {
+		pager = &postgres.Pager{Token: pToken.Token, Size: pToken.Size}
+	} else {
+		pager = &postgres.Pager{}
+	}
+	roles, nextPageToken, err := client.ListRoles(ctx, pager)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
roles, nextPageToken, err := client.ListRoles(ctx, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
return nil, "", nil, err
}
var pager *postgres.Pager
if pToken != nil {
pager = &postgres.Pager{Token: pToken.Token, Size: pToken.Size}
} else {
pager = &postgres.Pager{}
}
roles, nextPageToken, err := client.ListRoles(ctx, pager)
if err != nil {
return nil, "", nil, err
}

@MarcusGoldschmidt MarcusGoldschmidt changed the title Goldschmidt/multiple databases Multiple database support Apr 17, 2025
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: 0

♻️ Duplicate comments (1)
pkg/connector/sequence.go (1)

32-34: ⚠️ Potential issue

Early return for nil checks is good, but inconsistent with line 50

While adding nil checks for both parentResourceID and pToken is good defensive programming, there's a potential nil pointer dereference at line 50.

 if parentResourceID == nil || pToken == nil {
   return nil, "", nil, nil
 }

 // Later in the code...
-sequences, nextPageToken, err := client.ListSequences(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
+sequences, nextPageToken, err := client.ListSequences(ctx, parentID, &postgres.Pager{
+  Token: pToken.Token,
+  Size:  pToken.Size,
+})

This doesn't fully address the nil-pointer risk mentioned in the previous review comment, as we're still directly accessing pToken.Token and pToken.Size.

🧹 Nitpick comments (3)
pkg/connector/sequence.go (3)

40-48: Properly implemented database ID parsing and client retrieval

The code correctly parses the database ID from the resource ID and retrieves the appropriate client from the pool.

Consider adding a comment about the format of the resource ID being parsed:

+// Parse the resource ID to extract the database ID and parent ID
+// Format: {type}:{database}:{id}
 db, parentID, err := parseWithDatabaseID(parentResourceID.Resource)
 if err != nil {
   return nil, "", nil, err
 }

74-80: Database ID parsed but not used in Entitlements method

The code parses the database ID and resource ID but doesn't use them in the Entitlements method.

It appears the parsed values are intended for future implementation mentioned in the TODO comment. Consider either:

  1. Adding a comment explaining why the parsing is needed now but values aren't used, or
  2. Deferring the parsing until it's actually needed (when the TODO is implemented)
-_, _, err := parseWithDatabaseID(resource.Id.Resource)
-if err != nil {
-  return nil, "", nil, err
-}
-
-// TODO: add entitlements for sequences
+// TODO: add entitlements for sequences (will need to parse database ID when implemented)

92-104: Correctly implemented database context in Grants method

The Grants method properly extracts the database ID, retrieves the appropriate client, and uses it for subsequent operations.

Consider using a more descriptive variable name than rID to clarify that it's the sequence ID:

-db, rID, err := parseWithDatabaseID(resource.Id.Resource)
+db, sequenceID, err := parseWithDatabaseID(resource.Id.Resource)

 // Later in the code...
-sequence, err := client.GetSequence(ctx, rID)
+sequence, err := client.GetSequence(ctx, sequenceID)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1646dc0 and c82b6ff.

📒 Files selected for processing (4)
  • pkg/connector/helpers.go (3 hunks)
  • pkg/connector/procedure.go (4 hunks)
  • pkg/connector/sequence.go (5 hunks)
  • pkg/connector/table.go (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/connector/helpers.go
  • pkg/connector/procedure.go
  • pkg/connector/table.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
pkg/connector/sequence.go (1)
pkg/postgres/client.go (1)
  • ClientDatabasesPool (13-22)
🔇 Additional comments (3)
pkg/connector/sequence.go (3)

21-23: Correctly updated field to support multi-database connectivity

The change from a single client to a client pool aligns with the PR objective to support multiple databases.


63-63: Resource ID correctly formatted to include database context

Updating the resource ID formatting to include the database ID ensures proper resource identification across multiple databases.


121-125: Constructor correctly updated to accept client pool

The constructor change properly aligns with the struct field change, ensuring consistent initialization.

@laurenleach laurenleach self-requested a review May 1, 2025 22:15
return nil, "", nil, err
}

// TODO: add entitlements for sequences
Copy link
Contributor

Choose a reason for hiding this comment

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

this is done right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, but I need to add database name as prefix for this entitlement

return nil, "", nil, err
}

// TODO: add dat
Copy link
Contributor

Choose a reason for hiding this comment

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

is this done?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I need to add database name prefix for this entitlement, thanks

@MarcusGoldschmidt MarcusGoldschmidt merged commit d5b4d33 into main May 7, 2025
4 checks passed
@MarcusGoldschmidt MarcusGoldschmidt deleted the goldschmidt/multiple-databases branch May 7, 2025 22:06
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.

3 participants