Problemet med Booleans i SQLite
Om du någonsin har arbetat med SQLite bör du vara medveten om vilka datatyper som stöds och Boolean
är inte en av dem. Mer specifikt som det står här:
2.1. boolesk datatyp
SQLite har inte en separat boolesk lagringsklass. Istället lagras booleska värden som heltal 0 (falskt) och 1 (sant).
SQLite känner igen nyckelorden "TRUE" och "FALSE", från och med version 3.23.0 (2018-04-02), men dessa nyckelord är egentligen bara alternativa stavningar för heltalsliteralerna 1 respektive 0.
De flesta JavaScript-bibliotek för SQLite3 stöder inte TRUE
och FALSE
nyckelord och de kräver att du förbereder påståendena i din kod med hjälp av heltal. Till exempel, i better-sqlite3 skulle du behöva göra detta:
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;
Använder number
istället för boolean
över hela din app skulle ge en fruktansvärd utvecklarupplevelse (plus förmodligen använda mer minne).
Du kan använda en hjälpfunktion för att transformera dina nyttolastobjekts boolean egenskaper till nummer (Jag hade faktiskt gjort det här en gång tidigare), men då måste du köra det manuellt innan varje fråga. Usch. Skulle det inte vara bra om denna logik kördes i bakgrunden, varje gång vi förberedde och körde ett uttalande?
Välkommen ES6 Proxies 👋
En av de nyare JavaScript-funktionerna är Proxy
objekt. Proxies är i huvudsak "fällor" som fångar upp objektoperationer som getters, seters och funktionsanrop. Använda Proxies vi kan modifiera SQLite JS wrapper-biblioteket för att exekvera vår egen logik, ungefär som en mellanprogramvara.
Skriva hjälpfunktionen
För att underlätta utvecklingen kommer vi att använda mapValues
&isPlainObject
verktygsfunktioner från lodash , men du kan givetvis koda din egen. Funktionen nedan kommer att mappa genom ett objekt (en nivå djupt) och konvertera värden av typen boolean
för att skriva number
.
import { mapValues } from 'lodash';
const booleanEntriesToNumbers = (object) =>
mapValues(object, (value) =>
typeof value === 'boolean' ? Number(value) : value
);
Använda proxyservrar för att avlyssna frågeanrop
Nedan importerar vi better-sqlite3
biblioteket och skapa en ny databasinstans. Efteråt åsidosätter vi standard prepare
metod med vår egen, som i sin tur åsidosätter metoderna run
, get
och all
genom att skapa en ny proxy för var och en. Du kan naturligtvis skapa en proxy för vilken annan metod du vill.
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;
I huvudsak, en gång ett samtal till prepare
metoden utlöses säger vi till JavaScript:Vänta! Vi vill modifiera detta funktionsanrop. Istället för att köra den logik som den ursprungliga utvecklaren avsåg, vill vi istället köra vår egen logik först (som är kartläggningen av objektets nyttolast). Efter att ha kört vår egen logik returnerar vi resultatet av att anropa den ursprungliga metoden genom att använda call
för att binda this
argument. Vill du läsa mer om hur proxy fungerar, läs här. För vår implementering använde vi apply
metod här.
Tack för att du läste det här inlägget, jag hoppas att det hjälpte någon som arbetar med SQLite i JavaScript 👊