Merge 'SDK: enable indices everywhere' from Nikita Sivukhin

Indices enabled by default in the shell - so let's enable them also in
the SDK

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Reviewed-by: Preston Thorpe <preston@turso.tech>

Closes #2581
This commit is contained in:
Pekka Enberg
2025-08-13 18:44:41 +03:00
committed by GitHub
9 changed files with 113 additions and 19 deletions

View File

@@ -23,10 +23,10 @@ pub struct ConnectArgs {
pub async fn connect(args: ConnectArgs) -> RustConnection {
let database = if args.url == ":memory:" {
let io: Arc<dyn turso_core::IO> = Arc::new(turso_core::MemoryIO::new());
turso_core::Database::open_file(io, args.url.as_str(), false, false)
turso_core::Database::open_file(io, args.url.as_str(), false, true)
} else {
let io: Arc<dyn turso_core::IO> = Arc::new(turso_core::PlatformIO::new().unwrap());
turso_core::Database::open_file(io, args.url.as_str(), false, false)
turso_core::Database::open_file(io, args.url.as_str(), false, true)
}
.unwrap();
let connection = database.connect().unwrap();

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"math"
"slices"
"testing"
_ "github.com/tursodatabase/turso"
@@ -683,6 +684,53 @@ func TestParameterOrdering(t *testing.T) {
}
}
func TestIndex(t *testing.T) {
newConn, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Error opening new connection: %v", err)
}
sql := "CREATE TABLE users (name TEXT PRIMARY KEY, email TEXT)"
_, err = newConn.Exec(sql)
if err != nil {
t.Fatalf("Error creating table: %v", err)
}
sql = "CREATE INDEX email_idx ON users(email)"
_, err = newConn.Exec(sql)
if err != nil {
t.Fatalf("Error creating index: %v", err)
}
// Test inserting with parameters in a different order than
// the table definition.
sql = "INSERT INTO users VALUES ('alice', 'a@b.c'), ('bob', 'b@d.e')"
_, err = newConn.Exec(sql)
if err != nil {
t.Fatalf("Error inserting data: %v", err)
}
for filter, row := range map[string][]string{
"a@b.c": []string{"alice", "a@b.c"},
"b@d.e": []string{"bob", "b@d.e"},
} {
query := "SELECT * FROM users WHERE email = ?"
rows, err := newConn.Query(query, filter)
if err != nil {
t.Fatalf("Error executing query: %v", err)
}
for rows.Next() {
var name, email string
err := rows.Scan(&name, &email)
t.Log("name,email:", name, email)
if err != nil {
t.Fatal("Error scanning row: ", err)
}
if !slices.Equal([]string{name, email}, row) {
t.Fatal("Unexpected result", row, []string{name, email})
}
}
}
}
func slicesAreEq(a, b []byte) bool {
if len(a) != len(b) {
fmt.Printf("LENGTHS NOT EQUAL: %d != %d\n", len(a), len(b))

View File

@@ -20,7 +20,7 @@ pub unsafe extern "C" fn db_open(path: *const c_char) -> *mut c_void {
}
let path = unsafe { std::ffi::CStr::from_ptr(path) };
let path = path.to_str().unwrap();
let Ok((io, conn)) = Connection::from_uri(path, false, false, false) else {
let Ok((io, conn)) = Connection::from_uri(path, true, false, false) else {
panic!("Failed to open connection with path: {path}");
};
LimboConn::new(conn, io).to_ptr()

View File

@@ -68,7 +68,7 @@ pub extern "system" fn Java_tech_turso_core_TursoDB_openUtf8<'local>(
}
};
let db = match Database::open_file(io.clone(), &path, false, false) {
let db = match Database::open_file(io.clone(), &path, false, true) {
Ok(db) => db,
Err(e) => {
set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());

View File

@@ -66,7 +66,7 @@ impl Database {
let db_file = Arc::new(DatabaseFile::new(file));
let db =
turso_core::Database::open(io.clone(), &path, db_file, false, false).map_err(|e| {
turso_core::Database::open(io.clone(), &path, db_file, false, true).map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to open database: {e}"),

View File

@@ -315,10 +315,9 @@ impl Drop for Connection {
}
#[allow(clippy::arc_with_non_send_sync)]
#[pyfunction(signature = (path, experimental_indexes=None))]
pub fn connect(path: &str, experimental_indexes: Option<bool>) -> Result<Connection> {
let experimental_indexes = experimental_indexes.unwrap_or(true);
match turso_core::Connection::from_uri(path, experimental_indexes, false, false) {
#[pyfunction(signature = (path))]
pub fn connect(path: &str) -> Result<Connection> {
match turso_core::Connection::from_uri(path, true, false, false) {
Ok((io, conn)) => Ok(Connection { conn, _io: io }),
Err(e) => Err(PyErr::new::<ProgrammingError, _>(format!(
"Failed to create connection: {e:?}"

View File

@@ -89,6 +89,24 @@ def test_in_memory_fetchone_select_all_users(provider):
assert alice
assert alice == (1, "alice")
@pytest.mark.parametrize("provider", ["sqlite3", "turso"])
def test_in_memory_index(provider):
conn = connect(provider, ":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE users (name TEXT PRIMARY KEY, email TEXT)")
cursor.execute("CREATE INDEX email_idx ON users(email)")
cursor.execute("INSERT INTO users VALUES ('alice', 'a@b.c'), ('bob', 'b@d.e')")
cursor.execute("SELECT * FROM users WHERE email = 'a@b.c'")
alice = cursor.fetchall()
cursor.execute("SELECT * FROM users WHERE email = 'b@d.e'")
bob = cursor.fetchall()
conn.close()
assert alice == [("alice", "a@b.c")]
assert bob == [("bob", "b@d.e")]
@pytest.mark.parametrize("provider", ["sqlite3", "turso"])
def test_fetchone_select_all_users(provider):

View File

@@ -108,27 +108,19 @@ impl Builder {
io,
self.path.as_str(),
self.enable_mvcc,
indexes_enabled(),
true,
)?;
Ok(Database { inner: db })
}
path => {
let io: Arc<dyn turso_core::IO> = Arc::new(turso_core::PlatformIO::new()?);
let db =
turso_core::Database::open_file(io, path, self.enable_mvcc, indexes_enabled())?;
let db = turso_core::Database::open_file(io, path, self.enable_mvcc, true)?;
Ok(Database { inner: db })
}
}
}
}
fn indexes_enabled() -> bool {
#[cfg(feature = "experimental_indexes")]
return true;
#[cfg(not(feature = "experimental_indexes"))]
return false;
}
/// A database.
///
/// The `Database` object points to a database and allows you to connect to it

View File

@@ -297,3 +297,40 @@ async fn test_row_get_conversion_error() {
let result: Result<u32, _> = row.get(0);
assert!(matches!(result, Err(Error::ConversionFailure(_))));
}
#[tokio::test]
async fn test_index() {
let db = Builder::new_local(":memory:").build().await.unwrap();
let conn = db.connect().unwrap();
conn.execute("CREATE TABLE users (name TEXT PRIMARY KEY, email TEXT)", ())
.await
.unwrap();
conn.execute("CREATE INDEX email_idx ON users(email)", ())
.await
.unwrap();
conn.execute(
"INSERT INTO users VALUES ('alice', 'a@b.c'), ('bob', 'b@d.e')",
(),
)
.await
.unwrap();
let mut rows = conn
.query("SELECT * FROM users WHERE email = 'a@b.c'", ())
.await
.unwrap();
let row = rows.next().await.unwrap().unwrap();
assert!(row.get::<String>(0).unwrap() == "alice");
assert!(row.get::<String>(1).unwrap() == "a@b.c");
assert!(rows.next().await.unwrap().is_none());
let mut rows = conn
.query("SELECT * FROM users WHERE email = 'b@d.e'", ())
.await
.unwrap();
let row = rows.next().await.unwrap().unwrap();
assert!(row.get::<String>(0).unwrap() == "bob");
assert!(row.get::<String>(1).unwrap() == "b@d.e");
assert!(rows.next().await.unwrap().is_none());
}