> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-mintlify-8c05c8a2.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Les moteurs de table `Executable` et `ExecutablePool` vous permettent de définir une table dont les lignes sont générées à partir d’un script que vous définissez (en écrivant des lignes sur **stdout**).

# Moteurs de table Executable et ExecutablePool

Les moteurs de table `Executable` et `ExecutablePool` vous permettent de définir une table dont les lignes sont générées à partir d’un script que vous définissez (en écrivant des lignes sur **stdout**). Le script exécutable est stocké dans le répertoire `user_scripts` et peut lire des données à partir de n’importe quelle source.

* Tables `Executable` : le script est exécuté à chaque requête
* Tables `ExecutablePool` : maintiennent un pool de processus persistants et y puisent des processus pour les lectures

Vous pouvez, si vous le souhaitez, inclure une ou plusieurs requêtes d’entrée qui envoient leurs résultats vers **stdin** pour que le script puisse les lire.

<div id="creating-an-executable-table">
  ## Création d'une table `Executable`
</div>

Le moteur de table `Executable` nécessite deux paramètres : le nom du script et le format des données entrantes. Vous pouvez également transmettre une ou plusieurs requêtes d'entrée :

```sql theme={null}
Executable(script_name, format, [input_query...])
```

Voici les paramètres pertinents pour une table `Executable` :

* `send_chunk_header`
  * Description : envoie le nombre de lignes dans chaque chunk avant d’envoyer un chunk à traiter. Ce paramètre peut vous aider à écrire votre script plus efficacement en préallouant certaines ressources
  * Valeur par défaut : false
* `command_termination_timeout`
  * Description : délai d’expiration de fin de commande, en secondes
  * Valeur par défaut : 10
* `command_read_timeout`
  * Description : délai d’expiration pour la lecture des données depuis le stdout de la commande, en millisecondes
  * Valeur par défaut : 10000
* `command_write_timeout`
  * Description : délai d’expiration pour l’écriture des données vers le stdin de la commande, en millisecondes
  * Valeur par défaut : 10000

Prenons un exemple. Le script Python suivant s’appelle `my_script.py` et est enregistré dans le dossier `user_scripts`. Il lit un nombre `i` et affiche `i` chaînes aléatoires, chacune précédée d’un nombre séparé par une tabulation :

```python theme={null}
#!/usr/bin/python3

import sys
import string
import random

def main():

    # Read input value
    for number in sys.stdin:
        i = int(number)

        # Generate some random rows
        for id in range(0, i):
            letters = string.ascii_letters
            random_string =  ''.join(random.choices(letters ,k=10))
            print(str(id) + '\t' + random_string + '\n', end='')

        # Flush results to stdout
        sys.stdout.flush()

if __name__ == "__main__":
    main()
```

La table `my_executable_table` ci-dessous est construite à partir de la sortie de `my_script.py`, qui génère 10 chaînes aléatoires chaque fois que vous exécutez un `SELECT` sur `my_executable_table` :

```sql theme={null}
CREATE TABLE my_executable_table (
   x UInt32,
   y String
)
ENGINE = Executable('my_script.py', TabSeparated, (SELECT 10))
```

La création de la table est immédiate et n'invoque pas le script. Lorsque vous interrogez `my_executable_table`, le script est invoqué :

```sql theme={null}
SELECT * FROM my_executable_table
```

```response theme={null}
┌─x─┬─y──────────┐
│ 0 │ BsnKBsNGNH │
│ 1 │ mgHfBCUrWM │
│ 2 │ iDQAVhlygr │
│ 3 │ uNGwDuXyCk │
│ 4 │ GcFdQWvoLB │
│ 5 │ UkciuuOTVO │
│ 6 │ HoKeCdHkbs │
│ 7 │ xRvySxqAcR │
│ 8 │ LKbXPHpyDI │
│ 9 │ zxogHTzEVV │
└───┴────────────┘
```

<div id="passing-query-results-to-a-script">
  ## Transmettre les résultats d’une requête à un script
</div>

Les utilisateurs du site Hacker News laissent des commentaires. Python propose une boîte à outils de traitement automatique du langage naturel (`nltk`) avec un `SentimentIntensityAnalyzer` permettant de déterminer si les commentaires sont positifs, négatifs ou neutres, notamment en leur attribuant une valeur comprise entre -1 (un commentaire très négatif) et 1 (un commentaire très positif). Créons une table `Executable` qui calcule le sentiment des commentaires Hacker News à l’aide de `nltk`.

