58 lines
2.1 KiB
Markdown
58 lines
2.1 KiB
Markdown
---
|
|
title: "SQLite: IS NOT NULL AS Boolean"
|
|
aliases: [sqlite-not-null-boolean, sqlite-derived-boolean]
|
|
tags: [sqlite, sql, security, pattern]
|
|
sources: [sandbox]
|
|
created: 2026-05-05
|
|
updated: 2026-05-05
|
|
---
|
|
|
|
# SQLite: IS NOT NULL AS Boolean
|
|
|
|
When a column contains sensitive data (e.g. a password hash), never select it directly to answer a yes/no question. Use `(column IS NOT NULL) AS flag` to return a derived boolean instead.
|
|
|
|
## Key Takeaways
|
|
|
|
- `(password_hash IS NOT NULL) AS has_password` → returns `1` or `0`, never the hash
|
|
- Hash never leaves the database layer — client gets the correct boolean
|
|
- Standard pattern for any column that must stay server-side
|
|
|
|
## Pattern
|
|
|
|
```sql
|
|
-- BAD: leaks the hash to the application layer
|
|
SELECT id, email, password_hash FROM users WHERE id = ?;
|
|
|
|
-- GOOD: returns a boolean, hash stays in DB
|
|
SELECT id, email, (password_hash IS NOT NULL) AS has_password FROM users WHERE id = ?;
|
|
```
|
|
|
|
In SQLite, the expression evaluates to `1` (true) or `0` (false). Map to a JS/Python boolean at the application layer:
|
|
|
|
```js
|
|
// Node.js / better-sqlite3
|
|
const user = db.prepare(`
|
|
SELECT id, email, (password_hash IS NOT NULL) AS has_password
|
|
FROM users WHERE id = ?
|
|
`).get(userId);
|
|
|
|
// SQLite returns 1/0; coerce to boolean
|
|
user.hasPassword = Boolean(user.has_password);
|
|
```
|
|
|
|
## Bug This Fixes
|
|
|
|
The `userPublic()` helper was selecting `u.password_hash` directly. The result object included the raw hash — or, worse, `undefined` when the column was NULL — causing `hasPassword` to always evaluate to `false` (because the hash value was truthy but the field name wasn't mapped correctly).
|
|
|
|
Fix: replace the direct column with `(password_hash IS NOT NULL) AS has_password`.
|
|
|
|
## Generalisation
|
|
|
|
The same pattern applies to any secret or large binary:
|
|
- `(api_key IS NOT NULL) AS has_api_key`
|
|
- `(avatar_blob IS NOT NULL) AS has_avatar`
|
|
- `(refresh_token IS NOT NULL) AS is_linked`
|
|
|
|
## Related
|
|
|
|
- [[wiki/concepts/fastapi-response-model-silent-field-strip|fastapi-response-model-silent-field-strip]] — similar idea: strip sensitive fields at the serialization layer
|