Nouveau module SQLite pour PowerShell

Tout juste revenu de #PSConfEU, et alors que je m’occupe encore des tâches d’organisation (facturation, comptabilité, retours, préparation de la prochaine édition), j’ai réussi à publier synedgy.PSSQlite !

SQLite – base de données dans un fichier unique

Vous avez sans doute déjà utilisé des bases SQLite sans vous en rendre compte : ce sont des bases petites et rapides, encapsulées dans un seul fichier, intégrées dans des applications mobiles, desktop ou parfois côté serveur.

Ne sous-estimez pas sa puissance : malgré sa taille et sa simplicité, elles sont très performantes. SQLite est parfait pour des usages comme le cache de données PowerShell Universal ou une base locale pour un module ou logiciel. Autre avantage : la simplicité du fichier unique facilite les sauvegardes et restaurations pour les équipes opérationnelles !

Limitations de SQLite

SQLite a quelques compromis. Un système de types basique (qui fait aussi sa force) :

  • INTEGER
  • REAL
  • TEXT
  • BLOB
  • ANY

Une base de données (toute) verrouillée en écriture, ce qui peut poser problème si plusieurs connexions tentent d’écrire en même temps. Toutefois, SQLite est si rapide que cela reste rare, à condition d’écrire quelques lignes à la fois.

Pour une utilisation come cache, tel qu’avec PowerShell Universal, il est conseillé de lancer des jobs périodiques en évitant les chevauchements de processus.

Le module PowerShell synedgy.PSSqlite

Pourquoi créer un nouveau module alors qu’il en existe déjà plusieurs ?

SynEdgy.PSSQlite apporte deux améliorations majeures :

  1. Utiliser Microsoft.Data.SQLite au lieu de System.Data.SQLite.
  2. Permettre des opérations CRUD simples sans écrire de SQL.

Le premier avantage profite notamment à PowerShell Universal, où la librairie SQLite est déjà chargée, évitant des conflits de modules et handles sur les DLLs du module.

Le second simplifie la vie pour les développeurs PowerShell qui ne maîtrisent pas ou n’ont pas le temps d’apprendre SQL.

La persistance SQL sans connaître SQL

Connaître SQL est utile, et je ne recommande pas d’ignorer cette technologie sous le faux prétexte que vous êtes un auteur PowerShell et non un DBA. Cela dit, je comprends tout à fait qu’il s’agit d’un obstacle de plus qui empêche de faire les choses correctement. Pour ceux qui n’ont pas les compétences ou le temps d’investir dans l’apprentissage de SQL, j’ai voulu abaisser la barre d’entrée, pour vous aider à voir la valeur…

J’ai mis un exemple minimaliste sur la façon d’utiliser synedgy.PSSqlite dans le dépôt suivant : https://github.com/SynEdgy/SqlitExample

Le dépôt SqlitExample contient un module appelé MyModule et un README.md qui explique comment l’utiliser, la première étape étant de rendre ce module disponible avec Install-Module.

Le principe du module synedgy.PSSqlite est illustré par la fonction Get-Car. L’idée sous-jacente est qu’aucun SQL n’est requis, il suffit d’appeler Get-PSSQliteRow en spécifiant le nom de la table à consulter, de passer quelques autres paramètres que nous expliquerons plus tard, et le SQL est généré dynamiquement, exécuté, et les résultats renvoyés.

function Get-Car
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [string]
        $Make,
        
        [Parameter()]
        [string]
        $Model,

        [Parameter()]
        [string]
        $Colour,

        [Parameter()]
        [int]
        $Year
    )

    $getPSSqliteRowParams = @{
        SqliteDBConfig = (Get-myModuleConfig)
        TableName = 'Cars'
        ClauseData = $PSBoundParameters
        verbose = $Verbose.IsPresent -or $VerbosePreference -in @('Continue', 'Inquire')
    }

    Get-PSSqliteRow @getPSSqliteRowParams
}

Cette fonction transmet les $PSBoundParameters (les paramètres utilisés lors de l’invocation de la commande) au paramètre ClauseData de Get-PSSqliteRow. Ainsi, si vous exécutez
Get-Car -Colour yellow -Verbose
La sortie ressemblera à ceci :

Screenshot of a PowerShell terminal displaying the execution of a query for a car with the color yellow. The output includes details such as id, make, model, colour, and year of the car.
The generated SQL shows a WHERE clause with colour equal Yellow.

Comme vous pouvez le voir, le module génère une instruction SQL avec une clause WHERE qui effectue une validation insensible à la casse pour les lignes dont la colonne Couleur correspond à la couleur jaune. L’instruction SQL générée utilise des paramètres pour réduire les risques d’injections SQL.