Cet exemple utilise la table `hackernews` décrite [ici](/fr/reference/engines/table-engines/mergetree-family/textindexes#hacker-news-dataset). La table `hackernews` comprend une colonne `id` de type `UInt64` et une colonne `comment` de type `String`. Commençons par définir la table `Executable` :

```sql theme={null}
CREATE TABLE sentiment (
   id UInt64,
   sentiment Float32
)
ENGINE = Executable(
    'sentiment.py',
    TabSeparated,
    (SELECT id, comment FROM hackernews WHERE id > 0 AND comment != '' LIMIT 20)
);
```

Quelques remarques sur la table `sentiment` :

* Le fichier `sentiment.py` est placé dans le dossier `user_scripts` (le dossier par défaut du paramètre `user_scripts_path`)
* Le format `TabSeparated` signifie que notre script Python doit générer des lignes de données brutes contenant des valeurs séparées par des tabulations
* La requête sélectionne deux colonnes de `hackernews`. Le script Python devra extraire les valeurs de ces colonnes des lignes entrantes

Voici la définition de `sentiment.py` :

```python theme={null}
#!/usr/local/bin/python3.9

import sys
import nltk
from nltk.sentiment import SentimentIntensityAnalyzer

def main():
    sentiment_analyzer = SentimentIntensityAnalyzer()

    while True:
        try:
            row = sys.stdin.readline()
            if row == '':
                break

            split_line = row.split("\t")

            id = str(split_line[0])
            comment = split_line[1]

            score = sentiment_analyzer.polarity_scores(comment)['compound']
            print(id + '\t' + str(score) + '\n', end='')
            sys.stdout.flush()
        except BaseException as x:
            break

if __name__ == "__main__":
    main()
```

Quelques remarques sur notre script Python :

* Pour que cela fonctionne, vous devrez exécuter `nltk.downloader.download('vader_lexicon')`. Nous aurions pu l’inclure dans le script, mais il serait alors téléchargé chaque fois qu’une requête serait exécutée sur la table `sentiment`, ce qui n’est pas efficace
* Chaque valeur de `row` correspond à une ligne du result set de `SELECT id, comment FROM hackernews WHERE id > 0 AND comment != '' LIMIT 20`
* La ligne reçue est séparée par des tabulations ; nous extrayons donc `id` et `comment` à l’aide de la fonction Python `split`
* Le résultat de `polarity_scores` est un objet JSON contenant quelques values. Nous avons choisi de récupérer simplement la value `compound` de cet objet JSON
* N’oubliez pas que la table `sentiment` dans ClickHouse utilise le format `TabSeparated` et contient deux colonnes ; notre fonction `print` sépare donc ces colonnes par une tabulation

Chaque fois que vous écrivez une requête qui sélectionne des lignes dans la table `sentiment`, la requête `SELECT id, comment FROM hackernews WHERE id > 0 AND comment != '' LIMIT 20` est exécutée et son résultat est transmis à `sentiment.py`. Testons cela :

```sql theme={null}
SELECT *
FROM sentiment
```

La réponse se présente ainsi :

```response theme={null}
┌───────id─┬─sentiment─┐
│  7398199 │    0.4404 │
│ 21640317 │    0.1779 │
│ 21462000 │         0 │
│ 25168863 │         0 │
│ 25168978 │   -0.1531 │
│ 25169359 │         0 │
│ 25169394 │   -0.9231 │
│ 25169766 │    0.4137 │
│ 25172570 │    0.7469 │
│ 25173687 │    0.6249 │
│ 28291534 │         0 │
│ 28291669 │   -0.4767 │
│ 28291731 │         0 │
│ 28291949 │   -0.4767 │
│ 28292004 │    0.3612 │
│ 28292050 │    -0.296 │
│ 28292322 │         0 │
│ 28295172 │    0.7717 │
│ 28295288 │    0.4404 │
│ 21465723 │   -0.6956 │
└──────────┴───────────┘
```

<div id="creating-an-executablepool-table">
  ## Création d’une table `ExecutablePool`
</div>

La syntaxe de `ExecutablePool` est similaire à celle de `Executable`, mais quelques paramètres propres à une table `ExecutablePool` sont à noter :

* `pool_size`
  * Description : Taille du pool de processus. Si cette taille est de 0, aucune limite de taille n’est appliquée
  * Valeur par défaut : 16
* `max_command_execution_time`
  * Description : Durée maximale d’exécution de la commande, en secondes
  * Valeur par défaut : 10

Nous pouvons facilement convertir la table `sentiment` ci-dessus pour qu’elle utilise `ExecutablePool` au lieu de `Executable` :

```sql theme={null}
CREATE TABLE sentiment_pooled (
   id UInt64,
   sentiment Float32
)
ENGINE = ExecutablePool(
    'sentiment.py',
    TabSeparated,
    (SELECT id, comment FROM hackernews WHERE id > 0 AND comment != '' LIMIT 20000)
)
SETTINGS
    pool_size = 4;
```

ClickHouse maintiendra 4 processus à la demande lorsque votre client interrogera la table `sentiment_pooled`.
