--- title: Storage slug: Storage tags: - Interfaces - Storage - Todas_las_Categorías - Toolkit API - páginas_a_traducir ---

{{ Fx_minversion_header(2) }}

Storage es una API para la  base de datos SQLite. Responde a las llamadas entidades de confianza, es decir, componentes internos de Firefox y extensiones. hace referencia completa a todos los métodos y propiedades de las conexiones de la interfaz de la  base de datos, lee mozIStorageConnection.

El API está actualmente "unfrozen," lo que significa que está sujeto a cambios en cualquier momento. Es muy probable que el API cambie en la transición entre Firefox 2 y Firefox 3.

 

Nota: Storage no es lo mismo que la característica DOM:Storage que puede ser usada por páginas web para almacenar datos persistentes o la Session store API (una utilidad XPCOM de almacenaje para usar con las extensiones).

 

Empezando

Este documento cubre el API mozStorage y algunas peculiaridades de sqlite. No cubre SQL o el sqlite. Sin embargo, puedes encontrar varios enlaces útiles en la sección Ver también. Para obtener ayuda sobre el API mozStorage, puedes escribir a mozilla.dev.apps.firefox en el servidor de noticias news.mozilla.org. Para reportar errores, usa Bugzilla (producto "Toolkit", componente "Storage").

Bueno, aquí vamos. mozStorage fue diseñado igual que muchos otros sistemas de base de datos. El procedimiento general de su uso es:

Abrir una conexión

Para los usuarios de C++: La inicialización del servicio de storage debe hacerse desde el mismo hilo principal.Si lo inicializas por primera vez desde otro hilo, obtendrás un error. Por tanto, si quieres usar el servicio dentro de un hilo, asegúrate de llamar a getService desde el hilo principal para estar seguro de que el servicio ha sido creado.

Ejemplo de apertura de una conexión a "asdf.sqlite" en el directorio del perfil del usuario, en C++:

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á definido en {{ Source("storage/build/mozStorageCID.h") }}. Su valor es "@mozilla.org/storage/service;1"

Ejemplo en JavaScript:

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

var storageService = Components.classes["@mozilla.org/storage/service;1"]
                        .getService(Components.interfaces.mozIStorageService);
var mDBConn = storageService.openDatabase(file);
Nota: La función OpenDatabase está sujeta a cambios. Seguramente será simplificada y ampliada para que sea más difícil meterse en problemas.

Sería tentador nombrar a tu base de datos con un nombre terminado en ".sdb" por sqlite database, pero esto no es recomendable. Esta extensión es tratada de forma especial por Windows como una extensión para "Application Compatibility Database" y sus cambios están respaldados por el sistema automáticamente como parte del sistema de recuperación del sistema. Esto puede dar lugar a un tratamiento desmesurado de las operaciones del archivo.

Sentencias

Sigue los pasos para crear y ejecutar sentencias SQL en tu base de datos SQLite. Para una completa referencia, lee mozIStorageStatement.

Creando una sentencia

Hay dos maneras de crear una sentencia. Si no tienes parámetros y la sentencia no devuelve ningún dato, usa mozIStorageConnection.executeSimpleSQL.

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

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

De otra forma, deberías preparar la sentencia usando 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");

Este ejemplo usa el comodín "?1" para recoger un parámetro devuelto más adelante (lee la próxima sección).

Una vez que has preparado la sentencia, puedes agrupar su parámetros, ejecutarla y re usarla una y otra vez. Si utilizas una sentencia repetidamente, el uso de sentencias pre compiladas te brindará una mejora notable en la ejecución, ya que las consultas SQL no necesitan ser analizadas cada vez.

If you are familiar with sqlite, you may know that prepared statements are invalidated when the schema of the database changes. Fortunately, mozIStorageStatement detects the error and will recompile the statement as needed. Therefore, once you create a statement, you don't need to worry when changing the schema; all statements will continue to transparently work.

Agregar los parámetros

Generalmente es mucho mejor agregar los parámetros separadamente, en lugar de intentar construir una sentencia SQL en una cadena conteniendo los parámetros. Entre otras cosas, esto evita el ataque de inyección de SQL, ya que un parámetro suelto nunca podrá ser ejecutado como una sentencia SQL.

Se agregan los parámetros a una sentencia SQL reemplazando los comodines. Los comodines son llamados por orden, empezando con el "?1", luego el "?2", etcétera. Usa la función BindXXXParameter(0) BindXXXParameter(1)... para cambiar esos comodines.

Presta atención: Los indices en los comodines van a partir de 1. Los indices de las funciones de cambio, empiezan en 0. Esto es: el "?1" corresponde al parámetro 0, "?1" corresponde al parámetro 1, etcétera.