Des fonctions similaires existent pour toutes les opérations CRUD :

  • Get-PSSqliteRow
  • New-PSSqliteRow
  • Set-PSSqliteRow
  • Remove-PSSqliteRow

Elles ont été implémentées dans le module MyModule.psm1 pour gérer les objets Voiture dans la base de données. Aucun SQL n’est nécessaire.

Comment fonctionne la magie ?

Il n’y a pas de magie et c’est open source, donc je ne révélerai pas d’obscure astuce secrète…

Le module synedgy.PSSqlite s’attend à ce que le module appelant (dans ce cas myModule) ait le fichier yaml de définition de la base de données dans son dossier config.

Directory structure of a PowerShell module named 'myModule', showing a 'config' folder containing the PSSqlite configuration files and the main module script files, including 'myModule.psm1' and 'myModule.psd1'.
# ./config/myModule.PSSqliteConfig.yml
DatabasePath: $repository
DatabaseFile: test.db
version: 0.0.3
Schema:
  Tables:
    cars:
      columns:
        id:
          type: INTEGER
          PrimaryKey: true
          indexed: true
        make:
          type: TEXT
        model:
          type: TEXT
        colour:
          type: TEXT

        year:
          type: INTEGER

La partie schéma déclare les tables avec leur nom et leurs colonnes. Vous pouvez également ajouter des contraintes, mais c’est un autre sujet, pour une autre fois.

Ici, la table voitures a la colonne id qui est de type INTEGER et qui est la clé primaire (PRIMARY KEY). En SQLite, cela signifie qu’elle sera incrémentée automatiquement (alias ROWID). La table contient également les colonnes make, model et colour de type TEXT, ainsi que la colonne year de type INTEGER.

Avec ces informations, lorsque nous passons des clés/valeurs à ClauseData pour un SELECT (Get-) ou un DELETE (Remove-) ou à RowData pour un INSERT (New-) ou un UPDATE (Set-), lorsque la clé correspond au nom de la colonne, nous pouvons construire la requête SQL.

Mais puisque nous avons tous les détails de la table dans le schéma, ne pouvons-nous pas générer la base de données ? Absolument !

Générer des instructions CREATE

Puisque nous avons la plupart des informations nécessaires au schéma dans le fichier de configuration, synedgy.PSSQlite peut générer les instructions CREATE à partir d’une configuration, et initialiser votre base de données SQLite.

Screenshot of a PowerShell terminal showing the import of the synedgy.PSSqlite PowerShell module, along with commands to get database configuration and generate a CREATE TABLE statement for a SQLite database.

Mais ce n’est pas quelque chose que vous ferez souvent, vous voudrez généralement initialiser la base de données vous-même avec Initialize-PSSqliteDatabase.

PowerShell console output showing commands for initializing and querying a SQLite database using synedgy.PSSqlite module.

Comme vous pouvez le voir dans la capture d’écran ci-dessus, nous avons créé le fichier de base de données et exécuté le schéma sur la base de données, et la table cars est maintenant disponible dans cette base de données.
Il y a quelques autres choses qui se passent, comme cette table _metadata que nous avons ajoutée, mais nous y reviendrons plus tard…

Invoquer sa propre instruction SQL

Comme vous l’avez vu avec la commande précédente, ce n’est pas parce que le module offre un CRUD simple sans utiliser SQL que nous interdisons de l’invoquer directement. La commande Invoke-PSSqliteQuery vous permet justement de le faire, et elle est utile lorsque vous voulez personnaliser un peu plus l’instruction SQL que vous voulez exécuter.

# Loading the configuration
$dbconfig = Get-PSSqliteDBConfig -Path .\myModule\config\myModule.PSSqliteConfig.yml
# Creating the connection
$conn = New-PSSqliteConnection -ConnectionString $dbconfig.ConnectionString
# Invoking a custom SQL with Parameters
Invoke-PSSqliteQuery -SqliteConnection $conn -CommandText 'SELECT * FROM cars WHERE colour LIKE @colour' -Parameters @{colour = 'Yel%'}

<# assuming we had populated the DB
id     : 2
make   : Toyota
model  : Corolla
colour : Yellow
year   : 2024
#>

Utilisation de synedgy.PSSqlite dans votre propre module

L’objectif est de permettre à votre module de disposer d’une méthode cohérente pour persister et récupérer des objets dans une base de données. Lorsque vous concevez votre module, il y a quelques astuces qui peuvent vous faciliter le travail :

  • Stocker l’objet de configuration dans une variable à l’échelle du module
  • Avoir un getter pour l’objet de configuration pour obtenir de cette variable
  • Gérer votre connexion et utiliser -KeepAlive de manière adéquate.

L’objet de configuration

