O problema com booleanos no SQLite
Se você já trabalhou com SQLite, você deve estar ciente dos tipos de dados suportados e
Boolean
não é um deles. Mais especificamente como dito aqui:2.1. Tipo de dados booleano
O SQLite não possui uma classe de armazenamento booleana separada. Em vez disso, os valores booleanos são armazenados como inteiros 0 (falso) e 1 (verdadeiro).
O SQLite reconhece as palavras-chave "TRUE" e "FALSE", a partir da versão 3.23.0 (2018-04-02), mas essas palavras-chave são apenas grafias alternativas para os literais inteiros 1 e 0, respectivamente.
A maioria das bibliotecas JavaScript para SQLite3 não suportam
TRUE
e FALSE
palavras-chave e eles exigem que você prepare as instruções em seu código usando números inteiros. Por exemplo, em better-sqlite3 você teria que fazer isso: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
em vez de boolean
em todo o seu aplicativo seria uma experiência terrível para o desenvolvedor (além de provavelmente usar mais memória). Você pode usar uma função auxiliar para transformar o booleano de seus objetos de carga útil propriedades para números (Na verdade, eu tinha feito isso uma vez, no passado), mas você teria que executá-lo manualmente antes de cada consulta. Caramba. Não seria ótimo se essa lógica fosse executada em segundo plano, toda vez que preparássemos e executássemos uma instrução?
Bem-vindo, Proxies ES6 👋
Um dos recursos JavaScript mais recentes é o
Proxy
objeto. Proxy são essencialmente "armadilhas" que interceptam operações de objetos como getters, setters e chamadas de função. Usando proxies podemos modificar a biblioteca wrapper SQLite JS para executar nossa própria lógica, como um middleware. Escrevendo a função auxiliar
Para facilitar o desenvolvimento, usaremos
mapValues
&isPlainObject
funções utilitárias de lodash , mas é claro que você pode codificar suas próprias funções. A função abaixo irá mapear através de um objeto (um nível de profundidade) e converter valores do tipo boolean
para digitar number
. import { mapValues } from 'lodash';
const booleanEntriesToNumbers = (object) =>
mapValues(object, (value) =>
typeof value === 'boolean' ? Number(value) : value
);
Usando proxies para interceptar chamadas de consulta
Abaixo importamos
better-sqlite3
biblioteca e crie uma nova instância de banco de dados. Depois, sobrescrevemos o padrão prepare
método com o nosso, que por sua vez substitui os métodos run
, get
e all
, criando um novo proxy para cada um. É claro que você pode criar um proxy para qualquer outro método que desejar. 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;
Essencialmente, uma vez que uma chamada para o
prepare
é acionado, informamos ao JavaScript:Espere! Queremos modificar esta chamada de função. Em vez de executar a lógica que o desenvolvedor original pretendia, queremos executar nossa própria lógica primeiro (que é o mapeamento da carga útil do objeto). Depois de executar nossa própria lógica, retornamos o resultado da chamada do método original usando call
para vincular o this
argumento. Se você quiser ler mais sobre como os proxies funcionam, leia aqui. Para nossa implementação, usamos o apply
método aqui. Obrigado por ler este post, espero ter ajudado alguém que trabalha com SQLite em JavaScript 👊