From f16ec5dbfc944d4d8a0dc27f37460993dbebdd52 Mon Sep 17 00:00:00 2001 From: Davis Riedel Date: Fri, 21 Feb 2025 14:02:53 +0100 Subject: [PATCH 1/2] Add driver for native Bun SQL --- README.md | 24 +- examples/bun-sql/.gitignore | 176 ++++++++ examples/bun-sql/README.md | 15 + examples/bun-sql/bun.lock | 25 ++ examples/bun-sql/package.json | 11 + examples/bun-sql/src/db/query_sql.ts | 99 +++++ examples/bun-sql/src/main.ts | 46 ++ examples/bun-sql/tsconfig.json | 22 + examples/sqlc.dev.yaml | 9 + examples/sqlc.yaml | 11 +- src/app.ts | 5 +- src/drivers/bun-sql.ts | 622 +++++++++++++++++++++++++++ 12 files changed, 1061 insertions(+), 4 deletions(-) create mode 100644 examples/bun-sql/.gitignore create mode 100644 examples/bun-sql/README.md create mode 100644 examples/bun-sql/bun.lock create mode 100644 examples/bun-sql/package.json create mode 100644 examples/bun-sql/src/db/query_sql.ts create mode 100644 examples/bun-sql/src/main.ts create mode 100644 examples/bun-sql/tsconfig.json create mode 100644 src/drivers/bun-sql.ts diff --git a/README.md b/README.md index 5798b73..b151fb4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ sql: ## Supported engines and drivers -- PostgreSQL via [pg](https://www.npmjs.com/package/pg) or [postgres](https://www.npmjs.com/package/postgres). +- PostgreSQL via [pg](https://www.npmjs.com/package/pg), [postgres](https://www.npmjs.com/package/postgres) or [Bun SQL](https://bun.sh/docs/api/sql). - MySQL via [mysql2](https://www.npmjs.com/package/mysql2). - SQLite via [sqlite3](https://www.npmjs.com/package/better-sqlite3). @@ -291,6 +291,26 @@ sql: driver: postgres # npm package name ``` +### PostgreSQL and Bun SQL + +```yaml +version: '2' +plugins: +- name: ts + wasm: + url: https://downloads.sqlc.dev/plugin/sqlc-gen-typescript_0.1.4.wasm + sha256: e8628d800e9c48197e8cbbe289c74839f869cb282b2717ffc85eb622e81a036c +sql: +- schema: "schema.sql" + queries: "query.sql" + engine: postgresql + codegen: + - out: db + plugin: ts + options: + runtime: bun + driver: bun-sql # to use native SQL library of Bun 1.2 +``` ### MySQL and mysql2 @@ -390,4 +410,4 @@ Check the `Makefile` for details. ``` For more details on sqlc development, refer to the sqlc core development guide. This guide provides additional information on setting up and working with sqlc in general, which may be useful for contributors to this project. -https://docs.sqlc.dev/en/latest/guides/development.html \ No newline at end of file +https://docs.sqlc.dev/en/latest/guides/development.html diff --git a/examples/bun-sql/.gitignore b/examples/bun-sql/.gitignore new file mode 100644 index 0000000..ab5afb2 --- /dev/null +++ b/examples/bun-sql/.gitignore @@ -0,0 +1,176 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + diff --git a/examples/bun-sql/README.md b/examples/bun-sql/README.md new file mode 100644 index 0000000..ca7d54c --- /dev/null +++ b/examples/bun-sql/README.md @@ -0,0 +1,15 @@ +# bun-postgres + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run src/main.ts +``` + +This project was created using `bun init` in bun v1.0.11. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/examples/bun-sql/bun.lock b/examples/bun-sql/bun.lock new file mode 100644 index 0000000..5cdc8ee --- /dev/null +++ b/examples/bun-sql/bun.lock @@ -0,0 +1,25 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "bun-postgres", + "devDependencies": { + "bun-types": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@22.13.4", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg=="], + + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + + "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], + + "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + } +} diff --git a/examples/bun-sql/package.json b/examples/bun-sql/package.json new file mode 100644 index 0000000..423ee48 --- /dev/null +++ b/examples/bun-sql/package.json @@ -0,0 +1,11 @@ +{ + "name": "bun-postgres", + "module": "src/main.ts", + "type": "module", + "devDependencies": { + "bun-types": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/examples/bun-sql/src/db/query_sql.ts b/examples/bun-sql/src/db/query_sql.ts new file mode 100644 index 0000000..fa977f5 --- /dev/null +++ b/examples/bun-sql/src/db/query_sql.ts @@ -0,0 +1,99 @@ +// Code generated by sqlc. DO NOT EDIT. + +import { SQL } from "bun"; + +export const getAuthorQuery = `-- name: GetAuthor :one +SELECT id, name, bio FROM authors +WHERE id = $1 LIMIT 1`; + +export interface GetAuthorArgs { + id: string; +} + +export interface GetAuthorRow { + id: string; + name: string; + bio: string | null; +} + +export async function getAuthor(sql: SQL, args: GetAuthorArgs): Promise { + const rows = await sql.unsafe(getAuthorQuery, [args.id]).values(); + if (rows.length !== 1) { + return null; + } + const row = rows[0]; + if (!row) { + return null; + } + return { + id: row[0], + name: row[1], + bio: row[2] + }; +} + +export const listAuthorsQuery = `-- name: ListAuthors :many +SELECT id, name, bio FROM authors +ORDER BY name`; + +export interface ListAuthorsRow { + id: string; + name: string; + bio: string | null; +} + +export async function listAuthors(sql: SQL): Promise { + return (await sql.unsafe(listAuthorsQuery, []).values()).map(row => ({ + id: row[0], + name: row[1], + bio: row[2] + })); +} + +export const createAuthorQuery = `-- name: CreateAuthor :one +INSERT INTO authors ( + name, bio +) VALUES ( + $1, $2 +) +RETURNING id, name, bio`; + +export interface CreateAuthorArgs { + name: string; + bio: string | null; +} + +export interface CreateAuthorRow { + id: string; + name: string; + bio: string | null; +} + +export async function createAuthor(sql: SQL, args: CreateAuthorArgs): Promise { + const rows = await sql.unsafe(createAuthorQuery, [args.name, args.bio]).values(); + if (rows.length !== 1) { + return null; + } + const row = rows[0]; + if (!row) { + return null; + } + return { + id: row[0], + name: row[1], + bio: row[2] + }; +} + +export const deleteAuthorQuery = `-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = $1`; + +export interface DeleteAuthorArgs { + id: string; +} + +export async function deleteAuthor(sql: SQL, args: DeleteAuthorArgs): Promise { + await sql.unsafe(deleteAuthorQuery, [args.id]); +} + diff --git a/examples/bun-sql/src/main.ts b/examples/bun-sql/src/main.ts new file mode 100644 index 0000000..eca4612 --- /dev/null +++ b/examples/bun-sql/src/main.ts @@ -0,0 +1,46 @@ +import { SQL } from "bun"; + +import { + createAuthor, + deleteAuthor, + getAuthor, + listAuthors, +} from "./db/query_sql"; + +async function main() { + const sql = new SQL(); // You can also specify connection options here + + // Create an author + const author = await createAuthor(sql, { + name: "Seal", + bio: "Kissed from a rose", + }); + if (author === null) { + throw new Error("author not created"); + } + console.log(author); + + // List the authors + const authors = await listAuthors(sql); + console.log(authors); + + // Get that author + const seal = await getAuthor(sql, { id: author.id }); + if (seal === null) { + throw new Error("seal not found"); + } + console.log(seal); + + // Delete the author + await deleteAuthor(sql, { id: seal.id }); +} + +(async () => { + try { + await main(); + process.exit(0); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/examples/bun-sql/tsconfig.json b/examples/bun-sql/tsconfig.json new file mode 100644 index 0000000..7556e1d --- /dev/null +++ b/examples/bun-sql/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +} diff --git a/examples/sqlc.dev.yaml b/examples/sqlc.dev.yaml index f622ec1..3b1049f 100644 --- a/examples/sqlc.dev.yaml +++ b/examples/sqlc.dev.yaml @@ -40,6 +40,15 @@ sql: options: runtime: bun driver: postgres +- schema: "authors/postgresql/schema.sql" + queries: "authors/postgresql/query.sql" + engine: "postgresql" + codegen: + - plugin: ts + out: bun-sql/src/db + options: + runtime: bun + driver: bun-sql - schema: "authors/mysql/schema.sql" queries: "authors/mysql/query.sql" engine: "mysql" diff --git a/examples/sqlc.yaml b/examples/sqlc.yaml index e02996d..288e51e 100644 --- a/examples/sqlc.yaml +++ b/examples/sqlc.yaml @@ -41,6 +41,15 @@ sql: options: runtime: bun driver: postgres +- schema: "authors/postgresql/schema.sql" + queries: "authors/postgresql/query.sql" + engine: "postgresql" + codegen: + - plugin: ts + out: bun-sql/src/db + options: + runtime: bun + driver: bun-sql - schema: "authors/mysql/schema.sql" queries: "authors/mysql/query.sql" engine: "mysql" @@ -58,4 +67,4 @@ sql: out: bun-mysql2/src/db options: runtime: bun - driver: mysql2 \ No newline at end of file + driver: mysql2 diff --git a/src/app.ts b/src/app.ts index e3a077c..3517c0f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,7 +5,6 @@ import { readFileSync, writeFileSync, STDIO } from "javy/fs"; import { EmitHint, - FunctionDeclaration, NewLineKind, TypeNode, ScriptKind, @@ -31,6 +30,7 @@ import { argName, colName } from "./drivers/utlis"; import { Driver as Sqlite3Driver } from "./drivers/better-sqlite3"; import { Driver as PgDriver } from "./drivers/pg"; import { Driver as PostgresDriver } from "./drivers/postgres"; +import { Driver as BunSqlDriver } from "./drivers/bun-sql"; import { Mysql2Options, Driver as MysqlDriver } from "./drivers/mysql2"; // Read input from stdin @@ -90,6 +90,9 @@ function createNodeGenerator(options: Options): Driver { case "postgres": { return new PostgresDriver(); } + case "bun-sql": { + return new BunSqlDriver(); + } case "better-sqlite3": { return new Sqlite3Driver(); } diff --git a/src/drivers/bun-sql.ts b/src/drivers/bun-sql.ts new file mode 100644 index 0000000..240b2c8 --- /dev/null +++ b/src/drivers/bun-sql.ts @@ -0,0 +1,622 @@ +import { + SyntaxKind, + NodeFlags, + TypeNode, + factory, + FunctionDeclaration, +} from "typescript"; + +import { Parameter, Column } from "../gen/plugin/codegen_pb"; +import { argName, colName } from "./utlis"; +import { log } from "../logger"; + +function funcParamsDecl(iface: string | undefined, params: Parameter[]) { + let funcParams = [ + factory.createParameterDeclaration( + undefined, + undefined, + factory.createIdentifier("sql"), + undefined, + factory.createTypeReferenceNode( + factory.createIdentifier("SQL"), + undefined + ), + undefined + ), + ]; + + if (iface && params.length > 0) { + funcParams.push( + factory.createParameterDeclaration( + undefined, + undefined, + factory.createIdentifier("args"), + undefined, + factory.createTypeReferenceNode( + factory.createIdentifier(iface), + undefined + ), + undefined + ) + ); + } + + return funcParams; +} + +export class Driver { + columnType(column?: Column): TypeNode { + if (column === undefined || column.type === undefined) { + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + // Some of the type names have the `pgcatalog.` prefix. Remove this. + let typeName = column.type.name; + const pgCatalog = "pg_catalog."; + if (typeName.startsWith(pgCatalog)) { + typeName = typeName.slice(pgCatalog.length); + } + let typ: TypeNode = factory.createKeywordTypeNode(SyntaxKind.StringKeyword); + switch (typeName) { + case "aclitem": { + // string + break; + } + case "bigserial": { + // string + break; + } + case "bit": { + // string + break; + } + case "bool": { + typ = factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword); + break; + } + case "box": { + // string + break; + } + case "bpchar": { + // string + break; + } + case "bytea": { + // TODO: Is this correct or node-specific? + typ = factory.createTypeReferenceNode( + factory.createIdentifier("Buffer"), + undefined + ); + break; + } + case "cid": { + // string + break; + } + case "cidr": { + // string + break; + } + case "circle": { + // string + break; + } + case "date": { + typ = factory.createTypeReferenceNode( + factory.createIdentifier("Date"), + undefined + ); + break; + } + case "float4": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "float8": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "inet": { + // string + break; + } + case "int2": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "int4": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "int8": { + // string + break; + } + case "interval": { + // string + break; + } + case "json": { + typ = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + break; + } + case "jsonb": { + typ = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + break; + } + case "line": { + // string + break; + } + case "lseg": { + // string + break; + } + case "madaddr": { + // string + break; + } + case "madaddr8": { + // string + break; + } + case "money": { + // string + break; + } + case "oid": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "path": { + // string + break; + } + case "pg_node_tree": { + // string + break; + } + case "pg_snapshot": { + // string + break; + } + case "point": { + // string + break; + } + case "polygon": { + // string + break; + } + case "regproc": { + // string + break; + } + case "regrole": { + // string + break; + } + case "serial": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "serial2": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "serial4": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "serial8": { + // string + break; + } + case "smallserial": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "tid": { + // string + break; + } + case "text": { + // string + break; + } + case "time": { + // string + break; + } + case "timetz": { + // string + break; + } + case "timestamp": { + typ = factory.createTypeReferenceNode( + factory.createIdentifier("Date"), + undefined + ); + break; + } + case "timestamptz": { + typ = factory.createTypeReferenceNode( + factory.createIdentifier("Date"), + undefined + ); + break; + } + case "tsquery": { + // string + break; + } + case "tsvector": { + // string + break; + } + case "txid_snapshot": { + // string + break; + } + case "uuid": { + // string + break; + } + case "varbit": { + // string + break; + } + case "varchar": { + // string + break; + } + case "xid": { + // string + break; + } + case "xml": { + // string + break; + } + default: { + log(`unknown type ${column.type?.name}`); + break; + } + } + if (column.isArray || column.arrayDims > 0) { + let dims = Math.max(column.arrayDims || 1); + for (let i = 0; i < dims; i++) { + typ = factory.createArrayTypeNode(typ); + } + } + if (column.notNull) { + return typ; + } + return factory.createUnionTypeNode([ + typ, + factory.createLiteralTypeNode(factory.createNull()), + ]); + } + + preamble(_queries: unknown) { + return [ + factory.createImportDeclaration( + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([ + factory.createImportSpecifier( + false, + undefined, + factory.createIdentifier("SQL") + ), + ]) + ), + factory.createStringLiteral("bun"), + undefined + ), + ]; + } + + execDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + params: Parameter[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createKeywordTypeNode(SyntaxKind.VoidKeyword), + ]), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createAwaitExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("sql"), + factory.createIdentifier("unsafe") + ), + undefined, + [ + factory.createIdentifier(queryName), + factory.createArrayLiteralExpression( + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier(argName(i, param.column)) + ) + ), + false + ), + ] + ) + ) + ), + ], + true + ) + ); + } + + manyDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + returnIface: string, + params: Parameter[], + columns: Column[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createArrayTypeNode( + factory.createTypeReferenceNode( + factory.createIdentifier(returnIface), + undefined + ) + ), + ]), + factory.createBlock( + [ + factory.createReturnStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createAwaitExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("sql"), + factory.createIdentifier("unsafe") + ), + undefined, + [ + factory.createIdentifier(queryName), + factory.createArrayLiteralExpression( + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier( + argName(i, param.column) + ) + ) + ), + false + ), + ] + ), + factory.createIdentifier("values") + ), + undefined, + undefined + ) + ), + factory.createIdentifier("map") + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + "row" + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createObjectLiteralExpression( + columns.map((col, i) => + factory.createPropertyAssignment( + factory.createIdentifier(colName(i, col)), + factory.createElementAccessExpression( + factory.createIdentifier("row"), + factory.createNumericLiteral(`${i}`) + ) + ) + ), + true + ) + ), + ] + ) + ), + ], + true + ) + ); + } + + oneDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + returnIface: string, + params: Parameter[], + columns: Column[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createUnionTypeNode([ + factory.createTypeReferenceNode( + factory.createIdentifier(returnIface), + undefined + ), + factory.createLiteralTypeNode(factory.createNull()), + ]), + ]), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier("rows"), + undefined, + undefined, + factory.createAwaitExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("sql"), + factory.createIdentifier("unsafe") + ), + undefined, + [ + factory.createIdentifier(queryName), + factory.createArrayLiteralExpression( + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier( + argName(i, param.column) + ) + ) + ), + false + ), + ] + ), + factory.createIdentifier("values") + ), + undefined, + undefined + ) + ) + ), + ], + NodeFlags.Const | + // ts.NodeFlags.Constant | + NodeFlags.AwaitContext | + // ts.NodeFlags.Constant | + NodeFlags.ContextFlags | + NodeFlags.TypeExcludesFlags + ) + ), + factory.createIfStatement( + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("rows"), + factory.createIdentifier("length") + ), + factory.createToken(SyntaxKind.ExclamationEqualsEqualsToken), + factory.createNumericLiteral("1") + ), + factory.createBlock( + [factory.createReturnStatement(factory.createNull())], + true + ), + undefined + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + "row", + undefined, + undefined, + factory.createElementAccessExpression( + factory.createIdentifier("rows"), + factory.createNumericLiteral("0") + ) + ), + ], + NodeFlags.Const + ) + ), + factory.createIfStatement( + factory.createPrefixUnaryExpression( + SyntaxKind.ExclamationToken, + factory.createIdentifier("row") + ), + factory.createBlock( + [factory.createReturnStatement(factory.createNull())], + true + ), + undefined + ), + factory.createReturnStatement( + factory.createObjectLiteralExpression( + columns.map((col, i) => + factory.createPropertyAssignment( + factory.createIdentifier(colName(i, col)), + factory.createElementAccessExpression( + factory.createIdentifier("row"), + factory.createNumericLiteral(`${i}`) + ) + ) + ), + true + ) + ), + ], + true + ) + ); + } + + execlastidDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + params: Parameter[] + ): FunctionDeclaration { + throw new Error("Bun sql driver currently does not support :execlastid"); + } +} From ce7904482e88f47e4465673b42dcf05d734d97a7 Mon Sep 17 00:00:00 2001 From: Davis Riedel Date: Sat, 22 Feb 2025 01:22:40 +0100 Subject: [PATCH 2/2] Add driver for native Bun SQLite --- README.md | 23 +- examples/bun-sql/package.json | 2 +- examples/bun-sqlite/.gitignore | 176 +++++++++ examples/bun-sqlite/README.md | 15 + examples/bun-sqlite/bun.lock | 25 ++ examples/bun-sqlite/package.json | 11 + examples/bun-sqlite/src/db/query_sql.ts | 85 ++++ examples/bun-sqlite/src/main.ts | 46 +++ examples/bun-sqlite/tsconfig.json | 22 ++ examples/node-better-sqlite3/src/main.ts | 6 - examples/sqlc.dev.yaml | 9 + examples/sqlc.yaml | 11 +- src/app.ts | 6 +- src/drivers/bun-sqlite.ts | 475 +++++++++++++++++++++++ 14 files changed, 902 insertions(+), 10 deletions(-) create mode 100644 examples/bun-sqlite/.gitignore create mode 100644 examples/bun-sqlite/README.md create mode 100644 examples/bun-sqlite/bun.lock create mode 100644 examples/bun-sqlite/package.json create mode 100644 examples/bun-sqlite/src/db/query_sql.ts create mode 100644 examples/bun-sqlite/src/main.ts create mode 100644 examples/bun-sqlite/tsconfig.json create mode 100644 src/drivers/bun-sqlite.ts diff --git a/README.md b/README.md index b151fb4..d94ec1c 100644 --- a/README.md +++ b/README.md @@ -299,7 +299,7 @@ plugins: - name: ts wasm: url: https://downloads.sqlc.dev/plugin/sqlc-gen-typescript_0.1.4.wasm - sha256: e8628d800e9c48197e8cbbe289c74839f869cb282b2717ffc85eb622e81a036c + sha256: 9b2f4eca095a3ba1f0f5e971e371ae52f991396efdb63728fc3e8df92c31ff5f sql: - schema: "schema.sql" queries: "query.sql" @@ -354,6 +354,27 @@ sql: driver: better-sqlite3 # npm package name ``` +### SQLite and Bun SQLite + +```yaml +version: '2' +plugins: +- name: ts + wasm: + url: https://downloads.sqlc.dev/plugin/sqlc-gen-typescript_0.1.4.wasm + sha256: 9b2f4eca095a3ba1f0f5e971e371ae52f991396efdb63728fc3e8df92c31ff5f +sql: +- schema: "schema.sql" + queries: "query.sql" + engine: sqlite + codegen: + - out: db + plugin: ts + options: + runtime: bun + driver: bun-sqlite # to use native SQLite library of Bun +``` + ## Development If you want to build and test sqlc-gen-typescript locally, follow these steps: diff --git a/examples/bun-sql/package.json b/examples/bun-sql/package.json index 423ee48..2fe9069 100644 --- a/examples/bun-sql/package.json +++ b/examples/bun-sql/package.json @@ -1,5 +1,5 @@ { - "name": "bun-postgres", + "name": "bun-sql", "module": "src/main.ts", "type": "module", "devDependencies": { diff --git a/examples/bun-sqlite/.gitignore b/examples/bun-sqlite/.gitignore new file mode 100644 index 0000000..ab5afb2 --- /dev/null +++ b/examples/bun-sqlite/.gitignore @@ -0,0 +1,176 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + diff --git a/examples/bun-sqlite/README.md b/examples/bun-sqlite/README.md new file mode 100644 index 0000000..ca7d54c --- /dev/null +++ b/examples/bun-sqlite/README.md @@ -0,0 +1,15 @@ +# bun-postgres + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run src/main.ts +``` + +This project was created using `bun init` in bun v1.0.11. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/examples/bun-sqlite/bun.lock b/examples/bun-sqlite/bun.lock new file mode 100644 index 0000000..39a4371 --- /dev/null +++ b/examples/bun-sqlite/bun.lock @@ -0,0 +1,25 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "bun-sqlite", + "devDependencies": { + "bun-types": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="], + + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + + "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], + + "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + } +} diff --git a/examples/bun-sqlite/package.json b/examples/bun-sqlite/package.json new file mode 100644 index 0000000..2190fd6 --- /dev/null +++ b/examples/bun-sqlite/package.json @@ -0,0 +1,11 @@ +{ + "name": "bun-sqlite", + "module": "src/main.ts", + "type": "module", + "devDependencies": { + "bun-types": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/examples/bun-sqlite/src/db/query_sql.ts b/examples/bun-sqlite/src/db/query_sql.ts new file mode 100644 index 0000000..351ba9c --- /dev/null +++ b/examples/bun-sqlite/src/db/query_sql.ts @@ -0,0 +1,85 @@ +// Code generated by sqlc. DO NOT EDIT. + +import { Database } from "bun:sqlite"; + +export const getAuthorQuery = `-- name: GetAuthor :one +SELECT id, name, bio FROM authors +WHERE id = ? LIMIT 1`; + +export interface GetAuthorArgs { + id: number; +} + +export interface GetAuthorRow { + id: number; + name: string; + bio: string | null; +} + +export async function getAuthor(database: Database, args: GetAuthorArgs): Promise { + const stmt = database.prepare(getAuthorQuery); + const rows = stmt.values(args.id); + if (rows.length !== 1) { + return null; + } + const row = rows[0]; + if (!row) { + return null; + } + return { + id: row[0] as number, + name: row[1] as string, + bio: row[2] as string | null + }; +} + +export const listAuthorsQuery = `-- name: ListAuthors :many +SELECT id, name, bio FROM authors +ORDER BY name`; + +export interface ListAuthorsRow { + id: number; + name: string; + bio: string | null; +} + +export async function listAuthors(database: Database): Promise { + const stmt = database.prepare(listAuthorsQuery); + const rows = stmt.values(); + return rows.map(row => ({ + id: row[0] as number, + name: row[1] as string, + bio: row[2] as string | null + })); +} + +export const createAuthorQuery = `-- name: CreateAuthor :exec +INSERT INTO authors ( + name, bio +) VALUES ( + ?, ? +)`; + +export interface CreateAuthorArgs { + name: string; + bio: string | null; +} + +export async function createAuthor(database: Database, args: CreateAuthorArgs): Promise { + const stmt = database.prepare(createAuthorQuery); + stmt.run(args.name, args.bio); +} + +export const deleteAuthorQuery = `-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = ?`; + +export interface DeleteAuthorArgs { + id: number; +} + +export async function deleteAuthor(database: Database, args: DeleteAuthorArgs): Promise { + const stmt = database.prepare(deleteAuthorQuery); + stmt.run(args.id); +} + diff --git a/examples/bun-sqlite/src/main.ts b/examples/bun-sqlite/src/main.ts new file mode 100644 index 0000000..c01bc08 --- /dev/null +++ b/examples/bun-sqlite/src/main.ts @@ -0,0 +1,46 @@ +import { Database } from "bun:sqlite"; + +import { + createAuthor, + deleteAuthor, + getAuthor, + listAuthors, +} from "./db/query_sql"; + +async function main() { + const ddl = await Bun.file(`${import.meta.dir}/../../authors/sqlite/schema.sql`).text(); + const database = new Database(":memory:"); + + // Create tables + database.exec(ddl); + + // Create an author + await createAuthor(database, { + name: "Seal", + bio: "Kissed from a rose", + }); + + // List the authors + const authors = await listAuthors(database); + console.log(authors); + + // Get that author + const seal = await getAuthor(database, { id: authors[0].id }); + if (seal === null) { + throw new Error("seal not found"); + } + console.log(seal); + + // Delete the author + await deleteAuthor(database, { id: seal.id }); +} + +(async () => { + try { + await main(); + process.exit(0); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/examples/bun-sqlite/tsconfig.json b/examples/bun-sqlite/tsconfig.json new file mode 100644 index 0000000..7556e1d --- /dev/null +++ b/examples/bun-sqlite/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +} diff --git a/examples/node-better-sqlite3/src/main.ts b/examples/node-better-sqlite3/src/main.ts index 5ee0ee4..191c31c 100644 --- a/examples/node-better-sqlite3/src/main.ts +++ b/examples/node-better-sqlite3/src/main.ts @@ -9,12 +9,6 @@ import { listAuthors, } from "./db/query_sql"; -interface Author { - id: string; - name: string; - bio: string | null; -} - async function main() { const ddl = await readFile(join(__dirname, "../../authors/sqlite/schema.sql"), { encoding: 'utf-8' }); const database = new Database(":memory:"); diff --git a/examples/sqlc.dev.yaml b/examples/sqlc.dev.yaml index 3b1049f..5219967 100644 --- a/examples/sqlc.dev.yaml +++ b/examples/sqlc.dev.yaml @@ -79,3 +79,12 @@ sql: options: runtime: node driver: better-sqlite3 +- schema: "authors/sqlite/schema.sql" + queries: "authors/sqlite/query.sql" + engine: "sqlite" + codegen: + - plugin: ts + out: bun-sqlite/src/db + options: + runtime: bun + driver: bun-sqlite diff --git a/examples/sqlc.yaml b/examples/sqlc.yaml index 288e51e..57bb3ed 100644 --- a/examples/sqlc.yaml +++ b/examples/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: ts wasm: url: https://downloads.sqlc.dev/plugin/sqlc-gen-typescript_0.1.3.wasm - sha256: 287df8f6cc06377d67ad5ba02c9e0f00c585509881434d15ea8bd9fc751a9368 + sha256: fc0595e9eb137800bf863de769cd57abd8630ef5ef92f2c9d6faec271ca96d1e sql: - schema: "authors/postgresql/schema.sql" queries: "authors/postgresql/query.sql" @@ -68,3 +68,12 @@ sql: options: runtime: bun driver: mysql2 +- schema: "authors/sqlite/schema.sql" + queries: "authors/sqlite/query.sql" + engine: "sqlite" + codegen: + - plugin: ts + out: bun-sqlite/src/db + options: + runtime: bun + driver: bun-sqlite diff --git a/src/app.ts b/src/app.ts index 3517c0f..a12fb9b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -30,8 +30,9 @@ import { argName, colName } from "./drivers/utlis"; import { Driver as Sqlite3Driver } from "./drivers/better-sqlite3"; import { Driver as PgDriver } from "./drivers/pg"; import { Driver as PostgresDriver } from "./drivers/postgres"; -import { Driver as BunSqlDriver } from "./drivers/bun-sql"; import { Mysql2Options, Driver as MysqlDriver } from "./drivers/mysql2"; +import { Driver as BunSqlDriver } from "./drivers/bun-sql"; +import { Driver as BunSqliteDriver } from "./drivers/bun-sqlite"; // Read input from stdin const input = readInput(); @@ -93,6 +94,9 @@ function createNodeGenerator(options: Options): Driver { case "bun-sql": { return new BunSqlDriver(); } + case "bun-sqlite": { + return new BunSqliteDriver(); + } case "better-sqlite3": { return new Sqlite3Driver(); } diff --git a/src/drivers/bun-sqlite.ts b/src/drivers/bun-sqlite.ts new file mode 100644 index 0000000..c12275e --- /dev/null +++ b/src/drivers/bun-sqlite.ts @@ -0,0 +1,475 @@ +import { + SyntaxKind, + NodeFlags, + Node, + TypeNode, + factory, + FunctionDeclaration, +} from "typescript"; + +import { Parameter, Column, Query } from "../gen/plugin/codegen_pb"; +import { argName, colName } from "./utlis"; + +function funcParamsDecl(iface: string | undefined, params: Parameter[]) { + let funcParams = [ + factory.createParameterDeclaration( + undefined, + undefined, + factory.createIdentifier("database"), + undefined, + factory.createTypeReferenceNode( + factory.createIdentifier("Database"), + undefined + ), + undefined + ), + ]; + + if (iface && params.length > 0) { + funcParams.push( + factory.createParameterDeclaration( + undefined, + undefined, + factory.createIdentifier("args"), + undefined, + factory.createTypeReferenceNode( + factory.createIdentifier(iface), + undefined + ), + undefined + ) + ); + } + + return funcParams; +} + +export class Driver { + columnType(column?: Column): TypeNode { + if (column === undefined || column.type === undefined) { + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + + let typ: TypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + switch (column.type.name.toLowerCase()) { + case "int": + case "integer": + case "tinyint": + case "smallint": + case "mediumint": + case "bigint": + case "unsignedbigint": + case "int2": + case "int8": { + // TODO: Improve `BigInt` handling (https://github.com/WiseLibs/better-sqlite3/blob/v9.4.1/docs/integer.md) + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "varchar": + case "text": { + typ = factory.createKeywordTypeNode(SyntaxKind.StringKeyword); + break; + } + case "blob": { + typ = factory.createTypeReferenceNode( + factory.createIdentifier("Buffer"), + undefined + ); + break; + } + case "real": + case "double": + case "doubleprecision": + case "float": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "date": + case "datetime": { + typ = factory.createKeywordTypeNode(SyntaxKind.StringKeyword); + break; + } + case "boolean": + case "bool": + case "timestamp": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + } + + if (column.notNull) { + return typ; + } + + return factory.createUnionTypeNode([ + typ, + factory.createLiteralTypeNode(factory.createNull()), + ]); + } + + preamble(queries: Query[]) { + const imports: Node[] = [ + factory.createImportDeclaration( + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([ + factory.createImportSpecifier( + false, + undefined, + factory.createIdentifier("Database") + ), + ]) + ), + factory.createStringLiteral("bun:sqlite"), + undefined + ), + ]; + + return imports; + } + + execDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + params: Parameter[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createKeywordTypeNode(SyntaxKind.VoidKeyword), + ]), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier("stmt"), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("database"), + factory.createIdentifier("prepare") + ), + undefined, + [factory.createIdentifier(queryName)] + ) + ), + ], + NodeFlags.Const | NodeFlags.TypeExcludesFlags + ) + ), + factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("stmt"), + factory.createIdentifier("run") + ), + undefined, + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier(argName(i, param.column)) + ) + ) + ) + ), + ], + true + ) + ); + } + + oneDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + returnIface: string, + params: Parameter[], + columns: Column[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createUnionTypeNode([ + factory.createTypeReferenceNode( + factory.createIdentifier(returnIface), + undefined + ), + factory.createLiteralTypeNode(factory.createNull()), + ]), + ]), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier("stmt"), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("database"), + factory.createIdentifier("prepare") + ), + undefined, + [factory.createIdentifier(queryName)] + ) + ), + ], + NodeFlags.Const | NodeFlags.TypeExcludesFlags + ) + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier("rows"), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("stmt"), + factory.createIdentifier("values") + ), + undefined, + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier(argName(i, param.column)) + ) + ) + ) + ), + ], + NodeFlags.Const | + NodeFlags.ContextFlags | + NodeFlags.TypeExcludesFlags + ) + ), + factory.createIfStatement( + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("rows"), + factory.createIdentifier("length") + ), + factory.createToken(SyntaxKind.ExclamationEqualsEqualsToken), + factory.createNumericLiteral("1") + ), + factory.createBlock( + [factory.createReturnStatement(factory.createNull())], + true + ), + undefined + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + "row", + undefined, + undefined, + factory.createElementAccessExpression( + factory.createIdentifier("rows"), + factory.createNumericLiteral("0") + ) + ), + ], + NodeFlags.Const + ) + ), + factory.createIfStatement( + factory.createPrefixUnaryExpression( + SyntaxKind.ExclamationToken, + factory.createIdentifier("row") + ), + factory.createBlock( + [factory.createReturnStatement(factory.createNull())], + true + ), + undefined + ), + factory.createReturnStatement( + factory.createObjectLiteralExpression( + columns.map((col, i) => + factory.createPropertyAssignment( + factory.createIdentifier(colName(i, col)), + factory.createAsExpression( + factory.createElementAccessExpression( + factory.createIdentifier("row"), + factory.createNumericLiteral(`${i}`) + ), + this.columnType(col) + ) + ) + ), + true + ) + ), + ], + true + ) + ); + } + + manyDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + returnIface: string, + params: Parameter[], + columns: Column[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createArrayTypeNode( + factory.createTypeReferenceNode( + factory.createIdentifier(returnIface), + undefined + ) + ), + ]), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier("stmt"), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("database"), + factory.createIdentifier("prepare") + ), + undefined, + [factory.createIdentifier(queryName)] + ) + ), + ], + NodeFlags.Const | NodeFlags.TypeExcludesFlags + ) + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier("rows"), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("stmt"), + factory.createIdentifier("values") + ), + undefined, + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier(argName(i, param.column)) + ) + ) + ) + ), + ], + NodeFlags.Const | + NodeFlags.ContextFlags | + NodeFlags.TypeExcludesFlags + ) + ), + factory.createReturnStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("rows"), + factory.createIdentifier("map") + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + "row" + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createObjectLiteralExpression( + columns.map((col, i) => + factory.createPropertyAssignment( + factory.createIdentifier(colName(i, col)), + factory.createAsExpression( + factory.createElementAccessExpression( + factory.createIdentifier("row"), + factory.createNumericLiteral(`${i}`) + ), + this.columnType(col) + ) + ) + ), + true + ) + ), + ] + ) + ), + ], + true + ) + ); + } + + execlastidDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + params: Parameter[] + ): FunctionDeclaration { + throw new Error( + "Bun sqlite driver currently does not support :execlastid" + ); + } +}