From 4841bfd78a2dc153d2b8e3c6a80b6762b5ff6902 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 29 Jul 2025 14:56:14 +0300 Subject: [PATCH] serverless: Fix Connection.exec() We need to use sequence requests to handle multiple SQL statements for exec(): ``` DatabaseError { message: 'SQL string contains more than one statement', } ``` --- packages/turso-serverless/src/compat.ts | 31 +++++++++++++----- packages/turso-serverless/src/connection.ts | 5 ++- packages/turso-serverless/src/protocol.ts | 11 +++++-- packages/turso-serverless/src/session.ts | 36 ++++++++++++++++++++- 4 files changed, 68 insertions(+), 15 deletions(-) diff --git a/packages/turso-serverless/src/compat.ts b/packages/turso-serverless/src/compat.ts index 3114c2b7a..c5f954389 100644 --- a/packages/turso-serverless/src/compat.ts +++ b/packages/turso-serverless/src/compat.ts @@ -1,4 +1,4 @@ -import { Connection, connect, type Config as TursoConfig } from './connection.js'; +import { Session, type SessionConfig } from './session.js'; import { DatabaseError } from './error.js'; /** @@ -127,17 +127,17 @@ export interface Client { } class LibSQLClient implements Client { - private connection: Connection; + private session: Session; private _closed = false; constructor(config: Config) { this.validateConfig(config); - const tursoConfig: TursoConfig = { + const sessionConfig: SessionConfig = { url: config.url, authToken: config.authToken || '' }; - this.connection = connect(tursoConfig); + this.session = new Session(sessionConfig); } private validateConfig(config: Config): void { @@ -246,8 +246,15 @@ class LibSQLClient implements Client { normalizedStmt = this.normalizeStatement(stmtOrSql); } - const result = await this.connection.exec(normalizedStmt.sql, normalizedStmt.args); - return this.convertResult(result); + await this.session.sequence(normalizedStmt.sql); + // Return empty result set for sequence execution + return this.convertResult({ + columns: [], + columnTypes: [], + rows: [], + rowsAffected: 0, + lastInsertRowid: undefined + }); } catch (error: any) { throw new LibsqlError(error.message, "EXECUTE_ERROR"); } @@ -264,7 +271,7 @@ class LibSQLClient implements Client { return normalized.sql; // For now, ignore args in batch }); - const result = await this.connection.batch(sqlStatements, mode); + const result = await this.session.batch(sqlStatements); // Return array of result sets (simplified - actual implementation would be more complex) return [this.convertResult(result)]; @@ -283,7 +290,15 @@ class LibSQLClient implements Client { } async executeMultiple(sql: string): Promise { - throw new LibsqlError("Execute multiple not implemented", "NOT_IMPLEMENTED"); + try { + if (this._closed) { + throw new LibsqlError("Client is closed", "CLIENT_CLOSED"); + } + + await this.session.sequence(sql); + } catch (error: any) { + throw new LibsqlError(error.message, "EXECUTE_MULTIPLE_ERROR"); + } } async sync(): Promise { diff --git a/packages/turso-serverless/src/connection.ts b/packages/turso-serverless/src/connection.ts index c0231760c..b72149b0a 100644 --- a/packages/turso-serverless/src/connection.ts +++ b/packages/turso-serverless/src/connection.ts @@ -44,7 +44,6 @@ export class Connection { * Execute a SQL statement and return all results. * * @param sql - The SQL statement to execute - * @param args - Optional array of parameter values * @returns Promise resolving to the complete result set * * @example @@ -53,8 +52,8 @@ export class Connection { * console.log(result.rows); * ``` */ - async exec(sql: string, args: any[] = []): Promise { - return this.session.execute(sql, args); + async exec(sql: string): Promise { + return this.session.sequence(sql); } diff --git a/packages/turso-serverless/src/protocol.ts b/packages/turso-serverless/src/protocol.ts index b81348475..07a94e96c 100644 --- a/packages/turso-serverless/src/protocol.ts +++ b/packages/turso-serverless/src/protocol.ts @@ -47,9 +47,14 @@ export interface BatchRequest { }; } +export interface SequenceRequest { + type: 'sequence'; + sql: string; +} + export interface PipelineRequest { baton: string | null; - requests: (ExecuteRequest | BatchRequest)[]; + requests: (ExecuteRequest | BatchRequest | SequenceRequest)[]; } export interface PipelineResponse { @@ -58,8 +63,8 @@ export interface PipelineResponse { results: Array<{ type: 'ok' | 'error'; response?: { - type: 'execute' | 'batch'; - result: ExecuteResult; + type: 'execute' | 'batch' | 'sequence'; + result?: ExecuteResult; }; error?: { message: string; diff --git a/packages/turso-serverless/src/session.ts b/packages/turso-serverless/src/session.ts index f98c623dc..eb401fd03 100644 --- a/packages/turso-serverless/src/session.ts +++ b/packages/turso-serverless/src/session.ts @@ -1,10 +1,13 @@ import { executeCursor, + executePipeline, encodeValue, decodeValue, type CursorRequest, type CursorResponse, - type CursorEntry + type CursorEntry, + type PipelineRequest, + type SequenceRequest } from './protocol.js'; import { DatabaseError } from './error.js'; @@ -214,4 +217,35 @@ export class Session { lastInsertRowid }; } + + /** + * Execute a sequence of SQL statements separated by semicolons. + * + * @param sql - SQL string containing multiple statements separated by semicolons + * @returns Promise resolving when all statements are executed + */ + async sequence(sql: string): Promise { + const request: PipelineRequest = { + baton: this.baton, + requests: [{ + type: "sequence", + sql: sql + } as SequenceRequest] + }; + + const response = await executePipeline(this.baseUrl, this.config.authToken, request); + + this.baton = response.baton; + if (response.base_url) { + this.baseUrl = response.base_url; + } + + // Check for errors in the response + if (response.results && response.results[0]) { + const result = response.results[0]; + if (result.type === "error") { + throw new DatabaseError(result.error?.message || 'Sequence execution failed'); + } + } + } } \ No newline at end of file