Skip to content

feat: [#540] Move the Postgres driver to a single package#1

Merged
hwbrzzl merged 1 commit intomasterfrom
bowen/init
Jan 9, 2025
Merged

feat: [#540] Move the Postgres driver to a single package#1
hwbrzzl merged 1 commit intomasterfrom
bowen/init

Conversation

@hwbrzzl
Copy link
Copy Markdown
Contributor

@hwbrzzl hwbrzzl commented Jan 9, 2025

📑 Description

Closes goravel/goravel#540

Summary by CodeRabbit

  • New Features

    • Added PostgreSQL database configuration management
    • Introduced Docker container support for PostgreSQL databases
    • Implemented ORM driver for PostgreSQL connections
  • Bug Fixes

    • Added error handling for database configuration retrieval
    • Implemented connection retry mechanisms
  • Documentation

    • Added comprehensive interfaces and structs for database configuration
    • Created utility functions for port management and command execution
  • Tests

    • Comprehensive test suite for PostgreSQL database interactions
    • Added test coverage for configuration, Docker, and driver functionality

✅ Checks

  • Added test cases for my code

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 9, 2025

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 golangci-lint (1.62.2)

level=warning msg="[runner] Can't run linter goanalysis_metalinter: buildir: failed to load package : could not load export data: no export data for "github.com/goravel/framework/contracts/config""
level=error msg="Running error: can't run linter goanalysis_metalinter\nbuildir: failed to load package : could not load export data: no export data for "github.com/goravel/framework/contracts/config""

Walkthrough

The pull request introduces a comprehensive PostgreSQL driver package for the Goravel framework. It creates a standalone package github.com/goravel/postgres that encapsulates database configuration management, Docker container handling, service provider integration, and ORM driver implementation. The package provides robust configuration builders, testing utilities, and a complete implementation of database connection and management strategies for PostgreSQL databases.

Changes

File Change Summary
config.go Introduced ConfigBuilder struct for managing database configurations with methods for reading, writing, and default configuration handling
config_test.go Added test suite for ConfigBuilder with comprehensive test cases for configuration methods
contracts/config.go Defined new interfaces and structs for configuration management, including ConfigBuilder and FullConfig
docker.go Implemented Docker struct for managing PostgreSQL databases within Docker containers
docker_test.go Created test suite for Docker database interactions and configuration
errors.go Added OrmDatabaseConfigNotFound error variable
facades/postgres.go Introduced Postgres facade function for driver instantiation
go.mod Configured module dependencies and versioning
postgres.go Implemented Postgres struct with ORM driver interface and connection management
postgres_test.go Added driver name verification test
service_provider.go Created service provider for PostgreSQL integration
utils.go Added utility functions for port management and command execution

Assessment against linked issues

Objective Addressed Explanation
Move Postgres driver to separate package
Postgres as default DB driver

Finishing Touches

  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.

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. (Beta)
  • @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.

@hwbrzzl hwbrzzl marked this pull request as ready for review January 9, 2025 08:36
@hwbrzzl hwbrzzl merged commit 6fdc34a into master Jan 9, 2025
Copy link
Copy Markdown

@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: 12

🧹 Nitpick comments (9)
docker.go (2)

50-51: Correct the error message grammar

The error message in line 51 should be corrected for proper grammar and clarity.

Apply this diff to fix the error message:

-		return fmt.Errorf("no container id return when creating Postgres docker")
+		return fmt.Errorf("no container ID returned when creating Postgres Docker container")

144-155: Implement context with timeout in connection retries

The connect method attempts to connect up to 60 times with a 2-second interval, resulting in a potential 2-minute wait time if the database is unreachable. This could block the application unnecessarily.

Consider using context.Context with a timeout to manage retries and allow for cancellation if the connection cannot be established within a reasonable time frame.

service_provider.go (1)

25-27: Document empty Boot method

Add a comment explaining why the Boot method is empty or remove it if not needed.

+// Boot performs no operation as no boot-time initialization is required
 func (receiver *ServiceProvider) Boot(app foundation.Application) {
 }
contracts/config.go (2)

7-11: Improve ConfigBuilder interface documentation

Add detailed documentation for the interface methods to explain their purpose and return values.

 type ConfigBuilder interface {
+	// Config returns the current configuration
 	Config() contractsconfig.Config
+	// Reads returns a list of read-only database configurations
 	Reads() []FullConfig
+	// Writes returns a list of write-enabled database configurations
 	Writes() []FullConfig
 }

29-42: Document FullConfig fields

Add field documentation to explain the purpose of each configuration option.

 type FullConfig struct {
 	Config
+	// Driver specifies the database driver name
 	Driver       string
+	// Connection specifies the connection name in database config
 	Connection   string
+	// Prefix is added to all table names
 	Prefix       string
+	// Singular determines if table names should be singular
 	Singular     bool
+	// Charset specifies the character set for MySQL and SQLServer
 	Charset      string
+	// Loc specifies the time zone location for MySQL
 	Loc          string
+	// Sslmode specifies the SSL mode for PostgreSQL
 	Sslmode      string
+	// Timezone specifies the timezone for PostgreSQL
 	Timezone     string
+	// NoLowerCase prevents automatic lowercase conversion of table names
 	NoLowerCase  bool
+	// NameReplacer provides custom table name transformation
 	NameReplacer Replacer
 }
docker_test.go (2)

14-18: Consider using environment variables for test credentials.

While hardcoded credentials are acceptable for tests, using environment variables would allow for easier configuration changes and prevent accidental commits of sensitive values.

-const (
-	testDatabase = "goravel"
-	testUsername = "goravel"
-	testPassword = "Framework!123"
-)
+var (
+	testDatabase = getEnvOrDefault("TEST_DB", "goravel")
+	testUsername = getEnvOrDefault("TEST_USER", "goravel")
+	testPassword = getEnvOrDefault("TEST_PASS", "Framework!123")
+)
+
+func getEnvOrDefault(key, defaultValue string) string {
+	if value := os.Getenv(key); value != "" {
+		return value
+	}
+	return defaultValue
+}

51-56: Use parameterized queries to prevent SQL injection.

Even in tests, it's good practice to use parameterized queries to maintain consistency with production code patterns.

-	res := instance.Exec(`
-	CREATE TABLE users (
-	 id SERIAL PRIMARY KEY NOT NULL,
-	 name varchar(255) NOT NULL
-	);
-	`)
+	res := instance.Exec(`CREATE TABLE users (id SERIAL PRIMARY KEY NOT NULL, name varchar(255) NOT NULL)`)

-	res = instance.Exec(`
-	INSERT INTO users (name) VALUES ('goravel');
-	`)
+	res = instance.Exec(`INSERT INTO users (name) VALUES ($1)`, "goravel")

Also applies to: 59-61

config.go (1)

51-100: Consider refactoring the long method.

The fillDefault method is quite long with repeated config.GetString calls. Consider extracting the configuration retrieval logic into a separate method.

+func (c *ConfigBuilder) getConfigValue(key, defaultValue string) string {
+	return c.config.GetString(fmt.Sprintf("database.connections.%s.%s", c.connection, key), defaultValue)
+}

 func (c *ConfigBuilder) fillDefault(configs []contracts.Config) []contracts.FullConfig {
 	// ... existing checks ...
 	
 	for _, config := range configs {
 		fullConfig := contracts.FullConfig{
 			Config:      config,
 			Connection:  c.connection,
 			Driver:      driver,
-			Prefix:      c.config.GetString(fmt.Sprintf("database.connections.%s.prefix", c.connection)),
+			Prefix:      c.getConfigValue("prefix", ""),
 			// ... apply similar changes to other fields
 		}
 		// ... rest of the method
 	}
config_test.go (1)

21-25: Consider parameterizing the connection type.

The connection type is hardcoded as "mysql" in the test suite, but this is a Postgres package. Consider using a more appropriate default or making it configurable.

 func TestConfigTestSuite(t *testing.T) {
 	suite.Run(t, &ConfigTestSuite{
-		connection: "mysql",
+		connection: "postgres",
 	})
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9159361 and 6e1cae5.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (12)
  • config.go (1 hunks)
  • config_test.go (1 hunks)
  • contracts/config.go (1 hunks)
  • docker.go (1 hunks)
  • docker_test.go (1 hunks)
  • errors.go (1 hunks)
  • facades/postgres.go (1 hunks)
  • go.mod (1 hunks)
  • postgres.go (1 hunks)
  • postgres_test.go (1 hunks)
  • service_provider.go (1 hunks)
  • utils.go (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • errors.go
  • go.mod
🔇 Additional comments (5)
postgres.go (1)

146-148: ⚠️ Potential issue

Avoid including sensitive information in error logs

The DSN constructed in the connect method includes the username and password in plain text. Ensure that this information is not exposed in error messages or logs to prevent sensitive information leakage.

Review the code to confirm that the DSN or any error containing it is not logged. If any logging occurs, consider masking sensitive information before logging.

postgres_test.go (1)

10-14: Validate driver name consistency

The test function TestDriverName correctly ensures that the driver name has not been modified unexpectedly, which is important for maintaining compatibility.

docker_test.go (1)

87-97: LGTM! Well-structured test methods.

The test methods are focused and verify essential functionality.

config.go (1)

12-22: LGTM! Well-designed configuration builder.

The struct is well-encapsulated and follows good design practices.

config_test.go (1)

114-229: LGTM! Well-structured table-driven tests.

The test cases are comprehensive and well-organized, covering empty configs, default values, and custom configurations.

Comment thread docker.go
Comment on lines +71 to +94
func (r *Docker) Database(name string) (testing.DatabaseDriver, error) {
go func() {
gormDB, err := r.connect()
if err != nil {
color.Errorf("connect Postgres error: %v", err)
return
}

res := gormDB.Exec(fmt.Sprintf(`CREATE DATABASE "%s";`, name))
if res.Error != nil {
color.Errorf("create Postgres database error: %v", res.Error)
}

if err := r.close(gormDB); err != nil {
color.Errorf("close Postgres connection error: %v", err)
}
}()

postgresImpl := NewDocker(name, r.username, r.password)
postgresImpl.containerID = r.containerID
postgresImpl.port = r.port

return postgresImpl, nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle errors synchronously in the Database method

In the Database method, the database creation is performed asynchronously in a goroutine, and any errors are only logged but not returned. This can lead to silent failures and potential race conditions if the database is accessed before it's ready.

Consider modifying the code to perform database creation synchronously and properly handle errors by returning them to the caller.

Apply this diff to refactor the method:

-func (r *Docker) Database(name string) (testing.DatabaseDriver, error) {
-	go func() {
-		gormDB, err := r.connect()
-		if err != nil {
-			color.Errorf("connect Postgres error: %v", err)
-			return
-		}
-
-		res := gormDB.Exec(fmt.Sprintf(`CREATE DATABASE "%s";`, name))
-		if res.Error != nil {
-			color.Errorf("create Postgres database error: %v", res.Error)
-		}
-
-		if err := r.close(gormDB); err != nil {
-			color.Errorf("close Postgres connection error: %v", err)
-		}
-	}()
-
-	postgresImpl := NewDocker(name, r.username, r.password)
+	gormDB, err := r.connect()
+	if err != nil {
+		return nil, fmt.Errorf("connect Postgres error: %v", err)
+	}
+
+	res := gormDB.Exec(fmt.Sprintf(`CREATE DATABASE "%s";`, name))
+	if res.Error != nil {
+		return nil, fmt.Errorf("create Postgres database error: %v", res.Error)
+	}
+
+	if err := r.close(gormDB); err != nil {
+		return nil, fmt.Errorf("close Postgres connection error: %v", err)
+	}
+
+	postgresImpl := NewDocker(name, r.username, r.password)
 	postgresImpl.containerID = r.containerID
 	postgresImpl.port = r.port

 	return postgresImpl, nil
 }
📝 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
func (r *Docker) Database(name string) (testing.DatabaseDriver, error) {
go func() {
gormDB, err := r.connect()
if err != nil {
color.Errorf("connect Postgres error: %v", err)
return
}
res := gormDB.Exec(fmt.Sprintf(`CREATE DATABASE "%s";`, name))
if res.Error != nil {
color.Errorf("create Postgres database error: %v", res.Error)
}
if err := r.close(gormDB); err != nil {
color.Errorf("close Postgres connection error: %v", err)
}
}()
postgresImpl := NewDocker(name, r.username, r.password)
postgresImpl.containerID = r.containerID
postgresImpl.port = r.port
return postgresImpl, nil
}
func (r *Docker) Database(name string) (testing.DatabaseDriver, error) {
gormDB, err := r.connect()
if err != nil {
return nil, fmt.Errorf("connect Postgres error: %v", err)
}
res := gormDB.Exec(fmt.Sprintf(`CREATE DATABASE "%s";`, name))
if res.Error != nil {
return nil, fmt.Errorf("create Postgres database error: %v", res.Error)
}
if err := r.close(gormDB); err != nil {
return nil, fmt.Errorf("close Postgres connection error: %v", err)
}
postgresImpl := NewDocker(name, r.username, r.password)
postgresImpl.containerID = r.containerID
postgresImpl.port = r.port
return postgresImpl, nil
}

Comment thread postgres.go
Comment on lines +36 to +40
func (r *Postgres) Config() database.Config1 {
writers := r.configBuilder.Writes()
if len(writers) == 0 {
return database.Config1{}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the return type of the Config method

The return type database.Config1 does not exist and should be corrected to database.Config.

Apply this diff to fix the return type:

-func (r *Postgres) Config() database.Config1 {
+func (r *Postgres) Config() database.Config {
📝 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
func (r *Postgres) Config() database.Config1 {
writers := r.configBuilder.Writes()
if len(writers) == 0 {
return database.Config1{}
}
func (r *Postgres) Config() database.Config {
writers := r.configBuilder.Writes()
if len(writers) == 0 {
return database.Config1{}
}

Comment thread postgres.go
Comment on lines +120 to +130
func (r *Postgres) dns(config contracts.FullConfig) string {
if config.Dsn != "" {
return config.Dsn
}
if config.Host == "" {
return ""
}

return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s&timezone=%s&search_path=%s",
config.Username, config.Password, config.Host, config.Port, config.Database, config.Sslmode, config.Timezone, config.Schema)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Rename method from dns to dsn

The method name dns is likely a typo and should be dsn, which stands for Data Source Name.

Apply this diff to rename the method and its usages:

-func (r *Postgres) dns(config contracts.FullConfig) string {
+func (r *Postgres) dsn(config contracts.FullConfig) string {
     // ...
 }
 
-func (r *Postgres) configsToDialectors(configs []contracts.FullConfig) ([]gorm.Dialector, error) {
     // ...
-        dsn := r.dns(config)
+        dsn := r.dsn(config)
     // ...
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment thread facades/postgres.go
return nil
}

return instance.(*postgres.Postgres)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add type assertion safety check

The unsafe type assertion could panic if the instance is not of type *postgres.Postgres.

-	return instance.(*postgres.Postgres)
+	driver, ok := instance.(*postgres.Postgres)
+	if !ok {
+		return nil, fmt.Errorf("invalid postgres driver instance type: %T", instance)
+	}
+	return driver, nil

Committable suggestion skipped: line range outside the PR's diff.

Comment thread facades/postgres.go
Comment on lines +12 to +15
if postgres.App == nil {
log.Fatalln("please register postgres service provider")
return nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace log.Fatalln with error return

Using log.Fatalln in a library is not recommended as it forcefully terminates the application. Instead, return an error to let the caller decide how to handle it.

-	if postgres.App == nil {
-		log.Fatalln("please register postgres service provider")
-		return nil
-	}
+	if postgres.App == nil {
+		return nil, fmt.Errorf("postgres service provider not registered")
+	}

Committable suggestion skipped: line range outside the PR's diff.

Comment thread service_provider.go
DriverName = "postgres"
)

var App foundation.Application
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid global state

Using a global App variable makes the code harder to test and reason about. Consider making it a field of ServiceProvider.

-var App foundation.Application
+type ServiceProvider struct {
+	app foundation.Application
+}

Committable suggestion skipped: line range outside the PR's diff.

Comment thread utils.go
Comment on lines +17 to +18
// Used by TestContainer, to simulate the port is using.
var testPortUsing = false
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace global test variable

Using a global variable for testing makes the code harder to maintain and test. Consider using dependency injection or interface-based testing.

-var testPortUsing = false
+type portChecker interface {
+    isPortUsing(port int) bool
+}

Committable suggestion skipped: line range outside the PR's diff.

Comment thread utils.go
Comment on lines +47 to +60
func getValidPort() int {
for i := 0; i < 60; i++ {
random := rand.Intn(10000) + 10000
l, err := net.Listen("tcp", fmt.Sprintf(":%d", random))
if err != nil {
continue
}
defer l.Close()

return random
}

return 0
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve port allocation reliability

The current implementation has several issues:

  1. Magic numbers (10000, 60) should be constants
  2. Limited retries might fail in high-contention scenarios
  3. No context for timeout/cancellation
+const (
+    minPort = 10000
+    maxPort = 20000
+    maxRetries = 60
+)
+
-func getValidPort() int {
+func getValidPort(ctx context.Context) (int, error) {
-	for i := 0; i < 60; i++ {
-		random := rand.Intn(10000) + 10000
+	for i := 0; i < maxRetries; i++ {
+		select {
+		case <-ctx.Done():
+			return 0, ctx.Err()
+		default:
+			random := rand.Intn(maxPort-minPort) + minPort
+			l, err := net.Listen("tcp", fmt.Sprintf(":%d", random))
+			if err != nil {
+				continue
+			}
+			l.Close()
+			return random, nil
+		}
+	}
-		l, err := net.Listen("tcp", fmt.Sprintf(":%d", random))
-		if err != nil {
-			continue
-		}
-		defer l.Close()
-
-		return random
-	}
-
-	return 0
+	return 0, fmt.Errorf("failed to find available port after %d attempts", maxRetries)
 }
📝 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
func getValidPort() int {
for i := 0; i < 60; i++ {
random := rand.Intn(10000) + 10000
l, err := net.Listen("tcp", fmt.Sprintf(":%d", random))
if err != nil {
continue
}
defer l.Close()
return random
}
return 0
}
const (
minPort = 10000
maxPort = 20000
maxRetries = 60
)
func getValidPort(ctx context.Context) (int, error) {
for i := 0; i < maxRetries; i++ {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
random := rand.Intn(maxPort-minPort) + minPort
l, err := net.Listen("tcp", fmt.Sprintf(":%d", random))
if err != nil {
continue
}
l.Close()
return random, nil
}
}
return 0, fmt.Errorf("failed to find available port after %d attempts", maxRetries)
}

Comment thread utils.go
Comment on lines +89 to +103
func run(command string) (string, error) {
cmd := exec.Command("/bin/sh", "-c", command)

var out bytes.Buffer
var stderr bytes.Buffer

cmd.Stdout = &out
cmd.Stderr = &stderr

if err := cmd.Run(); err != nil {
return "", fmt.Errorf("%s: %s", err, stderr.String())
}

return str.Of(out.String()).Squish().String(), nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Secure command execution

The current implementation might be vulnerable to command injection. Consider:

  1. Validating command input
  2. Using proper argument escaping
  3. Adding timeout context
-func run(command string) (string, error) {
+func run(ctx context.Context, command string) (string, error) {
+	if err := validateCommand(command); err != nil {
+		return "", fmt.Errorf("invalid command: %w", err)
+	}
+
 	cmd := exec.Command("/bin/sh", "-c", command)
+	cmd.Cancel = ctx.Done()
 
 	var out bytes.Buffer
 	var stderr bytes.Buffer
 
 	cmd.Stdout = &out
 	cmd.Stderr = &stderr
 
 	if err := cmd.Run(); err != nil {
+		if ctx.Err() != nil {
+			return "", ctx.Err()
+		}
 		return "", fmt.Errorf("%s: %s", err, stderr.String())
 	}
 
 	return str.Of(out.String()).Squish().String(), nil
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment thread config.go
Comment on lines +32 to +39
func (c *ConfigBuilder) Reads() []contracts.FullConfig {
configs := c.config.Get(fmt.Sprintf("database.connections.%s.read", c.connection))
if readConfigs, ok := configs.([]contracts.Config); ok {
return c.fillDefault(readConfigs)
}

return nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for type assertions.

The type assertion could panic if the configuration value is of an unexpected type.

 func (c *ConfigBuilder) Reads() []contracts.FullConfig {
 	configs := c.config.Get(fmt.Sprintf("database.connections.%s.read", c.connection))
-	if readConfigs, ok := configs.([]contracts.Config); ok {
+	readConfigs, ok := configs.([]contracts.Config)
+	if configs != nil && !ok {
+		// Log or handle the type mismatch
+		return nil
+	}
+	if ok {
 		return c.fillDefault(readConfigs)
 	}
 
 	return nil
 }
📝 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
func (c *ConfigBuilder) Reads() []contracts.FullConfig {
configs := c.config.Get(fmt.Sprintf("database.connections.%s.read", c.connection))
if readConfigs, ok := configs.([]contracts.Config); ok {
return c.fillDefault(readConfigs)
}
return nil
}
func (c *ConfigBuilder) Reads() []contracts.FullConfig {
configs := c.config.Get(fmt.Sprintf("database.connections.%s.read", c.connection))
readConfigs, ok := configs.([]contracts.Config)
if configs != nil && !ok {
// Log or handle the type mismatch
return nil
}
if ok {
return c.fillDefault(readConfigs)
}
return nil
}

@hwbrzzl hwbrzzl deleted the bowen/init branch September 6, 2025 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ [Feature] Move Postgres, Mysql, etc. DB drivers to single packages

1 participant