mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-08 17:54:22 +01:00
Merge branch 'main' into java-nullability
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1217,6 +1217,7 @@ dependencies = [
|
||||
"miette",
|
||||
"mimalloc",
|
||||
"mockall",
|
||||
"nix 0.29.0",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"polling",
|
||||
|
||||
14
README.md
14
README.md
@@ -25,14 +25,12 @@
|
||||
|
||||
## Features
|
||||
|
||||
* In-process OLTP database engine library
|
||||
* Asynchronous I/O support on Linux with `io_uring`
|
||||
* SQLite compatibility ([status](COMPAT.md))
|
||||
* SQL dialect support
|
||||
* File format support
|
||||
* SQLite C API
|
||||
* JavaScript/WebAssembly bindings (_wip_)
|
||||
* Support for Linux, macOS, and Windows
|
||||
Limbo is an in-process OLTP database engine library that has:
|
||||
|
||||
* **Asynchronous I/O** support on Linux with `io_uring`
|
||||
* **SQLite compatibility** [[doc](COMPAT.md)] for SQL dialect, file formats, and the C API
|
||||
* **Language bindings** for JavaScript/WebAssembly, Rust, Python, and Java
|
||||
* **OS support** for Linux, macOS, and Windows
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.Properties;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class JDBC implements Driver {
|
||||
private static final String VALID_URL_PREFIX = "jdbc:limbo:";
|
||||
private static final String VALID_URL_PREFIX = "jdbc:sqlite:";
|
||||
|
||||
static {
|
||||
try {
|
||||
|
||||
@@ -20,13 +20,13 @@ class JDBCTest {
|
||||
@Test
|
||||
void non_null_connection_is_returned_when_valid_url_is_passed() throws Exception {
|
||||
String fileUrl = TestUtils.createTempFile();
|
||||
LimboConnection connection = JDBC.createConnection("jdbc:limbo:" + fileUrl, new Properties());
|
||||
LimboConnection connection = JDBC.createConnection("jdbc:sqlite:" + fileUrl, new Properties());
|
||||
assertThat(connection).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connection_can_be_retrieved_from_DriverManager() throws SQLException {
|
||||
try (Connection connection = DriverManager.getConnection("jdbc:limbo:sample.db")) {
|
||||
try (Connection connection = DriverManager.getConnection("jdbc:sqlite:sample.db")) {
|
||||
assertThat(connection).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ fallible-iterator = "0.3.0"
|
||||
hex = "0.4.3"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.20"
|
||||
nix = { version = "0.29.0", features = ["fs"] }
|
||||
sieve-cache = "0.1.4"
|
||||
sqlite3-parser = { path = "../vendored/sqlite3-parser" }
|
||||
thiserror = "1.0.61"
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use super::{common, Completion, File, OpenFlags, IO};
|
||||
use crate::{LimboError, Result};
|
||||
use libc::{c_short, fcntl, flock, iovec, F_SETLK};
|
||||
use log::{debug, trace};
|
||||
use rustix::fs::{self, FlockOperation, OFlags};
|
||||
use rustix::io_uring::iovec;
|
||||
use nix::fcntl::{FcntlArg, OFlag};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::io::ErrorKind;
|
||||
use std::os::fd::AsFd;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::rc::Rc;
|
||||
use thiserror::Error;
|
||||
@@ -138,12 +136,12 @@ impl IO for UringIO {
|
||||
.open(path)?;
|
||||
// Let's attempt to enable direct I/O. Not all filesystems support it
|
||||
// so ignore any errors.
|
||||
let fd = file.as_fd();
|
||||
let fd = file.as_raw_fd();
|
||||
if direct {
|
||||
match fs::fcntl_setfl(fd, OFlags::DIRECT) {
|
||||
Ok(_) => {}
|
||||
match nix::fcntl::fcntl(fd, FcntlArg::F_SETFL(OFlag::O_DIRECT)) {
|
||||
Ok(_) => {},
|
||||
Err(error) => debug!("Error {error:?} returned when setting O_DIRECT flag to read file. The performance of the system may be affected"),
|
||||
}
|
||||
};
|
||||
}
|
||||
let uring_file = Rc::new(UringFile {
|
||||
io: self.inner.clone(),
|
||||
@@ -201,39 +199,52 @@ pub struct UringFile {
|
||||
|
||||
impl File for UringFile {
|
||||
fn lock_file(&self, exclusive: bool) -> Result<()> {
|
||||
let fd = self.file.as_fd();
|
||||
let fd = self.file.as_raw_fd();
|
||||
let flock = flock {
|
||||
l_type: if exclusive {
|
||||
libc::F_WRLCK as c_short
|
||||
} else {
|
||||
libc::F_RDLCK as c_short
|
||||
},
|
||||
l_whence: libc::SEEK_SET as c_short,
|
||||
l_start: 0,
|
||||
l_len: 0, // Lock entire file
|
||||
l_pid: 0,
|
||||
};
|
||||
|
||||
// F_SETLK is a non-blocking lock. The lock will be released when the file is closed
|
||||
// or the process exits or after an explicit unlock.
|
||||
fs::fcntl_lock(
|
||||
fd,
|
||||
if exclusive {
|
||||
FlockOperation::LockExclusive
|
||||
let lock_result = unsafe { fcntl(fd, F_SETLK, &flock) };
|
||||
if lock_result == -1 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.kind() == std::io::ErrorKind::WouldBlock {
|
||||
return Err(LimboError::LockingError(
|
||||
"File is locked by another process".into(),
|
||||
));
|
||||
} else {
|
||||
FlockOperation::LockShared
|
||||
},
|
||||
)
|
||||
.map_err(|e| {
|
||||
let io_error = std::io::Error::from(e);
|
||||
let message = match io_error.kind() {
|
||||
ErrorKind::WouldBlock => {
|
||||
"Failed locking file. File is locked by another process".to_string()
|
||||
}
|
||||
_ => format!("Failed locking file, {}", io_error),
|
||||
};
|
||||
LimboError::LockingError(message)
|
||||
})?;
|
||||
|
||||
return Err(LimboError::IOError(err));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unlock_file(&self) -> Result<()> {
|
||||
let fd = self.file.as_fd();
|
||||
fs::fcntl_lock(fd, FlockOperation::Unlock).map_err(|e| {
|
||||
LimboError::LockingError(format!(
|
||||
let fd = self.file.as_raw_fd();
|
||||
let flock = flock {
|
||||
l_type: libc::F_UNLCK as c_short,
|
||||
l_whence: libc::SEEK_SET as c_short,
|
||||
l_start: 0,
|
||||
l_len: 0,
|
||||
l_pid: 0,
|
||||
};
|
||||
|
||||
let unlock_result = unsafe { fcntl(fd, F_SETLK, &flock) };
|
||||
if unlock_result == -1 {
|
||||
return Err(LimboError::LockingError(format!(
|
||||
"Failed to release file lock: {}",
|
||||
std::io::Error::from(e)
|
||||
))
|
||||
})?;
|
||||
std::io::Error::last_os_error()
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -250,7 +261,7 @@ impl File for UringFile {
|
||||
let len = buf.len();
|
||||
let buf = buf.as_mut_ptr();
|
||||
let iovec = io.get_iovec(buf, len);
|
||||
io_uring::opcode::Readv::new(fd, iovec as *const iovec as *const libc::iovec, 1)
|
||||
io_uring::opcode::Readv::new(fd, iovec, 1)
|
||||
.offset(pos as u64)
|
||||
.build()
|
||||
.user_data(io.ring.get_key())
|
||||
@@ -271,7 +282,7 @@ impl File for UringFile {
|
||||
let buf = buffer.borrow();
|
||||
trace!("pwrite(pos = {}, length = {})", pos, buf.len());
|
||||
let iovec = io.get_iovec(buf.as_ptr(), buf.len());
|
||||
io_uring::opcode::Writev::new(fd, iovec as *const iovec as *const libc::iovec, 1)
|
||||
io_uring::opcode::Writev::new(fd, iovec, 1)
|
||||
.offset(pos as u64)
|
||||
.build()
|
||||
.user_data(io.ring.get_key())
|
||||
|
||||
154
core/vdbe/mod.rs
154
core/vdbe/mod.rs
@@ -1459,92 +1459,86 @@ impl Program {
|
||||
let arg_count = func.arg_count;
|
||||
match &func.func {
|
||||
#[cfg(feature = "json")]
|
||||
crate::function::Func::Json(JsonFunc::Json) => {
|
||||
let json_value = &state.registers[*start_reg];
|
||||
let json_str = get_json(json_value);
|
||||
match json_str {
|
||||
Ok(json) => state.registers[*dest] = json,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "json")]
|
||||
crate::function::Func::Json(JsonFunc::JsonArray) => {
|
||||
let reg_values = &state.registers[*start_reg..*start_reg + arg_count];
|
||||
|
||||
let json_array = json_array(reg_values);
|
||||
|
||||
match json_array {
|
||||
Ok(json) => state.registers[*dest] = json,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "json")]
|
||||
crate::function::Func::Json(JsonFunc::JsonExtract) => {
|
||||
let result = match arg_count {
|
||||
0 => json_extract(&OwnedValue::Null, &[]),
|
||||
_ => {
|
||||
let val = &state.registers[*start_reg];
|
||||
let reg_values =
|
||||
&state.registers[*start_reg + 1..*start_reg + arg_count];
|
||||
|
||||
json_extract(val, reg_values)
|
||||
crate::function::Func::Json(json_func) => match json_func {
|
||||
JsonFunc::Json => {
|
||||
let json_value = &state.registers[*start_reg];
|
||||
let json_str = get_json(json_value);
|
||||
match json_str {
|
||||
Ok(json) => state.registers[*dest] = json,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
};
|
||||
}
|
||||
JsonFunc::JsonArray => {
|
||||
let reg_values =
|
||||
&state.registers[*start_reg..*start_reg + arg_count];
|
||||
|
||||
match result {
|
||||
Ok(json) => state.registers[*dest] = json,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "json")]
|
||||
crate::function::Func::Json(
|
||||
func @ (JsonFunc::JsonArrowExtract | JsonFunc::JsonArrowShiftExtract),
|
||||
) => {
|
||||
assert_eq!(arg_count, 2);
|
||||
let json = &state.registers[*start_reg];
|
||||
let path = &state.registers[*start_reg + 1];
|
||||
let func = match func {
|
||||
JsonFunc::JsonArrowExtract => json_arrow_extract,
|
||||
JsonFunc::JsonArrowShiftExtract => json_arrow_shift_extract,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let json_str = func(json, path);
|
||||
match json_str {
|
||||
Ok(json) => state.registers[*dest] = json,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "json")]
|
||||
crate::function::Func::Json(
|
||||
func @ (JsonFunc::JsonArrayLength | JsonFunc::JsonType),
|
||||
) => {
|
||||
let json_value = &state.registers[*start_reg];
|
||||
let path_value = if arg_count > 1 {
|
||||
Some(&state.registers[*start_reg + 1])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let func_result = match func {
|
||||
JsonFunc::JsonArrayLength => {
|
||||
json_array_length(json_value, path_value)
|
||||
let json_array = json_array(reg_values);
|
||||
|
||||
match json_array {
|
||||
Ok(json) => state.registers[*dest] = json,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
JsonFunc::JsonType => json_type(json_value, path_value),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
JsonFunc::JsonExtract => {
|
||||
let result = match arg_count {
|
||||
0 => json_extract(&OwnedValue::Null, &[]),
|
||||
_ => {
|
||||
let val = &state.registers[*start_reg];
|
||||
let reg_values = &state.registers
|
||||
[*start_reg + 1..*start_reg + arg_count];
|
||||
|
||||
match func_result {
|
||||
Ok(result) => state.registers[*dest] = result,
|
||||
Err(e) => return Err(e),
|
||||
json_extract(val, reg_values)
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(json) => state.registers[*dest] = json,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "json")]
|
||||
crate::function::Func::Json(JsonFunc::JsonErrorPosition) => {
|
||||
let json_value = &state.registers[*start_reg];
|
||||
match json_error_position(json_value) {
|
||||
Ok(pos) => state.registers[*dest] = pos,
|
||||
Err(e) => return Err(e),
|
||||
JsonFunc::JsonArrowExtract | JsonFunc::JsonArrowShiftExtract => {
|
||||
assert_eq!(arg_count, 2);
|
||||
let json = &state.registers[*start_reg];
|
||||
let path = &state.registers[*start_reg + 1];
|
||||
let json_func = match json_func {
|
||||
JsonFunc::JsonArrowExtract => json_arrow_extract,
|
||||
JsonFunc::JsonArrowShiftExtract => json_arrow_shift_extract,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let json_str = json_func(json, path);
|
||||
match json_str {
|
||||
Ok(json) => state.registers[*dest] = json,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
JsonFunc::JsonArrayLength | JsonFunc::JsonType => {
|
||||
let json_value = &state.registers[*start_reg];
|
||||
let path_value = if arg_count > 1 {
|
||||
Some(&state.registers[*start_reg + 1])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let func_result = match json_func {
|
||||
JsonFunc::JsonArrayLength => {
|
||||
json_array_length(json_value, path_value)
|
||||
}
|
||||
JsonFunc::JsonType => json_type(json_value, path_value),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match func_result {
|
||||
Ok(result) => state.registers[*dest] = result,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
JsonFunc::JsonErrorPosition => {
|
||||
let json_value = &state.registers[*start_reg];
|
||||
match json_error_position(json_value) {
|
||||
Ok(pos) => state.registers[*dest] = pos,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
},
|
||||
crate::function::Func::Scalar(scalar_func) => match scalar_func {
|
||||
ScalarFunc::Cast => {
|
||||
assert!(arg_count == 2);
|
||||
|
||||
Reference in New Issue
Block a user