# מסד נתונים והקמת MySQL: טבלאות, מפתחות, Docker ו-Driver<no value>

## מה בשיעור

השיעור הזה בנוי משני פרקים, באותו סדר כמו במצגת:

1. **פרק ראשון** — מהו Database: טבלה, שורה, עמודה, Primary Key וטיפוסי נתונים
2. **פרק שני** — הקמת MySQL: Docker, Database Client ו-Driver

## פרק ראשון: מהו Database — טבלה, שורה, עמודה, Primary Key וטיפוסי נתונים

### מה נלמד?

- **למה** צריך Database (מסד נתונים).
- מה זה **Relational Database** (מסד נתונים רלציוני).
- **Table** (טבלה), **Row** (שורה), **Column** (עמודה).
- **Primary Key** (מפתח ראשי).
- **Data Types** (טיפוסי נתונים) בסיסיים.

{{< box "tip" "מה לא נלמד השבוע (בכוונה)" >}}
השבוע לומדים **רק את הבסיס הדרוש ל-CRUD**: חיבור, טבלה אחת, ויצירה, קריאה, עדכון ומחיקה. **לא** נלמד JOIN, GROUP BY, HAVING, Sub-Queries, נרמול מתקדם, אינדקסים או Stored Procedures. את אלה תלמדו לעומק בהמשך המסלול.
{{< /box >}}

### למה צריך את זה?

בנושא הקבצים שמרנו נתונים בקובץ JSON. זה עבד לתוכנית אחת שרצה אצלנו. בשרת אמיתי, זה נשבר:

| בעיה בקובץ JSON | מה קורה |
|---|---|
| **הכול או כלום בקריאה** | כדי למצוא סטודנט אחד מתוך מיליון, טוענים את **כל** הקובץ ל-RAM. |
| **הכול או כלום בכתיבה** | כדי לשנות שדה אחד, כותבים את **כל** הקובץ מחדש. |
| **שני משתמשים בבת אחת** | שתי בקשות קוראות את הקובץ, כל אחת משנה משהו, וכל אחת כותבת. **השינוי של הראשונה נמחק.** |
| **קריסה באמצע כתיבה** | הקובץ נשאר חצי כתוב, ופגום. |
| **אין כללים** | שום דבר לא מונע שני סטודנטים עם אותו אימייל, או שדה חסר. |
| **שאילתות** | "כל הסטודנטים מחיפה מעל גיל 20" = לולאה בקוד שלנו, על כל הנתונים. |

**Database** פותר את כל אלה: קריאה וכתיבה של **בדיוק** מה שצריך, טיפול בהרבה משתמשים בו-זמנית, הגנה מפני קריסות, כללים שנאכפים **במסד עצמו**, ושפה (SQL) לשאילתות.

### הסבר מהבסיס

#### מהו Database?

**Database** (מסד נתונים) = מערכת לשמירה **מאורגנת** של נתונים, שמאפשרת לשמור, למצוא, לעדכן ולמחוק אותם ביעילות ובאמינות. התוכנה שמנהלת אותו נקראת **DBMS** (Database Management System): MySQL, PostgreSQL, SQLite...

#### Relational Database — טבלאות

הסוג הנפוץ ביותר הוא **מסד נתונים רלציוני**: הנתונים מאורגנים ב**טבלאות**, כמו גיליון Excel עם כללים נוקשים.

```text
Table: students
┌────┬────────┬─────┬────────────────┐
│ id │ name   │ age │ email          │   ← columns (the structure)
├────┼────────┼─────┼────────────────┤
│  1 │ Dana   │  24 │ dana@mail.com  │   ← row = one student
│  2 │ Avi    │  31 │ avi@mail.com   │   ← row
│  3 │ Noa    │  22 │ noa@mail.com   │   ← row
└────┴────────┴─────┴────────────────┘
  ↑
  primary key
```

| מושג | מה זה | ב-Python (מה שכבר מכירים) |
|---|---|---|
| **Table** (טבלה) | אוסף של רשומות מאותו סוג | Class (התבנית) + רשימה של כל האובייקטים |
| **Column** (עמודה) | שדה אחד, עם **שם** ו**טיפוס** קבועים | Attribute |
| **Row** (שורה / רשומה / Record) | פריט אחד | אובייקט אחד / dict אחד |
| **Primary Key** | עמודה שמזהה כל שורה באופן **ייחודי** | `student_id` |