Tambien puedes usar parámetros con nombre como: ":ejemplo" en lugar de "?xx".

Un comodín puede aparecer varias veces en una cadena SQL y todas las veces será reemplazado por el correspondiente valos. Los parámetros que no han sido agragados (unbound) serán interpretados como NULL

Los ejemplos a continuación, usan bindUTF8StringParameter() y bindInt32Parameter(). Para una lista de todas las demás funciones, lee mozIStorageStatement.

Ejemplo en 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, "hello"); // "hello" será sustituido por "?1"
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt32Parameter(1, 1234); // 1234 será sustituido por "?2"
NS_ENSURE_SUCCESS(rv, rv);

Ejemplo en JavaScript:

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

Si usas parámetros con nombre, deberías usar el método getParameterIndex para obtener el índice del parámetro con nombre. Aquí hay un ejemplo en JavaScript:

var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = :myfirstparam AND b > :mysecondparam");

var firstidx = statement.getParameterIndex(":myfirstparam");
statement.bindUTF8StringParameter(firstidx, "hello");

var secondidx = statement.getParameterIndex(":mysecondparam");
statement.bindInt32Parameter(secondidx, 1234);

Por supuesto, puedes mezclar parámetros con nombre y con índice en la misma consulta:

var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1 AND b > :mysecondparam");

statement.bindUTF8StringParameter(0, "hello");
// you can also use
// var firstidx = statement.getParameterIndex("?1");
// statement.bindUTF8StringParameter(firstidx, "hello");

var secondidx = statement.getParameterIndex(":mysecondparam");
statement.bindInt32Parameter(secondidx, 1234);

Si quieres usar la clausula WHERE con una expresión IN ( value-list ), las sentencias de Bindings no funcionarán. En su lugar, construye una cadena. Si no estas usando unstradas del usuario, la seguridad no es una de tus preocupaciones:

var ids = "3,21,72,89";
var sql = "DELETE FROM table WHERE id IN ( "+ ids +" )";

Ejecutar una sentencia

La manera principal de ejecutar una sentencia es con mozIStorageStatement.executeStep. Esta función te permite enumerar todos los resultados (filas / registros) que produzca tu sentencia y te notificará cuando no hay más resultados.

Después de una llamada a executeStep, debes usar la función de recogida apropiada en mozIStorageValueArray para recoger el valor en una fila (mozIStorageStatement implementa mozIStorageValueArray). El ejemplo de abajo sólo usa getInt32().

Puedes obtener el tipo de un valor desde mozIStorageValueArray.getTypeOfIndex, que devuelve el tipo de la columna especificada. Ten cuidado: sqlite no es una base de datos que maneje tipos. Si requieres un tipo diferente, sqlite hará lo mejor que pueda para convertirlo, y dará algún tipo por defecto si no lo consigue. Por tanto, es imposible obtener errores de tipo, pero puedes obtener datos extraños como valor devuelto.

En código C++ se puede también usar las funciones AsInt32, AsDouble, etcétera. que devuelven el valor como un valor más manejable en C++. Ten precaución, sin embargo, ya que no tendrás errores si tu índice es invalido. Es imposible obtener otros errores, yq que sqlite siempre convertirá los tipos, aún si no tienen sentido.

Ejemplo C++:

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

Ejemplo Javascript:

while (statement.executeStep()) {
  var value = statement.getInt32(0); // ¡usa la función correcta!
  // usa el valor...
}

La función mozIStorageStatement.execute() es útil cuando no estás obteniendo datos de la sentencia. Lo que hace es ejecutar la sentencia una vez y luego volverla al estado inicial. Esto puede ser útil para las sentencias de inserción, ya que realmente limpia el código:

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

Este es un ejemplo simple pero completo en JavaScript y XUL, de cómo ejecutar sentencias SQL en una base de datos: Image:TTRW2.zip.

Re crear una sentencia (reset)

Es importante re crear sentencias que no han sido usadas nuevamente. Las sentencias no re creadas, dejarán un bloqueo en las tablas y evitarán que otras sentencias puedan acceder a los datos. Sentencias de lectura no re creadas evitarán que funcionen las sentencias de escritura.

Cuando los objetos sentencias son liberados, su correspondiente sentencia en la base de datos es liberada. Si estás usando C++ y sabes que todas las referencias serán destruidas, no tienes que re crear explicitamente las sentencias. Además, si usas mozIStorageStatement.execute(), tampoco necesitas re crear explicitamente las sentencias, esta función lo hará por ti. En otro caso, llama a mozIStorageStatement.reset().

Los que usáis JavaScript, deberéis aseguraros de re crear las sentencias. Se particularmente escrupuloso con las excepciones. Querrás estar seguro de re crear las sentencias aún si ocurre una excepción, o los accesos subsiguientes pueden no ser posibles. Re crear una sentencia es relativamente poco pesado y no ocurre nada malo si ya ha sido re creada, así que no te preocupes por re creaciones innecesarias.

