El problema con los booleanos en SQLite
Si alguna vez ha trabajado con SQLite, debe conocer los tipos de datos admitidos y Boolean
no es uno de ellos. Más específicamente como se indica aquí:
2.1. Tipo de datos booleano
SQLite no tiene una clase de almacenamiento booleano separada. En su lugar, los valores booleanos se almacenan como números enteros 0 (falso) y 1 (verdadero).
SQLite reconoce las palabras clave "VERDADERO" y "FALSO", a partir de la versión 3.23.0 (2018-04-02), pero esas palabras clave son realmente ortografías alternativas para los literales enteros 1 y 0 respectivamente.
La mayoría de las bibliotecas de JavaScript para SQLite3 no son compatibles con TRUE
y FALSE
palabras clave y requieren que prepare las declaraciones en su código usando números enteros. Por ejemplo, en better-sqlite3 tendrías que hacer esto:
const payload = {
isActive: 1, // <======
username: 'Brad',
password: '1234',
email: '[email protected]',
};
const result = database
.prepare(
`INSERT INTO accounts(isActive, username, password, email) VALUES(@isActive, @username, @password, @email) `
)
.run({ bucketID, taskSiteID, name, username, password, email }).changes;
Usando number
en lugar de boolean
en toda la aplicación sería una experiencia de desarrollador terrible (además, probablemente usaría más memoria).
Podría usar una función auxiliar para transformar los booleanos de sus objetos de carga útil propiedades a números (De hecho, había hecho esto una vez, en el pasado), pero luego tendría que ejecutarlo manualmente antes de cada consulta. ¡Ay! ¿No sería genial si esta lógica se ejecutara en segundo plano, cada vez que preparamos y ejecutamos una declaración?
Bienvenidos servidores proxy ES6 👋
Una de las características más nuevas de JavaScript es el Proxy
objeto. Proxy son esencialmente "trampas" que interceptan operaciones de objetos como getters, setters y llamadas a funciones. Uso de proxy podemos modificar la biblioteca contenedora SQLite JS para ejecutar nuestra propia lógica, algo así como un middleware.
Escribiendo la función auxiliar
Para facilitar el desarrollo, vamos a utilizar mapValues
&isPlainObject
funciones de utilidad de lodash , pero, por supuesto, puede codificar las suyas propias. La siguiente función mapeará a través de un objeto (de un nivel de profundidad) y convertirá valores de tipo boolean
para escribir number
.
import { mapValues } from 'lodash';
const booleanEntriesToNumbers = (object) =>
mapValues(object, (value) =>
typeof value === 'boolean' ? Number(value) : value
);
Uso de proxies para interceptar llamadas de consulta
A continuación importamos better-sqlite3
biblioteca y crear una nueva instancia de base de datos. Luego, anulamos el prepare
predeterminado método con el nuestro, que a su vez anula los métodos run
, get
y all
, creando un nuevo proxy para cada uno. Por supuesto, puede crear un proxy para cualquier otro método que desee.
import Database from 'better-sqlite3';
// Create new database instance
const db = new Database(dbFilePath);
// We will use this function to override the default "prepare" method
const proxiedPrepare = new Proxy(db.prepare, {
apply: (prepare, prepareThisArg, [stringStatement]) => {
const statement = prepare.call(prepareThisArg, stringStatement);
// Override the default "run" method
statement.run = new Proxy(statement.run, {
apply: (run, runThisArg, args) => {
const mappedArgs = args.map((arg) =>
isPlainObject(arg) ? booleanEntriesToNumbers(arg) : arg
);
return run.call(runThisArg, ...mappedArgs);
},
});
// Override the default "get" method
statement.get = new Proxy(statement.get, {
apply: (get, getThisArg, args) => {
const mappedArgs = args.map((arg) =>
isPlainObject(arg) ? booleanEntriesToNumbers(arg) : arg
);
return get.call(getThisArg, ...mappedArgs);
},
});
// Override the default "all" method
statement.all = new Proxy(statement.all, {
apply: (all, allThisArg, args) => {
const mappedArgs = args.map((arg) =>
isPlainObject(arg) ? booleanEntriesToNumbers(arg) : arg
);
return all.call(allThisArg, ...mappedArgs);
},
});
return statement;
},
});
// Override the default "prepare" method
db.prepare = proxiedPrepare;
Esencialmente, una vez que se llama a prepare
se activa el método, le decimos a JavaScript:¡Espere! Queremos modificar esta llamada de función. En lugar de ejecutar la lógica que pretendía el desarrollador original, primero queremos ejecutar nuestra propia lógica (que es el mapeo de la carga útil del objeto). Después de ejecutar nuestra propia lógica, devolvemos el resultado de llamar al método original usando call
para vincular el this
argumento. Si desea leer más sobre cómo funcionan los proxies, lea aquí. Para nuestra implementación usamos apply
método aquí.
Gracias por leer esta publicación, espero que haya ayudado a alguien que trabaja con SQLite en JavaScript 👊