{{< box "analogy" "אנלוגיה: ארון תיקים במשרד" >}}
**Database** = הארון. **Table** = מגירה אחת ("סטודנטים"). **Columns** = הטופס המודפס שכל תיק בנוי עליו (שם, גיל, אימייל), זהה לכל התיקים. **Row** = תיק אחד, של סטודנט אחד. **Primary Key** = המספר על גב התיק: אף פעם לא חוזר, ולפיו מוציאים תיק בלי לעבור על כולם.
{{< /box >}}

#### Primary Key — מפתח ראשי

**Primary Key** = עמודה (בדרך כלל `id`) שערכה:

1. **ייחודי** — אין שתי שורות עם אותו ערך.
2. **לא ריק** — לכל שורה חייב להיות ערך.
3. **לא משתנה** — מזהה הוא לכל חיי השורה.

**למה לא להשתמש בשם?** כי יש שתי "דנה". **ובאימייל?** כי אנשים מחליפים אימייל. מזהה מספרי שהמסד מייצר לבד הוא הבחירה הבטוחה. (זוכרים את `Shop` שייצרה מזהים עם `max + 1`? המסד עושה את זה בשבילנו, ובצורה בטוחה יותר.)

וזה בדיוק ה-`{student_id}` שבנתיב של ה-API: `GET /students/7` = "תביא את השורה שה-Primary Key שלה הוא 7".

#### Data Types — טיפוסי נתונים בסיסיים

לכל עמודה יש **טיפוס**. ב-**MySQL** (המסד שנשתמש בו) יש הרבה טיפוסים, אבל זה מה שנצטרך:

| טיפוס SQL | מה שומרים | Python | דוגמה |
|---|---|---|---|
| **INT** | מספר שלם | `int` | `id`, `age`, `quantity` |
| **DECIMAL(10, 2)** | מספר עשרוני מדויק — 2 ספרות אחרי הנקודה | `Decimal` (כמו `float`, אבל מדויק) | `price` |
| **VARCHAR(100)** | טקסט — עד 100 תווים | `str` | `name`, `email` |
| (אין BOOLEAN אמיתי) | שומרים 0/1 ב-INT | `bool` | `is_active` |
| **NULL** | "אין ערך" | `None` | שדה אופציונלי ריק |

#### אילוצים (Constraints) בסיסיים

כללים שה**מסד** אוכף. גם אם יש באג בקוד, המסד יסרב:

| אילוץ | משמעות |
|---|---|
| `PRIMARY KEY` | מזהה ייחודי של השורה. |
| `AUTO_INCREMENT` | המסד מייצר את ה-id לבד, בסדר עולה. |
| `NOT NULL` | חובה למלא. |
| `UNIQUE` | אין שתי שורות עם אותו ערך (למשל אימייל). |

זה כמו ולידציה ב-`__init__` (נושא OOP), רק ש**המסד עצמו** אוכף אותה: שכבת הגנה נוספת.

#### SQL — השפה

**SQL** (Structured Query Language, נקרא "סיקוול" או "אס-קיו-אל") = השפה שבה מדברים עם מסד רלציוני. **Query** (שאילתה) = פקודה אחת ב-SQL. נכיר רק ארבע-חמש פקודות:

```text
CREATE TABLE   → create the structure      (day 3)
INSERT         → C - add a row             (day 3)
SELECT         → R - read rows             (day 3)
UPDATE         → U - change rows           (day 4)
DELETE         → D - remove rows           (day 4)
```

### תחביר

**תכנון טבלה** (תוצר של שלב התכנון, לפני כל קוד). כך נראה תכנון של טבלת `students`:

```text
Table: students
| column | type         | constraints                 |
|--------|--------------|-----------------------------|
| id     | INT          | AUTO_INCREMENT PRIMARY KEY  |
| name   | VARCHAR(100) | NOT NULL                    |
| age    | INT          | NOT NULL                    |
| email  | VARCHAR(100) | NOT NULL UNIQUE             |
```

וזו פקודת ה-SQL שמייצרת אותה (נכתוב ונריץ אותה בנושא מסדי נתונים ו-MySQL, כאן רק לראות לאן מתכנסים):

```sql
CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);
```

### דוגמת קוד ראשונה

**מ-Class של Python לתכנון טבלה.** ה-Class `Product` מנושא OOP:

