Storage

  • Raccourci de la révision : Storage
  • Titre de la révision : Storage
  • ID de la révision : 116234
  • Créé :
  • Créateur : Chbok
  • Version actuelle ? Non
  • Commentaire /* How to corrupt your database */ suite de la traduction

Contenu de la révision

{{template.Traduction_en_cours("Storage")}}

Storage est une API de base de données dans Firefox 2 et suivante pilotée par sqlite. Elle est disponible aux appels avec permission, tels que du code chrome et des extensions, mais pas aux pages Web. Son statut actuel est "en développement", ce qui signifie que les API peuvent être modifiées à n'importe quel moment. Il se peut en effet que l'API soit légèrement modifiée entre Firefox 2 alpha 2 et Firefox 2, et également entre Firefox 2 et Firefox 3.

Storage peut parfois être confondu avec la fonctionnalité WHATWG DOM storage de Firefox 2 qui permet à des pages Web d'enregistrer des données permanentes. L'API Storage s'adresse uniquement aux auteurs d'extensions et aux composants de Firefox.

Ce document traite l'API mozStorage et quelques particularités de sqlite. Il ne traite pas du SQL ou de l'utilisation de sqlite. Pour ces autres informations, vous devrez consulter vos références favorites sur SQL. Vous pouvez également consulter la documentation sur sqlite et particulièrement celle sur la compréhension du langage de requêtes sur sqlite. Pour obtenir plus d'aide sur l'API mozStorage, vous pouvez poster sur le serveur de news mozilla.dev.apps.firefox sur news.mozilla.org. Pour signaler des bogues, utilisez Bugzilla (product "Toolkit", component "Storage").

Consultez Storage:Performance pour optimiser les performances de connexion de votre base de données.

SQLite Database Browser est un outil libre, disponible sur plusieurs plateformes, permettant l'examen de bases de données existantes et le test des états SQL.

Préambule

mozStorage se présente comme n'importe quelle autres systèmes de bases de données. La procédure complète d'utilisation est la suivante :

  • Ouverture d'une connexion vers la base de données de votre choix.
  • Création d'une requête d'exécution de la connexion.
  • Liaison de paramètres à la requête si nécessaire.
  • Exécution de la requête.
  • Réinitialisation de la requête.

Ouverture d'une connexion

La première initialisation du service Storage doit se faire dans le processus d'exécution principal. Vous obtiendrez une erreur en voulant l'intialiser dans un autre processus. Vous pouvez toutefois utiliser le service depuis un processus en appelant la méthode getService du processus principal pour vérifier que le service a bien été créé.

Voici un exemple C++ d'ouverture d'une connexion vers "asdl.sqlite" dans le répertoire du profil de l'utilisateur :

nsCOMPtr<nsIFile> dbFile;
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                            getter_AddRefs(dbFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = dbFile->Append(NS_LITERAL_STRING("asdf.sqlite"));
NS_ENSURE_SUCCESS(rv, rv);

mDBService = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBService->OpenDatabase(dbFile, getter_AddRefs(mDBConn));
NS_ENSURE_SUCCESS(rv, rv);

MOZ_STORAGE_SERVICE_CONTRACTID est défini dans {{template.Source("storage/build/mozStorageCID.h")}}. Sa valeur est "@mozilla.org/storage/service;1".

Voici un exemple JavaScript :

var file = Components.classes["@mozilla.org/file/directory_service;1"]
                     .getService(Components.interfaces.nsIProperties)
                     .get("ProfD", Components.interfaces.nsIFile);
file.append("asdf.sqlite");

var storageService = Components.classes["@mozilla.org/storage/service;1"]
                        .getService(Components.interfaces.mozIStorageService);
var mDBConn = storageService.openDatabase(file);
Note : la fonction openDatabase risque d'être modifiée. Elle sera améliorée et simplifiée pour réduire les difficultés d'utilisation.

Il est déconseillé de nommer votre base de données avec une extension en ".sdb" pour sqlite database. En effet, Windows reconnaît cette extension comme une "base de données de compatibilité des applications" et les modifications sont inscrites dans la fonctionnalité de restauration système.

Création d'une requête

Il existe deux méthodes pour créer une requête. Si vous n'avez aucun paramètre et si la requête ne renvoie aucune valeur, utilisez mozIStorageConnection.executeSimpleSQL.

C++:
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE foo (a INTEGER)"));

JS:
mDBConn.executeSimpleSQL("CREATE TABLE foo (a INTEGER)");

Autrement, vous devrez préparer une requête en utilisant mozIStorageConnection.createStatement :

C++:
nsCOMPtr<mozIStorageStatement> statement;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM foo WHERE a = ?1"),
                              getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);

JS:
var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1");

Cet exemple utilise un sélecteur "?1" comme paramètre qui sera renseigné ultérieurement (voir le chapitre suivant).

Après avoir préparé une requête, vous pouvez lui lier des paramètres, l'exécuter et la réinitialiser autant de fois que vous le souhaitez. Si vous devez faire une requête fréquemment, l'utilisation d'une requête précompilée augmentera les performances de manière significative car la requête SQL n'aura pas à être traitée à chaque fois.

Si vous êtes familier avec sqlite, vous devez savoir que les requêtes préparées deviennent invalides lorsque la structure de la base de données est modifiée. Heureusement, mozIStorageStatement détecte l'erreur et recompile la requête si nécessaire. Ainsi, après avoir créer une requête, vous n'avez pas à vous soucier d'une modification de structure ; toutes les requêtes continueront à fonctionner de manière transparente.

Liaison de paramètres

