Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions challenge-13/submissions/Johrespi/solution-template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package main

import (
"database/sql"
"errors"
"fmt"

_ "github.com/mattn/go-sqlite3"
)

// Product represents a product in the inventory system
type Product struct {
ID int64
Name string
Price float64
Quantity int
Category string
}

// ProductStore manages product operations
type ProductStore struct {
db *sql.DB
}

// NewProductStore creates a new ProductStore with the given database connection
func NewProductStore(db *sql.DB) *ProductStore {
return &ProductStore{db: db}
}

// InitDB sets up a new SQLite database and creates the products table
func InitDB(dbPath string) (*sql.DB, error) {
// TODO: Open a SQLite database connection
// TODO: Create the products table if it doesn't exist
// The table should have columns: id, name, price, quantity, category

db, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, err
}

createTableQuery := ` CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL,
quantity INTEGER NOT NULL,
category TEXT NOT NULL
);`

_, err = db.Exec(createTableQuery)
if err != nil {
return nil, err
}

return db, nil
}

// CreateProduct adds a new product to the database
func (ps *ProductStore) CreateProduct(product *Product) error {
// TODO: Insert the product into the database
// TODO: Update the product.ID with the database-generated ID
stmt := `INSERT INTO products (name, price, quantity, category) VALUES(?, ?, ?, ?)`

dbResult, err := ps.db.Exec(stmt, product.Name, product.Price, product.Quantity, product.Category)
if err != nil {
return err
}

id, err := dbResult.LastInsertId()
if err != nil {
return err
}
product.ID = id

return nil
}

// GetProduct retrieves a product by ID
func (ps *ProductStore) GetProduct(id int64) (*Product, error) {
// TODO: Query the database for a product with the given ID
// TODO: Return a Product struct populated with the data or an error if not found

stmt := `SELECT id, name, price, quantity, category FROM products WHERE id = ?`
row := ps.db.QueryRow(stmt, id)

var product Product
err := row.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.New("product not found")
}
return nil, err
}

return &product, nil

}

// UpdateProduct updates an existing product
func (ps *ProductStore) UpdateProduct(product *Product) error {
// TODO: Update the product in the database
// TODO: Return an error if the product doesn't exist

p, err := ps.GetProduct(product.ID)
if err != nil {
return err
}

p.ID = product.ID
p.Name = product.Name
p.Price = product.Price
p.Quantity = product.Quantity
p.Category = product.Category

stmt := `UPDATE products SET name = ?, price = ?, quantity = ?, category = ? WHERE id = ?`

_, err = ps.db.Exec(stmt, p.Name, p.Price, p.Quantity, p.Category, p.ID)
if err != nil {
return err
}

return nil

}

// DeleteProduct removes a product by ID
func (ps *ProductStore) DeleteProduct(id int64) error {
// TODO: Delete the product from the database
// TODO: Return an error if the product doesn't exist

stmt := `DELETE FROM products WHERE id = ?`

result, err := ps.db.Exec(stmt, id)
if err != nil {
return err
}

rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.New("product not found")
}

return nil

}

// ListProducts returns all products with optional filtering by category
func (ps *ProductStore) ListProducts(category string) ([]*Product, error) {
// TODO: Query the database for products
// TODO: If category is not empty, filter by category
// TODO: Return a slice of Product pointers
stmt := `SELECT id, name, price, quantity, category FROM products
WHERE (? = '' OR category = ?)`

var products []*Product

rows, err := ps.db.Query(stmt, category, category)
if err != nil {
return nil, err
}

defer rows.Close()

for rows.Next() {
var product Product
err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category)
if err != nil {
return nil, err
}
products = append(products, &product)
}
Comment on lines +166 to +173
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix pointer reuse in ListProducts

product is a loop-scoped value, so every appended pointer ends up referencing the same memory and the slice returns multiple aliases of the last row. Please allocate a new struct per iteration before scanning to guarantee each entry points to its own product instance.

Apply this diff:

-	for rows.Next() {
-		var product Product
-		err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category)
-		if err != nil {
-			return nil, err
-		}
-		products = append(products, &product)
-	}
+	for rows.Next() {
+		product := &Product{}
+		if err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category); err != nil {
+			return nil, err
+		}
+		products = append(products, product)
+	}

(freecodecamp.org)