var statement = connection.createStatement(...);
try {
  // usa la sentencia...
} finally {
  statement.reset();
}

Los que usáis C++ debéis hacer lo mismo. Existe un objeto en {{ Source("storage/public/mozStorageHelper.h") }} llamado mozStorageStatementScoper que se asegurará que una sentencia dada sea re creada cuando sales del ámbito. Es muy recomendable que uses este objeto si te es posible.

void someClass::someFunction()
{
  mozStorageStatementScoper scoper(mStatement)
  // use the statement
}

id de la última inserción

Usa la propiedad lastInsertRowID en la conexión para obtener el id (id de la fila) de la última operación INSERT operation en la base de datos.
Esto es útil si tienes una columna en tu tabla que sea la clave primaria (como INTEGER PRIMARY KEY o INTEGER PRIMARY KEY AUTOINCREMENT) en cuyo caso SQLite asigna automáticamente un valor a cada fila insertada, si tu no proporcionas alguno. El valor devuelto es del tipo number en JS y long long en C++.

lastInsertRowID JS example:

var sql = "INSERT INTO contacts_table (number_col, name_col) VALUES (?1, ?2)"
var statement = mDBConn.createStatement(sql);
    statement.bindUTF8StringParameter(0, number);
    statement.bindUTF8StringParameter(1, name);
    statement.execute();
    statement.reset();

var rowid = mDBConn.lastInsertRowID;

Transacciones

mozIStorageConnection tiene funciones para las transacciones de comienzo y final. Si no usas transacciones explicitamente, se creará una transacción implícita por ti para cada sentencia. Esto tiene una gran implicación en el rendimiento. Hay una sobrecarga en cada transacción, especialmente para las transacciones de confianza. Por tanto verás una apreciable ganancia en el rendimiento, cuando estés haciendo ejecutando varias sentencias en una fila, si las agrupas en una transacción. Ver Storage:Performance para más detalles.

La mayor diferencia entre otros sitemas de base de datos y sqlite es que éste último no implementa transacciones anidadas. Esto significa que en el momento en que una transacción es abierta, no puedes abrir otra transacción. Puedes usar mozIStorageConnection.transactionInProgress para ver si la transacción está en progreso.

También puedes simplemente ejecutar "BEGIN TRANSACTION" y "END TRANSACTION" directamente como sentencias SQL (esto es lo que hace la conexión cuando llamas a las funciones). Sin embargo, el uso de mozIStorageConnection.beginTransaction y las funciones relacionadas, está fuertemente recomendado, ya que guarda el estado de la transacción en la conexión. De otra forma, el atributo transactionInProgress tendrá el valor erróneo.

sqlite tiene vario tipos de transacción:

Puedes pasar este tipo de transacción a mozIStorageConnection.beginTransactionAs para determinar que clase de transacción necesitas. Recuerda que si ya se ha iniciado otra transacción, esta operación no tendrá éxito. Generalmente, el tipo por defecto de TRANSACTION_DEFERRED es suficiente y no deberías usar otro tipo, a no ser, que realmente sepas porqué lo necesitas. Para más información, lee la documentación de sqlite: BEGIN TRANSACTION y locking.

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

// ... usa la conexión ...

if (ourTransaction)
  mDBConn.commitTransaction();

A partir de código C++, puedes usar la clase mozStorageTransaction definida en {{ Source("storage/public/mozStorageHelper.h") }}. Esta clase iniciará una transacción del tipo especificado en la conexión especificada y, cuando salga del entorno, bien confiará o bien volverá la transacción a su estado inicial. Si ya hay una transacción en progreso, la clase de ayuda de la transacción no hará nada.

También tiene funciones de confianza explicitas. El uso típico es que crees la clase que por defecto vuelva a su estado original (rollback) y luego realices la confianza de la transacción explicitamente cuando tenga éxito:

nsresult someFunction()
{
  // deferred transaction (the default) with rollback on failure
  mozStorageTransaction transaction(mDBConn, PR_FALSE);

  // ... use the connection ...

  // everything succeeded, now explicitly commit
  return transaction.Commit();
}

Cómo corromper tu base de datos

Bloqueo en SQLite

SQLite bloquea la base de datos enteramente, esto es, cualquier intento de lectura causará un que intento de escritura devuelva SQLITE_BUSY, y una escritura activa causará que cualquier intento de lectura devuelva SQLITE_BUSY. Una sentencia es considerada activa a partir del primer step() hasta que se llame a reset(). execute() llama a step() y reset() a un tiempo. Un problema común es olvidar reset() una sentencia después de haber terminado con step().

