Skip to content
This repository has been archived by the owner on Oct 18, 2023. It is now read-only.

Commit

Permalink
sqld: Add extended_code support (#768)
Browse files Browse the repository at this point in the history
* Add embedded replica test

* sqld: Add `extended_code` support
  • Loading branch information
LucioFranco authored Oct 13, 2023
1 parent 170fa4d commit a51ecff
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 16 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sqld-libsql-bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl<W: WalHook> Connection<W> {
///
/// # 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()
}
}
2 changes: 1 addition & 1 deletion sqld/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "bea8863", optional = true }
libsql = { git = "https://github.com/tursodatabase/libsql", rev = "8847ca05c", optional = true }
metrics = "0.21.1"
metrics-exporter-prometheus = "0.12.1"

Expand Down
1 change: 1 addition & 0 deletions sqld/proto/proxy.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ message Error {

ErrorCode code = 1;
string message = 2;
int32 extended_code = 3;
}

message ResultRows {
Expand Down
12 changes: 10 additions & 2 deletions sqld/src/connection/libsql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ impl<W: WalHook> Connection<W> {
current_frame_no_receiver: watch::Receiver<Option<FrameNo>>,
state: Arc<TxnState<W>>,
) -> Result<Self> {
let mut conn = open_conn(
let conn = open_conn(
path,
wal_methods,
hook_ctx,
Expand Down Expand Up @@ -519,7 +519,14 @@ impl<W: WalHook> Connection<W> {
match self.execute_query(&step.query, builder) {
// builder error interrupt 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)
Expand Down Expand Up @@ -570,6 +577,7 @@ impl<W: WalHook> Connection<W> {
.map_err(Error::LibSqlInvalidQueryParams)?;

let mut qresult = stmt.raw_query();

builder.begin_rows()?;
while let Some(row) = qresult.next()? {
builder.begin_row()?;
Expand Down
3 changes: 3 additions & 0 deletions sqld/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}`")]
Expand Down Expand Up @@ -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),
Expand Down
23 changes: 13 additions & 10 deletions sqld/src/rpc/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,23 @@ pub mod rpc {

impl From<SqldError> for Error {
fn from(other: SqldError) -> Self {
Error {
message: other.to_string(),
code: ErrorCode::from(other).into(),
}
}
}

impl From<SqldError> 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,
}
}
}
Expand Down
107 changes: 107 additions & 0 deletions sqld/tests/embedded_replica/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::path::PathBuf;

use crate::common::http::Client;
use crate::common::net::{init_tracing, TestServer, TurmoilAcceptor, TurmoilConnector};
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();
sim.host("primary", move || {
let path = path.clone();
async move {
let server = TestServer {
path: path.into(),
user_api_config: UserApiConfig {
http_acceptor: Some(TurmoilAcceptor::bind(([0, 0, 0, 0], 8080)).await?),
..Default::default()
},
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?,
tls_config: None,
}),
disable_namespaces: false,
disable_default_namespace: true,
..Default::default()
};

server.start().await?;

Ok(())
}
});
}

#[test]
fn embedded_replica() {
let mut sim = Builder::new().build();

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
// needs to configure the sqlite api. Thus if we init sqld first
// it will fail. To work around this we open a temp db in memory db
// to ensure we run libsql's init code first. This DB is not actually
// used in the test only for its run once init code.
//
// This does change the serialization mode for sqld but because the mode
// that we use in libsql is safer than the sqld one it is still safe.
let db = Database::open_in_memory().unwrap();
db.connect().unwrap();

make_primary(&mut sim, tmp.to_path_buf());

sim.client("client", async move {
let client = Client::new();
client
.post("http://primary:9090/v1/namespaces/foo/create", json!({}))
.await?;

let db = Database::open_with_remote_sync_connector(
tmp.join("embedded").to_str().unwrap(),
"http://foo.primary:8080",
"",
TurmoilConnector,
)?;

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(())
});

sim.run().unwrap();
}
1 change: 1 addition & 0 deletions sqld/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@

mod cluster;
mod common;
mod embedded_replica;
mod namespaces;
mod standalone;

0 comments on commit a51ecff

Please sign in to comment.