```python
class Product:
    def __init__(self, sku, name, price, stock=0):
        ...
```

**התרגום:**

| Attribute ב-Python | עמודה | טיפוס | אילוצים | למה |
|---|---|---|---|---|
| — | `id` | INT | AUTO_INCREMENT PRIMARY KEY | מזהה פנימי יציב |
| `sku` | `sku` | VARCHAR(20) | NOT NULL UNIQUE | ייחודי, חובה |
| `name` | `name` | VARCHAR(100) | NOT NULL | חובה |
| `price` | `price` | DECIMAL(10, 2) | NOT NULL | מספר עשרוני מדויק |
| `stock` | `stock` | INT | NOT NULL DEFAULT 0 | מספר שלם, ברירת מחדל 0 |

### פירוק הקוד שורה אחר שורה

| החלטה | נימוק |
|---|---|
| הוספת `id` למרות שיש `sku` | `sku` הוא מזהה **עסקי** שעלול להשתנות (מיתוג מחדש). `id` הוא מזהה **טכני** שלא משתנה לעולם. מקובל להחזיק את שניהם. |
| `sku` עם `UNIQUE` | הכלל "SKU ייחודי" נאכף במסד, גם אם קוד אחר ישכח לבדוק. |
| `price` כ-DECIMAL(10, 2) | מחיר עם אגורות. `DECIMAL` שומר את המספר **בדיוק** — בלי בעיות העיגול של `float` (`0.1 + 0.2`). |
| מה **לא** נכנס לטבלה | מתודות (`take`, `restock`) הן **התנהגות**, וחיות בקוד. המסד שומר **נתונים** בלבד. |

### דוגמה נוספת

**מ-dicts לטבלה, וטיפוסים "מבולגנים".** נתונים שהגיעו מקובץ JSON:

```python
students = [
    {"name": "Dana", "age": 24, "email": "dana@mail.com", "phone": "050-1234567"},
    {"name": "Avi", "age": "31", "email": "avi@mail.com"},
    {"name": "Noa", "age": 22, "email": "dana@mail.com", "phone": None},
]
```

**שאלות תכנון:**

1. **`phone` מופיע רק בחלק מהשורות.** ‏←‏ עמודה **בלי** `NOT NULL`. כשאין ערך, נשמר **NULL** (`None` ב-Python).
2. **`age` של אבי הוא `"31"` (מחרוזת).** ‏←‏ העמודה `INT`. **בקוד** נמיר `int(...)` לפני השמירה (או ש-Pydantic ימיר, בנושא Students API – שרת עם מסד נתונים).
3. **לנועה ולדנה אותו אימייל.** ‏←‏ עם `UNIQUE` על `email`, **המסד יסרב** לשמור את נועה. זה בדיוק מה שאנחנו רוצים (ונטפל בשגיאה הזו בנושא מסדי נתונים ו-MySQL).
4. **אין `id`.** → `AUTO_INCREMENT`: המסד ייתן 1, 2, 3.

```text
| column | type         | constraints                |
|--------|--------------|----------------------------|
| id     | INT          | AUTO_INCREMENT PRIMARY KEY |
| name   | VARCHAR(100) | NOT NULL                   |
| age    | INT          | NOT NULL                   |
| email  | VARCHAR(100) | NOT NULL UNIQUE            |
| phone  | VARCHAR(20)  | (nullable)                 |
```

