# Fichier: python_cheats/cheatsheets/no_sql_mongo_db.txt
# Cheatsheet MongoDB - Guide Ultra-Détaillé pour Grands Débutants

Voici le contenu complet :

```
# Fichier: python_cheats/cheatsheets/no_sql_mongo_db.txt
# Cheatsheet MongoDB - Guide Ultra-Détaillé pour Grands Débutants


[OK] CONCEPTS FONDAMENTAUX (EXPLICATIONS TRÈS DÉTAILLÉES)

# === QU'EST-CE QUE MONGODB ? ===

# Imagine que tu as une application (ex: un blog, un e-commerce, un réseau social)
# Tu dois stocker des données quelque part (utilisateurs, articles, commandes, etc.)

# Solution classique (SQL): Base de données relationnelle (MySQL, PostgreSQL)
# - Les données sont stockées dans des TABLES avec des COLONNES fixes
# - Exemple: Table "users" avec colonnes: id, nom, email, age
# - Très structuré, rigide, mais puissant pour les relations complexes

# MongoDB = Base de données NoSQL
# - Les données sont stockées dans des DOCUMENTS (comme des fichiers JSON)
# - Pas de tables, pas de colonnes fixes!
# - Très flexible, idéal pour des données qui changent souvent

# POURQUOI choisir MongoDB?
# 1. Flexibilité du schéma: Tu peux ajouter/enlever des champs à tout moment
# 2. Performance: Très rapide pour les lectures/écritures massives
# 3. Scalabilité: Facile de répartir les données sur plusieurs serveurs
# 4. Format naturel: Les données ressemblent à des objets JavaScript/Python
# 5. Pas de JOIN complexes: Les données liées peuvent être intégrées dans un seul document

# QUAND utiliser MongoDB?
# [OK] Application web/mobile avec données dynamiques
# [OK] Catalogues de produits (e-commerce)
# [OK] Réseaux sociaux (posts, commentaires, likes)
# [OK] Applications IoT (Internet of Things)
# [OK] Logs et analytics
# [OK] Données géospatiales
# [X] Transactions bancaires complexes (préférer PostgreSQL)
# [X] Applications nécessitant des JOIN SQL complexes


# === VOCABULAIRE MONGODB (TRÈS IMPORTANT!) ===

# BASE DE DONNÉES (Database)
# = Un conteneur pour plusieurs collections
# Analogie: Une base de données = un dossier sur ton ordinateur
# Exemple: "blog_db", "ecommerce_db", "users_db"

# COLLECTION
# = Équivalent d'une TABLE en SQL
# = Un groupe de documents similaires (mais pas obligatoirement identiques!)
# Analogie: Une collection = un fichier Excel avec plusieurs lignes
# Exemple: "users", "articles", "comments", "products"
# Note: Pas de schéma fixe! Chaque document peut avoir des champs différents

# DOCUMENT
# = Équivalent d'une LIGNE/ROW en SQL
# = Une entité de données (format BSON, similaire à JSON)
# Analogie: Un document = un objet Python (dictionnaire)
# Exemple:
# {
#   "_id": ObjectId("507f1f77bcf86cd799439011"),
#   "nom": "Alice",
#   "email": "alice@example.com",
#   "age": 25,
#   "hobbies": ["lecture", "voyage"]
# }

# CHAMP (Field)
# = Équivalent d'une COLONNE en SQL
# = Une paire clé-valeur dans un document
# Exemple: "nom": "Alice", "age": 25

# _id (Identifiant unique)
# = Clé primaire automatique créée par MongoDB
# = Type: ObjectId (12 bytes, unique)
# = Si tu ne le fournis pas, MongoDB le génère automatiquement
# Exemple: _id: ObjectId("507f1f77bcf86cd799439011")

# BSON (Binary JSON)
# = Format binaire utilisé par MongoDB pour stocker les documents
# = Plus efficace que JSON (supporte dates, binary data, etc.)
# = Tu n'as pas besoin de le gérer directement, MongoDB s'en charge


# === COMPARAISON SQL vs MongoDB ===

# SQL (MySQL/PostgreSQL):
# Database -> Tables -> Rows -> Columns
# Structure rigide, schéma fixe, JOIN pour relations

# MongoDB:
# Database -> Collections -> Documents -> Fields
# Structure flexible, pas de schéma, données imbriquées

# Exemple SQL:
# Table "users":
# | id | nom    | email              | age |
# |----|--------|-------------------|-----|
# | 1  | Alice  | alice@example.com | 25  |
# | 2  | Bob    | bob@example.com   | 30  |

# Équivalent MongoDB (Collection "users"):
# {
#   "_id": ObjectId("..."),
#   "nom": "Alice",
#   "email": "alice@example.com",
#   "age": 25
# }
# {
#   "_id": ObjectId("..."),
#   "nom": "Bob",
#   "email": "bob@example.com",
#   "age": 30
# }


# === AVANTAGES DE MONGODB ===

# 1. Flexibilité du schéma
# - Pas besoin de définir la structure à l'avance
# - Tu peux ajouter/enlever des champs à tout moment
# - Chaque document peut avoir des champs différents
# Exemple:
# Document 1: { "nom": "Alice", "age": 25 }
# Document 2: { "nom": "Bob", "ville": "Paris", "hobbies": ["sport"] }
# = C'est valide! Pas besoin de migration de schéma

# 2. Performance
# - Très rapide pour les lectures/écritures
# - Pas de JOIN coûteux, données imbriquées dans un seul document
# - Indexation puissante

# 3. Scalabilité horizontale (Sharding)
# - Tu peux répartir les données sur plusieurs serveurs
# - Gère facilement des millions/milliards de documents

# 4. Format naturel
# - Les données ressemblent à des objets JavaScript/Python
# - Pas de conversion compliquée

# 5. Requêtes puissantes
# - Filtres, projections, tri, agrégations
# - Recherche full-text, géospatiale


# === INCONVÉNIENTS DE MONGODB ===

# 1. Pas de transactions ACID complètes (avant v4.0)
# - Les transactions multi-documents sont possibles depuis v4.0
# - Mais moins robustes que PostgreSQL pour les cas critiques

# 2. Consommation mémoire
# - MongoDB garde beaucoup de données en RAM pour la performance
# - Peut être gourmand en ressources

# 3. Pas de JOIN SQL
# - Les relations complexes nécessitent des requêtes multiples
# - Ou l'imbrication de données (denormalization)

# 4. Redondance de données
# - Pour éviter les JOIN, on duplique souvent les données
# - Exemple: Stocker le nom de l'auteur dans chaque article
# - Augmente l'espace disque

# 5. Courbe d'apprentissage
# - Nouveau paradigme si tu viens du SQL
# - Requêtes avec des objets JavaScript/Python au lieu de SQL


# === COMMENT ÇA MARCHE? (ARCHITECTURE) ===

# MongoDB fonctionne en mode client-serveur:

# 1. Serveur MongoDB (mongod)
# = Le processus qui gère les données
# = Écoute sur le port 27017 par défaut
# = Stocke les données dans des fichiers sur le disque

# 2. Client MongoDB (mongo shell / pymongo / drivers)
# = Application qui se connecte au serveur
# = Envoie des commandes/requêtes
# = Reçoit les résultats

# Flux:
# Application Python -> pymongo (driver) -> Serveur MongoDB -> Stockage disque
#                                        v
#                                     Résultats


# === INSTALLATION DÉTAILLÉE ===

# === WINDOWS ===

# Méthode 1: Installer MongoDB localement (version Community gratuite)

# 1. Télécharger MongoDB Community Server:
#    https://www.mongodb.com/try/download/community
#    Choisis "Windows" et clique "Download"

# 2. Lancer l'installeur (fichier .msi)
#    - Clique "Next" plusieurs fois
#    - Choisis "Complete" installation
#    - Coche "Install MongoDB as a Service" (recommandé)
#    - Laisse le port par défaut: 27017

# 3. Vérifier l'installation:
#    Ouvre PowerShell et tape:
mongod --version
# Doit afficher: db version v7.x.x

# 4. Lancer MongoDB (si pas lancé automatiquement):
net start MongoDB

# 5. Se connecter avec le shell:
mongosh
# Doit afficher: test>


# Méthode 2: Utiliser MongoDB Atlas (Cloud gratuit)

# 1. Va sur: https://www.mongodb.com/cloud/atlas/register
# 2. Crée un compte gratuit
# 3. Crée un cluster gratuit (M0 Sandbox)
# 4. Configure un utilisateur et mot de passe
# 5. Ajoute ton IP à la whitelist
# 6. Copie la chaîne de connexion:
#    mongodb+srv://user:pass@cluster.mongodb.net/mydb


# === macOS ===

# Méthode 1: Homebrew (recommandé)

# Installer MongoDB Community:
brew tap mongodb/brew
brew install mongodb-community

# Lancer MongoDB:
brew services start mongodb-community

# Vérifier:
mongod --version

# Se connecter:
mongosh


# Méthode 2: Téléchargement manuel

# 1. Télécharger depuis:
#    https://www.mongodb.com/try/download/community
# 2. Extraire le fichier .tgz
# 3. Déplacer les binaires dans /usr/local/bin
# 4. Créer le dossier de données:
sudo mkdir -p /usr/local/var/mongodb
sudo mkdir -p /usr/local/var/log/mongodb

# 5. Lancer MongoDB:
mongod --dbpath /usr/local/var/mongodb --logpath /usr/local/var/log/mongodb/mongo.log --fork


# === Linux (Ubuntu/Debian) ===

# 1. Importer la clé GPG:
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -

# 2. Ajouter le dépôt MongoDB:
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

# 3. Mettre à jour et installer:
sudo apt-get update
sudo apt-get install -y mongodb-org

# 4. Lancer MongoDB:
sudo systemctl start mongod
sudo systemctl enable mongod

# 5. Vérifier:
sudo systemctl status mongod

# 6. Se connecter:
mongosh


# === INSTALLER PYMONGO (DRIVER PYTHON) ===

# PyMongo = Bibliothèque Python pour se connecter à MongoDB

# Avec pip:
pip install pymongo

# Avec un environnement virtuel (recommandé):
python -m venv venv
source venv/bin/activate  # Linux/macOS
venv\Scripts\activate     # Windows

pip install pymongo

# Vérifier l'installation:
python -c "import pymongo; print(pymongo.__version__)"
# Doit afficher: 4.x.x


# === MONGODB COMPASS (INTERFACE GRAPHIQUE) ===

# MongoDB Compass = Interface graphique pour explorer tes bases de données
# Très utile pour visualiser/modifier les données sans code

# 1. Télécharger:
#    https://www.mongodb.com/try/download/compass

# 2. Installer (fichier .exe/.dmg/.deb)

# 3. Lancer Compass

# 4. Se connecter:
#    - Connexion locale: mongodb://localhost:27017
#    - MongoDB Atlas: mongodb+srv://user:pass@cluster.mongodb.net/mydb

# 5. Explorer:
#    - Voir les bases de données
#    - Voir les collections
#    - Voir/modifier les documents
#    - Créer des index
#    - Exécuter des requêtes


[OK] PREMIERS PAS AVEC MONGODB

# === SE CONNECTER À MONGODB ===

# Avec le shell MongoDB (mongosh):

# Connexion locale:
mongosh
# Ou:
mongosh "mongodb://localhost:27017"

# Connexion à MongoDB Atlas:
mongosh "mongodb+srv://user:pass@cluster.mongodb.net/mydb"

# Affiche:
# Current Mongosh Log ID: 65a1b2c3d4e5f6789abcdef0
# Connecting to: mongodb://localhost:27017/
# Using MongoDB: 7.0.0
# test>


# === COMMANDES SHELL MONGODB ===

# Voir toutes les bases de données:
show dbs
# Affiche:
# admin   40.00 KiB
# config  60.00 KiB
# local   40.00 KiB

# Créer/utiliser une base de données:
use blog_db
# Affiche: switched to db blog_db
# Note: La base n'est créée que quand tu insères des données!

# Voir la base actuelle:
db
# Affiche: blog_db

# Voir les collections de la base actuelle:
show collections
# Affiche: (rien si aucune collection)

# Créer une collection:
db.createCollection("users")
# Affiche: { ok: 1 }

# Voir les collections:
show collections
# Affiche: users

# Supprimer une collection:
db.users.drop()
# Affiche: true

# Supprimer une base de données:
db.dropDatabase()
# Affiche: { ok: 1, dropped: 'blog_db' }

# Quitter le shell:
exit


# === INSÉRER DES DOCUMENTS ===

# Insérer un document unique:
db.users.insertOne({
  nom: "Alice",
  email: "alice@example.com",
  age: 25
})

# Affiche:
# {
#   acknowledged: true,
#   insertedId: ObjectId('65a1b2c3d4e5f6789abcdef0')
# }

# Explications:
# - db.users = la collection "users"
# - insertOne() = insère un seul document
# - { ... } = le document à insérer (format BSON/JSON)
# - insertedId = l'_id généré automatiquement


# Insérer plusieurs documents:
db.users.insertMany([
  { nom: "Bob", email: "bob@example.com", age: 30 },
  { nom: "Charlie", email: "charlie@example.com", age: 35 },
  { nom: "Diana", email: "diana@example.com", age: 28 }
])

# Affiche:
# {
#   acknowledged: true,
#   insertedIds: {
#     '0': ObjectId('...'),
#     '1': ObjectId('...'),
#     '2': ObjectId('...')
#   }
# }


# === LIRE DES DOCUMENTS ===

# Lire tous les documents:
db.users.find()
# Affiche:
# [
#   { _id: ObjectId('...'), nom: 'Alice', email: 'alice@example.com', age: 25 },
#   { _id: ObjectId('...'), nom: 'Bob', email: 'bob@example.com', age: 30 },
#   ...
# ]

# Lire un seul document:
db.users.findOne()
# Affiche: { _id: ObjectId('...'), nom: 'Alice', ... }

# Filtrer avec une condition:
db.users.find({ age: 25 })
# Affiche: les utilisateurs avec age = 25

db.users.find({ age: { $gte: 30 } })
# Affiche: les utilisateurs avec age >= 30

# Projeter certains champs seulement:
db.users.find({}, { nom: 1, email: 1, _id: 0 })
# Affiche: seulement nom et email (pas _id)

# Limiter le nombre de résultats:
db.users.find().limit(2)
# Affiche: les 2 premiers documents

# Trier les résultats:
db.users.find().sort({ age: 1 })
# Affiche: triés par age croissant (1 = croissant, -1 = décroissant)


# === METTRE À JOUR DES DOCUMENTS ===

# Mettre à jour un seul document:
db.users.updateOne(
  { nom: "Alice" },              // Filtre
  { $set: { age: 26 } }           // Modification
)

# Affiche:
# {
#   acknowledged: true,
#   matchedCount: 1,
#   modifiedCount: 1
# }

# Explications:
# - { nom: "Alice" } = trouve le document où nom = "Alice"
# - $set: { age: 26 } = modifie le champ "age" à 26


# Mettre à jour plusieurs documents:
db.users.updateMany(
  { age: { $lt: 30 } },           // Filtre: age < 30
  { $inc: { age: 1 } }             // Incrémente age de 1
)

# Affiche:
# {
#   acknowledged: true,
#   matchedCount: 2,
#   modifiedCount: 2
# }


# Remplacer un document entier:
db.users.replaceOne(
  { nom: "Bob" },
  { nom: "Bob", email: "bob_new@example.com", age: 31, ville: "Paris" }
)

# Attention: remplace TOUT le document (sauf _id)


# === SUPPRIMER DES DOCUMENTS ===

# Supprimer un seul document:
db.users.deleteOne({ nom: "Alice" })

# Affiche:
# {
#   acknowledged: true,
#   deletedCount: 1
# }


# Supprimer plusieurs documents:
db.users.deleteMany({ age: { $gte: 30 } })

# Affiche:
# {
#   acknowledged: true,
#   deletedCount: 2
# }


# Supprimer tous les documents d'une collection:
db.users.deleteMany({})

# Affiche:
# {
#   acknowledged: true,
#   deletedCount: 5
# }


[OK] UTILISER MONGODB AVEC PYTHON (PYMONGO)

# === SE CONNECTER À MONGODB ===

# Exemple simple de connexion locale:

from pymongo import MongoClient

# Se connecter au serveur MongoDB local
client = MongoClient("mongodb://localhost:27017/")

# Ou avec plus de détails:
client = MongoClient(host="localhost", port=27017)

# Vérifier la connexion:
print(client.list_database_names())
# Affiche: ['admin', 'config', 'local']


# Connexion à MongoDB Atlas (cloud):

client = MongoClient("mongodb+srv://user:pass@cluster.mongodb.net/mydb?retryWrites=true&w=majority")

# Remplace:
# - user: ton nom d'utilisateur Atlas
# - pass: ton mot de passe Atlas
# - cluster: ton nom de cluster
# - mydb: le nom de ta base de données


# === SÉLECTIONNER UNE BASE DE DONNÉES ===

# Méthode 1: Notation point
db = client.blog_db

# Méthode 2: Notation dictionnaire
db = client["blog_db"]

# Vérifier:
print(db.name)
# Affiche: blog_db


# === SÉLECTIONNER UNE COLLECTION ===

# Méthode 1: Notation point
users = db.users

# Méthode 2: Notation dictionnaire
users = db["users"]

# Vérifier:
print(users.name)
# Affiche: users


# === INSÉRER DES DOCUMENTS ===

# Insérer un document unique:

user = {
    "nom": "Alice",
    "email": "alice@example.com",
    "age": 25,
    "hobbies": ["lecture", "voyage"]
}

result = users.insert_one(user)

print(result.inserted_id)
# Affiche: ObjectId('65a1b2c3d4e5f6789abcdef0')

# Explications:
# - insert_one() retourne un objet InsertOneResult
# - inserted_id contient l'_id généré


# Insérer plusieurs documents:

new_users = [
    {"nom": "Bob", "email": "bob@example.com", "age": 30},
    {"nom": "Charlie", "email": "charlie@example.com", "age": 35},
    {"nom": "Diana", "email": "diana@example.com", "age": 28}
]

result = users.insert_many(new_users)

print(result.inserted_ids)
# Affiche: [ObjectId('...'), ObjectId('...'), ObjectId('...')]


# === LIRE DES DOCUMENTS ===

# Lire tous les documents:

for user in users.find():
    print(user)

# Affiche:
# {'_id': ObjectId('...'), 'nom': 'Alice', 'email': 'alice@example.com', 'age': 25, 'hobbies': ['lecture', 'voyage']}
# {'_id': ObjectId('...'), 'nom': 'Bob', 'email': 'bob@example.com', 'age': 30}
# ...


# Lire un seul document:

user = users.find_one({"nom": "Alice"})
print(user)
# Affiche: {'_id': ObjectId('...'), 'nom': 'Alice', ...}


# Filtrer avec des conditions:

# Égalité:
for user in users.find({"age": 25}):
    print(user["nom"])

# Comparaison:
for user in users.find({"age": {"$gte": 30}}):
    print(user["nom"], user["age"])

# Affiche:
# Bob 30
# Charlie 35


# Opérateurs de comparaison:
# $eq  : égal à
# $ne  : différent de
# $gt  : plus grand que
# $gte : plus grand ou égal
# $lt  : plus petit que
# $lte : plus petit ou égal
# $in  : dans une liste
# $nin : pas dans une liste

# Exemples:

# Age entre 25 et 30:
users.find({"age": {"$gte": 25, "$lte": 30}})

# Nom dans une liste:
users.find({"nom": {"$in": ["Alice", "Bob"]}})

# Age différent de 30:
users.find({"age": {"$ne": 30}})


# Opérateurs logiques:
# $and : ET logique
# $or  : OU logique
# $not : NON logique
# $nor : NI l'un NI l'autre

# Exemples:

# Age >= 30 ET nom = "Bob":
users.find({"$and": [{"age": {"$gte": 30}}, {"nom": "Bob"}]})

# Age < 25 OU age > 35:
users.find({"$or": [{"age": {"$lt": 25}}, {"age": {"$gt": 35}}]})


# Recherche dans des tableaux:

# Trouver les utilisateurs qui ont "lecture" dans leurs hobbies:
users.find({"hobbies": "lecture"})

# Trouver les utilisateurs qui ont AU MOINS UN des hobbies:
users.find({"hobbies": {"$in": ["lecture", "sport"]}})

# Trouver les utilisateurs qui ont TOUS les hobbies:
users.find({"hobbies": {"$all": ["lecture", "voyage"]}})


# Projection (sélectionner certains champs):

# Afficher seulement nom et email:
for user in users.find({}, {"nom": 1, "email": 1, "_id": 0}):
    print(user)

# Affiche:
# {'nom': 'Alice', 'email': 'alice@example.com'}
# {'nom': 'Bob', 'email': 'bob@example.com'}

# Explications:
# - 1 = inclure le champ
# - 0 = exclure le champ
# - Par défaut, _id est toujours inclus (sauf si tu mets _id: 0)


# Limiter le nombre de résultats:

for user in users.find().limit(2):
    print(user["nom"])

# Affiche: les 2 premiers documents


# Sauter des documents:

for user in users.find().skip(2):
    print(user["nom"])

# Affiche: tous les documents sauf les 2 premiers


# Trier les résultats:

# Tri croissant (1):
for user in users.find().sort("age", 1):
    print(user["nom"], user["age"])

# Tri décroissant (-1):
for user in users.find().sort("age", -1):
    print(user["nom"], user["age"])

# Tri sur plusieurs champs:
for user in users.find().sort([("age", -1), ("nom", 1)]):
    print(user["nom"], user["age"])


# Compter les documents:

count = users.count_documents({})
print(count)
# Affiche: 4

count = users.count_documents({"age": {"$gte": 30}})
print(count)
# Affiche: 2


# === METTRE À JOUR DES DOCUMENTS ===

# Mettre à jour un seul document:

result = users.update_one(
    {"nom": "Alice"},           # Filtre
    {"$set": {"age": 26}}        # Modification
)

print(result.matched_count)    # Nombre de documents trouvés
print(result.modified_count)   # Nombre de documents modifiés

# Affiche:
# 1
# 1


# Opérateurs de modification:
# $set     : définir une valeur
# $unset   : supprimer un champ
# $inc     : incrémenter/décrémenter
# $mul     : multiplier
# $rename  : renommer un champ
# $min     : mettre à jour si la nouvelle valeur est plus petite
# $max     : mettre à jour si la nouvelle valeur est plus grande
# $currentDate : définir à la date actuelle

# Exemples:

# Incrémenter l'age de 1:
users.update_one({"nom": "Alice"}, {"$inc": {"age": 1}})

# Ajouter un nouveau champ:
users.update_one({"nom": "Alice"}, {"$set": {"ville": "Paris"}})

# Supprimer un champ:
users.update_one({"nom": "Alice"}, {"$unset": {"ville": ""}})

# Renommer un champ:
users.update_one({"nom": "Alice"}, {"$rename": {"nom": "name"}})

# Définir à la date actuelle:
from datetime import datetime
users.update_one({"nom": "Alice"}, {"$currentDate": {"last_modified": True}})


# Mettre à jour plusieurs documents:

result = users.update_many(
    {"age": {"$lt": 30}},        # Filtre: age < 30
    {"$inc": {"age": 1}}          # Incrémente age de 1
)

print(result.matched_count)
print(result.modified_count)


# Remplacer un document entier:

result = users.replace_one(
    {"nom": "Bob"},
    {"nom": "Bob", "email": "bob_new@example.com", "age": 31, "ville": "Lyon"}
)

print(result.matched_count)
print(result.modified_count)


# Upsert (insert si n'existe pas, update sinon):

result = users.update_one(
    {"nom": "Eve"},
    {"$set": {"email": "eve@example.com", "age": 22}},
    upsert=True
)

print(result.upserted_id)
# Affiche: ObjectId('...') si le document a été créé


# === SUPPRIMER DES DOCUMENTS ===

# Supprimer un seul document:

result = users.delete_one({"nom": "Alice"})

print(result.deleted_count)
# Affiche: 1


# Supprimer plusieurs documents:

result = users.delete_many({"age": {"$gte": 30}})

print(result.deleted_count)
# Affiche: 2


# Supprimer tous les documents:

result = users.delete_many({})

print(result.deleted_count)
# Affiche: 4 (tous les documents)


[OK] OPÉRATEURS MONGODB (TRÈS DÉTAILLÉS)

# === OPÉRATEURS DE COMPARAISON ===

# $eq (equal) - Égal à
users.find({"age": {"$eq": 25}})
# Équivalent à:
users.find({"age": 25})

# $ne (not equal) - Différent de
users.find({"age": {"$ne": 25}})

# $gt (greater than) - Plus grand que
users.find({"age": {"$gt": 30}})

# $gte (greater than or equal) - Plus grand ou égal
users.find({"age": {"$gte": 30}})

# $lt (less than) - Plus petit que
users.find({"age": {"$lt": 30}})

# $lte (less than or equal) - Plus petit ou égal
users.find({"age": {"$lte": 30}})

# $in - Dans une liste
users.find({"nom": {"$in": ["Alice", "Bob", "Charlie"]}})

# $nin - Pas dans une liste
users.find({"nom": {"$nin": ["Alice", "Bob"]}})


# === OPÉRATEURS LOGIQUES ===

# $and - ET logique (tous les critères doivent être vrais)
users.find({
    "$and": [
        {"age": {"$gte": 25}},
        {"age": {"$lte": 30}}
    ]
})

# Équivalent (implicite):
users.find({"age": {"$gte": 25, "$lte": 30}})


# $or - OU logique (au moins un critère doit être vrai)
users.find({
    "$or": [
        {"age": {"$lt": 25}},
        {"age": {"$gt": 35}}
    ]
})


# $not - NON logique (inverse la condition)
users.find({"age": {"$not": {"$gte": 30}}})


# $nor - NI l'un NI l'autre (aucun critère ne doit être vrai)
users.find({
    "$nor":[
        {"age": {"$lt": 25}},
        {"age": {"$gt": 35}}
    ]
})


# === OPÉRATEURS DE MODIFICATION ===

# $set - Définir/modifier un champ
users.update_one({"nom": "Alice"}, {"$set": {"age": 26, "ville": "Paris"}})

# $unset - Supprimer un champ
users.update_one({"nom": "Alice"}, {"$unset": {"ville": ""}})

# $inc - Incrémenter/décrémenter
users.update_one({"nom": "Alice"}, {"$inc": {"age": 1}})       # +1
users.update_one({"nom": "Alice"}, {"$inc": {"age": -1}})      # -1

# $mul - Multiplier
users.update_one({"nom": "Alice"}, {"$mul": {"age": 2}})       # age * 2

# $rename - Renommer un champ
users.update_one({"nom": "Alice"}, {"$rename": {"nom": "name"}})

# $min - Mettre à jour si la nouvelle valeur est plus petite
users.update_one({"nom": "Alice"}, {"$min": {"age": 20}})
# Si age actuel > 20, alors age = 20
# Si age actuel < 20, pas de changement

# $max - Mettre à jour si la nouvelle valeur est plus grande
users.update_one({"nom": "Alice"}, {"$max": {"age": 30}})
# Si age actuel < 30, alors age = 30
# Si age actuel > 30, pas de changement

# $currentDate - Définir à la date/timestamp actuel
users.update_one({"nom": "Alice"}, {"$currentDate": {"last_modified": True}})
# Définit last_modified à la date actuelle


# === OPÉRATEURS DE TABLEAU ===

# $push - Ajouter un élément à un tableau
users.update_one({"nom": "Alice"}, {"$push": {"hobbies": "natation"}})

# $addToSet - Ajouter un élément à un tableau (sans doublons)
users.update_one({"nom": "Alice"}, {"$addToSet": {"hobbies": "lecture"}})
# Si "lecture" existe déjà, ne fait rien

# $pop - Supprimer le premier/dernier élément
users.update_one({"nom": "Alice"}, {"$pop": {"hobbies": 1}})   # Dernier
users.update_one({"nom": "Alice"}, {"$pop": {"hobbies": -1}})  # Premier

# $pull - Supprimer tous les éléments correspondants
users.update_one({"nom": "Alice"}, {"$pull": {"hobbies": "lecture"}})

# $pullAll - Supprimer plusieurs éléments
users.update_one({"nom": "Alice"}, {"$pullAll": {"hobbies": ["lecture", "sport"]}})

# $each - Ajouter plusieurs éléments avec $push
users.update_one({"nom": "Alice"}, {"$push": {"hobbies": {"$each": ["yoga", "danse"]}}})

# $position - Insérer à une position spécifique avec $push
users.update_one({"nom": "Alice"}, {"$push": {"hobbies": {"$each": ["cuisine"], "$position": 0}}})
# Insère "cuisine" au début (position 0)

# $slice - Limiter la taille d'un tableau
users.update_one({"nom": "Alice"}, {"$push": {"hobbies": {"$each": ["photo"], "$slice": -3}}})
# Garde seulement les 3 derniers éléments


# === OPÉRATEURS D'ÉVALUATION ===

# $regex - Recherche avec expression régulière
users.find({"email": {"$regex": "^alice.*@example.com$"}})

# Options:
# i = insensible à la casse
users.find({"email": {"$regex": "alice", "$options": "i"}})

# $text - Recherche full-text (nécessite un index texte)
# D'abord créer un index:
users.create_index([("nom", "text")])
# Puis chercher:
users.find({"$text": {"$search": "Alice"}})

# $where - Condition JavaScript (lent, éviter si possible)
users.find({"$where": "this.age > 25"})


# === OPÉRATEURS DE TABLEAU (REQUÊTES) ===

# $all - Le tableau contient tous les éléments
users.find({"hobbies": {"$all": ["lecture", "voyage"]}})

# $elemMatch - Un élément du tableau correspond à toutes les conditions
users.find({"scores": {"$elemMatch": {"$gte": 80, "$lt": 90}}})

# $size - Le tableau a une taille spécifique
users.find({"hobbies": {"$size": 3}})


[OK] INDEX MONGODB (PERFORMANCE)

# === QU'EST-CE QU'UN INDEX? ===

# Imagine que tu as 1 million d'utilisateurs dans ta collection
# Tu cherches un utilisateur par son email
# Sans index: MongoDB doit parcourir TOUS les documents (lent!)
# Avec index: MongoDB utilise une structure optimisée (rapide!)

# Analogie: Un index de livre
# - Sans index: tu lis le livre entier pour trouver un mot
# - Avec index: tu vas directement à la page

# POURQUOI créer des index?
# - Accélérer les requêtes (find, update, delete)
# - Réduire la charge CPU/mémoire
# - Améliorer l'expérience utilisateur

# QUAND créer des index?
# - Sur les champs souvent utilisés dans les filtres
# - Sur les champs utilisés pour trier
# - Sur les champs utilisés dans les jointures ($lookup)

# INCONVÉNIENTS des index:
# - Consomment de l'espace disque
# - Ralentissent les insertions/mises à jour (MongoDB doit mettre à jour l'index)
# - Trop d'index = contre-productif


# === CRÉER UN INDEX ===

# Index simple (un seul champ):

users.create_index("email")

# Ou avec plus de détails:
users.create_index([("email", 1)])  # 1 = croissant, -1 = décroissant

# Affiche:
# 'email_1'


# Index unique (pas de doublons):

users.create_index("email", unique=True)

# Essayer d'insérer un email en double:
users.insert_one({"nom": "Test", "email": "alice@example.com"})
# Erreur: E11000 duplicate key error


# Index composé (plusieurs champs):

users.create_index([("nom", 1), ("age", -1)])

# Utile pour les requêtes comme:
users.find({"nom": "Alice", "age": {"$gte": 25}})


# Index texte (recherche full-text):

users.create_index([("nom", "text"), ("email", "text")])

# Puis chercher:
users.find({"$text": {"$search": "Alice"}})


# Index géospatial (coordonnées GPS):

# D'abord ajouter des coordonnées:
users.insert_one({
    "nom": "Restaurant",
    "location": {
        "type": "Point",
        "coordinates": [2.3522, 48.8566]  # [longitude, latitude]
    }
})

# Créer l'index:
users.create_index([("location", "2dsphere")])

# Chercher les restaurants près d'une position:
users.find({
    "location": {
        "$near": {
            "$geometry": {
                "type": "Point",
                "coordinates": [2.35, 48.85]
            },
            "$maxDistance": 1000  # 1000 mètres
        }
    }
})


# === VOIR LES INDEX ===

# Lister tous les index:
print(users.list_indexes())

# Affiche:
# [
#   {'v': 2, 'key': {'_id': 1}, 'name': '_id_'},
#   {'v': 2, 'key': {'email': 1}, 'name': 'email_1', 'unique': True}
# ]

# Note: MongoDB crée automatiquement un index sur _id


# === SUPPRIMER UN INDEX ===

# Supprimer un index par son nom:
users.drop_index("email_1")

# Supprimer tous les index (sauf _id):
users.drop_indexes()


# === ANALYSER LA PERFORMANCE D'UNE REQUÊTE ===

# Utiliser explain() pour voir si MongoDB utilise un index:

result = users.find({"email": "alice@example.com"}).explain()
print(result["executionStats"])

# Affiche:
# {
#   'executionSuccess': True,
#   'nReturned': 1,
#   'totalDocsExamined': 1,  # Nombre de documents examinés
#   'executionTimeMillis': 0
# }

# Si totalDocsExamined = nReturned: Index utilisé (bon!)
# Si totalDocsExamined >> nReturned: Pas d'index (mauvais!)


[OK] AGRÉGATION MONGODB (PIPELINES)

# === QU'EST-CE QUE L'AGRÉGATION? ===

# L'agrégation = Traiter/transformer des données en plusieurs étapes
# Analogie: Une chaîne de production dans une usine

# Exemple SQL:
# SELECT age, COUNT(*) FROM users GROUP BY age

# Équivalent MongoDB (agrégation):
users.aggregate([
    {"$group": {"_id": "$age", "count": {"$sum": 1}}}
])


# === PIPELINE D'AGRÉGATION ===

# Un pipeline = Une séquence d'étapes (stages)
# Chaque étape transforme les données et les passe à l'étape suivante

# Stages principaux:
# $match     : Filtrer les documents (comme find())
# $project   : Sélectionner/transformer les champs
# $group     : Grouper les documents et calculer des agrégats
# $sort      : Trier les documents
# $limit     : Limiter le nombre de documents
# $skip      : Sauter des documents
# $unwind    : Déplier un tableau
# $lookup    : Jointure avec une autre collection
# $addFields : Ajouter de nouveaux champs calculés
# $count     : Compter le nombre de documents


# === EXEMPLES D'AGRÉGATION ===

# Exemple 1: Compter le nombre d'utilisateurs par âge

pipeline = [
    {"$group": {"_id": "$age", "count": {"$sum": 1}}}
]

result = users.aggregate(pipeline)

for doc in result:
    print(doc)

# Affiche:
# {'_id': 25, 'count': 2}
# {'_id': 30, 'count': 1}
# {'_id': 35, 'count': 1}


# Exemple 2: Calculer l'âge moyen

pipeline = [
    {"$group": {"_id": None, "avg_age": {"$avg": "$age"}}}
]

result = users.aggregate(pipeline)

for doc in result:
    print(doc)

# Affiche:
# {'_id': None, 'avg_age': 28.75}


# Exemple 3: Filtrer puis grouper

pipeline = [
    {"$match": {"age": {"$gte": 25}}},              # Filtrer age >= 25
    {"$group": {"_id": "$age", "count": {"$sum": 1}}}  # Grouper par age
]

result = users.aggregate(pipeline)

for doc in result:
    print(doc)


# Exemple 4: Trier par nombre décroissant

pipeline = [
    {"$group": {"_id": "$age", "count": {"$sum": 1}}},
    {"$sort": {"count": -1}}                         # Trier par count décroissant
]

result = users.aggregate(pipeline)


# Exemple 5: Projeter certains champs

pipeline = [
    {"$project": {"nom": 1, "email": 1, "_id": 0}}
]

result = users.aggregate(pipeline)

# Affiche seulement nom et email


# Exemple 6: Ajouter un champ calculé

pipeline = [
    {"$addFields": {"age_plus_10": {"$add": ["$age", 10]}}}
]

result = users.aggregate(pipeline)

# Affiche chaque document avec un nouveau champ age_plus_10


# Exemple 7: Déplier un tableau ($unwind)

# Document:
# {
#   "nom": "Alice",
#   "hobbies": ["lecture", "voyage", "sport"]
# }

# Après $unwind:
# {"nom": "Alice", "hobbies": "lecture"}
# {"nom": "Alice", "hobbies": "voyage"}
# {"nom": "Alice", "hobbies": "sport"}

pipeline = [
    {"$unwind": "$hobbies"}
]

result = users.aggregate(pipeline)


# Exemple 8: Compter le nombre total de documents

pipeline = [
    {"$count": "total"}
]

result = users.aggregate(pipeline)

for doc in result:
    print(doc)

# Affiche:
# {'total': 4}


# === OPÉRATEURS D'AGRÉGATION ===

# Opérateurs arithmétiques:
# $add       : addition
# $subtract  : soustraction
# $multiply  : multiplication
# $divide    : division
# $mod       : modulo

# Opérateurs de groupe:
# $sum       : somme
# $avg       : moyenne
# $min       : minimum
# $max       : maximum
# $first     : premier élément
# $last      : dernier élément
# $push      : ajouter à un tableau
# $addToSet  : ajouter à un tableau (sans doublons)

# Opérateurs de chaîne:
# $concat    : concaténer des chaînes
# $substr    : extraire une sous-chaîne
# $toLower   : convertir en minuscules
# $toUpper   : convertir en majuscules

# Opérateurs de tableau:
# $size      : taille d'un tableau
# $slice     : extraire une partie d'un tableau
# $arrayElemAt : élément à un index

# Opérateurs de date:
# $year      : année
# $month     : mois
# $dayOfMonth: jour du mois
# $hour      : heure
# $minute    : minute
# $second    : seconde


# === JOINTURE AVEC $lookup ===

# Imagine deux collections:
# - "users": utilisateurs
# - "orders": commandes

# Chaque commande a un champ "user_id" qui référence un utilisateur

# Jointure:

pipeline = [
    {
        "$lookup": {
            "from": "orders",           # Collection à joindre
            "localField": "_id",         # Champ de la collection actuelle
            "foreignField": "user_id",   # Champ de la collection jointe
            "as": "user_orders"          # Nom du champ résultat
        }
    }
]

result = users.aggregate(pipeline)

for doc in result:
    print(doc)

# Affiche:
# {
#   '_id': ObjectId('...'),
#   'nom': 'Alice',
#   'email': 'alice@example.com',
#   'user_orders': [
#     {'_id': ObjectId('...'), 'product': 'Livre', 'price': 20},
#     {'_id': ObjectId('...'), 'product': 'Stylo', 'price': 2}
#   ]
# }


[OK] TRANSACTIONS MONGODB (ACID)

# === QU'EST-CE QU'UNE TRANSACTION? ===

# Une transaction = Un groupe d'opérations qui doivent toutes réussir ou toutes échouer
# Analogie: Un transfert bancaire
# - Débiter le compte A
# - Créditer le compte B
# Si l'une des opérations échoue, tout est annulé (rollback)

# ACID:
# - Atomicity: Tout ou rien
# - Consistency: Les données restent cohérentes
# - Isolation: Les transactions ne s'interfèrent pas
# - Durability: Les modifications sont persistantes

# POURQUOI les transactions?
# - Garantir l'intégrité des données
# - Éviter les états incohérents
# - Gérer les erreurs proprement

# QUAND utiliser les transactions?
# - Opérations financières
# - Réservations (hôtel, vol, etc.)
# - Inventaire (stock de produits)
# - Tout scénario où plusieurs documents doivent être modifiés ensemble


# === UTILISER LES TRANSACTIONS ===

# Prérequis:
# - MongoDB 4.0+ (transactions sur replica set)
# - MongoDB 4.2+ (transactions sur sharded cluster)

# Exemple: Transférer de l'argent entre deux comptes

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")
db = client.bank_db

accounts = db.accounts

# Créer deux comptes:
accounts.insert_one({"name": "Alice", "balance": 1000})
accounts.insert_one({"name": "Bob", "balance": 500})


# Transaction:

# Démarrer une session:
with client.start_session() as session:
    # Démarrer la transaction:
    with session.start_transaction():
        try:
            # Débiter le compte d'Alice:
            accounts.update_one(
                {"name": "Alice"},
                {"$inc": {"balance": -100}},
                session=session
            )
            
            # Créditer le compte de Bob:
            accounts.update_one(
                {"name": "Bob"},
                {"$inc": {"balance": 100}},
                session=session
            )
            
            # Si tout est OK, commiter:
            session.commit_transaction()
            print("Transaction réussie!")
            
        except Exception as e:
            # En cas d'erreur, rollback:
            session.abort_transaction()
            print(f"Transaction échouée: {e}")


# Vérifier:
print(accounts.find_one({"name": "Alice"}))
# Affiche: {'_id': ObjectId('...'), 'name': 'Alice', 'balance': 900}

print(accounts.find_one({"name": "Bob"}))
# Affiche: {'_id': ObjectId('...'), 'name': 'Bob', 'balance': 600}


# === TRANSACTIONS AVEC ERREUR ===

# Simuler une erreur:

with client.start_session() as session:
    with session.start_transaction():
        try:
            accounts.update_one(
                {"name": "Alice"},
                {"$inc": {"balance": -100}},
                session=session
            )
            
            # Simuler une erreur:
            raise Exception("Erreur simulée!")
            
            accounts.update_one(
                {"name": "Bob"},
                {"$inc": {"balance": 100}},
                session=session
            )
            
            session.commit_transaction()
            
        except Exception as e:
            session.abort_transaction()
            print(f"Transaction échouée: {e}")

# Vérifier:
print(accounts.find_one({"name": "Alice"}))
# Affiche: {'_id': ObjectId('...'), 'name': 'Alice', 'balance': 900}
# Le balance n'a PAS changé (rollback!)


[OK] MODÉLISATION DES DONNÉES (SCHÉMA DESIGN)

# === EMBEDDED vs REFERENCED ===

# MongoDB permet deux façons de modéliser les relations:
# 1. Embedded (imbriqué): Les données liées sont dans le même document
# 2. Referenced (référencé): Les données liées sont dans des documents séparés

# POURQUOI choisir l'un ou l'autre?
# - Dépend de la fréquence d'accès
# - Dépend de la taille des données
# - Dépend de la cohérence nécessaire


# === EMBEDDED (IMBRIQUÉ) ===

# Exemple: Blog avec articles et commentaires

# Un article avec ses commentaires imbriqués:
article = {
    "_id": ObjectId("..."),
    "title": "Mon premier article",
    "content": "Contenu de l'article...",
    "author": "Alice",
    "comments": [
        {
            "author": "Bob",
            "text": "Super article!",
            "date": datetime(2024, 1, 15)
        },
        {
            "author": "Charlie",
            "text": "Merci pour le partage!",
            "date": datetime(2024, 1, 16)
        }
    ]
}

db.articles.insert_one(article)


# AVANTAGES:
# - Une seule requête pour récupérer l'article et ses commentaires
# - Très rapide
# - Cohérence garantie (tout dans un document)

# INCONVÉNIENTS:
# - Limite de 16 MB par document
# - Si trop de commentaires, le document devient énorme
# - Difficile de chercher tous les commentaires d'un utilisateur


# QUAND utiliser Embedded?
# [OK] Les données liées sont toujours lues ensemble
# [OK] Les données liées sont petites
# [OK] Relation one-to-few (un-à-peu)
# [OK] Pas besoin d'accéder aux sous-documents séparément


# === REFERENCED (RÉFÉRENCÉ) ===

# Exemple: Articles et commentaires séparés

# Article:
article = {
    "_id": ObjectId("65a1b2c3d4e5f6789abcdef0"),
    "title": "Mon premier article",
    "content": "Contenu de l'article...",
    "author": "Alice"
}

db.articles.insert_one(article)


# Commentaires (dans une collection séparée):
comments = [
    {
        "article_id": ObjectId("65a1b2c3d4e5f6789abcdef0"),  # Référence à l'article
        "author": "Bob",
        "text": "Super article!",
        "date": datetime(2024, 1, 15)
    },
    {
        "article_id": ObjectId("65a1b2c3d4e5f6789abcdef0"),
        "author": "Charlie",
        "text": "Merci pour le partage!",
        "date": datetime(2024, 1, 16)
    }
]

db.comments.insert_many(comments)


# Récupérer l'article et ses commentaires:

article = db.articles.find_one({"_id": ObjectId("65a1b2c3d4e5f6789abcdef0")})
comments = list(db.comments.find({"article_id": article["_id"]}))

print(article)
print(comments)


# AVANTAGES:
# - Pas de limite de taille
# - Facile de chercher tous les commentaires d'un utilisateur
# - Évite la duplication de données

# INCONVÉNIENTS:
# - Deux requêtes pour récupérer l'article et ses commentaires
# - Plus lent
# - Pas de cohérence garantie (si l'article est supprimé, les commentaires restent)


# QUAND utiliser Referenced?
# [OK] Les données liées sont rarement lues ensemble
# [OK] Les données liées sont volumineuses
# [OK] Relation one-to-many (un-à-beaucoup)
# [OK] Besoin d'accéder aux sous-documents séparément
# [OK] Besoin de partager les sous-documents entre plusieurs documents


# === HYBRID APPROACH (APPROCHE HYBRIDE) ===

# Combiner Embedded et Referenced

# Exemple: Stocker les informations essentielles (embedded) et référencer pour les détails

# Article avec résumé des commentaires:
article = {
    "_id": ObjectId("65a1b2c3d4e5f6789abcdef0"),
    "title": "Mon premier article",
    "content": "Contenu de l'article...",
    "author": "Alice",
    "comments_count": 2,                      # Résumé
    "recent_comments": [                       # Derniers commentaires (embedded)
        {"author": "Bob", "text": "Super article!"},
        {"author": "Charlie", "text": "Merci!"}
    ]
}

# Tous les commentaires (dans une collection séparée):
db.comments.insert_many([...])


# AVANTAGES:
# - Une requête pour l'article et les commentaires récents
# - Deux requêtes si tu veux TOUS les commentaires
# - Compromis entre performance et flexibilité


# === RÈGLES DE MODÉLISATION ===

# 1. Modéliser selon les patterns d'accès
# - Comment les données sont lues?
# - Quelles requêtes sont les plus fréquentes?

# 2. Éviter les JOIN si possible
# - Imbriqué ou dupliqué plutôt que référencé

# 3. Limiter la taille des documents
# - Maximum 16 MB par document
# - Si trop gros, utiliser Referenced

# 4. Utiliser des index
# - Sur les champs fréquemment filtrés/triés

# 5. Denormalisation (duplication)
# - Acceptable si ça améliore la performance
# - Exemple: Stocker le nom de l'auteur dans chaque article


[OK] SÉCURITÉ MONGODB

# === AUTHENTIFICATION ===

# Par défaut, MongoDB n'a PAS d'authentification!
# = N'importe qui peut se connecter et lire/modifier les données
# = TRÈS DANGEREUX en production!

# Activer l'authentification:

# 1. Créer un utilisateur admin:

use admin
db.createUser({
    user: "admin",
    pwd: "securepassword",
    roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
})


# 2. Redémarrer MongoDB avec --auth:

mongod --auth --dbpath /data/db


# 3. Se connecter avec authentification:

mongosh -u admin -p securepassword --authenticationDatabase admin


# Avec PyMongo:

client = MongoClient(
    "mongodb://admin:securepassword@localhost:27017/",
    authSource="admin"
)


# === CRÉER DES UTILISATEURS AVEC DES RÔLES ===

# Rôles prédéfinis:
# - read: Lecture seule
# - readWrite: Lecture et écriture
# - dbAdmin: Administration de la base
# - userAdmin: Gestion des utilisateurs
# - clusterAdmin: Administration du cluster

# Créer un utilisateur pour une base spécifique:

use blog_db
db.createUser({
    user: "blog_user",
    pwd: "blogpassword",
    roles: [{ role: "readWrite", db: "blog_db" }]
})


# Se connecter avec cet utilisateur:

client = MongoClient(
    "mongodb://blog_user:blogpassword@localhost:27017/blog_db"
)


# === CHIFFREMENT ===

# Chiffrer les données au repos (on disk):

mongod --dbpath /data/db --enableEncryption --encryptionKeyFile /path/to/keyfile


# Chiffrer les connexions (SSL/TLS):

mongod --tlsMode requireTLS --tlsCertificateKeyFile /path/to/cert.pem


# Avec PyMongo:

client = MongoClient(
    "mongodb://localhost:27017/",
    tls=True,
    tlsCAFile="/path/to/ca.pem"
)


# === LIMITER LES ADRESSES IP ===

# Limiter l'accès à certaines IP:

mongod --bind_ip 127.0.0.1,192.168.1.100


# Ou dans le fichier de configuration (/etc/mongod.conf):

net:
  bindIp: 127.0.0.1,192.168.1.100


# === AUDITING (JOURNALISATION) ===

# Activer l'audit pour enregistrer toutes les actions:

mongod --auditDestination file --auditFormat JSON --auditPath /var/log/mongodb/audit.json


[OK] DÉPLOIEMENT EN PRODUCTION

# === REPLICA SET (HAUTE DISPONIBILITÉ) ===

# Un replica set = Un groupe de serveurs MongoDB qui se répliquent
# - Primary: Serveur principal (lectures/écritures)
# - Secondary: Serveurs secondaires (lectures seules, répliquent le Primary)
# - Arbiter: Serveur qui vote en cas de panne (pas de données)

# POURQUOI un replica set?
# - Haute disponibilité: Si le Primary tombe, un Secondary devient Primary
# - Backups automatiques
# - Lectures distribuées

# Créer un replica set:

# 1. Lancer 3 instances MongoDB:

mongod --replSet myReplicaSet --port 27017 --dbpath /data/db1
mongod --replSet myReplicaSet --port 27018 --dbpath /data/db2
mongod --replSet myReplicaSet --port 27019 --dbpath /data/db3


# 2. Se connecter au premier serveur:

mongosh --port 27017


# 3. Initialiser le replica set:

rs.initiate({
    _id: "myReplicaSet",
    members: [
        { _id: 0, host: "localhost:27017" },
        { _id: 1, host: "localhost:27018" },
        { _id: 2, host: "localhost:27019" }
    ]
})


# 4. Vérifier le statut:

rs.status()


# Se connecter avec PyMongo:

client = MongoClient("mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=myReplicaSet")


# === SHARDING (SCALABILITÉ HORIZONTALE) ===

# Sharding = Répartir les données sur plusieurs serveurs
# - Chaque shard (serveur) contient une partie des données
# - Un routeur (mongos) dirige les requêtes vers le bon shard

# POURQUOI le sharding?
# - Gérer des milliards de documents
# - Distribuer la charge sur plusieurs serveurs
# - Augmenter les performances

# Créer un cluster shardé:

# 1. Lancer un config server:

mongod --configsvr --replSet configReplSet --port 27019 --dbpath /data/configdb


# 2. Lancer deux shards:

mongod --shardsvr --replSet shard1 --port 27018 --dbpath /data/shard1
mongod --shardsvr --replSet shard2 --port 27020 --dbpath /data/shard2


# 3. Lancer le routeur (mongos):

mongos --configdb configReplSet/localhost:27019


# 4. Se connecter au routeur et ajouter les shards:

sh.addShard("shard1/localhost:27018")
sh.addShard("shard2/localhost:27020")


# 5. Activer le sharding pour une base:

sh.enableSharding("blog_db")


# 6. Shard une collection:

sh.shardCollection("blog_db.users", { "_id": "hashed" })


# === MONGODB ATLAS (CLOUD) ===

# MongoDB Atlas = Service cloud géré par MongoDB
# - Gratuit jusqu'à 512 MB (tier M0)
# - Haute disponibilité automatique
# - Backups automatiques
# - Monitoring et alertes
# - Pas de gestion de serveurs

# Créer un cluster sur Atlas:

# 1. Va sur: https://www.mongodb.com/cloud/atlas/register
# 2. Crée un compte gratuit
# 3. Crée un cluster gratuit (M0 Sandbox)
# 4. Configure un utilisateur et mot de passe
# 5. Ajoute ton IP à la whitelist
# 6. Copie la chaîne de connexion:

mongodb+srv://user:pass@cluster.mongodb.net/mydb?retryWrites=true&w=majority


# Se connecter avec PyMongo:

from pymongo import MongoClient

client = MongoClient("mongodb+srv://user:pass@cluster.mongodb.net/mydb?retryWrites=true&w=majority")


# === BACKUPS ===

# Backup avec mongodump:

mongodump --uri="mongodb://localhost:27017/blog_db" --out=/backup/


# Restaurer avec mongorestore:

mongorestore --uri="mongodb://localhost:27017/" /backup/blog_db/


# === MONITORING ===

# Voir les statistiques d'une collection:

db.users.stats()


# Voir les statistiques du serveur:

db.serverStatus()


# Voir les opérations en cours:

db.currentOp()


# Tuer une opération:

db.killOp(<opid>)


[OK] BONNES PRATIQUES

# 1. Toujours utiliser des index
# - Sur les champs fréquemment filtrés/triés
# - Analyser avec explain()

# 2. Limiter la taille des documents
# - Maximum 16 MB
# - Si trop gros, utiliser GridFS

# 3. Utiliser des projections
# - Ne récupérer que les champs nécessaires
# - Réduit la bande passante

# 4. Utiliser des connexion pools
# - Réutiliser les connexions
# - Plus performant

# 5. Activer l'authentification en production
# - Créer des utilisateurs avec des rôles spécifiques
# - Ne jamais utiliser l'utilisateur admin

# 6. Faire des backups réguliers
# - Automatiser avec mongodump
# - Tester les restaurations

# 7. Monitorer les performances
# - Utiliser MongoDB Compass
# - Utiliser les logs
# - Utiliser Atlas pour le monitoring automatique

# 8. Éviter les requêtes N+1
# - Utiliser $lookup ou embedded
# - Pas de boucles avec find()

# 9. Utiliser les transactions pour les opérations critiques
# - Transferts d'argent
# - Réservations
# - Inventaire

# 10. Tester localement avant de déployer
# - Utiliser Docker pour les tests
# - Utiliser des données de test


[OK] EXEMPLES COMPLETS

# === EXEMPLE 1: BLOG COMPLET ===

from pymongo import MongoClient
from datetime import datetime
from bson import ObjectId

# Connexion
client = MongoClient("mongodb://localhost:27017/")
db = client.blog_db

users = db.users
articles = db.articles
comments = db.comments


# Créer un utilisateur
def create_user(name, email, password):
    user = {
        "name": name,
        "email": email,
        "password": password,  # En prod, utiliser bcrypt!
        "created_at": datetime.now()
    }
    result = users.insert_one(user)
    return result.inserted_id


# Créer un article
def create_article(user_id, title, content):
    # Récupérer le nom de l'utilisateur (denormalization)
    user = users.find_one({"_id": user_id})
    
    article = {
        "user_id": user_id,
        "author_name": user["name"],  # Duplication pour performance
        "title": title,
        "content": content,
        "created_at": datetime.now(),
        "views": 0,
        "likes": 0
    }
    result = articles.insert_one(article)
    return result.inserted_id


# Créer un commentaire
def create_comment(article_id, user_id, text):
    user = users.find_one({"_id": user_id})
    
    comment = {
        "article_id": article_id,
        "user_id": user_id,
        "author_name": user["name"],
        "text": text,
        "created_at": datetime.now()
    }
    result = comments.insert_one(comment)
    return result.inserted_id


# Lire un article avec ses commentaires
def get_article_with_comments(article_id):
    article = articles.find_one({"_id": article_id})
    article_comments = list(comments.find({"article_id": article_id}))
    
    article["comments"] = article_comments
    return article


# Liker un article
def like_article(article_id):
    articles.update_one(
        {"_id": article_id},
        {"$inc": {"likes": 1}}
    )


# Voir un article (incrémenter les vues)
def view_article(article_id):
    articles.update_one(
        {"_id": article_id},
        {"$inc": {"views": 1}}
    )


# Chercher des articles par titre
def search_articles(query):
    # Créer un index texte:
    articles.create_index([("title", "text"), ("content", "text")])
    
    # Chercher:
    results = articles.find({"$text": {"$search": query}})
    return list(results)


# Tester:

# Créer des utilisateurs
alice_id = create_user("Alice", "alice@example.com", "password123")
bob_id = create_user("Bob", "bob@example.com", "password456")

# Créer un article
article_id = create_article(alice_id, "Mon premier article", "Contenu de l'article...")

# Créer des commentaires
create_comment(article_id, bob_id, "Super article!")
create_comment(article_id, alice_id, "Merci Bob!")

# Liker l'article
like_article(article_id)

# Voir l'article
view_article(article_id)

# Récupérer l'article avec commentaires
article = get_article_with_comments(article_id)
print(article)


# === EXEMPLE 2: E-COMMERCE ===

products = db.products
orders = db.orders
cart = db.cart


# Créer un produit
def create_product(name, price, stock):
    product = {
        "name": name,
        "price": price,
        "stock": stock,
        "created_at": datetime.now()
    }
    result = products.insert_one(product)
    return result.inserted_id


# Ajouter au panier
def add_to_cart(user_id, product_id, quantity):
    product = products.find_one({"_id": product_id})
    
    cart.update_one(
        {"user_id": user_id},
        {
            "$push": {
                "items": {
                    "product_id": product_id,
                    "name": product["name"],
                    "price": product["price"],
                    "quantity": quantity
                }
            }
        },
        upsert=True
    )


# Passer une commande (avec transaction)
def place_order(user_id):
    with client.start_session() as session:
        with session.start_transaction():
            try:
                # Récupérer le panier
                user_cart = cart.find_one({"user_id": user_id}, session=session)
                
                # Vérifier le stock et décrémenter
                for item in user_cart["items"]:
                    product = products.find_one({"_id": item["product_id"]}, session=session)
                    
                    if product["stock"] < item["quantity"]:
                        raise Exception(f"Stock insuffisant pour {product['name']}")
                    
                    # Décrémenter le stock
                    products.update_one(
                        {"_id": item["product_id"]},
                        {"$inc": {"stock": -item["quantity"]}},
                        session=session
                    )
                
                # Créer la commande
                order = {
                    "user_id": user_id,
                    "items": user_cart["items"],
                    "total": sum(item["price"] * item["quantity"] for item in user_cart["items"]),
                    "status": "pending",
                    "created_at": datetime.now()
                }
                orders.insert_one(order, session=session)
                
                # Vider le panier
                cart.delete_one({"user_id": user_id}, session=session)
                
                session.commit_transaction()
                print("Commande passée avec succès!")
                
            except Exception as e:
                session.abort_transaction()
                print(f"Erreur: {e}")


# Tester:

# Créer des produits
laptop_id = create_product("Laptop", 1200, 10)
mouse_id = create_product("Mouse", 25, 50)

# Créer un utilisateur
alice_id = create_user("Alice", "alice@example.com", "password123")

# Ajouter au panier
add_to_cart(alice_id, laptop_id, 1)
add_to_cart(alice_id, mouse_id, 2)

# Passer une commande
place_order(alice_id)


# === EXEMPLE 3: RÉSEAU SOCIAL ===

users = db.users
posts = db.posts
follows = db.follows


# Suivre un utilisateur
def follow_user(follower_id, followee_id):
    follows.insert_one({
        "follower_id": follower_id,
        "followee_id": followee_id,
        "created_at": datetime.now()
    })


# Créer un post
def create_post(user_id, text, image_url=None):
    user = users.find_one({"_id": user_id})
    
    post = {
        "user_id": user_id,
        "author_name": user["name"],
        "text": text,
        "image_url": image_url,
        "likes": [],
        "comments": [],
        "created_at": datetime.now()
    }
    result = posts.insert_one(post)
    return result.inserted_id


# Liker un post
def like_post(post_id, user_id):
    posts.update_one(
        {"_id": post_id},
        {"$addToSet": {"likes": user_id}}  # Pas de doublons
    )


# Commenter un post
def comment_post(post_id, user_id, text):
    user = users.find_one({"_id": user_id})
    
    comment = {
        "user_id": user_id,
        "author_name": user["name"],
        "text": text,
        "created_at": datetime.now()
    }
    
    posts.update_one(
        {"_id": post_id},
        {"$push": {"comments": comment}}
    )


# Récupérer le feed (posts des personnes suivies)
def get_feed(user_id):
    # Récupérer les ID des personnes suivies
    following = follows.find({"follower_id": user_id})
    following_ids = [f["followee_id"] for f in following]
    
    # Récupérer les posts
    feed = posts.find({"user_id": {"$in": following_ids}}).sort("created_at", -1).limit(20)
    
    return list(feed)


# Tester:

alice_id = create_user("Alice", "alice@example.com", "password123")
bob_id = create_user("Bob", "bob@example.com", "password456")

# Alice suit Bob
follow_user(alice_id, bob_id)

# Bob crée un post
post_id = create_post(bob_id, "Hello world!", "image.jpg")

# Alice like le post
like_post(post_id, alice_id)

# Alice commente
comment_post(post_id, alice_id, "Super post!")

# Feed d'Alice
feed = get_feed(alice_id)
print(feed)


[OK] RESSOURCES & LIENS

# Documentation officielle MongoDB:
# https://www.mongodb.com/docs/

# PyMongo Documentation:
# https://pymongo.readthedocs.io/

# MongoDB University (cours gratuits):
# https://university.mongodb.com/

# MongoDB Atlas (cloud gratuit):
# https://www.mongodb.com/cloud/atlas

# MongoDB Compass (interface graphique):
# https://www.mongodb.com/products/compass

# Stack Overflow MongoDB:
# https://stackoverflow.com/questions/tagged/mongodb

# MongoDB Community Forums:
# https://www.mongodb.com/community/forums/

# MongoDB GitHub:
# https://github.com/mongodb/mongo

# Awesome MongoDB (liste de ressources):
# https://github.com/ramnes/awesome-mongodb


[OK] COMMANDES SHELL MONGODB (RÉFÉRENCE RAPIDE)

# Connexion:
mongosh
mongosh "mongodb://localhost:27017"
mongosh "mongodb+srv://user:pass@cluster.mongodb.net/mydb"

# Bases de données:
show dbs                  # Lister les bases
use mydb                  # Utiliser une base
db                        # Afficher la base actuelle
db.dropDatabase()         # Supprimer la base actuelle

# Collections:
show collections          # Lister les collections
db.createCollection("users")  # Créer une collection
db.users.drop()           # Supprimer une collection

# Documents:
db.users.insertOne({...})           # Insérer un document
db.users.insertMany([...])          # Insérer plusieurs documents
db.users.find()                     # Lire tous les documents
db.users.findOne({...})             # Lire un document
db.users.updateOne({...}, {...})    # Mettre à jour un document
db.users.updateMany({...}, {...})   # Mettre à jour plusieurs documents
db.users.deleteOne({...})           # Supprimer un document
db.users.deleteMany({...})          # Supprimer plusieurs documents
db.users.replaceOne({...}, {...})   # Remplacer un document

# Index:
db.users.createIndex({email: 1})    # Créer un index
db.users.getIndexes()               # Lister les index
db.users.dropIndex("email_1")       # Supprimer un index

# Agrégation:
db.users.aggregate([...])           # Pipeline d'agrégation

# Statistiques:
db.users.stats()                    # Stats d'une collection
db.serverStatus()                   # Stats du serveur

# Utilisateurs:
db.createUser({...})                # Créer un utilisateur
db.getUsers()                       # Lister les utilisateurs
db.dropUser("username")             # Supprimer un utilisateur

# Divers:
exit                                # Quitter le shell
```

Ce guide couvre absolument **toutes les fonctionnalités essentielles de MongoDB** pour un débutant, avec des explications détaillées sur le POURQUOI, COMMENT et QUAND utiliser chaque concept ! [BRAVO]