Vous verrez que les fonctions New, Get, Set, Remove-PSSqliteRow nécessitent la transmission de la configuration de la base de données. Au lieu de créer l’objet à partir du fichier YAML à chaque fois, vous pouvez faire ceci :

function Get-myModuleConfig
{
    [CmdletBinding()]
    param
    (
        [Parameter(DontShow)]
        [string]
        $ConfigFile,

        [Parameter()]
        [switch]
        $Force
    )

    if  ($null -eq $script:MyModuleDBConfig -or $Force)
    {
        if ([string]::IsNullOrEmpty($ConfigFile))
        {
            # Retrieve configuration from the current's module config subdirectory
            $ConfigFile = Get-PSSqliteDBConfigFile
        }
        else
        {
            Write-Verbose -Message ('Loading configuration from {0}' -f $ConfigFile)
        }

        $script:MyModuleDBConfig = Get-PSSqliteDBConfig -ConfigFile $ConfigFile

    }

    return $script:MyModuleDBConfig
}

Chaque fois que vous aurez besoin de l’objet de configuration, il sera récupéré à partir d’un emplacement mis en cache (votre variable de portée du module). Sauf s’il n’est pas déjà défini, auquel cas il sera récupéré à la volée et mis en cache.

Par défaut, Get-PSSqliteDBConfigFile va chercher dans le dossier de configuration du module de l’appelant un fichier qui ressemble à ./SQLiteConfig.yml et l’utilise pour créer l’objet de configuration.

Maintenant, à chaque fois que l’objet de configuration est récupéré, il vous suffit d’appeler votre fonction getter, et après un chargement initial depuis Yaml, il sera lu depuis la mémoire.

Gestion des connexions SQL

Chaque fois qu’une instruction SQL est exécutée via une connexion, le module tente de fermer la connexion par défaut. Cela évite de laisser une poignée ouverte sur le fichier de la base de données, ce qui bloque la manipulation du fichier.

Chaque fonction *-SqliteRow accepte un paramètre -SqlConnection, ce qui vous permet de passer une connexion existante. Si vous utilisez également le paramètre -KeepAlive, la connexion ne sera pas fermée à chaque fois pour éviter la surcharge.

# load the config objecty
$dbconfig = Get-PSSqliteDBConfig -Path .\myModule\config\myModule.PSSqliteConfig.yml
# create the connection object
$conn = New-PSSqliteConnection -ConnectionString $dbconfig.ConnectionString
# Insert into the table cars, keepingthe connection opened
$null = New-PSSqliteRow -SqliteDBConfig $dbconfig -SqliteConnection $conn -TableName 'cars' -RowData @{make = 'Renault'; model = 'R5 e-tech'; year = 2025; colour = 'yellow'} -KeepAlive
<# look at the $conn object
$conn
Handle            : SQLitePCL.sqlite3
ConnectionString  : Data Source=C:\dev\SqlitExample\test.db;
Database          : main
DataSource        : C:\dev\SqlitExample\test.db
DefaultTimeout    : 30
ServerVersion     : 3.46.1
State             : Open
ConnectionTimeout : 15
CanCreateBatch    : False
Site              :
Container         :
#>

# Get the rows, without keeping the connection opened
Get-PSSqliteRow -SqliteConnection $conn -TableName 'cars' -SqliteDBConfig $dbconfig | FT -a

id make    model     colour year
-- ----    -----     ------ ----
 1 Toyota  Corolla   Blue   2020
 2 Toyota  Corolla   Yellow 2024
 3 Renault R5 e-tech yellow 2025
 4 Renault R5 e-tech yellow 2025
 5 Renault R5 e-tech yellow 2025
 
# Checking on the connection
$conn.state
# Closed

Et ensuite ?

Bien que ces fonctionnalités vous permettront probablement d’aller plus loin dans la plupart des cas, il y a quelques idées que j’aimerais mettre en oeuvre :

  • Supporter l’insertion en masse (Bulk insert) afin de pouvoir insérer rapidement de nombreuses lignes en une seule transaction (ou revenir en arrière)
  • Exporter un schéma de base de données existant vers un fichier yaml
  • Avoir un schéma JSON pour la validation du fichier de configuration et l’intellisense

Mais je sais que la priorité est maintenant d’écrire plus de tests et de découvrir les cas limites que les utilisateurs pourraient rencontrer.

Si vous rencontrez des problèmes ou pensez à des améliorations, n’hésitez pas à les partager dans les problèmes sur le repo GitHub : https://github.com/SynEdgy/synedgy.pssqlite


Discover more from SynEdgy

Subscribe to get the latest posts sent to your email.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from SynEdgy

Subscribe now to keep reading and get access to the full archive.

Continue reading

Discover more from SynEdgy

Subscribe now to keep reading and get access to the full archive.

Continue reading