**למה `phone` הוא VARCHAR ולא INT?** כי `050-1234567` מכיל מקף, ומספר טלפון שמתחיל ב-0 יאבד את האפס כ-INT. **כלל:** "מספר" שלא עושים עליו חשבון (טלפון, מיקוד, ת"ז) ‏←‏ VARCHAR.

### טעות נפוצה

**הטעות 1: Primary Key "טבעי" שעלול להשתנות או לחזור.** שם, אימייל, מספר טלפון. ביום שדנה מחליפה אימייל, כל מי שהצביע עליה לפי אימייל "מאבד" אותה. **id מספרי** של המסד.

**הטעות 2: לשמור רשימה בתוך עמודה אחת.**

```text
| id | name | grades     |
|  1 | Dana | "90,85,70" |
```

אי אפשר לחשב ממוצע ב-SQL, אי אפשר לחפש "כל מי שקיבל 90", וקשה לעדכן ציון אחד. בעולם הרלציוני, ציונים הם **טבלה נפרדת** (`grades`, עם `student_id`). את זה תלמדו לעומק בהמשך המסלול (קשרים בין טבלאות ו-JOIN). השבוע נעבוד עם **טבלה אחת** ושדות פשוטים.

**הטעות 3: לוותר על `NOT NULL` ו-`UNIQUE`, "כי הקוד בודק".** הקוד בודק **היום**. מחר מישהו יכתוב סקריפט ייבוא שעוקף את הקוד, או שיהיה באג. המסד הוא קו ההגנה האחרון.

**הטעות 4: לחשוב ש-SQL "תוכנית".** SQL הוא שפה **הצהרתית** (Declarative): אומרים **מה** רוצים ("כל הסטודנטים מחיפה"), ולא **איך** למצוא (בלי לולאות). המסד מחליט איך לבצע ביעילות.

### סיכום

- **Database** פותר את בעיות קובץ ה-JSON: קריאה וכתיבה ממוקדות, הרבה משתמשים בו-זמנית, עמידות לקריסות, כללים ושאילתות.
- **מסד רלציוני** = טבלאות. **Table** ≈ Class + כל האובייקטים. **Column** ≈ Attribute (עם טיפוס). **Row** ≈ אובייקט אחד.
- **Primary Key** = מזהה ייחודי, לא ריק ולא משתנה. בדרך כלל `id INT AUTO_INCREMENT PRIMARY KEY`.
- טיפוסים ב-MySQL: **INT**, **DECIMAL**, **VARCHAR**, ו-**NULL** (= `None`). bool נשמר כ-0/1. טלפון ומיקוד נשמרים כ-VARCHAR.
- אילוצים: **NOT NULL**, **UNIQUE**. המסד אוכף כללים גם כשהקוד טועה.
- **SQL** = שפה הצהרתית. השבוע: CREATE TABLE, INSERT, SELECT, UPDATE, DELETE, וזהו.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

### דוקומנטציה רשמית

- [MySQL — Creating and Using a Database](https://dev.mysql.com/doc/refman/8.4/en/database-use.html) — מסד, טבלה ושורות
- [MySQL — Data Types](https://dev.mysql.com/doc/refman/8.4/en/data-types.html) — INT, VARCHAR ועוד
- [MySQL — CREATE TABLE](https://dev.mysql.com/doc/refman/8.4/en/create-table.html) — הגדרת עמודות ו-PRIMARY KEY

<!-- docs:end -->

## פרק שני: הקמת MySQL — Docker, Database Client ו-Driver

### מה נלמד?

- מה זה **שרת מסד נתונים**, ולמה נעבוד עם **MySQL**.
- מה זה **קונטיינר**, ואיך מריצים את MySQL ב-**Docker** — בפקודה אחת.
- איך רואים את המסד מ-VS Code עם התוסף **Database Client**.
- מה זה **Driver**, ואיך Python מתחבר ל-MySQL.
- איפה שומרים את **פרטי החיבור** — ולמה לא בקוד.

### למה צריך את זה?

לפני שכותבים `INSERT` אחד, צריך לענות על שאלות "צנרת": איפה המסד רץ? איך הקוד שלנו מגיע אליו? מה פרטי החיבור? בעבודה אמיתית, הדבר הראשון שמקבלים בפרויקט חדש הוא בדיוק זה: "הנה פרטי החיבור למסד". צריך לדעת מה לעשות איתם.

### הסבר מהבסיס

#### מסד נתונים הוא שרת

**MySQL** הוא **שרת מסד נתונים**: תוכנה נפרדת (תהליך — Process, נושא מחשב, מערכת הפעלה ורשת!) שרצה כל הזמן ו**מאזינה על פורט** — ב-MySQL זה פורט **3306**. הקוד שלנו לא פותח קובץ — הוא **מתחבר ברשת** לשרת, בדיוק כמו דפדפן שמתחבר לאתר (נושא רשתות: IP + Port).

| | **MySQL** (שרת מסד נתונים) |
|---|---|
| איפה | תהליך נפרד שמאזין על פורט **3306** |
| איך מתחברים | ברשת: **Host, Port, משתמש, סיסמה** ושם המסד |
| מי מתחבר | כמה תוכנות ושרתים — **בבת אחת** |
| איפה זה בעולם | אתרים ומערכות בכל גודל. גם PostgreSQL ו-SQL Server עובדים כך |

#### למה Docker?

להתקין MySQL ישירות על המחשב זה תהליך ארוך: אשף התקנה, סיסמאות, שירות שרץ ברקע, והגדרות ששונות בין Windows ל-Mac. ומה שעובד אצל אחד — לא תמיד עובד אצל השני.

**Docker** (שהתקנו בשבוע הראשון) פותר את זה: הוא מריץ תוכנות מוכנות בתוך **קונטיינר**.

- **Image** — התבנית: "MySQL בגרסה 8.4, מוכן לעבודה". מורידים אותו פעם אחת.
- **Container** — עותק **שרץ** מה-Image. מבודד משאר המחשב, עם כל מה שהתוכנה צריכה.

{{< box "analogy" "אנלוגיה: מכולה בנמל" >}}
מכולה (Container) נראית אותו דבר בכל אונייה ובכל נמל — לא משנה מה בתוכה. כך גם קונטיינר של Docker: אותו MySQL, עם אותן הגדרות, רץ בדיוק אותו דבר אצל כל הסטודנטים, בכל מערכת הפעלה.
{{< /box >}}

#### Database Client — המסד עם ממשק

את MySQL אפשר לראות ולתפעל בלי שורת קוד אחת, עם תוסף ל-VS Code בשם **Database Client** (של Weijan Chen). הוא מציג **עץ** של מסדים וטבלאות, פותח טבלה כמו גיליון, ומריץ SQL ומציג את התוצאה **כטבלה**. נשתמש בו כל השבוע כדי **לראות** מה הקוד שלנו עשה במסד.

#### Driver — המתורגמן

הקוד שלנו כתוב ב-Python. MySQL מדבר ב"פרוטוקול" משלו (נושא רשתות: פרוטוקול = כללי תקשורת). צריך **מתווך** שמתרגם: **Driver** (נקרא גם Database Connector) = ספרייה שיודעת:

1. **להתחבר** לשרת מסד מסוג מסוים.
2. **לשלוח** אליו פקודות SQL.
3. **להחזיר** את התוצאות כנתונים של Python (tuples, int, str).

ל-MySQL נשתמש ב-Driver הרשמי: **`mysql-connector-python`**. מתקינים אותו עם `pip`, ובקוד כותבים `import mysql.connector`.

{{< box "analogy" "אנלוגיה: מתאם חשמל בחוץ לארץ" >}}
המכשיר שלכם (קוד Python) עם תקע ישראלי, והשקע בקיר (המסד) אמריקאי. **Driver** = המתאם. לכל סוג שקע מתאם משלו, אבל המכשיר עצמו לא משתנה.
{{< /box >}}

#### פרטי החיבור — ולא בקוד!

כדי להתחבר ל-MySQL צריך **חמישה פרטים**:

| פרט | אצלנו | מה זה |
|---|---|---|
| `host` | `localhost` | איפה השרת — המחשב שלנו |
| `port` | `3306` | הפורט של MySQL |
| `user` | `root` | שם המשתמש |
| `password` | `kodkod` | הסיסמה שהגדרנו בקונטיינר |
| `database` | `school` | איזה מסד בתוך השרת |

בפרויקט אמיתי יש כאן **סיסמה אמיתית**. אם היא כתובה בקוד והקוד עלה ל-GitHub, **היא דלפה** (נושא Python!). הנוהג המקצועי:

- פרטי החיבור נשמרים ב**משתני סביבה** (Environment Variables), או בקובץ `.env` שנמצא ב-**`.gitignore`**.
- הקוד **קורא** אותם: `os.environ.get("DB_PASSWORD")`.

### תחביר

**הרצת MySQL בקונטיינר** — פקודה אחת, פעם אחת (ב-PowerShell; ב-Mac מחליפים את `` ` `` ב-`\`):

```powershell
docker run --name kodkod-mysql `
  -e MYSQL_ROOT_PASSWORD=kodkod `
  -e MYSQL_DATABASE=school `
  -p 3306:3306 -d mysql:8.4
```

| חלק | משמעות |
|---|---|
| `docker run` | יוצר ומפעיל **קונטיינר** חדש |
| `--name kodkod-mysql` | שם לקונטיינר, כדי שנוכל לפנות אליו |
| `-e MYSQL_ROOT_PASSWORD=kodkod` | משתנה סביבה: הסיסמה של המשתמש `root` |
| `-e MYSQL_DATABASE=school` | משתנה סביבה: מסד בשם `school` שנוצר אוטומטית |
| `-p 3306:3306` | הפורט 3306 במחשב שלנו מוביל ל-MySQL שבתוך הקונטיינר |
| `-d` | רץ ברקע (detached), וה-Terminal נשאר פנוי |
| `mysql:8.4` | ה-**Image**: MySQL בגרסה 8.4 |

**פקודות שימושיות:**

```powershell
docker ps                     # which containers are running now
docker start kodkod-mysql     # start it again (after a restart of the computer)
docker stop kodkod-mysql      # stop it (the data is kept)
```

**חיבור מ-Python:**

```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password="kodkod",
    database="school",
)
connection.close()
```

- `mysql.connector.connect(...)` — פותח חיבור רשת לשרת. אם השרת לא רץ — שגיאה.
- `port=3306` הוא ברירת המחדל, ואפשר לוותר עליו.

### דוגמת קוד ראשונה

**הקמת מסד הנתונים של השבוע**, צעד אחר צעד.

**1. מריצים את MySQL** (Docker Desktop צריך להיות פתוח):

```powershell
docker run --name kodkod-mysql `
  -e MYSQL_ROOT_PASSWORD=kodkod `
  -e MYSQL_DATABASE=school `
  -p 3306:3306 -d mysql:8.4
docker ps
```

בהרצה הראשונה Docker מוריד את ה-Image (כמה דקות). `docker ps` מראה שהקונטיינר רץ:

```text
CONTAINER ID   IMAGE       STATUS         PORTS                    NAMES
ffaa661f5047   mysql:8.4   Up 10 seconds  0.0.0.0:3306->3306/tcp   kodkod-mysql
```

**2. מתחברים מ-VS Code:**

1. לשונית **Extensions** ‏←‏ מחפשים **Database Client** (של Weijan Chen) ‏←‏ **Install**.
2. בסרגל הצד מופיע אייקון של מסד נתונים. לוחצים עליו ‏←‏ **+** (Create Connection) ‏←‏ בוחרים **MySQL**.
3. ממלאים: Host `127.0.0.1`, Port `3306`, Username `root`, Password `kodkod`, Database `school` → **Connect**.
4. בעץ מופיע החיבור, ובתוכו המסד `school` — עדיין בלי טבלאות.

**3. פרויקט Python:**

```powershell
mkdir ~\dev\kodkod6\week-4\school-db
cd ~\dev\kodkod6\week-4\school-db
git init
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install mysql-connector-python
```

`.gitignore`:

```text
__pycache__/
.venv/
.env
```

`setup_db.py`:

```python
import os

import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password=os.environ.get("DB_PASSWORD", "kodkod"),
    database="school",
)
print("Connected:", connection.is_connected())
print("Server version:", connection.server_info)
connection.close()
print("Connected after close:", connection.is_connected())
```

**פלט:**

```text
Connected: True
Server version: 8.4.11
Connected after close: False
```

### פירוק הקוד שורה אחר שורה

| קוד | הסבר |
|---|---|
| `import mysql.connector` | ה-**Driver**. הותקן עם `pip install mysql-connector-python`. |
| `host="localhost"`, `port=3306` | **איפה** השרת: המחשב שלנו, בפורט שפתחנו עם `-p 3306:3306`. |
| `user="root"` | המשתמש הראשי של MySQL. |
| `os.environ.get("DB_PASSWORD", "kodkod")` | הסיסמה — **ממשתנה סביבה** אם הוגדר, ואחרת ברירת המחדל של סביבת הלימוד. |
| `database="school"` | המסד שנוצר עם `-e MYSQL_DATABASE=school`. |
| `connection.is_connected()` | `True` — החיבור פתוח. |
| `connection.server_info` | גרסת שרת ה-MySQL. אצלכם המספר עשוי להיות שונה. |
| `connection.close()` | סוגר את החיבור (בשיעור הבא: למה זה חשוב). |
| אין `*.db` ב-`.gitignore` | הנתונים יושבים **בקונטיינר**, לא בתיקיית הפרויקט. ל-Git נכנס רק **הקוד** שיוצר את המבנה. |

### דוגמה נוספת

#### הקונטיינר אחרי הפעלה מחדש של המחשב

אחרי Restart, Docker Desktop עולה — אבל הקונטיינר **לא** רץ. מריצים שוב `docker run`? **לא** — השם `kodkod-mysql` כבר תפוס:

```text
docker: Error response from daemon: Conflict. The container name "/kodkod-mysql" is already in use by container ...
```

הקונטיינר קיים, רק עצור. מפעילים אותו:

```powershell
docker start kodkod-mysql
```

**כל הנתונים נשמרו** — הטבלאות והשורות נשארות בקונטיינר עד שמוחקים אותו (`docker rm`).

#### כשהשרת לא רץ

אם Docker Desktop סגור או הקונטיינר עצור, החיבור מ-Python נכשל:

```text
mysql.connector.errors.InterfaceError: 2003: Can't connect to MySQL server on 'localhost:3306' (Errno 10061: No connection could be made because the target machine actively refused it)
```

**`Can't connect`** = אין מי שמאזין בפורט 3306. בודקים עם `docker ps`, ומפעילים עם `docker start kodkod-mysql`.

### טעות נפוצה

**הטעות 1: Docker Desktop לא פתוח.**

```text
failed to connect to the docker API ... The system cannot find the file specified.
```

פקודות `docker` מדברות עם Docker Desktop. פותחים אותו, מחכים שיסיים לעלות, ומנסים שוב.

**הטעות 2: הפורט תפוס.**

```text
Bind for 0.0.0.0:3306 failed: port is already allocated
```

כבר רץ משהו בפורט 3306 — בדרך כלל MySQL שהותקן בעבר ישירות על המחשב. אפשר להשתמש בפורט אחר במחשב שלנו: `-p 3307:3306`, ואז מתחברים עם `port=3307`.

**הטעות 3: סיסמה של מסד בקוד שעולה ל-GitHub.**

```python
connection = mysql.connector.connect(host="prod-db.company.com", user="admin", password="MyRealPassword123")
```

גם אם מוחקים ב-Commit הבא, הסיסמה נשארת בהיסטוריה (נושא Python!). **משתני סביבה / `.env` ב-`.gitignore`.** (הסיסמה `kodkod` שלנו היא של סביבת לימוד מקומית בלבד.)

### סיכום

- **MySQL** = שרת מסד נתונים: תהליך שמאזין על פורט **3306**, ומתחברים אליו ברשת.
- **Docker** מריץ את MySQL בתוך **קונטיינר** — בפקודה אחת, ואצל כולם אותו דבר. `docker run` פעם אחת, ואחר כך `docker start kodkod-mysql`.
- **Database Client** (תוסף ל-VS Code) = ממשק למסד: עץ טבלאות, הרצת SQL ותוצאות כטבלה.
- **Driver** = ספרייה שמתרגמת בין Python למסד. ל-MySQL: **`mysql-connector-python`** (`import mysql.connector`).
- חמישה פרטי חיבור: `host`, `port`, `user`, `password`, `database`.
- סיסמאות לא נכתבות בקוד: משתני סביבה או `.env` ב-`.gitignore`.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

### דוקומנטציה רשמית

- [Docker Hub — MySQL official image](https://hub.docker.com/_/mysql) — docker run ומשתני הסביבה של MySQL
- [Database Client for VS Code](https://marketplace.visualstudio.com/items?itemName=cweijan.vscode-database-client2) — התוסף לצפייה במסד
- [Connector/Python — Installation](https://dev.mysql.com/doc/connector-python/en/connector-python-installation.html) — התקנת ה-Driver, כולל התקנה עם pip

<!-- docs:end -->

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## תרגול

התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.

### תרגיל 1: טבלה, שורה, עמודה

```text
id | name | age | email
---+------+-----+---------------
 1 | Dana |  24 | dana@mail.com
 2 | Avi  |  31 | avi@mail.com
```

1. כמה שורות וכמה עמודות יש בטבלה?
2. מה מתאר כל שורה, ומה מתארת כל עמודה?
3. איזו עמודה מתאימה להיות Primary Key, ולמה לא `name`?

{{< solution >}}
1. **2 שורות** ו-**4 עמודות**.
2. כל **שורה** (Row) היא **סטודנט אחד** — רשומה. כל **עמודה** (Column) היא **שדה** אחד, עם טיפוס קבוע, שיש לכל הסטודנטים.
3. **`id`** — הוא ייחודי, לא משתנה אף פעם, ונוצר אוטומטית. `name` לא מתאים: יכולים להיות שני סטודנטים בשם דנה, ושם יכול להשתנות.
{{< /solution >}}

### תרגיל 2: טיפוסים ואילוצים

כתבו את פקודת ה-SQL שיוצרת טבלת `students` עם:

- `id` — מספר שלם, Primary Key, שנוצר אוטומטית;
- `name` — טקסט, חובה;
- `age` — מספר שלם, חובה;
- `email` — טקסט, חובה וייחודי.

{{< solution >}}
```sql
CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);
```

- `INT` / `VARCHAR(100)` — הטיפוס של כל עמודה: מספר שלם, וטקסט של עד 100 תווים.
- `NOT NULL` — אי אפשר להשאיר ריק.
- `UNIQUE` — אין שני סטודנטים עם אותו אימייל.
- `AUTO_INCREMENT` — המסד בוחר את ה-`id` לבד.
{{< /solution >}}

### תרגיל 3: מה המסד היה תופס?

```text
students = [
    {"name": "Dana", "age": 24, "email": "d@x.com"},
    {"name": "Avi", "age": 24, "email": None},
    {"name": "Noa", "age": 22, "email": "d@x.com"},
]
```

אילו מהנתונים האלה טבלת `students` מהתרגיל הקודם הייתה דוחה, ובגלל איזה אילוץ? ולמה ברשימה של Python אף אחד לא עצר אותם?

{{< solution >}}
- **Avi** — אימייל `None` ‏←‏ נדחה בגלל **`NOT NULL`**.
- **Noa** — אותו אימייל כמו דנה ‏←‏ נדחה בגלל **`UNIQUE`**.
- **Dana** — תקינה.

ברשימה של Python **אין כללים**: כל ערך נכנס. במסד הנתונים הכללים מוגדרים **פעם אחת**, בטבלה, וחלים על כל מי שכותב אליה — מכל תוכנה.
{{< /solution >}}

### תרגיל 4: להרים את MySQL

1. מה עושה כל חלק בפקודה הבאה?
2. איך בודקים שהקונטיינר רץ?
3. איך מפעילים אותו שוב אחרי הפעלה מחדש של המחשב — בלי ליצור חדש?

```text
docker run --name kodkod-mysql -e MYSQL_ROOT_PASSWORD=kodkod -e MYSQL_DATABASE=school -p 3306:3306 -d mysql:8.4
```

{{< solution >}}
| חלק | משמעות |
|---|---|
| `docker run` | יוצר ומפעיל **קונטיינר** חדש |
| `--name kodkod-mysql` | שם לקונטיינר, כדי שנוכל לפנות אליו |
| `-e MYSQL_ROOT_PASSWORD=kodkod` | הסיסמה של המשתמש `root` |
| `-e MYSQL_DATABASE=school` | מסד בשם `school` שנוצר אוטומטית |
| `-p 3306:3306` | הפורט 3306 במחשב שלנו מוביל ל-MySQL שבקונטיינר |
| `-d` | רץ ברקע, וה-Terminal נשאר פנוי |
| `mysql:8.4` | ה-**Image**: MySQL בגרסה 8.4 |

2. `docker ps` — מציג את הקונטיינרים שרצים כרגע.
3. `docker start kodkod-mysql` — מפעיל את הקונטיינר הקיים. הנתונים שלו נשמרים. `docker run` שוב היה נכשל, כי השם כבר תפוס.
{{< /solution >}}

### תרגיל 5: חיבור ראשון

כתבו קוד שמתחבר למסד `school` שבקונטיינר, מדפיס אם החיבור פעיל, סוגר אותו, ומדפיס שוב.

**פלט צפוי:**

```text
Connected: True
Connected after close: False
```

{{< solution >}}
```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password="kodkod",
    database="school",
)
print("Connected:", connection.is_connected())
connection.close()
print("Connected after close:", connection.is_connected())
```

```text
Connected: True
Connected after close: False
```

`mysql.connector.connect` פותח חיבור רשת לשרת ה-MySQL שבקונטיינר. אם הקונטיינר לא רץ — מקבלים שגיאה: `Can't connect to MySQL server`.
{{< /solution >}}

<!-- exercises:end -->
