SQLite
 sql >> Database >  >> RDS >> SQLite

Come gestire i valori booleani in SQLite usando i proxy JavaScript

Il problema con i booleani in SQLite

Se hai mai lavorato con SQLite, dovresti essere a conoscenza dei tipi di dati supportati e di Boolean non è uno di loro. Più precisamente come indicato qui:

2.1. Tipo di dati booleano

SQLite non ha una classe di archiviazione booleana separata. Al contrario, i valori booleani vengono memorizzati come numeri interi 0 (falso) e 1 (vero).

SQLite riconosce le parole chiave "TRUE" e "FALSE", a partire dalla versione 3.23.0 (2018-04-02) ma quelle parole chiave sono in realtà solo ortografie alternative per i letterali interi 1 e 0 rispettivamente.

La maggior parte delle librerie JavaScript per SQLite3 non supporta TRUE e FALSE parole chiave e richiedono di preparare le istruzioni nel codice utilizzando numeri interi. Ad esempio, in better-sqlite3 dovresti fare questo:

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 invece di boolean nell'intera app rappresenterebbe un'esperienza di sviluppo terribile (oltre a probabilmente utilizzare più memoria).

Potresti usare una funzione di supporto per trasformare i tuoi oggetti payload in booleano proprietà in numeri (In realtà l'avevo fatto una volta, in passato), ma dovresti eseguirlo manualmente prima di ogni query. Yikes. Non sarebbe fantastico se questa logica venisse eseguita in background, ogni volta che preparavamo ed eseguivamo un'istruzione?

Benvenuto ai proxy ES6 👋 

Una delle funzionalità JavaScript più recenti è il Proxy oggetto. Proxy sono essenzialmente "trappole" che intercettano operazioni sugli oggetti come getter, setter e chiamate di funzione. Utilizzo di proxy possiamo modificare la libreria wrapper SQLite JS per eseguire la nostra logica, un po' come un middleware.

Scrittura della funzione di supporto

Per facilitare lo sviluppo, utilizzeremo mapValues &isPlainObject funzioni di utilità da lodash , ma puoi ovviamente codificare le tue. La funzione seguente eseguirà il mapping tramite un oggetto (profondo di un livello) e convertirà valori di tipo boolean per digitare number .

import { mapValues } from 'lodash';

const booleanEntriesToNumbers = (object) =>
  mapValues(object, (value) =>
    typeof value === 'boolean' ? Number(value) : value
  );

Utilizzo di proxy per intercettare le chiamate di query

Di seguito importiamo better-sqlite3 libreria e creare una nuova istanza di database. Successivamente, sovrascriviamo il predefinito prepare metodo con il nostro, che a sua volta sovrascrive i metodi run , get e all , creando un nuovo proxy per ciascuno. Ovviamente puoi creare un proxy per qualsiasi altro metodo che desideri.

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;

In sostanza, una volta chiamata al prepare viene attivato, diciamo a JavaScript:Aspetta! Vogliamo modificare questa chiamata di funzione. Invece di eseguire la logica prevista dallo sviluppatore originale, vogliamo invece eseguire prima la nostra logica (che è la mappatura del payload dell'oggetto). Dopo aver eseguito la nostra logica, restituiamo il risultato della chiamata al metodo originale utilizzando call per vincolare il this discussione. Se vuoi saperne di più su come funzionano i proxy, leggi qui. Per la nostra implementazione abbiamo utilizzato apply metodo qui.

Grazie per aver letto questo post, spero che abbia aiutato qualcuno a lavorare con SQLite in JavaScript 👊