SQLite
 sql >> Base de Dados >  >> RDS >> SQLite

Como lidar com valores booleanos no SQLite usando JavaScript Proxies

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 👊