fix: eliminate N+1 queries in get_custom_commands (#26)

Expand the JOIN SELECT to include all creator fields
(created_at, total_commands, active_commands) and build the
CustomCommandCreatorModel directly from joined row data instead of
issuing a separate SELECT per command. Reduces 26 queries to 2 for
a full 25-command page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-03-05 16:03:28 -06:00
parent 580e8ea031
commit e311de93aa

View File

@ -273,7 +273,10 @@ async def get_custom_commands(
sql = f"""
SELECT cc.*, creator.discord_id as creator_discord_id,
creator.username as creator_username,
creator.display_name as creator_display_name
creator.display_name as creator_display_name,
creator.created_at as creator_created_at,
creator.total_commands as creator_total_commands,
creator.active_commands as creator_active_commands
FROM custom_commands cc
LEFT JOIN custom_command_creators creator ON cc.creator_id = creator.id
{where_clause}
@ -298,24 +301,23 @@ async def get_custom_commands(
command_dict["tags"] = json.loads(command_dict["tags"])
except Exception:
command_dict["tags"] = []
# Get full creator information
creator_id = command_dict["creator_id"]
creator_cursor = db.execute_sql(
"SELECT * FROM custom_command_creators WHERE id = %s", (creator_id,)
)
creator_result = creator_cursor.fetchone()
if creator_result:
# Create complete creator object
creator_columns = [desc[0] for desc in creator_cursor.description]
creator_dict = dict(zip(creator_columns, creator_result))
# Convert datetime to ISO string
if creator_dict.get("created_at") and hasattr(
creator_dict["created_at"], "isoformat"
):
creator_dict["created_at"] = creator_dict[
"created_at"
].isoformat()
# Build creator object from joined data (avoids N+1 queries)
if command_dict.get("creator_discord_id") is not None:
creator_created_at = command_dict.get("creator_created_at")
if creator_created_at and hasattr(creator_created_at, "isoformat"):
creator_created_at = creator_created_at.isoformat()
creator_dict = {
"id": command_dict["creator_id"],
"discord_id": command_dict["creator_discord_id"],
"username": command_dict["creator_username"],
"display_name": command_dict.get("creator_display_name"),
"created_at": creator_created_at,
"total_commands": command_dict.get("creator_total_commands", 0),
"active_commands": command_dict.get(
"creator_active_commands", 0
),
}
try:
creator_model = CustomCommandCreatorModel(**creator_dict)
command_dict["creator"] = creator_model
@ -325,13 +327,15 @@ async def get_custom_commands(
)
command_dict["creator"] = None
else:
# No creator found, set to None
command_dict["creator"] = None
# Remove the individual creator fields now that we have the creator object
# Remove joined creator fields before building the command model
command_dict.pop("creator_discord_id", None)
command_dict.pop("creator_username", None)
command_dict.pop("creator_display_name", None)
command_dict.pop("creator_created_at", None)
command_dict.pop("creator_total_commands", None)
command_dict.pop("creator_active_commands", None)
# Convert datetime fields to ISO strings
for field in ["created_at", "updated_at", "last_used"]:
@ -1055,3 +1059,4 @@ async def get_custom_command(command_id: int):
except Exception as e:
logger.error(f"Error getting custom command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e))