Skip to content
Merged
Show file tree
Hide file tree
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
20 changes: 18 additions & 2 deletions 10-databases/visualizer/LAB-VISUALIZER.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ graph LR
└── style.css # Dark theme styling
```

## Data Model

The lab uses a **university enrollment** scenario pre-loaded with
sample data:

| Table | Rows | Description |
| --- | --- | --- |
| `students` | 10 | Name, email, major (e.g., Alice Johnson, Computer Science) |
| `courses` | 4 | CS101, CS201, MATH101, PHYS101 with capacity and enrolled count |
| `enrollments` | 10 | Links students to courses (foreign keys with unique constraint) |
| `access_log` | 10,000 | Simulated resource access records for indexing exercises |

This is the same data model used across all Module 10 labs (MySQL,
MongoDB, Cassandra), so you can compare how each database handles the
same scenario differently.
Comment on lines +169 to +183
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The UI change removed the collapsible <details> SQL Console, but the lab instructions still say "click SQL Console to expand it" (later in the doc). Please update those steps to match the new always-visible console so students don’t get stuck looking for an expand control.

Copilot uses AI. Check for mistakes.

---

## Task 1: Explore the Environment
Expand Down Expand Up @@ -342,7 +358,7 @@ remain unchanged.
In the SQL Console (Primary), run:

```sql
SELECT c.code, c.enrolled FROM courses ORDER BY code;
SELECT code, enrolled FROM courses ORDER BY code;
```

Confirm the counts match what the sidebar shows. The failed
Expand Down Expand Up @@ -376,7 +392,7 @@ accessing resource-10.
1. Set Student ID to `3`, Resource to `resource-10`
1. Click **Run EXPLAIN**

The result shows **Rows scanned: ~9,894** in red, with Key: **NONE
The result shows **Rows scanned: ~10,000** in red, with Key: **NONE
(full scan)**. MySQL examined nearly all 10,000 rows to find a
handful of matches. Check the sidebar: Indexes shows **None**:

Expand Down
20 changes: 19 additions & 1 deletion 10-databases/visualizer/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
};

let animating = false;
let pollTimer = null;

Check warning on line 81 in 10-databases/visualizer/app.js

View workflow job for this annotation

GitHub Actions / Frontend Linting

'pollTimer' is assigned a value but never used

function $(sel) { return document.querySelector(sel); }
function $$(sel) { return document.querySelectorAll(sel); }
Expand Down Expand Up @@ -465,7 +465,7 @@

// --- SQL Console ---

let consoleHistory = [];

Check warning on line 468 in 10-databases/visualizer/app.js

View workflow job for this annotation

GitHub Actions / Frontend Linting

'consoleHistory' is never reassigned. Use 'const' instead
let consoleHistoryIndex = -1;

async function doSqlExec() {
Expand All @@ -473,7 +473,7 @@
const query = input.value.trim();
if (!query) return;

const target = document.querySelector('input[name="sql-target"]:checked').value;
const target = document.querySelector('.target-btn.active')?.dataset.target || 'primary';
const output = $('#console-output');

// Add to history
Expand Down Expand Up @@ -564,6 +564,24 @@
consoleHistoryIndex >= 0 ? consoleHistory[consoleHistoryIndex] : '';
}
});

// Target toggle buttons
$$('.target-btn').forEach(btn => {
btn.addEventListener('click', () => {
$$('.target-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const target = btn.dataset.target;
const prompt = $('.console-prompt');
if (prompt) {
prompt.textContent = target === 'replica' ? 'replica>' : 'mysql>';
}
Comment on lines +569 to +577
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

When switching targets, the code updates CSS classes and the prompt text but does not expose the selected state to assistive tech. If you add aria-pressed/radiogroup semantics to the target buttons, also update the corresponding ARIA attributes here when toggling.

Suggested change
$$('.target-btn').forEach(btn => {
btn.addEventListener('click', () => {
$$('.target-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const target = btn.dataset.target;
const prompt = $('.console-prompt');
if (prompt) {
prompt.textContent = target === 'replica' ? 'replica>' : 'mysql>';
}
const setConsoleTarget = (selectedBtn) => {
$$('.target-btn').forEach(b => {
const isSelected = b === selectedBtn;
b.classList.toggle('active', isSelected);
b.setAttribute('aria-pressed', isSelected ? 'true' : 'false');
});
const target = selectedBtn.dataset.target;
const prompt = $('.console-prompt');
if (prompt) {
prompt.textContent = target === 'replica' ? 'replica>' : 'mysql>';
}
};
const activeTargetBtn = $('.target-btn.active');
$$('.target-btn').forEach(btn => {
btn.setAttribute('aria-pressed', btn === activeTargetBtn ? 'true' : 'false');
btn.addEventListener('click', () => {
setConsoleTarget(btn);

Copilot uses AI. Check for mistakes.
});
});

// Clear button
$('#console-clear').addEventListener('click', () => {
$('#console-output').innerHTML = '';
});
}

function initButtons() {
Expand Down
26 changes: 13 additions & 13 deletions 10-databases/visualizer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -260,20 +260,20 @@ <h3>Event Log</h3>
</div>

<div class="sql-console">
<details id="console-details">
<summary>SQL Console</summary>
<div class="console-body">
<div class="console-target">
<label><input type="radio" name="sql-target" value="primary" checked /> Primary</label>
<label><input type="radio" name="sql-target" value="replica" /> Replica</label>
</div>
<div id="console-output" class="console-output"></div>
<div class="console-input-row">
<input type="text" id="console-input" placeholder="SELECT * FROM students LIMIT 5;" />
<button id="console-submit" class="btn btn-primary">Run</button>
</div>
<div class="console-header">
<span class="console-title">SQL Console</span>
<div class="console-target-toggle">
<button class="target-btn active" data-target="primary">Primary</button>
<button class="target-btn" data-target="replica">Replica</button>
Comment on lines +265 to +267
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The Primary/Replica toggle is implemented as two buttons with only a visual "active" state. For accessibility, consider adding proper pressed/selected semantics (e.g. aria-pressed on each button, or a radiogroup pattern) so screen readers can determine which target is selected.

Suggested change
<div class="console-target-toggle">
<button class="target-btn active" data-target="primary">Primary</button>
<button class="target-btn" data-target="replica">Replica</button>
<div class="console-target-toggle" role="group" aria-label="SQL console target">
<button
type="button"
class="target-btn active"
data-target="primary"
aria-pressed="true"
>
Primary
</button>
<button
type="button"
class="target-btn"
data-target="replica"
aria-pressed="false"
>
Replica
</button>

Copilot uses AI. Check for mistakes.
</div>
</details>
<button id="console-clear" class="console-clear-btn" title="Clear output">Clear</button>
</div>
<div id="console-output" class="console-output"></div>
<div class="console-input-row">
<span class="console-prompt">mysql&gt;</span>
<input type="text" id="console-input" placeholder="SELECT * FROM students LIMIT 5;" />
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The SQL input relies on placeholder text but has no associated <label>/accessible name. Add a <label for="console-input"> (can be visually hidden) or an aria-label so the field is discoverable for screen readers.

Suggested change
<input type="text" id="console-input" placeholder="SELECT * FROM students LIMIT 5;" />
<input
type="text"
id="console-input"
aria-label="SQL query input"
placeholder="SELECT * FROM students LIMIT 5;"
/>

Copilot uses AI. Check for mistakes.
<button id="console-submit" class="btn btn-primary">Run</button>
</div>
</div>
</div>
</footer>
Expand Down
7 changes: 6 additions & 1 deletion 10-databases/visualizer/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,13 @@ def sql_exec(body):
if not query:
return {"error": "Empty query"}

# Strip mysql CLI formatting suffix (\G) -- not valid SQL
query = query.rstrip(";").rstrip()
if query.endswith("\\G"):
query = query[:-2].rstrip()

# Reject multi-statement queries
if ";" in query.rstrip(";"):
if ";" in query:
return {"error": "Multi-statement queries are not allowed"}
Comment on lines +371 to 378
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Current \G stripping happens after stripping only trailing ';', so a query like SELECT 1;\G becomes SELECT 1; after removing \G and then gets rejected by the multi-statement check. Strip the \\G suffix first (or strip it and then remove a trailing semicolon) before checking for internal semicolons.

Copilot uses AI. Check for mistakes.

# Allowlist: only permit known safe SQL commands
Expand Down
128 changes: 96 additions & 32 deletions 10-databases/visualizer/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -435,12 +435,12 @@ h3 {
color: var(--color-text-muted);
}

/* --- Bottom Bar (Event Log) --- */
/* --- Bottom Bar --- */
.bottom-bar {
background: var(--color-bg-surface);
border-top: 1px solid var(--color-border);
padding: 0.75rem 1rem;
max-height: var(--bottom-bar-height);
height: 260px;
}
Comment on lines +438 to 444
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

.bottom-bar now uses a hard-coded height: 260px, but --bottom-bar-height is still defined at the top of the file and no longer used anywhere. Consider switching back to height/max-height based on var(--bottom-bar-height) (and updating the variable if needed) to keep sizing configurable and avoid an unused design token.

Copilot uses AI. Check for mistakes.

.event-log {
Expand Down Expand Up @@ -519,68 +519,121 @@ h3 {
.sql-console {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
background: #0a0e17;
border-radius: var(--radius-lg);
border: 1px solid var(--color-border);
overflow: hidden;
}

.sql-console details {
height: 100%;
.console-header {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 6px 12px;
background: rgba(255, 255, 255, 0.03);
border-bottom: 1px solid var(--color-border);
}

.sql-console summary {
cursor: pointer;
font-size: 0.9rem;
.console-title {
font-size: 0.8rem;
font-weight: 600;
color: var(--color-text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}

.console-body {
.console-target-toggle {
display: flex;
flex-direction: column;
gap: 0.5rem;
background: var(--color-bg);
border-radius: 6px;
padding: 2px;
gap: 2px;
}

.console-target {
display: flex;
gap: 1rem;
font-size: 0.8rem;
.target-btn {
padding: 3px 12px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--color-text-muted);
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}

.console-target label {
display: flex;
align-items: center;
gap: 4px;
.target-btn.active {
background: var(--color-primary);
color: #fff;
}

.target-btn:hover:not(.active) {
color: var(--color-text);
}

.console-clear-btn {
margin-left: auto;
padding: 2px 8px;
border: 1px solid var(--color-border);
border-radius: 4px;
background: transparent;
color: var(--color-text-muted);
font-size: 0.7rem;
cursor: pointer;
transition: all 0.15s ease;
}

.console-clear-btn:hover {
color: var(--color-error);
border-color: var(--color-error);
}

.console-output {
font-family: var(--font-mono);
font-size: 0.75rem;
background: var(--color-bg);
border-radius: var(--radius);
padding: 0.5rem;
max-height: 100px;
background: #0a0e17;
padding: 8px 12px;
flex: 1;
overflow-y: auto;
white-space: pre-wrap;
color: var(--color-text);
color: #a3e635;
}
Comment on lines 519 to 602
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This redesign introduces several hard-coded hex colors (e.g. #0a0e17, #a3e635, #22d3ee) while the rest of the theme uses CSS variables (e.g. --color-*). To keep the theme consistent and easier to adjust later, consider defining console-specific variables (or reusing existing tokens) instead of embedding raw hex values throughout these rules.

Copilot uses AI. Check for mistakes.

.console-input-row {
display: flex;
gap: 0.5rem;
align-items: center;
gap: 0;
border-top: 1px solid var(--color-border);
background: rgba(255, 255, 255, 0.02);
}

.console-prompt {
padding: 8px 4px 8px 12px;
color: #22d3ee;
font-family: var(--font-mono);
font-size: 0.8rem;
font-weight: 600;
white-space: nowrap;
}

.console-input-row input {
flex: 1;
padding: 6px 10px;
border-radius: var(--radius);
border: 1px solid var(--color-border);
background: var(--color-bg);
padding: 8px 8px;
border: none;
background: transparent;
color: var(--color-text);
font-family: var(--font-mono);
font-size: 0.8rem;
outline: none;
}

.console-input-row .btn {
border-radius: 0;
padding: 8px 16px;
border: none;
border-left: 1px solid var(--color-border);
}

.console-result-table {
Expand All @@ -602,14 +655,25 @@ h3 {
}

.console-query {
color: var(--color-primary);
color: #22d3ee;
margin-top: 6px;
}

.console-error {
color: var(--color-error);
color: #f87171;
}

.console-meta {
color: var(--color-text-muted);
color: #94a3b8;
font-size: 0.7rem;
margin-bottom: 4px;
}

.console-result-table th {
color: #a3e635;
background: rgba(163, 230, 53, 0.08);
}

.console-result-table td {
color: #e2e8f0;
}
Loading