Mientras que una conexión SQLite dada es capaz de tener muchas sentencias abiertas, su modelo de bloqueo limita lo que estas sentencias pueden realmente hacer simultáneamente (leer o escribir). De hecho es posible que muchas sentencias estén leyendo al mismo tiempo, sin embargo, no es posible que varias sentencias estén leyendo y escribiendo a la vez en la misma tabla -- aún cuando deriven de la misma conexión.

El modelo de bloque de SQLite se conoce como "two-tiered": nivel de conexión y nivel de tabla. La mayoría de las personas están familiarizadas con el nivel de conexión (base de datos): múltiples lecturas pero solo una escritura. El nivel de tabla (B-Tree) es lo que a veces puede resultar confuso. (Internamente, cada tabla en la base de datos tiene su propio B-Tree, de modo que tabla y "B-Tree" son técnicamente sinonimos).

Bloqueo a nivel de tabla

Podrías pensar que si tienes sólo una conexión, i esta bloquea la base da datos para escritura, podrías usar multiples sentencias para hacer lo que quieras. No exactamente. Debes tener en cuenta que los bloqueos a nivel de tabla (B-Tree) que están mantenidos por sentencias se mantienen a lo largo de la base de datos (por ejemplo, las sentencias de manejo SELECT).

La regla general es esta: una sentencia de manejo puede not modificar una tabla (B-Tree) que otras sentencias de manejo están leyendo (tienen el cursor abierto sobre ellas) -- aún cuando la sentencia de manejo comparte la misma conexión (contenido de la transacción, bloqueo de la base de datos, etcétera) con las otras sentencias de manejo. Intentar hacer eso, también bloqueará (o devolverá SQLITE_BUSY).

Este problema a menudo aparece cuando intestas interactuar en una tabla usando una sentencia mientras modificas registros en la tabla con otra sentencia. Esto no funcionará (o entraña muchas posibilidades de no funcionar, dependiendo del optimizador (ver más abajo)). Las sentencias de modificación causarán un bloque ya que la sentencia de lectura tiene el cursor abierto sobre la tabla.

Trabajando con los problemas de bloqueo

La solución es seguir (1) como se dice más arriba. Teóricamente, (2) actualmente no debería funcionar con SQLite 3.x En este escenario, los bloqueos de base de datos entran en juego (con múltiples conexiones) adicionalmente a los bloqueos de tablas. La conexión 2 (conexiones de cambio) no serán capaces de modificar (escribir en) la base de datos mientras que la conexión 1 (conexión de lectura) esté leyendo. La conexión 2 requerirá un bloqueo exclusivo para ejecutar una sentencia de modificación SQL, que no podrá obtener mientras la conexión 1 tenga sentencias activas leyendo la base de datos (La conexión 1 tiene un bloqueo de lectura compartido durante este tiempo que prohíbe a cualquier otra conexión tener un bloqueo exclusivo).

Otra opción es usar una tabla temporal. Crea una tabla temporal que contenga los resultados de la tabla de interés, interactúa con ella (poniendo los bloqueos de la tabla con las sentencias de lectura en la tabla temporal) y entonces las sentencias de modificación pueden hacer cambios en la tabla real sin ningún problema). Esto puede hacerse con sentencias derivadas de una conexión simple (contexto de la transacción). Esta escena ocurre algunas veces 'detrás del escenario' ya que ORDERED BY puede producir tablas temporales internamente. Sin embargo, no es seguro asumir que el optimizador hará esto en todos los casos. Crear tablas temporales explicitamente, es la única forma segura de realizar esta última opción.

Seguridad de los hilos

El servicio mozStorage y sqlite son hilos seguros. Sin embargo, ningún otro mozStorage u objeto sqlite son hilos seguros.

De nada vale, sin embargo, que los autores de las extensiones de JavaScript del navegador, esten menos impactados por estas restricciones de lo que estaban al principio. Si una base de datos es creada y usada de forma exclusiva desde JavaScript, la seguridad de los hilos no será, normalmente, una preocupación. El motor JavaScript interno de Firefox (SpiderMonkey) ejecuta el JavaScript dentro de un único y persistente hilo, excepto cuando JavaScript se ejecuta en un hilo diferente o es ejecutado a partir de una llamada realizada desde otro hilo (p.e. desde interfaces de stream o delde la red). Con excepción del uso incorrecto de JavaScript en múltiples hilos, los problemas solo deberían ocurrir si se accede aun hilo asociado a una base de datos previamente en uso a través de mozStorage.

Ver también

 

{{ languages( { "es": "es/Almacenamiento", "fr": "fr/Storage", "ja": "ja/Storage", "pl": "pl/Storage", "en": "en/Storage" } ) }}