Il est généralement préférable de lier tous les paramètres séparément plutôt que d'essayer de construire à la volée des chaînes SQL contenant ces paramètres. En plus d'autres aspects, ce mode de fonctionnement permet d'éviter une attaque par injection SQL puisque les paramètres liés ne sont jamais exécutés en SQL.

Les sélecteurs inclus dans la requête sont liés aux paramètres. Les sélecteurs sont indexés, en commençant par "?1", puis "?2"... Vous devez utiliser les fonctions BindXXXParameter(0) BindXXXParameter(1)... pour lier ces sélecteurs.

Attention : Les indices des sélecteurs débutent à partir de 1. Les entiers passés aux fonctions de liaison débutent à partir de 0. Cela signifie que "?1" correspond au paramètre 0, "?2" correspond au paramètre 1, etc.

Un sélecteur peut apparaître plusieurs fois dans la chaîne SQL et tous les instances seront remplacées par la valeur liée. Les paramètres non liés seront interprétés comme NULL.

Les fonctions de liaison disponible dans mozIStorageStatement (voir {{template.Source("storage/public/mozIStorageStatement.idl")}}) sont :

  • bindUTF8StringParameter(in unsigned long aParamIndex, in AUTF8String aValue)
  • bindStringParameter(in unsigned long aParamIndex, in AString aValue)
  • bindDoubleParameter(in unsigned long aParamIndex, in double aValue)
  • bindInt32Parameter(in unsigned long aParamIndex, in long aValue)
  • bindInt64Parameter(in unsigned long aParamIndex, in long long aValue)
  • bindNullParameter(in unsigned long aParamIndex)
  • bindBlobParameter(in unsigned long aParamIndex, {{mediawiki.external('array,const,size_is(aValueSize)')}} in octet aValue, in unsigned long ValueSize) (pour des données binaires)

Exemple C++ :

nsCOMPtr<mozIStorageStatement> statement;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM foo WHERE a = ?1 AND b > ?2"),
                              getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindUTF8StringParameter(0, "bonjour"); // "bonjour" sera substitué à "?1"
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt32Parameter(1, 1234); // 1234 sera substitué à "?2"
NS_ENSURE_SUCCESS(rv, rv);

Exemple Javascript :

var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1 AND b > ?2");
statement.bindUTF8StringParameter(0, "bonjour");
statement.bindInt32Parameter(1, 1234);

Exécution d'une requête

La principale manière d'exécuter une requête se fait avec la fonction mozIStorageStatement.executeStep. Cette fonction vous permet de récupérer chaque ligne produite par la requête en vous notifiant lorsqu'il n'y a plus de résultats.

Après un appel de executeStep, vous récupérez les données par une des fonctions de mozIStorageValueArray (voir {{template.Source("storage/public/mozIStorageValueArray.idl")}}). mozIStorageStatement implémente mozIStorageValueArray. Ces fonctions sont :

  • long getInt32(in unsigned long aIndex);
  • long long getInt64(in unsigned long aIndex);
  • double getDouble(in unsigned long aIndex);
  • AUTF8String getUTF8String(in unsigned long aIndex);
  • AString getString(in unsigned long aIndex);
  • void getBlob(in unsigned long aIndex, out unsigned long aDataSize, {{mediawiki.external('array,size_is(aDataSize)')}} out octet aData); Attention : la donnée sera NULL si dataSize est à 0.
  • boolean getIsNull(in unsigned long aIndex); Retourne true si la cellule est NULL (ce qui différent pour une chaîne vide).

Vous pouvez obtenir le type de la valeur d'une colonne spécifiée avec mozIStorageValueArray.getTypeOfIndex. Soyez prudent, sqlite n'est pas une base de données typée. N'importe quel type de données peut être placé dans une cellule, indépendamment du type de la colonne. Si vous lisez une donnée d'un type différent, sqlite fera de son mieux pour la convertir, et vous proposera une valeur par défaut si c'est impossible. De ce fait, il n'est pas possible d'obtenir les erreurs de typage ce qui peut engendrer des résultats surprenants.

Les codes C++ peuvent également utiliser des fonctions AsInt32, AsDouble, etc. pour adapter la valeur retournée à un type C++. Prenez garde toutefois car aucune erreur ne vous sera signalée si votre index est invalide. D'autres erreurs sont impossibles car sqlite convertira toujours les types, même si cela n'a aucun sens.

Exemple C++ :

PRBool hasMoreData;
while (NS_SUCCEEDED(statement->ExecuteStep(&hasMoreData)) && hasMoreData) {
  PRInt32 value = statement->AsInt32(0);
  // utilisez la valeur...
}

Exemple Javascript :

while (statement.executeStep()) {
  var value = statement.getInt32(0);
  // utilisez la valeur...
}

mozIStorageStatement.execute() est une fonction pratique lorsque votre requête n'a pas besoin de retourner de valeurs. Elle effectue la requête en une seule étape et se réinitialise. Elle sert surtout pour des requêtes d'insertion en simplifiant le code :

var statement = mDBConn.createStatement("INSERT INTO my_table VALUES (?1)");
statement.bindInt32Parameter(52);
statement.execute();

Réinitialisation d'une requête

Il est important de réinitialiser les requêtes qui ne servent plus. Une requête d'écriture non réinitialisée laissera un vérou sur les tables et interdira à d'autres requêtes d'y accéder. Une requête de lecture non réinitialisée interdira toute écriture.

Lorsque l'objet requête est libéré, la base de données à laquelle il était lié est fermée. Si vous utilisez C++ en sachant que toutes les références seront détruites, vous n'avez pas à réinitialiser explicitement la requête. De même, avec l'appel de la fonction mozIStorageStatement.execute(), il est inutile de réinitialiser la requête ; cette fonction le fera pour vous. Dans les autres cas, appelez mozIStorageStatement.reset().