📝 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
for rows.Next() {
var product Product
err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category)
if err != nil {
return nil, err
}
products = append(products, &product)
}
for rows.Next() {
product := &Product{}
if err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category); err != nil {
return nil, err
}
products = append(products, product)
}
🤖 Prompt for AI Agents
challenge-13/submissions/Johrespi/solution-template.go around lines 166 to 173:
the loop reuses a single loop-scoped variable "product" and appends its pointer
each iteration, causing all slice entries to point to the same struct; fix by
allocating a new Product instance inside the loop for each row (e.g., create a
new variable or take address of a fresh struct) and scan into that fresh
instance before appending so each slice element references its own product.


return products, nil
Comment on lines +166 to +175
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Surface iteration errors from rows.Err()

If the driver reports an error after rows.Next() stops, it’s currently ignored and you return partial data. Add a rows.Err() check before returning to propagate those failures.

Apply this diff:

 	for rows.Next() {
 		product := &Product{}
 		if err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category); err != nil {
 			return nil, err
 		}
 		products = append(products, product)
 	}
+
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
 
 	return products, nil

(freecodecamp.org)

📝 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
for rows.Next() {
var product Product
err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category)
if err != nil {
return nil, err
}
products = append(products, &product)
}
return products, nil
for rows.Next() {
product := &Product{}
if err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.Quantity, &product.Category); err != nil {
return nil, err
}
products = append(products, product)
}
if err := rows.Err(); err != nil {
return nil, err
}
return products, nil
🤖 Prompt for AI Agents
In challenge-13/submissions/Johrespi/solution-template.go around lines 166 to
175, the code iterates rows and returns products without checking for any error
reported by the driver after iteration; add a check for rows.Err() after the for
loop and before returning to surface any iteration errors and return that error
(or wrap it) instead of silently returning partial results.


}

// BatchUpdateInventory updates the quantity of multiple products in a single transaction
func (ps *ProductStore) BatchUpdateInventory(updates map[int64]int) error {
// TODO: Start a transaction
// TODO: For each product ID in the updates map, update its quantity
// TODO: If any update fails, roll back the transaction
// TODO: Otherwise, commit the transaction

tx, err := ps.db.Begin()
if err != nil {
return err
}

defer func() {
if err != nil {
tx.Rollback()
}
}()

stmt := `UPDATE products SET quantity = ? WHERE id = ?`

for productID, newQuantity := range updates {
result, err := tx.Exec(stmt, newQuantity, productID)
if err != nil {
return err
}

rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return fmt.Errorf("product with ID %d not found", productID)
}

}
return tx.Commit()
Comment on lines +186 to +214
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guarantee rollback when Commit fails

Because the function returns tx.Commit() directly, err stays nil and the deferred rollback never runs on commit failure. Capture the commit error first, run an explicit rollback on failure, and only then return. Converting to a named return makes the defer pattern work as intended.

Apply this diff:

-func (ps *ProductStore) BatchUpdateInventory(updates map[int64]int) error {
+func (ps *ProductStore) BatchUpdateInventory(updates map[int64]int) (err error) {
 	tx, err := ps.db.Begin()
 	if err != nil {
 		return err
 	}
 
 	defer func() {
 		if err != nil {
-			tx.Rollback()
+			_ = tx.Rollback()
 		}
 	}()
 
 	stmt := `UPDATE products SET quantity = ? WHERE id = ?`
 
 	for productID, newQuantity := range updates {
-		result, err := tx.Exec(stmt, newQuantity, productID)
-		if err != nil {
-			return err
-		}
+		result, execErr := tx.Exec(stmt, newQuantity, productID)
+		if execErr != nil {
+			err = execErr
+			return err
+		}
 
-		rowsAffected, err := result.RowsAffected()
-		if err != nil {
-			return err
-		}
+		rowsAffected, rowsErr := result.RowsAffected()
+		if rowsErr != nil {
+			err = rowsErr
+			return err
+		}
 		if rowsAffected == 0 {
-			return fmt.Errorf("product with ID %d not found", productID)
+			err = fmt.Errorf("product with ID %d not found", productID)
+			return err
 		}
 
 	}
-	return tx.Commit()
+	if commitErr := tx.Commit(); commitErr != nil {
+		_ = tx.Rollback()
+		return commitErr
+	}
+	return nil
 }

(tip.golang.org)

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

}

func main() {
// Optional: you can write code here to test your implementation
}
Loading