question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Fail to cast boolean column: SQLite3 can only bind numbers, strings, bigints, buffers, and null

See original GitHub issue

I got that error because one table has a ~date~ boolean column, the following is the code that I’m using

import type { ColumnType, Generated, Insertable, Selectable } from 'kysely';

export interface TaskTable {
  id: Generated<string>;
  title: string;
  completed: boolean;
  createdAt: ColumnType<Date, string | undefined, never>;
}

export type Task = Selectable<TaskTable>;

export type CreateTask = Pick<Insertable<TaskTable>, 'title'>
import Database from 'better-sqlite3';
import { Kysely, SqliteDialect } from 'kysely';

export interface DB {
  tasks: TaskTable;
}

export const db = new Kysely<DB>({
  dialect: new SqliteDialect({
    database: new Database('kysely.db'),
  }),
});
import { randomUUID } from 'node:crypto';

export class TaskService {
  constructor(private readonly db: Kysely<DB>) {}

  async create(newTask: CreateTask): Promise<Task> {
    const task = await this.db /* The error was thrown here */
      .insertInto('tasks')
      .values({
        id: randomUUID(),
        title: newTask.title,
        completed: false,
      })
      .returningAll()
      .executeTakeFirstOrThrow();

    return task;
  }

  async findAll(): Promise<Task[]> {
    return this.db.selectFrom('tasks').selectAll().execute();
  }
}
-- The DDL
CREATE TABLE tasks (
    id VARCHAR (36) PRIMARY KEY NOT NULL,
    title TEXT CHECK (ifnull(length(title), 0) > 0) NOT NULL,
    completed BOOLEAN DEFAULT (false) NOT NULL,
    createdAt DATETIME DEFAULT (datetime('now', 'localtime')) NOT NULL
);
Stacktrace
TypeError: SQLite3 can only bind numbers, strings, bigints, buffers, and null
    at SqliteConnection.executeQuery (/home/user/experimenting-with-kysely/node_modules/.pnpm/kysely@0.19.12/node_modules/kysely/dist/cjs/dialect/sqlite/sqlite-driver.js:56:28)
    at /home/user/experimenting-with-kysely/node_modules/.pnpm/kysely@0.19.12/node_modules/kysely/dist/cjs/query-executor/query-executor-base.js:36:45
    at DefaultConnectionProvider.provideConnection (/home/user/experimenting-with-kysely/node_modules/.pnpm/kysely@0.19.12/node_modules/kysely/dist/cjs/driver/default-connection-provider.js:12:26)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async DefaultQueryExecutor.executeQuery (/home/user/experimenting-with-kysely/node_modules/.pnpm/kysely@0.19.12/node_modules/kysely/dist/cjs/query-executor/query-executor-base.js:35:16)
    at async InsertQueryBuilder.execute (/home/user/experimenting-with-kysely/node_modules/.pnpm/kysely@0.19.12/node_modules/kysely/dist/cjs/query-builder/insert-query-builder.js:418:24)
    at async InsertQueryBuilder.executeTakeFirst (/home/user/experimenting-with-kysely/node_modules/.pnpm/kysely@0.19.12/node_modules/kysely/dist/cjs/query-builder/insert-query-builder.js:431:26)
    at async InsertQueryBuilder.executeTakeFirstOrThrow (/home/user/experimenting-with-kysely/node_modules/.pnpm/kysely@0.19.12/node_modules/kysely/dist/cjs/query-builder/insert-query-builder.js:443:24)
    at async TaskService.create (/home/user/experimenting-with-kysely/src/services/TaskService.ts:12:18)
    at async TaskController.create (/home/user/experimenting-with-kysely/src/controllers/TaskController.ts:10:18)

Is this error caused by a limitation of the driver?

  • Yes

how can I cast the boolean to number and back to boolean using Kysely?

Dependency versions

better-sqlite3 v7.6.2 kysely v0.19.12 typescript v4.7.4

OS

Manjaro 21.3.4 x86_64 Node.js 16.15.1

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
steidacommented, Jul 25, 2022

@igalklebanov I use branded types SqliteBoolean and SqliteDateTime with cast helper.

/**
 * SQLite has no Boolean datatype. Use cast(true|false|SqliteBoolean).
 * https://www.sqlite.org/quirks.html#no_separate_boolean_datatype
 */
export const SqliteBoolean = z
  .number()
  .refine(brand("SqliteBoolean", (n) => n === 0 || n === 1));
export type SqliteBoolean = z.infer<typeof SqliteBoolean>;

/**
 * SQLite has no DateTime datatype. Use cast(new Date()|SqliteDateTime).
 * https://www.sqlite.org/quirks.html#no_separate_datetime_datatype
 */
export const SqliteDateTime = z
  .string()
  .refine(brand("SqliteDateTime", (s) => !isNaN(new Date(s).getTime())));
export type SqliteDateTime = z.infer<typeof SqliteDateTime>;

export function cast(value: boolean): SqliteBoolean;
export function cast(value: SqliteBoolean): boolean;
export function cast(value: Date): SqliteDateTime;
export function cast(value: SqliteDateTime): Date;
export function cast(
  value: boolean | SqliteBoolean | Date | SqliteDateTime
): boolean | SqliteBoolean | Date | SqliteDateTime {
  if (typeof value === "boolean")
    return (value === true ? 1 : 0) as SqliteBoolean;
  if (typeof value === "number") return value === 1;
  if (value instanceof Date) return value.toISOString() as SqliteDateTime;
  return new Date(value);
}
1reaction
koskimascommented, Jul 25, 2022

This already does the conversion in the other direction:

export class SqliteBooleanPlugin implements KyselyPlugin {
  readonly #transformer = new SqliteBooleanTransformer()

  transformQuery(args: PluginTransformQueryArgs): RootOperationNode {
    return this.#transformer.transformNode(args.node)
  }

  transformResult(
    args: PluginTransformResultArgs
  ): Promise<QueryResult<UnknownRow>> {
    return Promise.resolve(args.result)
  }
}

class SqliteBooleanTransformer extends OperationNodeTransformer {
  transformValue(node: ValueNode): ValueNode {
    return {
      ...super.transformValue(node),
      value: typeof node.value === 'boolean' ? (node.value ? 1 : 0) : node.value
    }
  }
}

The other direction is the tricky one.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Error when passing javascript true/false as sqlite parameters
Fail to cast boolean column: SQLite3 can only bind numbers, strings, bigints, buffers, and null koskimas/kysely#123.
Read more >
sqlite3 can only bind numbers, strings, bigints, buffers, and null ...
Describe the bug When I want to change the value of a boolean field in the sqlite3 database, I encounter the following error:...
Read more >
Bind Boolean value in Sqlite - Stack Overflow
SQLite does not distinguish booleans from ints. So you can just use bind_int. The only real storage classes are NULL, INTEGER, REAL, TEXT, ......
Read more >
Bug when converting string to boolean? - SQLite Forum
It is neither a NULL. I expected it should implicitly convert the string to a number/Boolean when I compare it to a number/Boolean....
Read more >
SQLiteODBC Documentation - ch-werner.de
This installs the SQLite ODBC driver and creates a System DSN. ... default empty OEMCP (boolean) Win32 only: if true, translate strings from/to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found