En JavaScript, toutes les requêtes doivent être réinitialisées. Soyez prudent au sujet des exceptions. Assurez vous que vos requêtes soient réinitialisées même si une exception est déclenchée, sinon l'accès à la base de données ne sera plus possible. La réinitialisation d'une requête est une opération légère sans conséquence, donc n'hésitez pas à effectuer même des réinitialisations superflues.

var statement = connection.createStatement(...);
try {
  // utilisez la requête...
} finally {
  statement.reset();
}

Les scripts C++ doivent faire de même. L'objet de contexte mozStorageStatementScoper dans {{template.Source("storage/public/mozStorageHelper.h")}} s'assurera qu'une requête donnée est réinitialisée lorsque le contexte est quitté. Il est fortement recommandé d'utiliser cet objet.

void someClass::someFunction()
{
  mozStorageStatementScoper scoper(mStatement)
  // utilisez la requête...
}

Transactions

mozIStorageConnection dispose de fonctions pour débuter et clore des transactions. Même si vous n'utilisez pas explicitement les transactions, une transaction implicite sera créée pour chacune de vos requêtes. Ceci a des implications majeures en terme de performance. Chaque transaction, et spécialement les validations, occasionne un délai supplémentaire. Les performances seront meilleures si vous placez plusieurs requêtes dans une même transaction. Consultez Storage:Performance pour plus d'informations sur les performances.

La différence principale avec d'autres systèmes de base de données est que sqlite ne supporte pas les transactions imbriquées. C'est-à-dire qu'une fois une transaction ouverte, vous ne pouvez pas en ouvrir une autre. Vous pouvez vérifier si une transaction est en cours de traitement grâce à mozIStorageConnection.transactionInProgress.

Vous pouvez également exécuter les commandes SQL "BEGIN TRANSACTION" et "END TRANSACTION" directement (c'est ce que fait la connexion avec l'appel des fonctions). Cependant, il est fortement recommandé d'utiliser mozIStorageConnection.beginTransaction et des fonctions associées parce qu'elles mémorisent l'état de la transaction dans la connexion. Dans le cas contraire, l'attribut transactionInProgress aura une valeur erronée.

sqlite comprend différents types de transactions :

  • mozIStorageConnection.TRANSACTION_DEFERRED: par défaut. Le verrou sur la base de données est obtenu lorsque c'est nécessaire (normalement la première fois où vous exécutez une requête dans la transaction).
  • mozIStorageConnection.TRANSACTION_IMMEDIATE: Vérouillage immédiat en lecture de la base de données.
  • mozIStorageConnection.TRANSACTION_EXCLUSIVE: Vérouillage immédiat en écriture de la base de données.

Vous pouvez définir le type de la transaction en le transmettant par mozIStorageConnection.beginTransactionAs. Gardez en tête que si une autre transaction a déjà démarré, cette opération échouera. Habituellement, le type par défaut TRANSACTION_DEFERRED suffit, et à moins de savoir exactement ce que vous faites, vous n'aurez pas besoin des autres types. Pour plus d'informations, consultez la document sqlite sur BEGIN TRANSACTION et le vérouillage.

var ourTransaction = false;
if (mDBConn.transactionInProgress) {
  ourTransaction = true;
  mDBConn.beginTransactionAs(mDBConn.TRANSACTION_DEFERRED);
}

// ... utilisez la connexion ...

if (ourTransaction)
  mDBConn.commitTransaction();

Dans un code C++, vous pouvez utiliser la classe helper mozStorageTransaction définie dans {{template.Source("storage/public/mozStorageHelper.h")}}. Cette classe démarrera une transaction du type donné sur la connexion spécifiée lorsqu'elle rentre dans le contexte d'exécution, et fera une validation ou une annulation de la transaction lorsqu'elle sort du contexte. Si une autre transaction est en cours, la classe helper de transaction n'effectuera aucune action.

Elle dispose également de fonctions pour réaliser explicitement des validations. L'utilisation classique est de définir en premier une classe d'annulation par défaut, et ensuite valider explicitement la transaction si le processus a réussi :

nsresult someFunction()
{
  // Définir (par défaut) la transaction avec une annulation en cas d'échec
  mozStorageTransaction transaction(mDBConn, PR_FALSE);

  // ... utilisez la connexion ...

  // tout s'est bien passé, alors validation explicite
  return transaction.Commit();
}

Comment corrompre votre base de données

  • Ouvrez plus d'une connexion vers le même fichier dont le nom n'est pas déterminé strictement identique par un strcmp, comme par exemple "my.db" et "../dir/my.db" ou sous Windows (insensible à la casse) "my.db" et "My.db". Sqlite essaiera de traiter chacun de ces cas, mais vous ne devriez pas compter là dessus.
  • Accédez à une base de données depuis un lien symbolique ou physique.
  • Ouvrez des connexions vers la même base de données depuis plus d'un processus (voir "Processus surs" ci-dessous).
  • Accédez à une connexion ou une requête depuis plus d'un processus (voir "Processus surs" ci-dessous).
  • Ouvrez la base de données depuis un programme externe pendant qu'elle est ouverte dans Mozilla. Le cache de Mozilla corrompt le fichier verrou normal dans sqlite pour lui permettre de travailler en sécurité.

Thread safety

