diff --git a/Cargo.lock b/Cargo.lock index 0d02d44f..cdb03085 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2353,7 +2353,7 @@ dependencies = [ [[package]] name = "libsql" version = "0.1.11" -source = "git+https://github.com/tursodatabase/libsql.git?rev=6f5d54374d91f78510870a653af46f385dd67fac#6f5d54374d91f78510870a653af46f385dd67fac" +source = "git+https://github.com/tursodatabase/libsql?rev=8847ca05c#8847ca05cf2df9811d8151230b008850694ebe31" dependencies = [ "anyhow", "async-trait", @@ -2411,7 +2411,7 @@ dependencies = [ [[package]] name = "libsql-sys" version = "0.2.14" -source = "git+https://github.com/tursodatabase/libsql.git?rev=6f5d54374d91f78510870a653af46f385dd67fac#6f5d54374d91f78510870a653af46f385dd67fac" +source = "git+https://github.com/tursodatabase/libsql?rev=8847ca05c#8847ca05cf2df9811d8151230b008850694ebe31" dependencies = [ "bindgen 0.66.1", "cc", diff --git a/sqld-libsql-bindings/src/lib.rs b/sqld-libsql-bindings/src/lib.rs index 89aa8e1e..4cbf4ab1 100644 --- a/sqld-libsql-bindings/src/lib.rs +++ b/sqld-libsql-bindings/src/lib.rs @@ -111,7 +111,7 @@ impl Connection { /// /// # Safety /// The caller is responsible for the returned pointer. - pub unsafe fn handle(&mut self) -> *mut sqlite3 { + pub unsafe fn handle(&self) -> *mut sqlite3 { self.conn.handle() } } diff --git a/sqld/Cargo.toml b/sqld/Cargo.toml index 69d77a0a..9ef5e1d2 100644 --- a/sqld/Cargo.toml +++ b/sqld/Cargo.toml @@ -69,7 +69,7 @@ hyper-rustls = { git = "https://github.com/rustls/hyper-rustls.git", rev = "163b rustls-pemfile = "1.0.3" rustls = "0.21.7" async-stream = "0.3.5" -libsql = { git = "https://github.com/tursodatabase/libsql.git", rev = "6f5d54374d91f78510870a653af46f385dd67fac", optional = true } +libsql = { git = "https://github.com/tursodatabase/libsql", rev = "8847ca05c", optional = true } metrics = "0.21.1" metrics-exporter-prometheus = "0.12.1" diff --git a/sqld/proto/proxy.proto b/sqld/proto/proxy.proto index 065c95a2..87da8f96 100644 --- a/sqld/proto/proxy.proto +++ b/sqld/proto/proxy.proto @@ -42,6 +42,7 @@ message Error { ErrorCode code = 1; string message = 2; + int32 extended_code = 3; } message ResultRows { diff --git a/sqld/src/connection/libsql.rs b/sqld/src/connection/libsql.rs index 25dd4a6b..69ad2868 100644 --- a/sqld/src/connection/libsql.rs +++ b/sqld/src/connection/libsql.rs @@ -374,7 +374,7 @@ impl Connection { current_frame_no_receiver: watch::Receiver>, state: Arc>, ) -> Result { - let mut conn = open_conn( + let conn = open_conn( path, wal_methods, hook_ctx, @@ -519,7 +519,14 @@ impl Connection { match self.execute_query(&step.query, builder) { // builder error interupt the execution of query. we should exit immediately. Err(e @ Error::BuilderError(_)) => return Err(e), - Err(e) => { + Err(mut e) => { + if let Error::RusqliteError(err) = e { + let extended_code = + unsafe { rusqlite::ffi::sqlite3_extended_errcode(self.conn.handle()) }; + + e = Error::RusqliteErrorExtended(err, extended_code as i32); + }; + builder.step_error(e)?; enabled = false; (0, None) @@ -570,6 +577,7 @@ impl Connection { .map_err(Error::LibSqlInvalidQueryParams)?; let mut qresult = stmt.raw_query(); + builder.begin_rows()?; while let Some(row) = qresult.next()? { builder.begin_row()?; diff --git a/sqld/src/error.rs b/sqld/src/error.rs index 5343d8db..342bb52b 100644 --- a/sqld/src/error.rs +++ b/sqld/src/error.rs @@ -20,6 +20,8 @@ pub enum Error { IOError(#[from] std::io::Error), #[error(transparent)] RusqliteError(#[from] rusqlite::Error), + #[error("{0}")] + RusqliteErrorExtended(rusqlite::Error, i32), #[error("Failed to execute query via RPC. Error code: {}, message: {}", .0.code, .0.message)] RpcQueryError(crate::rpc::proxy::rpc::Error), #[error("Failed to execute queries via RPC protocol: `{0}`")] @@ -106,6 +108,7 @@ impl IntoResponse for Error { LibSqlTxBusy => self.format_err(StatusCode::TOO_MANY_REQUESTS), IOError(_) => self.format_err(StatusCode::INTERNAL_SERVER_ERROR), RusqliteError(_) => self.format_err(StatusCode::INTERNAL_SERVER_ERROR), + RusqliteErrorExtended(_, _) => self.format_err(StatusCode::INTERNAL_SERVER_ERROR), RpcQueryError(_) => self.format_err(StatusCode::BAD_REQUEST), RpcQueryExecutionError(_) => self.format_err(StatusCode::INTERNAL_SERVER_ERROR), DbValueError(_) => self.format_err(StatusCode::BAD_REQUEST), diff --git a/sqld/src/rpc/proxy.rs b/sqld/src/rpc/proxy.rs index 5ba004a7..9ef12425 100644 --- a/sqld/src/rpc/proxy.rs +++ b/sqld/src/rpc/proxy.rs @@ -37,20 +37,23 @@ pub mod rpc { impl From for Error { fn from(other: SqldError) -> Self { - Error { - message: other.to_string(), - code: ErrorCode::from(other).into(), - } - } - } - - impl From for ErrorCode { - fn from(other: SqldError) -> Self { - match other { + let code = match other { SqldError::LibSqlInvalidQueryParams(_) => ErrorCode::SqlError, SqldError::LibSqlTxTimeout => ErrorCode::TxTimeout, SqldError::LibSqlTxBusy => ErrorCode::TxBusy, _ => ErrorCode::Internal, + }; + + let extended_code = if let SqldError::RusqliteErrorExtended(_, code) = &other { + *code + } else { + 0 + }; + + Error { + message: other.to_string(), + code: code as i32, + extended_code, } } } diff --git a/sqld/tests/embedded_replica/mod.rs b/sqld/tests/embedded_replica/mod.rs index 1a6c3d11..309073f3 100644 --- a/sqld/tests/embedded_replica/mod.rs +++ b/sqld/tests/embedded_replica/mod.rs @@ -6,6 +6,7 @@ use libsql::Database; use serde_json::json; use sqld::config::{AdminApiConfig, RpcServerConfig, UserApiConfig}; use turmoil::{Builder, Sim}; +use uuid::Uuid; fn make_primary(sim: &mut Sim, path: PathBuf) { init_tracing(); @@ -21,6 +22,7 @@ fn make_primary(sim: &mut Sim, path: PathBuf) { admin_api_config: Some(AdminApiConfig { acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await?, connector: TurmoilConnector, + disable_metrics: false, }), rpc_server_config: Some(RpcServerConfig { acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await?, @@ -42,7 +44,11 @@ fn make_primary(sim: &mut Sim, path: PathBuf) { fn embedded_replica() { let mut sim = Builder::new().build(); - let tmp = std::env::temp_dir(); + let tmp_dir_name = Uuid::new_v4().simple().to_string(); + + let tmp = std::env::temp_dir().join(tmp_dir_name); + + tracing::debug!("tmp dir: {:?}", tmp); // We need to ensure that libsql's init code runs before we do anything // with rusqlite in sqld. This is because libsql has saftey checks and @@ -71,9 +77,29 @@ fn embedded_replica() { TurmoilConnector, )?; - let n = db.sync().await.unwrap(); + let n = db.sync().await?; + assert_eq!(n, 0); + + let conn = db.connect()?; + + conn.execute("CREATE TABLE user (id INTEGER NOT NULL PRIMARY KEY)", ()) + .await?; + + let n = db.sync().await?; assert_eq!(n, 2); + let err = conn + .execute("INSERT INTO user(id) VALUES (1), (1)", ()) + .await + .unwrap_err(); + + let libsql::Error::RemoteSqliteFailure(code, extended_code, _) = err else { + panic!() + }; + + assert_eq!(code, 3); + assert_eq!(extended_code, 1555); + Ok(()) });