The mozStorage service and sqlite are threadsafe. However, no other mozStorage or sqlite objects or operations are threadsafe.

  • The storage service must be created on the main thread. If you want to access the service from another thread, you should be sure that you call getService from the main thread ahead of time.
  • You can not access a connection or statement from multiple threads. These storage objects are not threadsafe, and the sqlite representations of them are not threadsafe either. Even if you do locking and ensure that only one thread is doing something at once, there may be problems. This case hasn't been tested, and there may be some internal per-thread state in sqlite. It is strongly advised that you don't do this.
  • You can not access a single database from multiple connections from different threads. Normally, sqlite allows this. However, we do sqlite3_enable_shared_cache(1); (see sqlite shared-cache mode) which makes multiple connections share the same cache. This is important for performance. However, there is no lock for cache access, meaning it will break if you use if from more than one thread.

SQLite Locking

SQLite locks the entire database; that is, any active readers will cause an attempt to write to return SQLITE_BUSY, and an active writer will cause any attempt to read to return SQLITE_BUSY. A statement is considered active from the first step() until reset() is called. execute() calls step() and reset() in one go. A common problem is forgetting to reset() a statement after you've finished step()'ing through.

While a given SQLite connection is capable of having multiple statements open, its locking model limits what these statements can do concurrently (reading or writing). It is in fact possible for multiple statements to be actively reading at one time. It is not possible, however, for multiple statements to be reading and writing at one time on the same table -- even if they are derived from the same connection.

SQLite has a two-tiered locking model: connection level and table level. Most people are familiar with the connection (database) level locking: multiple readers but only one writer. The table-level (B-Tree) locks are what can sometimes be confusing. (Internally, each table in the database has its own B-Tree, so "table" and "B-Tree" are technically synonymous).

Table-level locks

You would think that if you have only one connection, and it locks the database for writing, you could use multiple statements to do whatever you want. Not entirely. You must be aware of table-level (B-Tree) locks, which are maintined by statement handles traversing the database (i.e. open SELECT statements).

The general rule is this: a statement handle may not modify a table (B-Tree) which other statement handles are reading (have open cursors on) -- even if that statement handle shares the same connection (transaction context, database lock, etc.) with the other statement handles. Attempts to do so will still block (or return SQLITE_BUSY).

This problem often crops up when you attempt to iterate over a table with one statement and modify records within it using another statement. This will not work (or carries a high probability of not working, depending on the optimizer's involvement (see below)). The modifying statement will block because the reading statement has an open cursor on the table.

Working around locking problems

The solution is to follow (1) as described above. Theoretically, (2) actually shouldn't work with SQLite 3.x. In this scenario, database locks come into play (with multiple connections) in addition to table locks. Connection 2 (modifying connection) will not be able to modify (write to) the database while the Connection 1 (reading connection) is reading it. Connection 2 will require an exclusive lock to execute a modifying SQL command, which it cannot get as long as Connection 1 has active statements reading the database (Connection 1 has a shared read lock during this time which prohibits any other connections from getting an exclusive lock).

Another option is to use a temporary table. Create a temporary table containing the results of the table of interest, iterate over it (putting the reading statement's table lock on the temp table) and then the modifing statement can make changes to the real table without any problem. This can be done with statements derived from a single connection (transaction context). This scenario sometimes happens behind the scenes anyway as ORDER BY can produce temporary tables internally. However, it is not safe to assume that the optimizer will do this in all cases. Explicitly creating a temporary table is only safe way to do perform this latter option.

{{ wiki.languages( { "en": "en/Storage", "es": "es/Almacenamiento", "pl": "pl/Storage" } ) }}

Source de la révision

<p>{{template.Traduction_en_cours("Storage")}}

</p><p>Storage est une API de base de données dans <a href="fr/Firefox_2">Firefox 2</a> et suivante pilotée par <a class="external" href="http://www.sqlite.org/">sqlite</a>. Elle est disponible aux appels avec permission, tels que du code <a href="fr/Chrome">chrome</a> et des <a href="fr/Extensions">extensions</a>, mais <i>pas</i> aux pages Web. Son statut actuel est "en développement", ce qui signifie que les API peuvent être modifiées à n'importe quel moment. Il se peut en effet que l'API soit légèrement modifiée entre Firefox 2 alpha 2 et Firefox 2, et également entre Firefox 2 et Firefox 3.
</p><p>Storage peut parfois être confondu avec la fonctionnalité <a class="external" href="http://www.whatwg.org/specs/web-apps/current-work/#scs-client-side">WHATWG DOM storage</a> de Firefox 2 qui permet à des pages Web d'enregistrer des données permanentes. L'API Storage s'adresse uniquement aux auteurs d'extensions et aux composants de Firefox.
</p><p>Ce document traite l'API mozStorage et quelques particularités de sqlite. Il <i>ne</i> traite <i>pas</i> du SQL ou de l'utilisation de sqlite. Pour ces autres informations, vous devrez consulter vos références favorites sur SQL. Vous pouvez également consulter <a class="external" href="http://www.sqlite.org/docs.html">la documentation sur sqlite</a> et particulièrement celle sur <a class="external" href="http://www.sqlite.org/lang.html">la compréhension du langage de requêtes sur sqlite</a>. Pour obtenir plus d'aide sur l'API mozStorage, vous pouvez poster sur le serveur de news mozilla.dev.apps.firefox sur news.mozilla.org. Pour signaler des bogues, utilisez <a class="external" href="https://bugzilla.mozilla.org/enter_bug.cgi?product=Toolkit&amp;component=Storage">Bugzilla</a> (product "Toolkit", component "Storage").
</p><p>Consultez <a href="fr/Storage/Performance">Storage:Performance</a> pour optimiser les performances de connexion de votre base de données.
</p><p><a class="external" href="http://sqlitebrowser.sourceforge.net/">SQLite Database Browser</a> est un outil libre, disponible sur plusieurs plateformes, permettant l'examen de bases de données existantes et le test des états SQL.
</p>
<h4 name="Pr.C3.A9ambule"> Préambule </h4>
<p>mozStorage se présente comme n'importe quelle autres systèmes de bases de données. La procédure complète d'utilisation est la suivante :
</p>
<ul><li> Ouverture d'une connexion vers la base de données de votre choix.
</li><li> Création d'une requête d'exécution de la connexion.
</li><li> Liaison de paramètres à la requête si nécessaire.
</li><li> Exécution de la requête.
</li><li> Réinitialisation de la requête.
</li></ul>
<h4 name="Ouverture_d.27une_connexion"> Ouverture d'une connexion </h4>
<p>La première initialisation du service Storage doit se faire dans le processus d'exécution principal. Vous obtiendrez une erreur en voulant l'intialiser dans un autre processus. Vous pouvez toutefois utiliser le service depuis un processus en appelant la méthode getService du processus principal pour vérifier que le service a bien été créé.
</p><p>Voici un exemple C++ d'ouverture d'une connexion vers "asdl.sqlite" dans le répertoire du profil de l'utilisateur :
</p>
<pre>nsCOMPtr&lt;nsIFile&gt; dbFile;
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                            getter_AddRefs(dbFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = dbFile-&gt;Append(NS_LITERAL_STRING("asdf.sqlite"));
NS_ENSURE_SUCCESS(rv, rv);

mDBService = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &amp;rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBService-&gt;OpenDatabase(dbFile, getter_AddRefs(mDBConn));
NS_ENSURE_SUCCESS(rv, rv);
</pre>
<p><code>MOZ_STORAGE_SERVICE_CONTRACTID</code> est défini dans {{template.Source("storage/build/mozStorageCID.h")}}. Sa valeur est <code>"@mozilla.org/storage/service;1"</code>.
</p><p>Voici un exemple JavaScript :
</p>
<pre>var file = Components.classes["@mozilla.org/file/directory_service;1"]
                     .getService(Components.interfaces.nsIProperties)
                     .get("ProfD", Components.interfaces.nsIFile);
file.append("asdf.sqlite");

var storageService = Components.classes["@mozilla.org/storage/service;1"]
                        .getService(Components.interfaces.mozIStorageService);
var mDBConn = storageService.openDatabase(file);
</pre>
<dl><dd><div class="note">Note : la fonction openDatabase risque d'être modifiée. Elle sera améliorée et simplifiée pour réduire les difficultés d'utilisation.</div>
</dd></dl>
<p>Il est déconseillé de nommer votre base de données avec une extension en ".sdb" pour <b>s</b>qlite <b>d</b>ata<b>b</b>ase. En effet, Windows reconnaît cette extension comme une "base de données de compatibilité des applications" et les modifications sont inscrites dans la fonctionnalité de restauration système.
</p>
<h4 name="Cr.C3.A9ation_d.27une_requ.C3.AAte"> Création d'une requête </h4>
<p>Il existe deux méthodes pour créer une requête. Si vous n'avez aucun paramètre et si la requête ne renvoie aucune valeur, utilisez <code>mozIStorageConnection.executeSimpleSQL</code>.
</p>
<pre>C++:
rv = mDBConn-&gt;ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE foo (a INTEGER)"));

JS:
mDBConn.executeSimpleSQL("CREATE TABLE foo (a INTEGER)");
</pre>
<p>Autrement, vous devrez préparer une requête en utilisant <code>mozIStorageConnection.createStatement</code> :
</p>
<pre>C++:
nsCOMPtr&lt;mozIStorageStatement&gt; statement;
rv = mDBConn-&gt;CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM foo WHERE a = ?1"),
                              getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);

JS:
var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1");
</pre>
<p>Cet exemple utilise un sélecteur "?1" comme paramètre qui sera renseigné ultérieurement (voir le chapitre suivant).
</p><p>Après avoir préparé une requête, vous pouvez lui lier des paramètres, l'exécuter et la réinitialiser autant de fois que vous le souhaitez. Si vous devez faire une requête fréquemment, l'utilisation d'une requête précompilée augmentera les performances de manière significative car la requête SQL n'aura pas à être traitée à chaque fois.
</p><p>Si vous êtes familier avec sqlite, vous devez savoir que les requêtes préparées deviennent invalides lorsque la structure de la base de données est modifiée. Heureusement, mozIStorageStatement détecte l'erreur et recompile la requête si nécessaire. Ainsi, après avoir créer une requête, vous n'avez pas à vous soucier d'une modification de structure ; toutes les requêtes continueront à fonctionner de manière transparente.
</p>
<h4 name="Liaison_de_param.C3.A8tres"> Liaison de paramètres </h4>
<p>Il est généralement préférable de lier tous les paramètres séparément plutôt que d'essayer de construire à la volée des chaînes SQL contenant ces paramètres. En plus d'autres aspects, ce mode de fonctionnement permet d'éviter une attaque par injection SQL puisque les paramètres liés ne sont jamais exécutés en SQL.
</p><p>Les sélecteurs inclus dans la requête sont liés aux paramètres. Les sélecteurs sont indexés, en commençant par "?1", puis "?2"... Vous devez utiliser les fonctions BindXXXParameter(0) BindXXXParameter(1)... pour lier ces sélecteurs.
</p>
<dl><dd><div class="note">Attention : Les indices des sélecteurs débutent à partir de 1. Les entiers passés aux fonctions de liaison débutent à partir de 0.  Cela signifie que "?1" correspond au paramètre 0, "?2" correspond au paramètre 1, etc.</div>
</dd></dl>
<p>Un sélecteur peut apparaître plusieurs fois dans la chaîne SQL et tous les instances seront remplacées par la valeur liée. Les paramètres non liés seront interprétés comme NULL.
</p><p>Les fonctions de liaison disponible dans <code>mozIStorageStatement</code> (voir {{template.Source("storage/public/mozIStorageStatement.idl")}}) sont :
</p>
<ul><li> <code>bindUTF8StringParameter(in unsigned long aParamIndex, in AUTF8String aValue)</code>
</li><li> <code>bindStringParameter(in unsigned long aParamIndex, in AString aValue)</code>
</li><li> <code>bindDoubleParameter(in unsigned long aParamIndex, in double aValue)</code>
</li><li> <code>bindInt32Parameter(in unsigned long aParamIndex, in long aValue)</code>
</li><li> <code>bindInt64Parameter(in unsigned long aParamIndex, in long long aValue)</code>
</li><li> <code>bindNullParameter(in unsigned long aParamIndex)</code>
</li><li> <code>bindBlobParameter(in unsigned long aParamIndex, {{mediawiki.external('array,const,size_is(aValueSize)')}} in octet aValue, in unsigned long ValueSize)</code> (pour des données binaires)
</li></ul>
<p>Exemple C++ :
</p>
<pre>nsCOMPtr&lt;mozIStorageStatement&gt; statement;
rv = mDBConn-&gt;CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM foo WHERE a = ?1 AND b &gt; ?2"),
                              getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
rv = statement-&gt;BindUTF8StringParameter(0, "bonjour"); // "bonjour" sera substitué à "?1"
NS_ENSURE_SUCCESS(rv, rv);
rv = statement-&gt;BindInt32Parameter(1, 1234); // 1234 sera substitué à "?2"
NS_ENSURE_SUCCESS(rv, rv);
</pre>
<p>Exemple Javascript :
</p>
<pre>var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1 AND b &gt; ?2");
statement.bindUTF8StringParameter(0, "bonjour");
statement.bindInt32Parameter(1, 1234);
</pre>
<h4 name="Ex.C3.A9cution_d.27une_requ.C3.AAte"> Exécution d'une requête </h4>
<p>La principale manière d'exécuter une requête se fait avec la fonction <code>mozIStorageStatement.executeStep</code>. Cette fonction vous permet de récupérer chaque ligne produite par la requête en vous notifiant lorsqu'il n'y a plus de résultats.
</p><p>Après un appel de <code>executeStep</code>, vous récupérez les données par une des fonctions de mozIStorageValueArray (voir {{template.Source("storage/public/mozIStorageValueArray.idl")}}). mozIStorageStatement implémente mozIStorageValueArray. Ces fonctions sont :
</p>
<ul><li> <code>long getInt32(in unsigned long aIndex);</code>
</li><li> <code>long long getInt64(in unsigned long aIndex);</code>
</li><li> <code>double getDouble(in unsigned long aIndex);</code>
</li><li> <code>AUTF8String getUTF8String(in unsigned long aIndex);</code>
</li><li> <code>AString getString(in unsigned long aIndex);</code>
</li><li> <code>void getBlob(in unsigned long aIndex, out unsigned long aDataSize, {{mediawiki.external('array,size_is(aDataSize)')}} out octet aData);</code> Attention : la donnée sera NULL si dataSize est à 0.
</li><li> <code>boolean getIsNull(in unsigned long aIndex);</code> Retourne true si la cellule est NULL (ce qui différent pour une chaîne vide).
</li></ul>
<p>Vous pouvez obtenir le type de la valeur d'une colonne spécifiée avec <code>mozIStorageValueArray.getTypeOfIndex</code>. Soyez prudent, sqlite n'est pas une base de données typée. N'importe quel type de données peut être placé dans une cellule, indépendamment du type de la colonne. Si vous lisez une donnée d'un type différent, sqlite fera de son mieux pour la convertir, et vous proposera une valeur par défaut si c'est impossible. De ce fait, il n'est pas possible d'obtenir les erreurs de typage ce qui peut engendrer des résultats surprenants.
</p><p>Les codes C++ peuvent également utiliser des fonctions <code>AsInt32</code>, <code>AsDouble</code>, etc. pour adapter la valeur retournée à un type C++. Prenez garde toutefois car aucune erreur ne vous sera signalée si votre index est invalide. D'autres erreurs sont impossibles car sqlite convertira toujours les types, même si cela n'a aucun sens.
</p><p>Exemple C++ :
</p>
<pre>PRBool hasMoreData;
while (NS_SUCCEEDED(statement-&gt;ExecuteStep(&amp;hasMoreData)) &amp;&amp; hasMoreData) {
  PRInt32 value = statement-&gt;AsInt32(0);
  // utilisez la valeur...
}
</pre>
<p>Exemple Javascript :
</p>
<pre>while (statement.executeStep()) {
  var value = statement.getInt32(0);
  // utilisez la valeur...
}
</pre>
<p><code>mozIStorageStatement.execute()</code> est une fonction pratique lorsque votre requête n'a pas besoin de retourner de valeurs. Elle effectue la requête en une seule étape et se réinitialise. Elle sert surtout pour des requêtes d'insertion en simplifiant le code :
</p>
<pre>var statement = mDBConn.createStatement("INSERT INTO my_table VALUES (?1)");
statement.bindInt32Parameter(52);
statement.execute();
</pre>
<h4 name="R.C3.A9initialisation_d.27une_requ.C3.AAte"> Réinitialisation d'une requête </h4>
<p>Il est important de réinitialiser les requêtes qui ne servent plus. Une requête d'écriture non réinitialisée laissera un vérou sur les tables et interdira à d'autres requêtes d'y accéder. Une requête de lecture non réinitialisée interdira toute écriture.
</p><p>Lorsque l'objet requête est libéré, la base de données à laquelle il était lié est fermée. Si vous utilisez C++ en sachant que toutes les références seront détruites, vous n'avez pas à réinitialiser explicitement la requête. De même, avec l'appel de la fonction <code>mozIStorageStatement.execute()</code>, il est inutile de réinitialiser la requête ; cette fonction le fera pour vous. Dans les autres cas, appelez <code>mozIStorageStatement.reset()</code>.
</p><p>En JavaScript, toutes les requêtes doivent être réinitialisées. Soyez prudent au sujet des exceptions. Assurez vous que vos requêtes soient réinitialisées même si une exception est déclenchée, sinon l'accès à la base de données ne sera plus possible. La réinitialisation d'une requête est une opération légère sans conséquence, donc n'hésitez pas à effectuer même des réinitialisations superflues.
</p>
<pre>var statement = connection.createStatement(...);
try {
  // utilisez la requête...
} finally {
  statement.reset();
}
</pre>
<p>Les scripts C++ doivent faire de même. L'objet de contexte mozStorageStatementScoper dans {{template.Source("storage/public/mozStorageHelper.h")}} s'assurera qu'une requête donnée est réinitialisée lorsque le contexte est quitté. Il est fortement recommandé d'utiliser cet objet.
</p>
<pre>void someClass::someFunction()
{
  mozStorageStatementScoper scoper(mStatement)
  // utilisez la requête...
}
</pre>
<h4 name="Transactions"> Transactions </h4>
<p>mozIStorageConnection dispose de fonctions pour débuter et clore des transactions. Même si vous n'utilisez pas explicitement les transactions, une transaction implicite sera créée pour chacune de vos requêtes. Ceci a des implications majeures en terme de performance. Chaque transaction, et spécialement les validations, occasionne un délai supplémentaire. Les performances seront meilleures si vous placez plusieurs requêtes dans une même transaction. Consultez <a href="fr/Storage/Performance">Storage:Performance</a> pour plus d'informations sur les performances.
</p><p>La différence principale avec d'autres systèmes de base de données est que sqlite ne supporte pas les transactions imbriquées. C'est-à-dire qu'une fois une transaction ouverte, vous ne pouvez pas en ouvrir une autre. Vous pouvez vérifier si une transaction est en cours de traitement grâce à <code>mozIStorageConnection.transactionInProgress</code>.
</p><p>Vous pouvez également exécuter les commandes SQL "BEGIN TRANSACTION" et "END TRANSACTION" directement (c'est ce que fait la connexion avec l'appel des fonctions). Cependant, il est <i>fortement</i> recommandé d'utiliser <code>mozIStorageConnection.beginTransaction</code> et des fonctions associées parce qu'elles mémorisent l'état de la transaction dans la connexion. Dans le cas contraire, l'attribut <code>transactionInProgress</code> aura une valeur erronée.
</p><p>sqlite comprend différents types de transactions :
</p>
<ul><li> mozIStorageConnection.TRANSACTION_DEFERRED: par défaut. Le verrou sur la base de données est obtenu lorsque c'est nécessaire (normalement la première fois où vous exécutez une requête dans la transaction).
</li></ul>
<ul><li> mozIStorageConnection.TRANSACTION_IMMEDIATE: Vérouillage immédiat en lecture de la base de données.
</li></ul>
<ul><li> mozIStorageConnection.TRANSACTION_EXCLUSIVE: Vérouillage immédiat en écriture de la base de données.
</li></ul>
<p>Vous pouvez définir le type de la transaction en le transmettant par <code>mozIStorageConnection.beginTransactionAs</code>. Gardez en tête que si une autre transaction a déjà démarré, cette opération échouera. Habituellement, le type par défaut TRANSACTION_DEFERRED suffit, et à moins de savoir exactement ce que vous faites, vous n'aurez pas besoin des autres types. Pour plus d'informations, consultez la document sqlite sur <a class="external" href="http://www.sqlite.org/lang_transaction.html">BEGIN TRANSACTION</a> et <a class="external" href="http://www.sqlite.org/lockingv3.html">le vérouillage</a>.
</p>
<pre>var ourTransaction = false;
if (mDBConn.transactionInProgress) {
  ourTransaction = true;
  mDBConn.beginTransactionAs(mDBConn.TRANSACTION_DEFERRED);
}

// ... utilisez la connexion ...

if (ourTransaction)
  mDBConn.commitTransaction();
</pre>
<p>Dans un code C++, vous pouvez utiliser la classe helper mozStorageTransaction définie dans {{template.Source("storage/public/mozStorageHelper.h")}}. Cette classe démarrera une transaction du type donné sur la connexion spécifiée lorsqu'elle rentre dans le contexte d'exécution, et fera une validation ou une annulation de la transaction lorsqu'elle sort du contexte. Si une autre transaction est en cours, la classe helper de transaction n'effectuera aucune action.
</p><p>Elle dispose également de fonctions pour réaliser explicitement des validations. L'utilisation classique est de définir en premier une classe d'annulation par défaut, et ensuite valider explicitement la transaction si le processus a réussi :
</p>
<pre>nsresult someFunction()
{
  // Définir (par défaut) la transaction avec une annulation en cas d'échec
  mozStorageTransaction transaction(mDBConn, PR_FALSE);

  // ... utilisez la connexion ...

  // tout s'est bien passé, alors validation explicite
  return transaction.Commit();
}
</pre>
<h4 name="Comment_corrompre_votre_base_de_donn.C3.A9es"> Comment corrompre votre base de données </h4>
<ul><li> Lisez ce document : <a class="external" href="http://www.sqlite.org/lockingv3.html">File locking and concurrency in sqlite version 3</a>, en particulier le chapitre sur la corruption.
</li></ul>
<ul><li> Ouvrez plus d'une connexion vers le même fichier dont le nom n'est pas déterminé strictement identique par un <code>strcmp</code>, comme par exemple "my.db" et "../dir/my.db" ou sous Windows (insensible à la casse) "my.db" et "My.db". Sqlite essaiera de traiter chacun de ces cas, mais vous ne devriez pas compter là dessus.
</li></ul>
<ul><li> Accédez à une base de données depuis un lien symbolique ou physique.
</li></ul>
<ul><li> Ouvrez des connexions vers la même base de données depuis plus d'un processus (voir "Processus surs" ci-dessous).
</li></ul>
<ul><li> Accédez à une connexion ou une requête depuis plus d'un processus (voir "Processus surs" ci-dessous).
</li></ul>
<ul><li> Ouvrez la base de données depuis un programme externe pendant qu'elle est ouverte dans Mozilla. Le cache de Mozilla corrompt le fichier verrou normal dans sqlite pour lui permettre de travailler en sécurité.
</li></ul>
<h4 name="Thread_safety"> Thread safety </h4>
<p>The mozStorage service and sqlite are threadsafe. However, no other mozStorage or sqlite objects or operations are threadsafe.
</p>
<ul><li> The storage service must be created on the main thread. If you want to access the service from another thread, you should be sure that you call getService from the main thread ahead of time.
</li></ul>
<ul><li> You can not access a connection or statement from multiple threads. These storage objects are not threadsafe, and the sqlite representations of them are not threadsafe either. Even if you do locking and ensure that only one thread is doing something at once, there may be problems. This case hasn't been tested, and there may be some internal per-thread state in sqlite. It is strongly advised that you don't do this.
</li></ul>
<ul><li> You can not access a single database from multiple connections from different threads. Normally, sqlite allows this. However, we do <code>sqlite3_enable_shared_cache(1);</code> (see <a class="external" href="http://www.sqlite.org/sharedcache.html">sqlite shared-cache mode</a>) which makes multiple connections share the same cache. This is important for performance. However, there is no lock for cache access, meaning it will break if you use if from more than one thread.
</li></ul>
<h4 name="SQLite_Locking"> SQLite Locking </h4>
<p>SQLite locks the entire database; that is, any active readers will cause an attempt to write to return SQLITE_BUSY, and an active writer will cause any attempt to read to return SQLITE_BUSY.  A statement is considered active from the first step() until reset() is called.  execute() calls step() and reset() in one go.  A common problem is forgetting to reset() a statement after you've finished step()'ing through.
</p><p>While a given SQLite connection is capable of having multiple statements open, its locking model limits what these statements can do concurrently (reading or writing). It is in fact possible for multiple statements to be actively reading at one time. It is not possible, however, for multiple statements to be reading and writing at one time <i>on the same table</i> -- even if they are derived from the same connection.
</p><p>SQLite has a two-tiered locking model: connection level and table level. Most people are familiar with the connection (database) level locking: multiple readers but only one writer. The table-level (B-Tree) locks are what can sometimes be confusing. (Internally, each table in the database has its own B-Tree, so "table" and "B-Tree" are technically synonymous).
</p>
<h5 name="Table-level_locks"> Table-level locks </h5>
<p>You would think that if you have only one connection, and it locks the database for writing, you could use multiple statements to do whatever you want. Not entirely. You must be aware of table-level (B-Tree) locks, which are maintined by statement handles traversing the database (i.e. open SELECT statements).
</p><p>The general rule is this: a statement handle may <b>not</b> modify a table (B-Tree) which other statement handles are reading (have open cursors on) -- even if that statement handle shares the same connection (transaction context, database lock, etc.) with the other statement handles. <i>Attempts to do so will still block (or return SQLITE_BUSY)</i>.
</p><p>This problem often crops up when you attempt to iterate over a table with one statement and modify records within it using another statement. This will not work (or carries a high probability of not working, depending on the optimizer's involvement (see below)). The modifying statement will block because the reading statement has an open cursor on the table.
</p>
<h5 name="Working_around_locking_problems"> Working around locking problems </h5>
<p>The solution is to follow (1) as described above. Theoretically, (2) actually shouldn't work with SQLite 3.x. In this scenario, database locks come into play (with multiple connections) in addition to table locks. Connection 2 (modifying connection) will not be able to modify (write to) the database while the Connection 1 (reading connection) is reading it. Connection 2 will require an exclusive lock to execute a modifying SQL command, which it cannot get as long as Connection 1 has active statements reading the database (Connection 1 has a shared read lock during this time which prohibits any other connections from getting an exclusive lock).
</p><p>Another option is to use a temporary table. Create a temporary table containing the results of the table of interest, iterate over it (putting the reading statement's table lock on the temp table) and then the modifing statement can make changes to the real table without any problem. This can be done with statements derived from a single connection (transaction context). This scenario sometimes happens behind the scenes anyway as ORDER BY can produce temporary tables internally. However, it is not safe to assume that the optimizer will do this in all cases. Explicitly creating a temporary table is only safe way to do perform this latter option.
</p>{{ wiki.languages( { "en": "en/Storage", "es": "es/Almacenamiento", "pl": "pl/Storage" } ) }}
Revenir à cette révision