diff --git a/CHANGELOG.md b/CHANGELOG.md index fee38e12c..e8189a2e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -218,7 +218,7 @@ * Add support for iif() function (Alex Miller) -* Add suport for last_insert_rowid() function (Krishna Vishal) +* Add support for last_insert_rowid() function (Krishna Vishal) * Add support JOIN USING and NATURAL JOIN (Jussi Saurio) @@ -303,7 +303,7 @@ - `ORDER BY` support for nullable sorting columns and qualified identifiers (Jussi Saurio) -- Fix `.schema` command crash in the CLI ([#212](https://github.com/penberg/limbo/issues/212) (Jussi Saurio) +- Fix `.schema` command crash in the CLI ([#212](https://github.com/tursodatabase/limbo/issues/212) (Jussi Saurio) ## 0.0.2 - 2024-07-24 diff --git a/COMPAT.md b/COMPAT.md index 772d53a43..e64369bbe 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -53,7 +53,7 @@ The current status of Limbo is: | CREATE TRIGGER | No | | | CREATE VIEW | No | | | CREATE VIRTUAL TABLE | No | | -| DELETE | No | | +| DELETE | Yes | | | DETACH DATABASE | No | | | DROP INDEX | No | | | DROP TABLE | No | | @@ -86,7 +86,7 @@ The current status of Limbo is: | UPDATE | No | | | UPSERT | No | | | VACUUM | No | | -| WITH clause | No | | +| WITH clause | Partial | No RECURSIVE, no MATERIALIZED, only SELECT supported in CTEs | #### [PRAGMA](https://www.sqlite.org/pragma.html) @@ -160,7 +160,7 @@ The current status of Limbo is: | PRAGMA temp_store_directory | Not Needed | deprecated in SQLite | | PRAGMA threads | No | | | PRAGMA trusted_schema | No | | -| PRAGMA user_version | No | | +| PRAGMA user_version | Partial | Only read implemented | | PRAGMA vdbe_addoptrace | No | | | PRAGMA vdbe_debug | No | | | PRAGMA vdbe_listing | No | | @@ -514,7 +514,7 @@ Modifiers: | PrevAsync | Yes | | | PrevAwait | Yes | | | Program | No | | -| ReadCookie | No | | +| ReadCookie | Partial| no temp databases, only user_version supported | | Real | Yes | | | RealAffinity | Yes | | | Remainder | Yes | | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cad33a8db..ac4566c00 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ cargo bench --bench benchmark -- --profile-time=5 ## Finding things to work on -The issue tracker has issues tagged with [good first issue](https://github.com/penberg/limbo/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22), +The issue tracker has issues tagged with [good first issue](https://github.com/tursodatabase/limbo/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22), which are considered to be things to work on to get going. If you're interested in working on one of them, comment on the issue tracker, and we're happy to help you get going. ## Submitting your work diff --git a/Cargo.lock b/Cargo.lock index 819311184..6a6c9f418 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,29 +399,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" -[[package]] -name = "cli-table" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53f9241f288a7b12c56565f04aaeaeeab6b8923d42d99255d4ca428b4d97f89" -dependencies = [ - "cli-table-derive", - "csv", - "termcolor", - "unicode-width", -] - -[[package]] -name = "cli-table-derive" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e83a93253aaae7c74eb7428ce4faa6e219ba94886908048888701819f82fb94" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "clipboard-win" version = "4.5.0" @@ -449,6 +426,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "comfy-table" +version = "7.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width 0.2.0", +] + [[package]] name = "comma" version = "1.0.0" @@ -492,8 +480,6 @@ version = "0.0.14" dependencies = [ "anyhow", "assert_cmd", - "clap", - "dirs", "env_logger 0.10.2", "limbo_core", "log", @@ -501,7 +487,6 @@ dependencies = [ "rand_chacha 0.9.0", "rexpect", "rusqlite", - "rustyline", "tempfile", ] @@ -595,6 +580,28 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.8.0", + "crossterm_winapi", + "parking_lot", + "rustix", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -711,12 +718,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - [[package]] name = "either" version = "1.13.0" @@ -736,6 +737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", + "regex", ] [[package]] @@ -767,7 +769,10 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ + "anstream", + "anstyle", "env_filter", + "humantime", "log", ] @@ -891,12 +896,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fragile" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -1564,7 +1563,7 @@ version = "0.0.14" dependencies = [ "anyhow", "clap", - "cli-table", + "comfy-table", "csv", "ctrlc", "dirs", @@ -1607,11 +1606,11 @@ name = "limbo_core" version = "0.0.14" dependencies = [ "built", - "bumpalo", "cfg_block", "chrono", "criterion", "crossbeam-skiplist", + "env_logger 0.11.6", "fallible-iterator 0.3.0", "getrandom 0.2.15", "hex", @@ -1632,7 +1631,6 @@ dependencies = [ "log", "miette", "mimalloc", - "mockall", "parking_lot", "pest", "pest_derive", @@ -1641,13 +1639,13 @@ dependencies = [ "quickcheck", "quickcheck_macros", "rand 0.8.5", + "rand_chacha 0.9.0", "regex", "regex-syntax", "rstest", "rusqlite", "rustix", "serde", - "sieve-cache", "sqlite3-parser", "strum", "tempfile", @@ -1673,7 +1671,6 @@ name = "limbo_ext" version = "0.0.14" dependencies = [ "limbo_macros", - "log", ] [[package]] @@ -1707,7 +1704,6 @@ name = "limbo_regexp" version = "0.0.14" dependencies = [ "limbo_ext", - "log", "mimalloc", "regex", ] @@ -1717,7 +1713,6 @@ name = "limbo_series" version = "0.0.14" dependencies = [ "limbo_ext", - "log", "mimalloc", "quickcheck", "quickcheck_macros", @@ -1848,7 +1843,7 @@ dependencies = [ "terminal_size", "textwrap", "thiserror 1.0.69", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -1898,32 +1893,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "mockall" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "mockall_derive", - "predicates", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.96", -] - [[package]] name = "nibble_vec" version = "0.1.0" @@ -2704,7 +2673,7 @@ dependencies = [ "radix_trie", "scopeguard", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.14", "utf8parse", "winapi", ] @@ -2786,12 +2755,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "sieve-cache" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bf3a9dccf2c079bf1465d449a485c85b36443caf765f2f127bfec28b180f75" - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3033,7 +2996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "unicode-linebreak", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -3201,6 +3164,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unindent" version = "0.2.3" diff --git a/README.md b/README.md index 480933581..8f2665ef0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Limbo is a _work-in-progress_, in-process OLTP database engine library written i * **Language bindings** for JavaScript/WebAssembly, Rust, Go, Python, and [Java](bindings/java) * **OS support** for Linux, macOS, and Windows -In the future, we will be also workin on: +In the future, we will be also working on: * **Integrated vector search** for embeddings and vector similarity. * **`BEGIN CONCURRENT`** for improved write throughput. diff --git a/bindings/go/Cargo.toml b/bindings/go/Cargo.toml index b73902c5f..e268d0a27 100644 --- a/bindings/go/Cargo.toml +++ b/bindings/go/Cargo.toml @@ -17,7 +17,7 @@ io_uring = ["limbo_core/io_uring"] [dependencies] -limbo_core = { path = "../../core/" } +limbo_core = { path = "../../core" } [target.'cfg(target_os = "linux")'.dependencies] -limbo_core = { path = "../../core/", features = ["io_uring"] } +limbo_core = { path = "../../core", features = ["io_uring"] } diff --git a/bindings/go/limbo_test.go b/bindings/go/limbo_test.go index 31b1fc3b4..9527faa5f 100644 --- a/bindings/go/limbo_test.go +++ b/bindings/go/limbo_test.go @@ -89,7 +89,7 @@ func TestFunctions(t *testing.T) { } _, err = stmt.Exec(60, "TestFunction", 400) if err != nil { - t.Fatalf("Error executing statment with arguments: %v", err) + t.Fatalf("Error executing statement with arguments: %v", err) } stmt.Close() stmt, err = conn.Prepare("SELECT baz FROM test where foo = ?") diff --git a/bindings/go/rs_src/rows.rs b/bindings/go/rs_src/rows.rs index 714c73e23..ae2094d60 100644 --- a/bindings/go/rs_src/rows.rs +++ b/bindings/go/rs_src/rows.rs @@ -75,7 +75,7 @@ pub extern "C" fn rows_get_value(ctx: *mut c_void, col_idx: usize) -> *const c_v let ctx = LimboRows::from_ptr(ctx); if let Some(row) = ctx.stmt.row() { - if let Some(value) = row.values.get(col_idx) { + if let Some(value) = row.get_values().get(col_idx) { let value = value.to_value(); return LimboValue::from_value(&value).to_ptr(); } diff --git a/bindings/go/rs_src/statement.rs b/bindings/go/rs_src/statement.rs index e8cec0618..d068f01c6 100644 --- a/bindings/go/rs_src/statement.rs +++ b/bindings/go/rs_src/statement.rs @@ -116,12 +116,12 @@ pub extern "C" fn stmt_query( let val = arg.to_value(&mut pool); statement.bind_at(NonZero::new(i + 1).unwrap(), val); } - // ownership of the statement is transfered to the LimboRows object. + // ownership of the statement is transferred to the LimboRows object. LimboRows::new(statement, stmt.conn).to_ptr() } pub struct LimboStatement<'conn> { - /// If 'query' is ran on the statement, ownership is transfered to the LimboRows object + /// If 'query' is ran on the statement, ownership is transferred to the LimboRows object pub statement: Option, pub conn: &'conn mut LimboConn, pub err: Option, diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index 9b78b1597..fd38d1e65 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -12,6 +12,6 @@ crate-type = ["cdylib"] path = "rs_src/lib.rs" [dependencies] -limbo_core = { path = "../../core" } +limbo_core = { path = "../../core", features = ["io_uring"] } jni = "0.21.1" thiserror = "2.0.9" diff --git a/bindings/java/rs_src/limbo_db.rs b/bindings/java/rs_src/limbo_db.rs index 16cb3d66b..65f6dafc0 100644 --- a/bindings/java/rs_src/limbo_db.rs +++ b/bindings/java/rs_src/limbo_db.rs @@ -21,7 +21,6 @@ impl LimboDB { Box::into_raw(Box::new(self)) as jlong } - #[allow(dead_code)] pub fn drop(ptr: jlong) { let _boxed = unsafe { Box::from_raw(ptr as *mut LimboDB) }; } @@ -97,6 +96,15 @@ pub extern "system" fn Java_org_github_tursodatabase_core_LimboDB_connect0<'loca conn.to_ptr() } +#[no_mangle] +pub extern "system" fn Java_org_github_tursodatabase_core_LimboDB_close0<'local>( + _env: JNIEnv<'local>, + _obj: JObject<'local>, + db_pointer: jlong, +) { + LimboDB::drop(db_pointer); +} + #[no_mangle] pub extern "system" fn Java_org_github_tursodatabase_core_LimboDB_throwJavaException<'local>( mut env: JNIEnv<'local>, diff --git a/bindings/java/rs_src/limbo_statement.rs b/bindings/java/rs_src/limbo_statement.rs index dd1feb55a..efd87681d 100644 --- a/bindings/java/rs_src/limbo_statement.rs +++ b/bindings/java/rs_src/limbo_statement.rs @@ -102,10 +102,9 @@ fn row_to_obj_array<'local>( env: &mut JNIEnv<'local>, row: &limbo_core::Row, ) -> Result> { - let obj_array = - env.new_object_array(row.values.len() as i32, "java/lang/Object", JObject::null())?; + let obj_array = env.new_object_array(row.len() as i32, "java/lang/Object", JObject::null())?; - for (i, value) in row.values.iter().enumerate() { + for (i, value) in row.get_values().iter().enumerate() { let value = value.to_value(); let obj = match value { limbo_core::Value::Null => JObject::null(), diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/AbstractDB.java b/bindings/java/src/main/java/org/github/tursodatabase/core/AbstractDB.java deleted file mode 100644 index 4906acd9c..000000000 --- a/bindings/java/src/main/java/org/github/tursodatabase/core/AbstractDB.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.github.tursodatabase.core; - -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Interface to Limbo. It provides some helper functions used by other parts of the driver. The goal - * of the helper functions here are not only to provide functionality, but to handle contractual - * differences between the JDBC specification and the Limbo API. - */ -public abstract class AbstractDB { - protected final String url; - protected final String filePath; - private final AtomicBoolean closed = new AtomicBoolean(true); - - public AbstractDB(String url, String filePath) { - this.url = url; - this.filePath = filePath; - } - - public boolean isClosed() { - return closed.get(); - } - - /** Aborts any pending operation and returns at its earliest opportunity. */ - public abstract void interrupt() throws SQLException; - - /** - * Creates an SQLite interface to a database for the given connection. - * - * @param openFlags Flags for opening the database. - * @throws SQLException if a database access error occurs. - */ - public final synchronized void open(int openFlags) throws SQLException { - open0(filePath, openFlags); - } - - protected abstract void open0(String fileName, int openFlags) throws SQLException; - - /** - * Closes a database connection and finalizes any remaining statements before the closing - * operation. - * - * @throws SQLException if a database access error occurs. - */ - public final synchronized void close() throws SQLException { - // TODO: add implementation - throw new SQLFeatureNotSupportedException(); - } - - /** - * Connects to a database. - * - * @return Pointer to the connection. - */ - public abstract long connect() throws SQLException; - - /** - * Creates an SQLite interface to a database with the provided open flags. - * - * @param fileName The database to open. - * @param openFlags Flags for opening the database. - * @return pointer to database instance - * @throws SQLException if a database access error occurs. - */ - protected abstract long openUtf8(byte[] fileName, int openFlags) throws SQLException; - - /** - * Closes the SQLite interface to a database. - * - * @throws SQLException if a database access error occurs. - */ - protected abstract void close0() throws SQLException; -} diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboConnection.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboConnection.java index 017fc2ec6..6217ca76c 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboConnection.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboConnection.java @@ -15,7 +15,7 @@ public class LimboConnection { private final String url; private final long connectionPtr; - private final AbstractDB database; + private final LimboDB database; private boolean closed; public LimboConnection(String url, String filePath) throws SQLException { @@ -34,7 +34,7 @@ public class LimboConnection { this.connectionPtr = this.database.connect(); } - private static AbstractDB open(String url, String filePath, Properties properties) + private static LimboDB open(String url, String filePath, Properties properties) throws SQLException { return LimboDBFactory.open(url, filePath, properties); } @@ -61,7 +61,7 @@ public class LimboConnection { return closed; } - public AbstractDB getDatabase() { + public LimboDB getDatabase() { return database; } diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java index e1d619239..64b005d35 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java @@ -7,7 +7,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; -import java.util.concurrent.locks.ReentrantLock; import org.github.tursodatabase.LimboErrorCode; import org.github.tursodatabase.annotations.NativeInvocation; import org.github.tursodatabase.annotations.VisibleForTesting; @@ -16,14 +15,15 @@ import org.github.tursodatabase.utils.Logger; import org.github.tursodatabase.utils.LoggerFactory; /** This class provides a thin JNI layer over the SQLite3 C API. */ -public final class LimboDB extends AbstractDB { +public final class LimboDB implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(LimboDB.class); // Pointer to database instance private long dbPointer; private boolean isOpen; + private final String url; + private final String filePath; private static boolean isLoaded; - private ReentrantLock dbLock = new ReentrantLock(); static { if ("The Android Project".equals(System.getProperty("java.vm.vendor"))) { @@ -176,23 +176,26 @@ public final class LimboDB extends AbstractDB { // TODO: receive config as argument private LimboDB(String url, String filePath) { - super(url, filePath); + this.url = url; + this.filePath = filePath; } // TODO: add support for JNI - @Override - protected native long openUtf8(byte[] file, int openFlags) throws SQLException; - - // TODO: add support for JNI - @Override - protected native void close0() throws SQLException; - - // TODO: add support for JNI - @Override public native void interrupt(); - @Override - protected void open0(String filePath, int openFlags) throws SQLException { + public boolean isClosed() { + return !this.isOpen; + } + + public boolean isOpen() { + return this.isOpen; + } + + public void open(int openFlags) throws SQLException { + open0(filePath, openFlags); + } + + private void open0(String filePath, int openFlags) throws SQLException { if (isOpen) { throw LimboExceptionUtils.buildLimboException( LimboErrorCode.LIMBO_ETC.code, "Already opened"); @@ -209,13 +212,24 @@ public final class LimboDB extends AbstractDB { isOpen = true; } - @Override + private native long openUtf8(byte[] file, int openFlags) throws SQLException; + public long connect() throws SQLException { return connect0(dbPointer); } private native long connect0(long databasePtr) throws SQLException; + @Override + public void close() throws Exception { + if (!isOpen) return; + + close0(dbPointer); + isOpen = false; + } + + private native void close0(long databasePtr) throws SQLException; + @VisibleForTesting native void throwJavaException(int errorCode) throws SQLException; diff --git a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java index 8bb03e70a..896397c84 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java @@ -13,6 +13,8 @@ public class JDBC4Connection implements Connection { private final LimboConnection connection; + private Map> typeMap = new HashMap<>(); + public JDBC4Connection(String url, String filePath) throws SQLException { this.connection = new LimboConnection(url, filePath); } @@ -47,22 +49,8 @@ public class JDBC4Connection implements Connection { } @Override - public PreparedStatement prepareStatement(String sql) throws SQLException { - return new JDBC4PreparedStatement(this, sql); - } - - @Override - @SkipNullableCheck - public CallableStatement prepareCall(String sql) throws SQLException { - // TODO - return null; - } - - @Override - @SkipNullableCheck public String nativeSQL(String sql) throws SQLException { - // TODO - return ""; + return sql; } @Override @@ -115,13 +103,10 @@ public class JDBC4Connection implements Connection { } @Override - public void setCatalog(String catalog) throws SQLException { - // TODO - } + public void setCatalog(String catalog) throws SQLException {} @Override public String getCatalog() throws SQLException { - // TODO return ""; } @@ -148,41 +133,30 @@ public class JDBC4Connection implements Connection { // TODO } - @Override - @SkipNullableCheck - public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) - throws SQLException { - // TODO - return null; - } - - @Override - @SkipNullableCheck - public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) - throws SQLException { - // TODO - return null; - } - @Override public Map> getTypeMap() throws SQLException { - // TODO - return new HashMap<>(); + return this.typeMap; } @Override public void setTypeMap(Map> map) throws SQLException { - // TODO - } - - @Override - public void setHoldability(int holdability) throws SQLException { - // TODO + synchronized (this) { + this.typeMap = map; + } } @Override public int getHoldability() throws SQLException { - return 0; + connection.checkOpen(); + return ResultSet.CLOSE_CURSORS_AT_COMMIT; + } + + @Override + public void setHoldability(int holdability) throws SQLException { + connection.checkOpen(); + if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT) { + throw new SQLException("Limbo only supports CLOSE_CURSORS_AT_COMMIT"); + } } @Override @@ -210,76 +184,95 @@ public class JDBC4Connection implements Connection { } @Override - @SkipNullableCheck - public PreparedStatement prepareStatement( - String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) - throws SQLException { - // TODO - return null; + public CallableStatement prepareCall(String sql) throws SQLException { + return prepareCall( + sql, + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.CLOSE_CURSORS_AT_COMMIT); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + return prepareCall(sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT); } @Override - @SkipNullableCheck public CallableStatement prepareCall( String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { - // TODO - return null; + throw new SQLException("Limbo does not support stored procedures"); + } + + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + return prepareStatement( + sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT); + } + + @Override + public PreparedStatement prepareStatement( + String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + connection.checkOpen(); + connection.checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability); + return new JDBC4PreparedStatement(this, sql); } @Override - @SkipNullableCheck public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { - // TODO - return null; + return prepareStatement(sql); } @Override - @SkipNullableCheck public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { - // TODO - return null; + // TODO: maybe we can enhance this functionality by using columnIndexes + return prepareStatement(sql); } @Override - @SkipNullableCheck public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { - // TODO - return null; + // TODO: maybe we can enhance this functionality by using columnNames + return prepareStatement(sql); } @Override - @SkipNullableCheck public Clob createClob() throws SQLException { - // TODO - return null; + throw new SQLFeatureNotSupportedException("createClob not supported"); } @Override - @SkipNullableCheck public Blob createBlob() throws SQLException { - // TODO - return null; + throw new SQLFeatureNotSupportedException("createBlob not supported"); } @Override - @SkipNullableCheck public NClob createNClob() throws SQLException { - // TODO - return null; + throw new SQLFeatureNotSupportedException("createNClob not supported"); } @Override @SkipNullableCheck public SQLXML createSQLXML() throws SQLException { - // TODO - return null; + throw new SQLFeatureNotSupportedException("createSQLXML not supported"); } @Override public boolean isValid(int timeout) throws SQLException { - // TODO - return false; + if (isClosed()) { + return false; + } + + try (Statement statement = createStatement()) { + return statement.execute("select 1;"); + } } @Override @@ -333,7 +326,11 @@ public class JDBC4Connection implements Connection { @Override public void abort(Executor executor) throws SQLException { - // TODO + if (isClosed()) { + return; + } + + close(); } @Override diff --git a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4PreparedStatement.java index fa487f014..b04ed71c4 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4PreparedStatement.java @@ -23,6 +23,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.util.Calendar; import org.github.tursodatabase.annotations.SkipNullableCheck; +import org.github.tursodatabase.core.LimboResultSet; public class JDBC4PreparedStatement extends JDBC4Statement implements PreparedStatement { @@ -45,7 +46,11 @@ public class JDBC4PreparedStatement extends JDBC4Statement implements PreparedSt @Override public int executeUpdate() throws SQLException { - // TODO + requireNonNull(this.statement); + final LimboResultSet resultSet = statement.getResultSet(); + resultSet.consumeAll(); + + // TODO: return updated count return 0; } diff --git a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java index 260b7cbcb..fa11544bc 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java @@ -252,10 +252,8 @@ public class JDBC4Statement implements Statement { } @Override - @SkipNullableCheck - public Connection getConnection() throws SQLException { - // TODO - return null; + public Connection getConnection() { + return connection; } @Override @@ -273,32 +271,32 @@ public class JDBC4Statement implements Statement { @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { - // TODO - return 0; + // TODO: enhance + return executeUpdate(sql); } @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { - // TODO - return 0; + // TODO: enhance + return executeUpdate(sql); } @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { - // TODO - return 0; + // TODO: enhance + return executeUpdate(sql); } @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { - // TODO - return false; + // TODO: enhance + return execute(sql); } @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { - // TODO - return false; + // TODO: enhance + return execute(sql); } @Override diff --git a/bindings/java/src/test/java/org/github/tursodatabase/core/LimboDBTest.java b/bindings/java/src/test/java/org/github/tursodatabase/core/LimboDBTest.java index ca75ac4c7..e2fcc8204 100644 --- a/bindings/java/src/test/java/org/github/tursodatabase/core/LimboDBTest.java +++ b/bindings/java/src/test/java/org/github/tursodatabase/core/LimboDBTest.java @@ -2,6 +2,7 @@ package org.github.tursodatabase.core; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.sql.SQLException; import org.github.tursodatabase.LimboErrorCode; @@ -13,16 +14,27 @@ public class LimboDBTest { @Test void db_should_open_normally() throws Exception { - String dbPath = TestUtils.createTempFile(); LimboDB.load(); + String dbPath = TestUtils.createTempFile(); LimboDB db = LimboDB.create("jdbc:sqlite" + dbPath, dbPath); db.open(0); } @Test - void should_throw_exception_when_opened_twice() throws Exception { - String dbPath = TestUtils.createTempFile(); + void db_should_close_normally() throws Exception { LimboDB.load(); + String dbPath = TestUtils.createTempFile(); + LimboDB db = LimboDB.create("jdbc:sqlite" + dbPath, dbPath); + db.open(0); + db.close(); + + assertFalse(db.isOpen()); + } + + @Test + void should_throw_exception_when_opened_twice() throws Exception { + LimboDB.load(); + String dbPath = TestUtils.createTempFile(); LimboDB db = LimboDB.create("jdbc:sqlite:" + dbPath, dbPath); db.open(0); @@ -31,8 +43,8 @@ public class LimboDBTest { @Test void throwJavaException_should_throw_appropriate_java_exception() throws Exception { - String dbPath = TestUtils.createTempFile(); LimboDB.load(); + String dbPath = TestUtils.createTempFile(); LimboDB db = LimboDB.create("jdbc:sqlite:" + dbPath, dbPath); final int limboExceptionCode = LimboErrorCode.LIMBO_ETC.code; diff --git a/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java index 1bc4fb526..6302bc899 100644 --- a/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java +++ b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java @@ -84,4 +84,15 @@ class JDBC4ConnectionTest { connection.createStatement( ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, -1)); } + + @Test + void isValid_should_return_true_on_open_connection() throws SQLException { + assertTrue(connection.isValid(10)); + } + + @Test + void isValid_should_return_false_on_closed_connection() throws SQLException { + connection.close(); + assertFalse(connection.isValid(10)); + } } diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index 6157fa579..43639f94c 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -16,7 +16,7 @@ extension-module = ["pyo3/extension-module"] [dependencies] anyhow = "1.0" -limbo_core = { path = "../../core" } +limbo_core = { path = "../../core", features = ["io_uring"] } pyo3 = { version = "0.22.4", features = ["anyhow"] } [build-dependencies] diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index 3dd269b0e..4ed6066ce 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -40,8 +40,8 @@ dev = [ ] [project.urls] -Homepage = "https://github.com/penberg/limbo" -Source = "https://github.com/penberg/limbo" +Homepage = "https://github.com/tursodatabase/limbo" +Source = "https://github.com/tursodatabase/limbo" [tool.maturin] bindings = 'pyo3' diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index fb8189c2d..31a82389d 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -300,7 +300,7 @@ pub fn connect(path: &str) -> Result { fn row_to_py(py: Python, row: &limbo_core::Row) -> PyObject { let py_values: Vec = row - .values + .get_values() .iter() .map(|value| match value.to_value() { limbo_core::Value::Null => py.None(), diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index bdf101c56..a5c3a3829 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true repository.workspace = true [dependencies] -limbo_core = { path = "../../core" } +limbo_core = { path = "../../core", features = ["io_uring"] } thiserror = "2.0.9" [dev-dependencies] diff --git a/bindings/rust/src/params.rs b/bindings/rust/src/params.rs index c15b6adb5..a627cdd3f 100644 --- a/bindings/rust/src/params.rs +++ b/bindings/rust/src/params.rs @@ -18,7 +18,7 @@ use sealed::Sealed; /// /// Many functions in this library let you pass parameters to libsql. Doing this /// lets you avoid any risk of SQL injection, and is simpler than escaping -/// things manually. These functions generally contain some paramter that generically +/// things manually. These functions generally contain some parameter that generically /// accepts some implementation this trait. /// /// # Positional parameters @@ -29,7 +29,7 @@ use sealed::Sealed; /// by doing `(1, "foo")`. /// - For hetergeneous parameter lists of 16 or greater, the [`limbo_libsql::params!`] is supported /// by doing `limbo_libsql::params![1, "foo"]`. -/// - For homogeneous paramter types (where they are all the same type), const arrays are +/// - For homogeneous parameter types (where they are all the same type), const arrays are /// supported by doing `[1, 2, 3]`. /// /// # Example (positional) @@ -58,13 +58,13 @@ use sealed::Sealed; /// # } /// ``` /// -/// # Named paramters +/// # Named parameters /// /// - For heterogeneous parameter lists of 16 or less items a tuple syntax is supported /// by doing `(("key1", 1), ("key2", "foo"))`. /// - For hetergeneous parameter lists of 16 or greater, the [`limbo_libsql::params!`] is supported /// by doing `limbo_libsql::named_params!["key1": 1, "key2": "foo"]`. -/// - For homogeneous paramter types (where they are all the same type), const arrays are +/// - For homogeneous parameter types (where they are all the same type), const arrays are /// supported by doing `[("key1", 1), ("key2, 2), ("key3", 3)]`. /// /// # Example (named) diff --git a/bindings/wasm/lib.rs b/bindings/wasm/lib.rs index 26f877199..aecfecd2e 100644 --- a/bindings/wasm/lib.rs +++ b/bindings/wasm/lib.rs @@ -76,7 +76,7 @@ impl RowIterator { Ok(limbo_core::StepResult::Row) => { let row = stmt.row().unwrap(); let row_array = Array::new(); - for value in &row.values { + for value in row.get_values() { let value = value.to_value(); let value = to_js_value(value); row_array.push(&value); @@ -117,7 +117,7 @@ impl Statement { Ok(limbo_core::StepResult::Row) => { let row = stmt.row().unwrap(); let row_array = js_sys::Array::new(); - for value in &row.values { + for value in row.get_values() { let value = value.to_value(); let value = to_js_value(value); row_array.push(&value); @@ -140,7 +140,7 @@ impl Statement { Ok(limbo_core::StepResult::Row) => { let row = stmt.row().unwrap(); let row_array = js_sys::Array::new(); - for value in &row.values { + for value in row.get_values() { let value = value.to_value(); let value = to_js_value(value); row_array.push(&value); @@ -228,9 +228,9 @@ impl limbo_core::File for File { Ok(()) } - fn pread(&self, pos: usize, c: Rc) -> Result<()> { - let r = match &*c { - limbo_core::Completion::Read(r) => r, + fn pread(&self, pos: usize, c: limbo_core::Completion) -> Result<()> { + let r = match &c { + limbo_core::Completion::Read(ref r) => r, _ => unreachable!(), }; { @@ -247,10 +247,10 @@ impl limbo_core::File for File { &self, pos: usize, buffer: Rc>, - c: Rc, + c: limbo_core::Completion, ) -> Result<()> { - let w = match &*c { - limbo_core::Completion::Write(w) => w, + let w = match &c { + limbo_core::Completion::Write(ref w) => w, _ => unreachable!(), }; let buf = buffer.borrow(); @@ -260,7 +260,7 @@ impl limbo_core::File for File { Ok(()) } - fn sync(&self, c: Rc) -> Result<()> { + fn sync(&self, c: limbo_core::Completion) -> Result<()> { self.vfs.sync(self.fd); c.complete(0); Ok(()) @@ -331,9 +331,9 @@ impl DatabaseStorage { } impl limbo_core::DatabaseStorage for DatabaseStorage { - fn read_page(&self, page_idx: usize, c: Rc) -> Result<()> { - let r = match c.as_ref() { - limbo_core::Completion::Read(r) => r, + fn read_page(&self, page_idx: usize, c: limbo_core::Completion) -> Result<()> { + let r = match c { + limbo_core::Completion::Read(ref r) => r, _ => unreachable!(), }; let size = r.buf().len(); @@ -350,7 +350,7 @@ impl limbo_core::DatabaseStorage for DatabaseStorage { &self, page_idx: usize, buffer: Rc>, - c: Rc, + c: limbo_core::Completion, ) -> Result<()> { let size = buffer.borrow().len(); let pos = (page_idx - 1) * size; @@ -358,7 +358,7 @@ impl limbo_core::DatabaseStorage for DatabaseStorage { Ok(()) } - fn sync(&self, _c: Rc) -> Result<()> { + fn sync(&self, _c: limbo_core::Completion) -> Result<()> { todo!() } } diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 2265a7799..d7c799bb9 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -7,7 +7,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/penberg/limbo" + "url": "https://github.com/tursodatabase/limbo" }, "type": "module", "main": "./node/dist/index.cjs", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1886627e4..d73256e93 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -21,7 +21,7 @@ path = "main.rs" [dependencies] anyhow = "1.0.75" clap = { version = "4.5", features = ["derive"] } -cli-table = "0.4.7" +comfy-table = "7.1.4" dirs = "5.0.1" env_logger = "0.10.1" limbo_core = { path = "../core" } @@ -31,4 +31,5 @@ csv = "1.3.1" miette = { version = "7.4.0", features = ["fancy"] } [features] +default = ["io_uring"] io_uring = ["limbo_core/io_uring"] diff --git a/cli/app.rs b/cli/app.rs index 7fa7b70f0..b4d029648 100644 --- a/cli/app.rs +++ b/cli/app.rs @@ -2,8 +2,7 @@ use crate::{ import::{ImportFile, IMPORT_HELP}, opcodes_dictionary::OPCODE_DESCRIPTIONS, }; -use cli_table::format::{Border, HorizontalLine, Separator, VerticalLine}; -use cli_table::{Cell, Style, Table}; +use comfy_table::{Attribute, Cell, CellAlignment, ContentArrangement, Row, Table}; use limbo_core::{Database, LimboError, Statement, StepResult, Value}; use clap::{Parser, ValueEnum}; @@ -628,7 +627,7 @@ impl Limbo { match rows.step() { Ok(StepResult::Row) => { let row = rows.row().unwrap(); - for (i, value) in row.values.iter().enumerate() { + for (i, value) in row.get_values().iter().enumerate() { let value = value.to_value(); if i > 0 { let _ = self.writer.write(b"|"); @@ -670,35 +669,42 @@ impl Limbo { println!("Query interrupted."); return Ok(()); } - let mut table_rows: Vec> = vec![]; + let mut table = Table::new(); + table + .set_content_arrangement(ContentArrangement::Dynamic) + .set_truncation_indicator("…") + .apply_modifier("││──├─┼┤│─┼├┤┬┴┌┐└┘"); if rows.num_columns() > 0 { - let columns = (0..rows.num_columns()) + let header = (0..rows.num_columns()) .map(|i| { - rows.get_column_name(i) - .map(|name| name.cell().bold(true)) - .unwrap_or_else(|| " ".cell()) + let name = rows.get_column_name(i).cloned().unwrap_or_default(); + Cell::new(name).add_attribute(Attribute::Bold) }) .collect::>(); - table_rows.push(columns); + table.set_header(header); } loop { match rows.step() { Ok(StepResult::Row) => { - let row = rows.row().unwrap(); - table_rows.push( - row.values - .iter() - .map(|value| match value.to_value() { - Value::Null => self.opts.null_value.clone().cell(), - Value::Integer(i) => i.to_string().cell(), - Value::Float(f) => f.to_string().cell(), - Value::Text(s) => s.cell(), - Value::Blob(b) => { - format!("{}", String::from_utf8_lossy(b)).cell() - } - }) - .collect(), - ); + let record = rows.row().unwrap(); + let mut row = Row::new(); + row.max_height(1); + for value in record.get_values() { + let (content, alignment) = match value.to_value() { + Value::Null => { + (self.opts.null_value.clone(), CellAlignment::Left) + } + Value::Integer(i) => (i.to_string(), CellAlignment::Right), + Value::Float(f) => (f.to_string(), CellAlignment::Right), + Value::Text(s) => (s.to_string(), CellAlignment::Left), + Value::Blob(b) => ( + String::from_utf8_lossy(b).to_string(), + CellAlignment::Left, + ), + }; + row.add_cell(Cell::new(content).set_alignment(alignment)); + } + table.add_row(row); } Ok(StepResult::IO) => { self.io.run_once()?; @@ -718,7 +724,10 @@ impl Limbo { } } } - self.print_table(table_rows); + + if table.header().is_some() { + let _ = self.write_fmt(format_args!("{}", table)); + } } }, Ok(None) => {} @@ -734,40 +743,6 @@ impl Limbo { Ok(()) } - fn print_table(&mut self, table_rows: Vec>) { - if table_rows.is_empty() { - return; - } - - let horizontal_line = HorizontalLine::new('┌', '┐', '┬', '─'); - let horizontal_line_mid = HorizontalLine::new('├', '┤', '┼', '─'); - let horizontal_line_bottom = HorizontalLine::new('└', '┘', '┴', '─'); - let vertical_line = VerticalLine::new('│'); - - let border = Border::builder() - .top(horizontal_line) - .bottom(horizontal_line_bottom) - .left(vertical_line.clone()) - .right(vertical_line.clone()) - .build(); - - let separator = Separator::builder() - .column(Some(vertical_line)) - .row(Some(horizontal_line_mid)) - .build(); - - if let Ok(table) = table_rows - .table() - .border(border) - .separator(separator) - .display() - { - let _ = self.write_fmt(format_args!("{}", table)); - } else { - let _ = self.writeln("Error displaying table."); - } - } - fn display_schema(&mut self, table: Option<&str>) -> anyhow::Result<()> { let sql = match table { Some(table_name) => format!( @@ -787,7 +762,7 @@ impl Limbo { StepResult::Row => { let row = rows.row().unwrap(); if let Some(Value::Text(schema)) = - row.values.first().map(|v| v.to_value()) + row.get_values().first().map(|v| v.to_value()) { let _ = self.write_fmt(format_args!("{};", schema)); found = true; @@ -847,7 +822,7 @@ impl Limbo { StepResult::Row => { let row = rows.row().unwrap(); if let Some(Value::Text(table)) = - row.values.first().map(|v| v.to_value()) + row.get_values().first().map(|v| v.to_value()) { tables.push_str(table); tables.push(' '); diff --git a/core/Cargo.toml b/core/Cargo.toml index 687f4ff19..0f5b77780 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,7 +14,7 @@ name = "limbo_core" path = "lib.rs" [features] -default = ["fs", "json", "uuid", "io_uring", "time"] +default = ["fs", "json", "uuid", "time"] fs = [] json = [ "dep:jsonb", @@ -47,7 +47,6 @@ fallible-iterator = "0.3.0" hex = "0.4.3" libc = "0.2.155" log = "0.4.20" -sieve-cache = "0.1.4" sqlite3-parser = { path = "../vendored/sqlite3-parser" } thiserror = "1.0.61" getrandom = { version = "0.2.15", features = ["js"] } @@ -61,7 +60,6 @@ serde = { version = "1.0", features = ["derive"] } pest = { version = "2.0", optional = true } pest_derive = { version = "2.0", optional = true } rand = "0.8.5" -bumpalo = { version = "3.16.0", features = ["collections", "boxed"] } limbo_macros = { path = "../macros" } limbo_uuid = { path = "../extensions/uuid", optional = true, features = ["static"] } limbo_regexp = { path = "../extensions/regexp", optional = true, features = ["static"] } @@ -88,13 +86,14 @@ criterion = { version = "0.5", features = [ "async", "async_futures", ] } -mockall = "0.13.0" rstest = "0.18.2" rusqlite = "0.29.0" tempfile = "3.8.0" quickcheck = { version = "1.0", default-features = false } quickcheck_macros = { version = "1.0", default-features = false } -rand = "0.8" # Required for quickcheck +rand = "0.8.5" # Required for quickcheck +rand_chacha = "0.9.0" +env_logger = "0.11.6" [[bench]] name = "benchmark" diff --git a/core/benches/benchmark.rs b/core/benches/benchmark.rs index 21b75424d..d2aef982b 100644 --- a/core/benches/benchmark.rs +++ b/core/benches/benchmark.rs @@ -12,7 +12,7 @@ fn rusqlite_open() -> rusqlite::Connection { } fn bench(criterion: &mut Criterion) { - // https://github.com/penberg/limbo/issues/174 + // https://github.com/tursodatabase/limbo/issues/174 // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features let enable_rusqlite = std::env::var("DISABLE_RUSQLITE_BENCHMARK").is_err(); diff --git a/core/ext/mod.rs b/core/ext/mod.rs index 67fd78491..8ac0bdcc2 100644 --- a/core/ext/mod.rs +++ b/core/ext/mod.rs @@ -127,7 +127,7 @@ impl Database { let Stmt::CreateTable { body, .. } = stmt else { return ResultCode::Error; }; - let Ok(columns) = columns_from_create_table_body(body) else { + let Ok(columns) = columns_from_create_table_body(*body) else { return ResultCode::Error; }; let vtab_module = self.vtab_modules.get(name).unwrap().clone(); @@ -153,6 +153,7 @@ impl Database { } pub fn register_builtins(&self) -> Result<(), String> { + #[allow(unused_variables)] let ext_api = self.build_limbo_ext(); #[cfg(feature = "uuid")] if unsafe { !limbo_uuid::register_extension_static(&ext_api).is_ok() } { diff --git a/core/functions/datetime.rs b/core/functions/datetime.rs index 8c5c0aaff..aeac63337 100644 --- a/core/functions/datetime.rs +++ b/core/functions/datetime.rs @@ -4,7 +4,6 @@ use crate::Result; use chrono::{ DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta, TimeZone, Timelike, Utc, }; -use std::rc::Rc; /// Execution of date/time/datetime functions #[inline(always)] @@ -48,8 +47,7 @@ enum DateTimeOutput { fn exec_datetime(values: &[OwnedValue], output_type: DateTimeOutput) -> OwnedValue { if values.is_empty() { - let now = - parse_naive_date_time(&OwnedValue::build_text(Rc::new("now".to_string()))).unwrap(); + let now = parse_naive_date_time(&OwnedValue::build_text("now")).unwrap(); let formatted_str = match output_type { DateTimeOutput::DateTime => now.format("%Y-%m-%d %H:%M:%S").to_string(), @@ -59,7 +57,7 @@ fn exec_datetime(values: &[OwnedValue], output_type: DateTimeOutput) -> OwnedVal }; // Parse here - return OwnedValue::build_text(Rc::new(formatted_str)); + return OwnedValue::build_text(&formatted_str); } if let Some(mut dt) = parse_naive_date_time(&values[0]) { // if successful, treat subsequent entries as modifiers @@ -85,17 +83,17 @@ fn modify_dt( match apply_modifier(dt, text_rc.as_str()) { Ok(true) => subsec_requested = true, Ok(false) => {} - Err(_) => return OwnedValue::build_text(Rc::new(String::new())), + Err(_) => return OwnedValue::build_text(""), } } else { - return OwnedValue::build_text(Rc::new(String::new())); + return OwnedValue::build_text(""); } } if is_leap_second(dt) || *dt > get_max_datetime_exclusive() { - return OwnedValue::build_text(Rc::new(String::new())); + return OwnedValue::build_text(""); } let formatted = format_dt(*dt, output_type, subsec_requested); - OwnedValue::build_text(Rc::new(formatted)) + OwnedValue::build_text(&formatted) } fn format_dt(dt: NaiveDateTime, output_type: DateTimeOutput, subsec: bool) -> String { @@ -662,7 +660,6 @@ fn parse_modifier(modifier: &str) -> Result { #[cfg(test)] mod tests { use super::*; - use std::rc::Rc; #[test] fn test_valid_get_date_from_time_value() { @@ -674,201 +671,135 @@ mod tests { let test_cases = vec![ // Format 1: YYYY-MM-DD (no timezone applicable) - ( - OwnedValue::build_text(Rc::new("2024-07-21".to_string())), - test_date_str, - ), + (OwnedValue::build_text("2024-07-21"), test_date_str), // Format 2: YYYY-MM-DD HH:MM + (OwnedValue::build_text("2024-07-21 22:30"), test_date_str), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30".to_string())), + OwnedValue::build_text("2024-07-21 22:30+02:00"), test_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30+02:00".to_string())), - test_date_str, - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30-05:00".to_string())), + OwnedValue::build_text("2024-07-21 22:30-05:00"), next_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 01:30+05:00".to_string())), + OwnedValue::build_text("2024-07-21 01:30+05:00"), prev_date_str, ), - ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30Z".to_string())), - test_date_str, - ), + (OwnedValue::build_text("2024-07-21 22:30Z"), test_date_str), // Format 3: YYYY-MM-DD HH:MM:SS + (OwnedValue::build_text("2024-07-21 22:30:45"), test_date_str), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45+02:00"), test_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45+02:00".to_string())), - test_date_str, - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45-05:00".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45-05:00"), next_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 01:30:45+05:00".to_string())), + OwnedValue::build_text("2024-07-21 01:30:45+05:00"), prev_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45Z".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45Z"), test_date_str, ), // Format 4: YYYY-MM-DD HH:MM:SS.SSS ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45.123".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45.123"), test_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45.123+02:00".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45.123+02:00"), test_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45.123-05:00".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45.123-05:00"), next_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 01:30:45.123+05:00".to_string())), + OwnedValue::build_text("2024-07-21 01:30:45.123+05:00"), prev_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45.123Z".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45.123Z"), test_date_str, ), // Format 5: YYYY-MM-DDTHH:MM + (OwnedValue::build_text("2024-07-21T22:30"), test_date_str), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30".to_string())), + OwnedValue::build_text("2024-07-21T22:30+02:00"), test_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30+02:00".to_string())), - test_date_str, - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30-05:00".to_string())), + OwnedValue::build_text("2024-07-21T22:30-05:00"), next_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T01:30+05:00".to_string())), + OwnedValue::build_text("2024-07-21T01:30+05:00"), prev_date_str, ), - ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30Z".to_string())), - test_date_str, - ), + (OwnedValue::build_text("2024-07-21T22:30Z"), test_date_str), // Format 6: YYYY-MM-DDTHH:MM:SS + (OwnedValue::build_text("2024-07-21T22:30:45"), test_date_str), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45+02:00"), test_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45+02:00".to_string())), - test_date_str, - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45-05:00".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45-05:00"), next_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T01:30:45+05:00".to_string())), + OwnedValue::build_text("2024-07-21T01:30:45+05:00"), prev_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45Z".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45Z"), test_date_str, ), // Format 7: YYYY-MM-DDTHH:MM:SS.SSS ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45.123".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45.123"), test_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45.123+02:00".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45.123+02:00"), test_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45.123-05:00".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45.123-05:00"), next_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T01:30:45.123+05:00".to_string())), + OwnedValue::build_text("2024-07-21T01:30:45.123+05:00"), prev_date_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45.123Z".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45.123Z"), test_date_str, ), // Format 8: HH:MM - ( - OwnedValue::build_text(Rc::new("22:30".to_string())), - "2000-01-01", - ), - ( - OwnedValue::build_text(Rc::new("22:30+02:00".to_string())), - "2000-01-01", - ), - ( - OwnedValue::build_text(Rc::new("22:30-05:00".to_string())), - "2000-01-02", - ), - ( - OwnedValue::build_text(Rc::new("01:30+05:00".to_string())), - "1999-12-31", - ), - ( - OwnedValue::build_text(Rc::new("22:30Z".to_string())), - "2000-01-01", - ), + (OwnedValue::build_text("22:30"), "2000-01-01"), + (OwnedValue::build_text("22:30+02:00"), "2000-01-01"), + (OwnedValue::build_text("22:30-05:00"), "2000-01-02"), + (OwnedValue::build_text("01:30+05:00"), "1999-12-31"), + (OwnedValue::build_text("22:30Z"), "2000-01-01"), // Format 9: HH:MM:SS - ( - OwnedValue::build_text(Rc::new("22:30:45".to_string())), - "2000-01-01", - ), - ( - OwnedValue::build_text(Rc::new("22:30:45+02:00".to_string())), - "2000-01-01", - ), - ( - OwnedValue::build_text(Rc::new("22:30:45-05:00".to_string())), - "2000-01-02", - ), - ( - OwnedValue::build_text(Rc::new("01:30:45+05:00".to_string())), - "1999-12-31", - ), - ( - OwnedValue::build_text(Rc::new("22:30:45Z".to_string())), - "2000-01-01", - ), + (OwnedValue::build_text("22:30:45"), "2000-01-01"), + (OwnedValue::build_text("22:30:45+02:00"), "2000-01-01"), + (OwnedValue::build_text("22:30:45-05:00"), "2000-01-02"), + (OwnedValue::build_text("01:30:45+05:00"), "1999-12-31"), + (OwnedValue::build_text("22:30:45Z"), "2000-01-01"), // Format 10: HH:MM:SS.SSS - ( - OwnedValue::build_text(Rc::new("22:30:45.123".to_string())), - "2000-01-01", - ), - ( - OwnedValue::build_text(Rc::new("22:30:45.123+02:00".to_string())), - "2000-01-01", - ), - ( - OwnedValue::build_text(Rc::new("22:30:45.123-05:00".to_string())), - "2000-01-02", - ), - ( - OwnedValue::build_text(Rc::new("01:30:45.123+05:00".to_string())), - "1999-12-31", - ), - ( - OwnedValue::build_text(Rc::new("22:30:45.123Z".to_string())), - "2000-01-01", - ), + (OwnedValue::build_text("22:30:45.123"), "2000-01-01"), + (OwnedValue::build_text("22:30:45.123+02:00"), "2000-01-01"), + (OwnedValue::build_text("22:30:45.123-05:00"), "2000-01-02"), + (OwnedValue::build_text("01:30:45.123+05:00"), "1999-12-31"), + (OwnedValue::build_text("22:30:45.123Z"), "2000-01-01"), // Test Format 11: 'now' - (OwnedValue::build_text(Rc::new("now".to_string())), &now), + (OwnedValue::build_text("now"), &now), // Format 12: DDDDDDDDDD (Julian date as float or integer) (OwnedValue::Float(2460512.5), test_date_str), (OwnedValue::Integer(2460513), test_date_str), @@ -878,7 +809,7 @@ mod tests { let result = exec_date(&[input.clone()]); assert_eq!( result, - OwnedValue::build_text(Rc::new(expected.to_string())), + OwnedValue::build_text(expected), "Failed for input: {:?}", input ); @@ -888,31 +819,31 @@ mod tests { #[test] fn test_invalid_get_date_from_time_value() { let invalid_cases = vec![ - OwnedValue::build_text(Rc::new("2024-07-21 25:00".to_string())), // Invalid hour - OwnedValue::build_text(Rc::new("2024-07-21 24:00:00".to_string())), // Invalid hour - OwnedValue::build_text(Rc::new("2024-07-21 23:60:00".to_string())), // Invalid minute - OwnedValue::build_text(Rc::new("2024-07-21 22:58:60".to_string())), // Invalid second - OwnedValue::build_text(Rc::new("2024-07-32".to_string())), // Invalid day - OwnedValue::build_text(Rc::new("2024-13-01".to_string())), // Invalid month - OwnedValue::build_text(Rc::new("invalid_date".to_string())), // Completely invalid string - OwnedValue::build_text(Rc::new("".to_string())), // Empty string - OwnedValue::Integer(i64::MAX), // Large Julian day - OwnedValue::Integer(-1), // Negative Julian day - OwnedValue::Float(f64::MAX), // Large float - OwnedValue::Float(-1.0), // Negative Julian day as float - OwnedValue::Float(f64::NAN), // NaN - OwnedValue::Float(f64::INFINITY), // Infinity - OwnedValue::Null, // Null value - OwnedValue::Blob(vec![1, 2, 3].into()), // Blob (unsupported type) + OwnedValue::build_text("2024-07-21 25:00"), // Invalid hour + OwnedValue::build_text("2024-07-21 24:00:00"), // Invalid hour + OwnedValue::build_text("2024-07-21 23:60:00"), // Invalid minute + OwnedValue::build_text("2024-07-21 22:58:60"), // Invalid second + OwnedValue::build_text("2024-07-32"), // Invalid day + OwnedValue::build_text("2024-13-01"), // Invalid month + OwnedValue::build_text("invalid_date"), // Completely invalid string + OwnedValue::build_text(""), // Empty string + OwnedValue::Integer(i64::MAX), // Large Julian day + OwnedValue::Integer(-1), // Negative Julian day + OwnedValue::Float(f64::MAX), // Large float + OwnedValue::Float(-1.0), // Negative Julian day as float + OwnedValue::Float(f64::NAN), // NaN + OwnedValue::Float(f64::INFINITY), // Infinity + OwnedValue::Null, // Null value + OwnedValue::Blob(vec![1, 2, 3].into()), // Blob (unsupported type) // Invalid timezone tests - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+24:00".to_string())), // Invalid timezone offset (too large) - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00-24:00".to_string())), // Invalid timezone offset (too small) - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+00:60".to_string())), // Invalid timezone minutes - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+00:00:00".to_string())), // Invalid timezone format (extra seconds) - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+".to_string())), // Incomplete timezone - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+Z".to_string())), // Invalid timezone format - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+00:00Z".to_string())), // Mixing offset and Z - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00UTC".to_string())), // Named timezone (not supported) + OwnedValue::build_text("2024-07-21T12:00:00+24:00"), // Invalid timezone offset (too large) + OwnedValue::build_text("2024-07-21T12:00:00-24:00"), // Invalid timezone offset (too small) + OwnedValue::build_text("2024-07-21T12:00:00+00:60"), // Invalid timezone minutes + OwnedValue::build_text("2024-07-21T12:00:00+00:00:00"), // Invalid timezone format (extra seconds) + OwnedValue::build_text("2024-07-21T12:00:00+"), // Incomplete timezone + OwnedValue::build_text("2024-07-21T12:00:00+Z"), // Invalid timezone format + OwnedValue::build_text("2024-07-21T12:00:00+00:00Z"), // Mixing offset and Z + OwnedValue::build_text("2024-07-21T12:00:00UTC"), // Named timezone (not supported) ]; for case in invalid_cases.iter() { @@ -935,163 +866,94 @@ mod tests { let test_cases = vec![ // Format 1: YYYY-MM-DD (no timezone applicable) - ( - OwnedValue::build_text(Rc::new("2024-07-21".to_string())), - "00:00:00", - ), + (OwnedValue::build_text("2024-07-21"), "00:00:00"), // Format 2: YYYY-MM-DD HH:MM - ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30".to_string())), - "22:30:00", - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30+02:00".to_string())), - "20:30:00", - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30-05:00".to_string())), - "03:30:00", - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30Z".to_string())), - "22:30:00", - ), + (OwnedValue::build_text("2024-07-21 22:30"), "22:30:00"), + (OwnedValue::build_text("2024-07-21 22:30+02:00"), "20:30:00"), + (OwnedValue::build_text("2024-07-21 22:30-05:00"), "03:30:00"), + (OwnedValue::build_text("2024-07-21 22:30Z"), "22:30:00"), // Format 3: YYYY-MM-DD HH:MM:SS + (OwnedValue::build_text("2024-07-21 22:30:45"), test_time_str), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45".to_string())), - test_time_str, - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45+02:00".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45+02:00"), prev_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45-05:00".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45-05:00"), next_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45Z".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45Z"), test_time_str, ), // Format 4: YYYY-MM-DD HH:MM:SS.SSS ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45.123".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45.123"), test_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45.123+02:00".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45.123+02:00"), prev_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45.123-05:00".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45.123-05:00"), next_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21 22:30:45.123Z".to_string())), + OwnedValue::build_text("2024-07-21 22:30:45.123Z"), test_time_str, ), // Format 5: YYYY-MM-DDTHH:MM - ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30".to_string())), - "22:30:00", - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30+02:00".to_string())), - "20:30:00", - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30-05:00".to_string())), - "03:30:00", - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30Z".to_string())), - "22:30:00", - ), + (OwnedValue::build_text("2024-07-21T22:30"), "22:30:00"), + (OwnedValue::build_text("2024-07-21T22:30+02:00"), "20:30:00"), + (OwnedValue::build_text("2024-07-21T22:30-05:00"), "03:30:00"), + (OwnedValue::build_text("2024-07-21T22:30Z"), "22:30:00"), // Format 6: YYYY-MM-DDTHH:MM:SS + (OwnedValue::build_text("2024-07-21T22:30:45"), test_time_str), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45".to_string())), - test_time_str, - ), - ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45+02:00".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45+02:00"), prev_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45-05:00".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45-05:00"), next_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45Z".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45Z"), test_time_str, ), // Format 7: YYYY-MM-DDTHH:MM:SS.SSS ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45.123".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45.123"), test_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45.123+02:00".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45.123+02:00"), prev_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45.123-05:00".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45.123-05:00"), next_time_str, ), ( - OwnedValue::build_text(Rc::new("2024-07-21T22:30:45.123Z".to_string())), + OwnedValue::build_text("2024-07-21T22:30:45.123Z"), test_time_str, ), // Format 8: HH:MM - ( - OwnedValue::build_text(Rc::new("22:30".to_string())), - "22:30:00", - ), - ( - OwnedValue::build_text(Rc::new("22:30+02:00".to_string())), - "20:30:00", - ), - ( - OwnedValue::build_text(Rc::new("22:30-05:00".to_string())), - "03:30:00", - ), - ( - OwnedValue::build_text(Rc::new("22:30Z".to_string())), - "22:30:00", - ), + (OwnedValue::build_text("22:30"), "22:30:00"), + (OwnedValue::build_text("22:30+02:00"), "20:30:00"), + (OwnedValue::build_text("22:30-05:00"), "03:30:00"), + (OwnedValue::build_text("22:30Z"), "22:30:00"), // Format 9: HH:MM:SS - ( - OwnedValue::build_text(Rc::new("22:30:45".to_string())), - test_time_str, - ), - ( - OwnedValue::build_text(Rc::new("22:30:45+02:00".to_string())), - prev_time_str, - ), - ( - OwnedValue::build_text(Rc::new("22:30:45-05:00".to_string())), - next_time_str, - ), - ( - OwnedValue::build_text(Rc::new("22:30:45Z".to_string())), - test_time_str, - ), + (OwnedValue::build_text("22:30:45"), test_time_str), + (OwnedValue::build_text("22:30:45+02:00"), prev_time_str), + (OwnedValue::build_text("22:30:45-05:00"), next_time_str), + (OwnedValue::build_text("22:30:45Z"), test_time_str), // Format 10: HH:MM:SS.SSS - ( - OwnedValue::build_text(Rc::new("22:30:45.123".to_string())), - test_time_str, - ), - ( - OwnedValue::build_text(Rc::new("22:30:45.123+02:00".to_string())), - prev_time_str, - ), - ( - OwnedValue::build_text(Rc::new("22:30:45.123-05:00".to_string())), - next_time_str, - ), - ( - OwnedValue::build_text(Rc::new("22:30:45.123Z".to_string())), - test_time_str, - ), + (OwnedValue::build_text("22:30:45.123"), test_time_str), + (OwnedValue::build_text("22:30:45.123+02:00"), prev_time_str), + (OwnedValue::build_text("22:30:45.123-05:00"), next_time_str), + (OwnedValue::build_text("22:30:45.123Z"), test_time_str), // Format 12: DDDDDDDDDD (Julian date as float or integer) (OwnedValue::Float(2460082.1), "14:24:00"), (OwnedValue::Integer(2460082), "12:00:00"), @@ -1110,31 +972,31 @@ mod tests { #[test] fn test_invalid_get_time_from_datetime_value() { let invalid_cases = vec![ - OwnedValue::build_text(Rc::new("2024-07-21 25:00".to_string())), // Invalid hour - OwnedValue::build_text(Rc::new("2024-07-21 24:00:00".to_string())), // Invalid hour - OwnedValue::build_text(Rc::new("2024-07-21 23:60:00".to_string())), // Invalid minute - OwnedValue::build_text(Rc::new("2024-07-21 22:58:60".to_string())), // Invalid second - OwnedValue::build_text(Rc::new("2024-07-32".to_string())), // Invalid day - OwnedValue::build_text(Rc::new("2024-13-01".to_string())), // Invalid month - OwnedValue::build_text(Rc::new("invalid_date".to_string())), // Completely invalid string - OwnedValue::build_text(Rc::new("".to_string())), // Empty string - OwnedValue::Integer(i64::MAX), // Large Julian day - OwnedValue::Integer(-1), // Negative Julian day - OwnedValue::Float(f64::MAX), // Large float - OwnedValue::Float(-1.0), // Negative Julian day as float - OwnedValue::Float(f64::NAN), // NaN - OwnedValue::Float(f64::INFINITY), // Infinity - OwnedValue::Null, // Null value - OwnedValue::Blob(vec![1, 2, 3].into()), // Blob (unsupported type) + OwnedValue::build_text("2024-07-21 25:00"), // Invalid hour + OwnedValue::build_text("2024-07-21 24:00:00"), // Invalid hour + OwnedValue::build_text("2024-07-21 23:60:00"), // Invalid minute + OwnedValue::build_text("2024-07-21 22:58:60"), // Invalid second + OwnedValue::build_text("2024-07-32"), // Invalid day + OwnedValue::build_text("2024-13-01"), // Invalid month + OwnedValue::build_text("invalid_date"), // Completely invalid string + OwnedValue::build_text(""), // Empty string + OwnedValue::Integer(i64::MAX), // Large Julian day + OwnedValue::Integer(-1), // Negative Julian day + OwnedValue::Float(f64::MAX), // Large float + OwnedValue::Float(-1.0), // Negative Julian day as float + OwnedValue::Float(f64::NAN), // NaN + OwnedValue::Float(f64::INFINITY), // Infinity + OwnedValue::Null, // Null value + OwnedValue::Blob(vec![1, 2, 3].into()), // Blob (unsupported type) // Invalid timezone tests - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+24:00".to_string())), // Invalid timezone offset (too large) - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00-24:00".to_string())), // Invalid timezone offset (too small) - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+00:60".to_string())), // Invalid timezone minutes - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+00:00:00".to_string())), // Invalid timezone format (extra seconds) - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+".to_string())), // Incomplete timezone - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+Z".to_string())), // Invalid timezone format - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00+00:00Z".to_string())), // Mixing offset and Z - OwnedValue::build_text(Rc::new("2024-07-21T12:00:00UTC".to_string())), // Named timezone (not supported) + OwnedValue::build_text("2024-07-21T12:00:00+24:00"), // Invalid timezone offset (too large) + OwnedValue::build_text("2024-07-21T12:00:00-24:00"), // Invalid timezone offset (too small) + OwnedValue::build_text("2024-07-21T12:00:00+00:60"), // Invalid timezone minutes + OwnedValue::build_text("2024-07-21T12:00:00+00:00:00"), // Invalid timezone format (extra seconds) + OwnedValue::build_text("2024-07-21T12:00:00+"), // Incomplete timezone + OwnedValue::build_text("2024-07-21T12:00:00+Z"), // Invalid timezone format + OwnedValue::build_text("2024-07-21T12:00:00+00:00Z"), // Mixing offset and Z + OwnedValue::build_text("2024-07-21T12:00:00UTC"), // Named timezone (not supported) ]; for case in invalid_cases { @@ -1441,7 +1303,7 @@ mod tests { } fn text(value: &str) -> OwnedValue { - OwnedValue::build_text(Rc::new(value.to_string())) + OwnedValue::build_text(value) } fn format(dt: NaiveDateTime) -> String { diff --git a/core/functions/printf.rs b/core/functions/printf.rs index 73e4bf4f3..7932aa391 100644 --- a/core/functions/printf.rs +++ b/core/functions/printf.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use crate::types::OwnedValue; use crate::LimboError; @@ -73,16 +71,15 @@ pub fn exec_printf(values: &[OwnedValue]) -> crate::Result { } } } - Ok(OwnedValue::build_text(Rc::new(result))) + Ok(OwnedValue::build_text(&result)) } #[cfg(test)] mod tests { use super::*; - use std::rc::Rc; fn text(value: &str) -> OwnedValue { - OwnedValue::build_text(Rc::new(value.to_string())) + OwnedValue::build_text(value) } fn integer(value: i64) -> OwnedValue { diff --git a/core/io/generic.rs b/core/io/generic.rs index 79bcde49c..a72b1837d 100644 --- a/core/io/generic.rs +++ b/core/io/generic.rs @@ -48,7 +48,7 @@ pub struct GenericFile { impl File for GenericFile { // Since we let the OS handle the locking, file locking is not supported on the generic IO implementation // No-op implementation allows compilation but provides no actual file locking. - fn lock_file(&self, exclusive: bool) -> Result<()> { + fn lock_file(&self, _exclusive: bool) -> Result<()> { Ok(()) } @@ -56,12 +56,12 @@ impl File for GenericFile { Ok(()) } - fn pread(&self, pos: usize, c: Rc) -> Result<()> { + fn pread(&self, pos: usize, c: Completion) -> Result<()> { let mut file = self.file.borrow_mut(); file.seek(std::io::SeekFrom::Start(pos as u64))?; { - let r = match c.as_ref() { - Completion::Read(r) => r, + let r = match c { + Completion::Read(ref r) => r, _ => unreachable!(), }; let mut buf = r.buf_mut(); @@ -72,12 +72,7 @@ impl File for GenericFile { Ok(()) } - fn pwrite( - &self, - pos: usize, - buffer: Rc>, - c: Rc, - ) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Rc>, c: Completion) -> Result<()> { let mut file = self.file.borrow_mut(); file.seek(std::io::SeekFrom::Start(pos as u64))?; let buf = buffer.borrow(); @@ -87,7 +82,7 @@ impl File for GenericFile { Ok(()) } - fn sync(&self, c: Rc) -> Result<()> { + fn sync(&self, c: Completion) -> Result<()> { let mut file = self.file.borrow_mut(); file.sync_all().map_err(|err| LimboError::IOError(err))?; c.complete(0); diff --git a/core/io/io_uring.rs b/core/io/io_uring.rs index eef5e523d..ebd1fcb61 100644 --- a/core/io/io_uring.rs +++ b/core/io/io_uring.rs @@ -19,7 +19,6 @@ enum UringIOError { IOUringCQError(i32), } -// Implement the Display trait to customize error messages impl fmt::Display for UringIOError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -39,7 +38,7 @@ pub struct UringIO { struct WrappedIOUring { ring: io_uring::IoUring, pending_ops: usize, - pub pending: [Option>; MAX_IOVECS as usize + 1], + pub pending: [Option; MAX_IOVECS as usize + 1], key: u64, } @@ -89,7 +88,7 @@ impl InnerUringIO { } impl WrappedIOUring { - fn submit_entry(&mut self, entry: &io_uring::squeue::Entry, c: Rc) { + fn submit_entry(&mut self, entry: &io_uring::squeue::Entry, c: Completion) { trace!("submit_entry({:?})", entry); self.pending[entry.get_user_data() as usize] = Some(c); unsafe { @@ -242,9 +241,9 @@ impl File for UringFile { Ok(()) } - fn pread(&self, pos: usize, c: Rc) -> Result<()> { - let r = match c.as_ref() { - Completion::Read(r) => r, + fn pread(&self, pos: usize, c: Completion) -> Result<()> { + let r = match c { + Completion::Read(ref r) => r, _ => unreachable!(), }; trace!("pread(pos = {}, length = {})", pos, r.buf().len()); @@ -264,12 +263,7 @@ impl File for UringFile { Ok(()) } - fn pwrite( - &self, - pos: usize, - buffer: Rc>, - c: Rc, - ) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Rc>, c: Completion) -> Result<()> { let mut io = self.io.borrow_mut(); let fd = io_uring::types::Fd(self.file.as_raw_fd()); let write = { @@ -285,7 +279,7 @@ impl File for UringFile { Ok(()) } - fn sync(&self, c: Rc) -> Result<()> { + fn sync(&self, c: Completion) -> Result<()> { let fd = io_uring::types::Fd(self.file.as_raw_fd()); let mut io = self.io.borrow_mut(); trace!("sync()"); diff --git a/core/io/memory.rs b/core/io/memory.rs index 18decf78a..164268d5e 100644 --- a/core/io/memory.rs +++ b/core/io/memory.rs @@ -78,9 +78,9 @@ impl File for MemoryFile { Ok(()) } - fn pread(&self, pos: usize, c: Rc) -> Result<()> { - let r = match &*c { - Completion::Read(r) => r, + fn pread(&self, pos: usize, c: Completion) -> Result<()> { + let r = match &c { + Completion::Read(ref r) => r, _ => unreachable!(), }; let buf_len = r.buf().len(); @@ -122,7 +122,7 @@ impl File for MemoryFile { Ok(()) } - fn pwrite(&self, pos: usize, buffer: Rc>, c: Rc) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Rc>, c: Completion) -> Result<()> { let buf = buffer.borrow(); let buf_len = buf.len(); if buf_len == 0 { @@ -159,7 +159,7 @@ impl File for MemoryFile { Ok(()) } - fn sync(&self, c: Rc) -> Result<()> { + fn sync(&self, c: Completion) -> Result<()> { // no-op c.complete(0); Ok(()) diff --git a/core/io/mod.rs b/core/io/mod.rs index f88a5d554..4f92ad3e1 100644 --- a/core/io/mod.rs +++ b/core/io/mod.rs @@ -12,9 +12,9 @@ use std::{ pub trait File { fn lock_file(&self, exclusive: bool) -> Result<()>; fn unlock_file(&self) -> Result<()>; - fn pread(&self, pos: usize, c: Rc) -> Result<()>; - fn pwrite(&self, pos: usize, buffer: Rc>, c: Rc) -> Result<()>; - fn sync(&self, c: Rc) -> Result<()>; + fn pread(&self, pos: usize, c: Completion) -> Result<()>; + fn pwrite(&self, pos: usize, buffer: Rc>, c: Completion) -> Result<()>; + fn sync(&self, c: Completion) -> Result<()>; fn size(&self) -> Result; } @@ -166,14 +166,17 @@ impl Buffer { cfg_block! { #[cfg(all(target_os = "linux", feature = "io_uring"))] { mod io_uring; + #[cfg(feature = "fs")] pub use io_uring::UringIO; mod unix; + #[cfg(feature = "fs")] pub use unix::UnixIO; pub use io_uring::UringIO as PlatformIO; } #[cfg(any(all(target_os = "linux",not(feature = "io_uring")), target_os = "macos"))] { mod unix; + #[cfg(feature = "fs")] pub use unix::UnixIO; pub use unix::UnixIO as PlatformIO; } diff --git a/core/io/unix.rs b/core/io/unix.rs index effd94bf5..d8301a611 100644 --- a/core/io/unix.rs +++ b/core/io/unix.rs @@ -22,6 +22,7 @@ pub struct UnixIO { } impl UnixIO { + #[cfg(feature = "fs")] pub fn new() -> Result { debug!("Using IO backend 'syscall'"); Ok(Self { @@ -118,10 +119,10 @@ impl IO for UnixIO { } enum CompletionCallback { - Read(Rc>, Rc, usize), + Read(Rc>, Completion, usize), Write( Rc>, - Rc, + Completion, Rc>, usize, ), @@ -173,11 +174,11 @@ impl File for UnixFile { Ok(()) } - fn pread(&self, pos: usize, c: Rc) -> Result<()> { + fn pread(&self, pos: usize, c: Completion) -> Result<()> { let file = self.file.borrow(); let result = { - let r = match c.as_ref() { - Completion::Read(r) => r, + let r = match c { + Completion::Read(ref r) => r, _ => unreachable!(), }; let mut buf = r.buf_mut(); @@ -201,7 +202,7 @@ impl File for UnixFile { } self.callbacks.borrow_mut().insert( fd as usize, - CompletionCallback::Read(self.file.clone(), c.clone(), pos), + CompletionCallback::Read(self.file.clone(), c, pos), ); Ok(()) } @@ -209,12 +210,7 @@ impl File for UnixFile { } } - fn pwrite( - &self, - pos: usize, - buffer: Rc>, - c: Rc, - ) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Rc>, c: Completion) -> Result<()> { let file = self.file.borrow(); let result = { let buf = buffer.borrow(); @@ -238,7 +234,7 @@ impl File for UnixFile { } self.callbacks.borrow_mut().insert( fd as usize, - CompletionCallback::Write(self.file.clone(), c.clone(), buffer.clone(), pos), + CompletionCallback::Write(self.file.clone(), c, buffer.clone(), pos), ); Ok(()) } @@ -246,7 +242,7 @@ impl File for UnixFile { } } - fn sync(&self, c: Rc) -> Result<()> { + fn sync(&self, c: Completion) -> Result<()> { let file = self.file.borrow(); let result = fs::fsync(file.as_fd()); match result { diff --git a/core/io/windows.rs b/core/io/windows.rs index 50acfcb50..d359c4575 100644 --- a/core/io/windows.rs +++ b/core/io/windows.rs @@ -54,12 +54,12 @@ impl File for WindowsFile { unimplemented!() } - fn pread(&self, pos: usize, c: Rc) -> Result<()> { + fn pread(&self, pos: usize, c: Completion) -> Result<()> { let mut file = self.file.borrow_mut(); file.seek(std::io::SeekFrom::Start(pos as u64))?; { - let r = match c.as_ref() { - Completion::Read(r) => r, + let r = match c { + Completion::Read(ref r) => r, _ => unreachable!(), }; let mut buf = r.buf_mut(); @@ -70,12 +70,7 @@ impl File for WindowsFile { Ok(()) } - fn pwrite( - &self, - pos: usize, - buffer: Rc>, - c: Rc, - ) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Rc>, c: Completion) -> Result<()> { let mut file = self.file.borrow_mut(); file.seek(std::io::SeekFrom::Start(pos as u64))?; let buf = buffer.borrow(); @@ -85,7 +80,7 @@ impl File for WindowsFile { Ok(()) } - fn sync(&self, c: Rc) -> Result<()> { + fn sync(&self, c: Completion) -> Result<()> { let file = self.file.borrow_mut(); file.sync_all().map_err(LimboError::IOError)?; c.complete(0); diff --git a/core/json/json_operations.rs b/core/json/json_operations.rs index a52b45760..e0e2ff09a 100644 --- a/core/json/json_operations.rs +++ b/core/json/json_operations.rs @@ -197,7 +197,7 @@ mod tests { } fn create_json(s: &str) -> OwnedValue { - OwnedValue::Text(Text::json(Rc::new(s.to_string()))) + OwnedValue::Text(Text::json(s)) } #[test] diff --git a/core/json/mod.rs b/core/json/mod.rs index bca1eb5de..c1a195b49 100644 --- a/core/json/mod.rs +++ b/core/json/mod.rs @@ -4,8 +4,6 @@ mod json_operations; mod json_path; mod ser; -use std::rc::Rc; - pub use crate::json::de::from_str; use crate::json::de::ordered_object; use crate::json::error::Error as JsonError; @@ -47,13 +45,13 @@ pub fn get_json(json_value: &OwnedValue, indent: Option<&str>) -> crate::Result< None => to_string(&json_val)?, }; - Ok(OwnedValue::Text(Text::json(Rc::new(json)))) + Ok(OwnedValue::Text(Text::json(&json))) } OwnedValue::Blob(b) => { // TODO: use get_json_value after we implement a single Struct // to represent both JSON and JSONB if let Ok(json) = jsonb::from_slice(b) { - Ok(OwnedValue::Text(Text::json(Rc::new(json.to_string())))) + Ok(OwnedValue::Text(Text::json(&json.to_string()))) } else { crate::bail_parse_error!("malformed JSON"); } @@ -66,7 +64,7 @@ pub fn get_json(json_value: &OwnedValue, indent: Option<&str>) -> crate::Result< None => to_string(&json_val)?, }; - Ok(OwnedValue::Text(Text::json(Rc::new(json)))) + Ok(OwnedValue::Text(Text::json(&json))) } } } @@ -130,7 +128,7 @@ pub fn json_array(values: &[OwnedValue]) -> crate::Result { } s.push(']'); - Ok(OwnedValue::Text(Text::json(Rc::new(s)))) + Ok(OwnedValue::Text(Text::json(&s))) } pub fn json_array_length( @@ -209,7 +207,7 @@ pub fn json_arrow_extract(value: &OwnedValue, path: &OwnedValue) -> crate::Resul if let Some(val) = extracted { let json = to_string(val)?; - Ok(OwnedValue::Text(Text::json(Rc::new(json)))) + Ok(OwnedValue::Text(Text::json(&json))) } else { Ok(OwnedValue::Null) } @@ -273,7 +271,7 @@ pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result crate::Result { let json = to_string(&extracted)?; if all_as_db { - Ok(OwnedValue::Text(Text::new(Rc::new(json)))) + Ok(OwnedValue::build_text(&json)) } else { - Ok(OwnedValue::Text(Text::json(Rc::new(json)))) + Ok(OwnedValue::Text(Text::json(&json))) } } } @@ -368,7 +366,7 @@ pub fn json_type(value: &OwnedValue, path: Option<&OwnedValue>) -> crate::Result Val::Removed => unreachable!(), }; - Ok(OwnedValue::Text(Text::json(Rc::new(val.to_string())))) + Ok(OwnedValue::Text(Text::json(val))) } /// Returns the value at the given JSON path. If the path does not exist, it returns None. @@ -656,7 +654,7 @@ pub fn json_object(values: &[OwnedValue]) -> crate::Result { .collect::, _>>()?; let result = crate::json::to_string(&value_map)?; - Ok(OwnedValue::Text(Text::json(Rc::new(result)))) + Ok(OwnedValue::Text(Text::json(&result))) } pub fn is_json_valid(json_value: &OwnedValue) -> crate::Result { @@ -698,13 +696,13 @@ pub fn json_quote(value: &OwnedValue) -> crate::Result { } escaped_value.push('"'); - Ok(OwnedValue::Text(Text::new(Rc::new(escaped_value)))) + Ok(OwnedValue::build_text(&escaped_value)) } // Numbers are unquoted in json OwnedValue::Integer(ref int) => Ok(OwnedValue::Integer(int.to_owned())), OwnedValue::Float(ref float) => Ok(OwnedValue::Float(float.to_owned())), OwnedValue::Blob(_) => crate::bail_constraint_error!("JSON cannot hold BLOB values"), - OwnedValue::Null => Ok(OwnedValue::Text(Text::new(Rc::new("null".to_string())))), + OwnedValue::Null => Ok(OwnedValue::build_text("null")), _ => { unreachable!() } @@ -713,12 +711,14 @@ pub fn json_quote(value: &OwnedValue) -> crate::Result { #[cfg(test)] mod tests { + use std::rc::Rc; + use super::*; use crate::types::OwnedValue; #[test] fn test_get_json_valid_json5() { - let input = OwnedValue::build_text(Rc::new("{ key: 'value' }".to_string())); + let input = OwnedValue::build_text("{ key: 'value' }"); let result = get_json(&input, None).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.as_str().contains("\"key\":\"value\"")); @@ -730,7 +730,7 @@ mod tests { #[test] fn test_get_json_valid_json5_double_single_quotes() { - let input = OwnedValue::build_text(Rc::new("{ key: ''value'' }".to_string())); + let input = OwnedValue::build_text("{ key: ''value'' }"); let result = get_json(&input, None).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.as_str().contains("\"key\":\"value\"")); @@ -742,7 +742,7 @@ mod tests { #[test] fn test_get_json_valid_json5_infinity() { - let input = OwnedValue::build_text(Rc::new("{ \"key\": Infinity }".to_string())); + let input = OwnedValue::build_text("{ \"key\": Infinity }"); let result = get_json(&input, None).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.as_str().contains("{\"key\":9e999}")); @@ -754,7 +754,7 @@ mod tests { #[test] fn test_get_json_valid_json5_negative_infinity() { - let input = OwnedValue::build_text(Rc::new("{ \"key\": -Infinity }".to_string())); + let input = OwnedValue::build_text("{ \"key\": -Infinity }"); let result = get_json(&input, None).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.as_str().contains("{\"key\":-9e999}")); @@ -766,7 +766,7 @@ mod tests { #[test] fn test_get_json_valid_json5_nan() { - let input = OwnedValue::build_text(Rc::new("{ \"key\": NaN }".to_string())); + let input = OwnedValue::build_text("{ \"key\": NaN }"); let result = get_json(&input, None).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.as_str().contains("{\"key\":null}")); @@ -778,7 +778,7 @@ mod tests { #[test] fn test_get_json_invalid_json5() { - let input = OwnedValue::build_text(Rc::new("{ key: value }".to_string())); + let input = OwnedValue::build_text("{ key: value }"); let result = get_json(&input, None); match result { Ok(_) => panic!("Expected error for malformed JSON"), @@ -788,7 +788,7 @@ mod tests { #[test] fn test_get_json_valid_jsonb() { - let input = OwnedValue::build_text(Rc::new("{\"key\":\"value\"}".to_string())); + let input = OwnedValue::build_text("{\"key\":\"value\"}"); let result = get_json(&input, None).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.as_str().contains("\"key\":\"value\"")); @@ -800,7 +800,7 @@ mod tests { #[test] fn test_get_json_invalid_jsonb() { - let input = OwnedValue::build_text(Rc::new("{key:\"value\"".to_string())); + let input = OwnedValue::build_text("{key:\"value\""); let result = get_json(&input, None); match result { Ok(_) => panic!("Expected error for malformed JSON"), @@ -845,8 +845,8 @@ mod tests { #[test] fn test_json_array_simple() { - let text = OwnedValue::build_text(Rc::new("value1".to_string())); - let json = OwnedValue::Text(Text::json(Rc::new("\"value2\"".to_string()))); + let text = OwnedValue::build_text("value1"); + let json = OwnedValue::Text(Text::json("\"value2\"")); let input = vec![text, json, OwnedValue::Integer(1), OwnedValue::Float(1.1)]; let result = json_array(&input).unwrap(); @@ -887,7 +887,7 @@ mod tests { #[test] fn test_json_array_length() { - let input = OwnedValue::build_text(Rc::new("[1,2,3,4]".to_string())); + let input = OwnedValue::build_text("[1,2,3,4]"); let result = json_array_length(&input, None).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 4); @@ -898,7 +898,7 @@ mod tests { #[test] fn test_json_array_length_empty() { - let input = OwnedValue::build_text(Rc::new("[]".to_string())); + let input = OwnedValue::build_text("[]"); let result = json_array_length(&input, None).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 0); @@ -909,12 +909,8 @@ mod tests { #[test] fn test_json_array_length_root() { - let input = OwnedValue::build_text(Rc::new("[1,2,3,4]".to_string())); - let result = json_array_length( - &input, - Some(&OwnedValue::build_text(Rc::new("$".to_string()))), - ) - .unwrap(); + let input = OwnedValue::build_text("[1,2,3,4]"); + let result = json_array_length(&input, Some(&OwnedValue::build_text("$"))).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 4); } else { @@ -924,7 +920,7 @@ mod tests { #[test] fn test_json_array_length_not_array() { - let input = OwnedValue::build_text(Rc::new("{one: [1,2,3,4]}".to_string())); + let input = OwnedValue::build_text("{one: [1,2,3,4]}"); let result = json_array_length(&input, None).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 0); @@ -935,12 +931,8 @@ mod tests { #[test] fn test_json_array_length_via_prop() { - let input = OwnedValue::build_text(Rc::new("{one: [1,2,3,4]}".to_string())); - let result = json_array_length( - &input, - Some(&OwnedValue::build_text(Rc::new("$.one".to_string()))), - ) - .unwrap(); + let input = OwnedValue::build_text("{one: [1,2,3,4]}"); + let result = json_array_length(&input, Some(&OwnedValue::build_text("$.one"))).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 4); } else { @@ -950,12 +942,8 @@ mod tests { #[test] fn test_json_array_length_via_index() { - let input = OwnedValue::build_text(Rc::new("[[1,2,3,4]]".to_string())); - let result = json_array_length( - &input, - Some(&OwnedValue::build_text(Rc::new("$[0]".to_string()))), - ) - .unwrap(); + let input = OwnedValue::build_text("[[1,2,3,4]]"); + let result = json_array_length(&input, Some(&OwnedValue::build_text("$[0]"))).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 4); } else { @@ -965,12 +953,8 @@ mod tests { #[test] fn test_json_array_length_via_index_not_array() { - let input = OwnedValue::build_text(Rc::new("[1,2,3,4]".to_string())); - let result = json_array_length( - &input, - Some(&OwnedValue::build_text(Rc::new("$[2]".to_string()))), - ) - .unwrap(); + let input = OwnedValue::build_text("[1,2,3,4]"); + let result = json_array_length(&input, Some(&OwnedValue::build_text("$[2]"))).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 0); } else { @@ -980,18 +964,14 @@ mod tests { #[test] fn test_json_array_length_via_index_bad_prop() { - let input = OwnedValue::build_text(Rc::new("{one: [1,2,3,4]}".to_string())); - let result = json_array_length( - &input, - Some(&OwnedValue::build_text(Rc::new("$.two".to_string()))), - ) - .unwrap(); + let input = OwnedValue::build_text("{one: [1,2,3,4]}"); + let result = json_array_length(&input, Some(&OwnedValue::build_text("$.two"))).unwrap(); assert_eq!(OwnedValue::Null, result); } #[test] fn test_json_array_length_simple_json_subtype() { - let input = OwnedValue::build_text(Rc::new("[1,2,3]".to_string())); + let input = OwnedValue::build_text("[1,2,3]"); let wrapped = get_json(&input, None).unwrap(); let result = json_array_length(&wrapped, None).unwrap(); @@ -1005,8 +985,8 @@ mod tests { #[test] fn test_json_extract_missing_path() { let result = json_extract( - &OwnedValue::build_text(Rc::new("{\"a\":2}".to_string())), - &[OwnedValue::build_text(Rc::new("$.x".to_string()))], + &OwnedValue::build_text("{\"a\":2}"), + &[OwnedValue::build_text("$.x")], ); match result { @@ -1016,10 +996,7 @@ mod tests { } #[test] fn test_json_extract_null_path() { - let result = json_extract( - &OwnedValue::build_text(Rc::new("{\"a\":2}".to_string())), - &[OwnedValue::Null], - ); + let result = json_extract(&OwnedValue::build_text("{\"a\":2}"), &[OwnedValue::Null]); match result { Ok(OwnedValue::Null) => (), @@ -1030,7 +1007,7 @@ mod tests { #[test] fn test_json_path_invalid() { let result = json_extract( - &OwnedValue::build_text(Rc::new("{\"a\":2}".to_string())), + &OwnedValue::build_text("{\"a\":2}"), &[OwnedValue::Float(1.1)], ); @@ -1042,28 +1019,28 @@ mod tests { #[test] fn test_json_error_position_no_error() { - let input = OwnedValue::build_text(Rc::new("[1,2,3]".to_string())); + let input = OwnedValue::build_text("[1,2,3]"); let result = json_error_position(&input).unwrap(); assert_eq!(result, OwnedValue::Integer(0)); } #[test] fn test_json_error_position_no_error_more() { - let input = OwnedValue::build_text(Rc::new(r#"{"a":55,"b":72 , }"#.to_string())); + let input = OwnedValue::build_text(r#"{"a":55,"b":72 , }"#); let result = json_error_position(&input).unwrap(); assert_eq!(result, OwnedValue::Integer(0)); } #[test] fn test_json_error_position_object() { - let input = OwnedValue::build_text(Rc::new(r#"{"a":55,"b":72,,}"#.to_string())); + let input = OwnedValue::build_text(r#"{"a":55,"b":72,,}"#); let result = json_error_position(&input).unwrap(); assert_eq!(result, OwnedValue::Integer(16)); } #[test] fn test_json_error_position_array() { - let input = OwnedValue::build_text(Rc::new(r#"["a",55,"b",72,,]"#.to_string())); + let input = OwnedValue::build_text(r#"["a",55,"b",72,,]"#); let result = json_error_position(&input).unwrap(); assert_eq!(result, OwnedValue::Integer(16)); } @@ -1098,8 +1075,8 @@ mod tests { #[test] fn test_json_object_simple() { - let key = OwnedValue::build_text(Rc::new("key".to_string())); - let value = OwnedValue::build_text(Rc::new("value".to_string())); + let key = OwnedValue::build_text("key"); + let value = OwnedValue::build_text("value"); let input = vec![key, value]; let result = json_object(&input).unwrap(); @@ -1111,17 +1088,15 @@ mod tests { #[test] fn test_json_object_multiple_values() { - let text_key = OwnedValue::build_text(Rc::new("text_key".to_string())); - let text_value = OwnedValue::build_text(Rc::new("text_value".to_string())); - let json_key = OwnedValue::build_text(Rc::new("json_key".to_string())); - let json_value = OwnedValue::Text(Text::json(Rc::new( - r#"{"json":"value","number":1}"#.to_string(), - ))); - let integer_key = OwnedValue::build_text(Rc::new("integer_key".to_string())); + let text_key = OwnedValue::build_text("text_key"); + let text_value = OwnedValue::build_text("text_value"); + let json_key = OwnedValue::build_text("json_key"); + let json_value = OwnedValue::Text(Text::json(r#"{"json":"value","number":1}"#)); + let integer_key = OwnedValue::build_text("integer_key"); let integer_value = OwnedValue::Integer(1); - let float_key = OwnedValue::build_text(Rc::new("float_key".to_string())); + let float_key = OwnedValue::build_text("float_key"); let float_value = OwnedValue::Float(1.1); - let null_key = OwnedValue::build_text(Rc::new("null_key".to_string())); + let null_key = OwnedValue::build_text("null_key"); let null_value = OwnedValue::Null; let input = vec![ @@ -1149,8 +1124,8 @@ mod tests { #[test] fn test_json_object_json_value_is_rendered_as_json() { - let key = OwnedValue::build_text(Rc::new("key".to_string())); - let value = OwnedValue::Text(Text::json(Rc::new(r#"{"json":"value"}"#.to_string()))); + let key = OwnedValue::build_text("key"); + let value = OwnedValue::Text(Text::json(r#"{"json":"value"}"#)); let input = vec![key, value]; let result = json_object(&input).unwrap(); @@ -1162,8 +1137,8 @@ mod tests { #[test] fn test_json_object_json_text_value_is_rendered_as_regular_text() { - let key = OwnedValue::build_text(Rc::new("key".to_string())); - let value = OwnedValue::Text(Text::new(Rc::new(r#"{"json":"value"}"#.to_string()))); + let key = OwnedValue::build_text("key"); + let value = OwnedValue::Text(Text::new(r#"{"json":"value"}"#)); let input = vec![key, value]; let result = json_object(&input).unwrap(); @@ -1175,11 +1150,11 @@ mod tests { #[test] fn test_json_object_nested() { - let key = OwnedValue::build_text(Rc::new("key".to_string())); - let value = OwnedValue::build_text(Rc::new("value".to_string())); + let key = OwnedValue::build_text("key"); + let value = OwnedValue::build_text("value"); let input = vec![key, value]; - let parent_key = OwnedValue::build_text(Rc::new("parent_key".to_string())); + let parent_key = OwnedValue::build_text("parent_key"); let parent_value = json_object(&input).unwrap(); let parent_input = vec![parent_key, parent_value]; @@ -1193,8 +1168,8 @@ mod tests { #[test] fn test_json_object_duplicated_keys() { - let key = OwnedValue::build_text(Rc::new("key".to_string())); - let value = OwnedValue::build_text(Rc::new("value".to_string())); + let key = OwnedValue::build_text("key"); + let value = OwnedValue::build_text("value"); let input = vec![key.clone(), value.clone(), key, value]; let result = json_object(&input).unwrap(); @@ -1218,7 +1193,7 @@ mod tests { #[test] fn test_json_object_non_text_key() { let key = OwnedValue::Integer(1); - let value = OwnedValue::build_text(Rc::new("value".to_string())); + let value = OwnedValue::build_text("value"); let input = vec![key, value]; match json_object(&input) { @@ -1229,8 +1204,8 @@ mod tests { #[test] fn test_json_odd_number_of_values() { - let key = OwnedValue::build_text(Rc::new("key".to_string())); - let value = OwnedValue::build_text(Rc::new("value".to_string())); + let key = OwnedValue::build_text("key"); + let value = OwnedValue::build_text("value"); let input = vec![key.clone(), value, key]; match json_object(&input) { @@ -1337,7 +1312,7 @@ mod tests { #[test] fn test_json_path_from_owned_value_root_strict() { - let path = OwnedValue::Text(Text::new(Rc::new("$".to_string()))); + let path = OwnedValue::Text(Text::new("$")); let result = json_path_from_owned_value(&path, true); assert!(result.is_ok()); @@ -1354,7 +1329,7 @@ mod tests { #[test] fn test_json_path_from_owned_value_root_non_strict() { - let path = OwnedValue::Text(Text::new(Rc::new("$".to_string()))); + let path = OwnedValue::Text(Text::new("$")); let result = json_path_from_owned_value(&path, false); assert!(result.is_ok()); @@ -1371,14 +1346,14 @@ mod tests { #[test] fn test_json_path_from_owned_value_named_strict() { - let path = OwnedValue::Text(Text::new(Rc::new("field".to_string()))); + let path = OwnedValue::Text(Text::new("field")); assert!(json_path_from_owned_value(&path, true).is_err()); } #[test] fn test_json_path_from_owned_value_named_non_strict() { - let path = OwnedValue::Text(Text::new(Rc::new("field".to_string()))); + let path = OwnedValue::Text(Text::new("field")); let result = json_path_from_owned_value(&path, false); assert!(result.is_ok()); @@ -1465,10 +1440,10 @@ mod tests { #[test] fn test_json_set_field_empty_object() { let result = json_set( - &OwnedValue::build_text(Rc::new("{}".to_string())), + &OwnedValue::build_text("{}"), &[ - OwnedValue::build_text(Rc::new("$.field".to_string())), - OwnedValue::build_text(Rc::new("value".to_string())), + OwnedValue::build_text("$.field"), + OwnedValue::build_text("value"), ], ); @@ -1476,17 +1451,17 @@ mod tests { assert_eq!( result.unwrap(), - OwnedValue::build_text(Rc::new(r#"{"field":"value"}"#.to_string())) + OwnedValue::build_text(r#"{"field":"value"}"#) ); } #[test] fn test_json_set_replace_field() { let result = json_set( - &OwnedValue::build_text(Rc::new(r#"{"field":"old_value"}"#.to_string())), + &OwnedValue::build_text(r#"{"field":"old_value"}"#), &[ - OwnedValue::build_text(Rc::new("$.field".to_string())), - OwnedValue::build_text(Rc::new("new_value".to_string())), + OwnedValue::build_text("$.field"), + OwnedValue::build_text("new_value"), ], ); @@ -1494,17 +1469,17 @@ mod tests { assert_eq!( result.unwrap(), - OwnedValue::build_text(Rc::new(r#"{"field":"new_value"}"#.to_string())) + OwnedValue::build_text(r#"{"field":"new_value"}"#) ); } #[test] fn test_json_set_set_deeply_nested_key() { let result = json_set( - &OwnedValue::build_text(Rc::new("{}".to_string())), + &OwnedValue::build_text("{}"), &[ - OwnedValue::build_text(Rc::new("$.object.doesnt.exist".to_string())), - OwnedValue::build_text(Rc::new("value".to_string())), + OwnedValue::build_text("$.object.doesnt.exist"), + OwnedValue::build_text("value"), ], ); @@ -1512,36 +1487,31 @@ mod tests { assert_eq!( result.unwrap(), - OwnedValue::build_text(Rc::new( - r#"{"object":{"doesnt":{"exist":"value"}}}"#.to_string() - )) + OwnedValue::build_text(r#"{"object":{"doesnt":{"exist":"value"}}}"#) ); } #[test] fn test_json_set_add_value_to_empty_array() { let result = json_set( - &OwnedValue::build_text(Rc::new("[]".to_string())), + &OwnedValue::build_text("[]"), &[ - OwnedValue::build_text(Rc::new("$[0]".to_string())), - OwnedValue::build_text(Rc::new("value".to_string())), + OwnedValue::build_text("$[0]"), + OwnedValue::build_text("value"), ], ); assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - OwnedValue::build_text(Rc::new(r#"["value"]"#.to_string())) - ); + assert_eq!(result.unwrap(), OwnedValue::build_text(r#"["value"]"#)); } #[test] fn test_json_set_add_value_to_nonexistent_array() { let result = json_set( - &OwnedValue::build_text(Rc::new("{}".to_string())), + &OwnedValue::build_text("{}"), &[ - OwnedValue::build_text(Rc::new("$.some_array[0]".to_string())), + OwnedValue::build_text("$.some_array[0]"), OwnedValue::Integer(123), ], ); @@ -1550,104 +1520,80 @@ mod tests { assert_eq!( result.unwrap(), - OwnedValue::build_text(Rc::new(r#"{"some_array":[123]}"#.to_string())) + OwnedValue::build_text(r#"{"some_array":[123]}"#) ); } #[test] fn test_json_set_add_value_to_array() { let result = json_set( - &OwnedValue::build_text(Rc::new("[123]".to_string())), - &[ - OwnedValue::build_text(Rc::new("$[1]".to_string())), - OwnedValue::Integer(456), - ], + &OwnedValue::build_text("[123]"), + &[OwnedValue::build_text("$[1]"), OwnedValue::Integer(456)], ); assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - OwnedValue::build_text(Rc::new("[123,456]".to_string())) - ); + assert_eq!(result.unwrap(), OwnedValue::build_text("[123,456]")); } #[test] fn test_json_set_add_value_to_array_out_of_bounds() { let result = json_set( - &OwnedValue::build_text(Rc::new("[123]".to_string())), - &[ - OwnedValue::build_text(Rc::new("$[200]".to_string())), - OwnedValue::Integer(456), - ], + &OwnedValue::build_text("[123]"), + &[OwnedValue::build_text("$[200]"), OwnedValue::Integer(456)], ); assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - OwnedValue::build_text(Rc::new("[123]".to_string())) - ); + assert_eq!(result.unwrap(), OwnedValue::build_text("[123]")); } #[test] fn test_json_set_replace_value_in_array() { let result = json_set( - &OwnedValue::build_text(Rc::new("[123]".to_string())), - &[ - OwnedValue::build_text(Rc::new("$[0]".to_string())), - OwnedValue::Integer(456), - ], + &OwnedValue::build_text("[123]"), + &[OwnedValue::build_text("$[0]"), OwnedValue::Integer(456)], ); assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - OwnedValue::build_text(Rc::new("[456]".to_string())) - ); + assert_eq!(result.unwrap(), OwnedValue::build_text("[456]")); } #[test] fn test_json_set_null_path() { let result = json_set( - &OwnedValue::build_text(Rc::new("{}".to_string())), + &OwnedValue::build_text("{}"), &[OwnedValue::Null, OwnedValue::Integer(456)], ); assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - OwnedValue::build_text(Rc::new("{}".to_string())) - ); + assert_eq!(result.unwrap(), OwnedValue::build_text("{}")); } #[test] fn test_json_set_multiple_keys() { let result = json_set( - &OwnedValue::build_text(Rc::new("[123]".to_string())), + &OwnedValue::build_text("[123]"), &[ - OwnedValue::build_text(Rc::new("$[0]".to_string())), + OwnedValue::build_text("$[0]"), OwnedValue::Integer(456), - OwnedValue::build_text(Rc::new("$[1]".to_string())), + OwnedValue::build_text("$[1]"), OwnedValue::Integer(789), ], ); assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - OwnedValue::build_text(Rc::new("[456,789]".to_string())) - ); + assert_eq!(result.unwrap(), OwnedValue::build_text("[456,789]")); } #[test] fn test_json_set_missing_value() { let result = json_set( - &OwnedValue::build_text(Rc::new("[123]".to_string())), - &[OwnedValue::build_text(Rc::new("$[0]".to_string()))], + &OwnedValue::build_text("[123]"), + &[OwnedValue::build_text("$[0]")], ); assert!(result.is_err()); @@ -1656,9 +1602,9 @@ mod tests { #[test] fn test_json_set_add_array_in_nested_object() { let result = json_set( - &OwnedValue::build_text(Rc::new("{}".to_string())), + &OwnedValue::build_text("{}"), &[ - OwnedValue::build_text(Rc::new("$.object[0].field".to_string())), + OwnedValue::build_text("$.object[0].field"), OwnedValue::Integer(123), ], ); @@ -1667,16 +1613,16 @@ mod tests { assert_eq!( result.unwrap(), - OwnedValue::build_text(Rc::new(r#"{"object":[{"field":123}]}"#.to_string())) + OwnedValue::build_text(r#"{"object":[{"field":123}]}"#) ); } #[test] fn test_json_set_add_array_in_array_in_nested_object() { let result = json_set( - &OwnedValue::build_text(Rc::new("{}".to_string())), + &OwnedValue::build_text("{}"), &[ - OwnedValue::build_text(Rc::new("$.object[0][0]".to_string())), + OwnedValue::build_text("$.object[0][0]"), OwnedValue::Integer(123), ], ); @@ -1685,19 +1631,19 @@ mod tests { assert_eq!( result.unwrap(), - OwnedValue::build_text(Rc::new(r#"{"object":[[123]]}"#.to_string())) + OwnedValue::build_text(r#"{"object":[[123]]}"#) ); } #[test] fn test_json_set_add_array_in_array_in_nested_object_out_of_bounds() { let result = json_set( - &OwnedValue::build_text(Rc::new("{}".to_string())), + &OwnedValue::build_text("{}"), &[ - OwnedValue::build_text(Rc::new("$.object[123].another".to_string())), - OwnedValue::build_text(Rc::new("value".to_string())), - OwnedValue::build_text(Rc::new("$.field".to_string())), - OwnedValue::build_text(Rc::new("value".to_string())), + OwnedValue::build_text("$.object[123].another"), + OwnedValue::build_text("value"), + OwnedValue::build_text("$.field"), + OwnedValue::build_text("value"), ], ); @@ -1705,7 +1651,7 @@ mod tests { assert_eq!( result.unwrap(), - OwnedValue::build_text(Rc::new(r#"{"field":"value"}"#.to_string())) + OwnedValue::build_text(r#"{"field":"value"}"#) ); } } diff --git a/core/lib.rs b/core/lib.rs index 94853aa80..86f7b4700 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -9,7 +9,7 @@ mod json; pub mod mvcc; mod parameters; mod pseudo; -mod result; +pub mod result; mod schema; mod storage; mod translate; @@ -42,7 +42,9 @@ use storage::btree::btree_init_page; use storage::database::FileStorage; use storage::page_cache::DumbLruPageCache; use storage::pager::allocate_page; +pub use storage::pager::PageRef; use storage::sqlite3_ondisk::{DatabaseHeader, DATABASE_HEADER_SIZE}; +pub use storage::wal::CheckpointMode; pub use storage::wal::WalFile; pub use storage::wal::WalFileShared; use types::OwnedValue; @@ -238,7 +240,7 @@ pub fn maybe_init_database_file(file: &Rc, io: &Arc) -> Result let completion = Completion::Write(WriteCompletion::new(Box::new(move |_| { *flag_complete.borrow_mut() = true; }))); - file.pwrite(0, contents.buffer.clone(), Rc::new(completion))?; + file.pwrite(0, contents.buffer.clone(), completion)?; } let mut limit = 100; loop { @@ -345,6 +347,7 @@ impl Connection { &self.schema.borrow(), *select, &self.db.syms.borrow(), + None, )?; optimize_plan(&mut plan, &self.schema.borrow())?; println!("{}", plan); diff --git a/core/schema.rs b/core/schema.rs index f4a6aee2b..aa38cd191 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -150,7 +150,7 @@ impl BTreeTable { let cmd = parser.next()?; match cmd { Some(Cmd::Stmt(Stmt::CreateTable { tbl_name, body, .. })) => { - create_table(tbl_name, body, root_page) + create_table(tbl_name, *body, root_page) } _ => todo!("Expected CREATE TABLE statement"), } diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 4a963581b..9ad55295b 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -2,7 +2,7 @@ use log::debug; use crate::storage::pager::Pager; use crate::storage::sqlite3_ondisk::{ - read_btree_cell, read_varint, write_varint, BTreeCell, DatabaseHeader, PageContent, PageType, + read_btree_cell, read_varint, BTreeCell, DatabaseHeader, PageContent, PageType, TableInteriorCell, TableLeafCell, }; @@ -76,7 +76,7 @@ macro_rules! return_if_locked { /// State machine of a write operation. /// May involve balancing due to overflow. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] enum WriteState { Start, BalanceStart, @@ -89,8 +89,10 @@ enum WriteState { struct WriteInfo { /// State of the write operation state machine. state: WriteState, - /// Pages allocated during the write operation due to balancing. - new_pages: RefCell>, + /// Pages involved in the split of the page due to balancing (splits_pages[0] is the balancing page, while other - fresh allocated pages) + split_pages: RefCell>, + /// Amount of cells from balancing page for every split page + split_pages_cells_count: RefCell>, /// Scratch space used during balancing. scratch_cells: RefCell>, /// Bookkeeping of the rightmost pointer so the PAGE_HEADER_OFFSET_RIGHTMOST_PTR can be updated. @@ -103,7 +105,8 @@ impl WriteInfo { fn new() -> WriteInfo { WriteInfo { state: WriteState::Start, - new_pages: RefCell::new(Vec::with_capacity(4)), + split_pages: RefCell::new(Vec::with_capacity(4)), + split_pages_cells_count: RefCell::new(Vec::with_capacity(4)), scratch_cells: RefCell::new(Vec::new()), rightmost_pointer: RefCell::new(None), page_copy: RefCell::new(None), @@ -156,7 +159,7 @@ pub struct BTreeCursor { /// current_page represents the current page being used in the tree and current_page - 1 would be /// the parent. Using current_page + 1 or higher is undefined behaviour. struct PageStack { - /// Pointer to the currenet page being consumed + /// Pointer to the current page being consumed current_page: RefCell, /// List of pages in the stack. Root page will be in index 0 stack: RefCell<[Option; BTCURSOR_MAX_DEPTH + 1]>, @@ -378,7 +381,7 @@ impl BTreeCursor { let record = crate::storage::sqlite3_ondisk::read_record(payload)?; if predicate.is_none() { - let rowid = match record.values.last() { + let rowid = match record.last_value() { Some(OwnedValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; @@ -395,7 +398,7 @@ impl BTreeCursor { SeekOp::EQ => &record == *index_key, }; if found { - let rowid = match record.values.last() { + let rowid = match record.last_value() { Some(OwnedValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; @@ -408,7 +411,7 @@ impl BTreeCursor { self.stack.advance(); let record = crate::storage::sqlite3_ondisk::read_record(payload)?; if predicate.is_none() { - let rowid = match record.values.last() { + let rowid = match record.last_value() { Some(OwnedValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; @@ -424,7 +427,7 @@ impl BTreeCursor { SeekOp::EQ => &record == *index_key, }; if found { - let rowid = match record.values.last() { + let rowid = match record.last_value() { Some(OwnedValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; @@ -489,18 +492,20 @@ impl BTreeCursor { let record = crate::storage::sqlite3_ondisk::read_record(payload)?; let found = match op { SeekOp::GT => { - &record.values[..record.values.len() - 1] > &index_key.values + record.get_values()[..record.len() - 1] > index_key.get_values()[..] } SeekOp::GE => { - &record.values[..record.values.len() - 1] >= &index_key.values + record.get_values()[..record.len() - 1] + >= index_key.get_values()[..] } SeekOp::EQ => { - record.values[..record.values.len() - 1] == index_key.values + record.get_values()[..record.len() - 1] + == index_key.get_values()[..] } }; self.stack.advance(); if found { - let rowid = match record.values.last() { + let rowid = match record.last_value() { Some(OwnedValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; @@ -742,13 +747,14 @@ impl BTreeCursor { // insert let overflow = { let contents = page.get().contents.as_mut().unwrap(); - debug!( - "insert_into_page(overflow, cell_count={})", - contents.cell_count() - ); - self.insert_into_cell(contents, cell_payload.as_slice(), cell_idx); - contents.overflow_cells.len() + let overflow_cells = contents.overflow_cells.len(); + debug!( + "insert_into_page(overflow, cell_count={}, overflow_cells={})", + contents.cell_count(), + overflow_cells + ); + overflow_cells }; let write_info = self .state @@ -983,7 +989,7 @@ impl BTreeCursor { db_header: Ref, ) -> Result { // NOTE: freelist is in ascending order of keys and pc - // unused_space is reserved bytes at the end of page, therefore we must substract from maxpc + // unused_space is reserved bytes at the end of page, therefore we must subtract from maxpc let mut free_list_pointer_addr = 1; let mut pc = page_ref.first_freeblock() as usize; @@ -1049,45 +1055,38 @@ impl BTreeCursor { matches!(self.state, CursorState::Write(_)), "Cursor must be in balancing state" ); - let state = self - .state - .write_info() - .expect("must be balancing") - .state - .clone(); - match state { - WriteState::BalanceStart => { - // drop divider cells and find right pointer - // NOTE: since we are doing a simple split we only finding the pointer we want to update (right pointer). - // Right pointer means cell that points to the last page, as we don't really want to drop this one. This one - // can be a "rightmost pointer" or a "cell". - // we always asumme there is a parent - let current_page = self.stack.top(); - { - // check if we don't need to balance - // don't continue if there are no overflow cells - let page = current_page.get().contents.as_mut().unwrap(); - if page.overflow_cells.is_empty() { - let write_info = self.state.mut_write_info().unwrap(); - write_info.state = WriteState::Finish; + loop { + let state = self.state.write_info().expect("must be balancing").state; + match state { + WriteState::BalanceStart => { + let current_page = self.stack.top(); + { + // check if we don't need to balance + // don't continue if there are no overflow cells + let page = current_page.get().contents.as_mut().unwrap(); + if page.overflow_cells.is_empty() { + let write_info = self.state.mut_write_info().unwrap(); + write_info.state = WriteState::Finish; + return Ok(CursorResult::Ok(())); + } + } + + if !self.stack.has_parent() { + self.balance_root(); return Ok(CursorResult::Ok(())); } + + let write_info = self.state.mut_write_info().unwrap(); + write_info.state = WriteState::BalanceNonRoot; + } + WriteState::BalanceNonRoot + | WriteState::BalanceGetParentPage + | WriteState::BalanceMoveUp => { + return_if_io!(self.balance_non_root()); } - if !self.stack.has_parent() { - self.balance_root(); - return Ok(CursorResult::Ok(())); - } - - let write_info = self.state.mut_write_info().unwrap(); - write_info.state = WriteState::BalanceNonRoot; - self.balance_non_root() + _ => unreachable!("invalid balance leaf state {:?}", state), } - WriteState::BalanceNonRoot - | WriteState::BalanceGetParentPage - | WriteState::BalanceMoveUp => self.balance_non_root(), - - _ => unreachable!("invalid balance leaf state {:?}", state), } } @@ -1096,12 +1095,7 @@ impl BTreeCursor { matches!(self.state, CursorState::Write(_)), "Cursor must be in balancing state" ); - let state = self - .state - .write_info() - .expect("must be balancing") - .state - .clone(); + let state = self.state.write_info().expect("must be balancing").state; let (next_write_state, result) = match state { WriteState::Start => todo!(), WriteState::BalanceStart => todo!(), @@ -1114,10 +1108,14 @@ impl BTreeCursor { let current_page = self.stack.top(); debug!("balance_non_root(page={})", current_page.get().id); + let current_page_inner = current_page.get(); + let current_page_contents = &mut current_page_inner.contents; + let current_page_contents = current_page_contents.as_mut().unwrap(); // Copy of page used to reference cell bytes. - // This needs to be saved somewhere safe so taht references still point to here, + // This needs to be saved somewhere safe so that references still point to here, // this will be store in write_info below - let page_copy = current_page.get().contents.as_ref().unwrap().clone(); + let page_copy = current_page_contents.clone(); + current_page_contents.overflow_cells.clear(); // In memory in order copy of all cells in pages we want to balance. For now let's do a 2 page split. // Right pointer in interior cells should be converted to regular cells if more than 2 pages are used for balancing. @@ -1125,47 +1123,77 @@ impl BTreeCursor { let mut scratch_cells = write_info.scratch_cells.borrow_mut(); scratch_cells.clear(); + let usable_space = self.usable_space(); for cell_idx in 0..page_copy.cell_count() { let (start, len) = page_copy.cell_get_raw_region( cell_idx, self.payload_overflow_threshold_max(page_copy.page_type()), self.payload_overflow_threshold_min(page_copy.page_type()), - self.usable_space(), + usable_space, ); - let buf = page_copy.as_ptr(); - scratch_cells.push(to_static_buf(&buf[start..start + len])); + let cell_buffer = to_static_buf(&page_copy.as_ptr()[start..start + len]); + scratch_cells.push(cell_buffer); } - for overflow_cell in &page_copy.overflow_cells { - scratch_cells - .insert(overflow_cell.index, to_static_buf(&overflow_cell.payload)); + // overflow_cells are stored in order - so we need to insert them in reverse order + for cell in page_copy.overflow_cells.iter().rev() { + scratch_cells.insert(cell.index, to_static_buf(&cell.payload)); } + // amount of cells for pages involved in split + // the algorithm accumulate cells in greedy manner with 2 conditions for split: + // 1. new cell will overflow single cell (accumulated + new > usable_space - header_size) + // 2. accumulated size already reach >50% of content_usable_size + // second condition is necessary, otherwise in case of small cells we will create a lot of almost empty pages + // + // if we have single overflow cell in a table leaf node - we still can have 3 split pages + // + // for example, if current page has 4 entries with size ~1/4 page size, and new cell has size ~page size + // then we will need 3 pages to distribute cells between them + let split_pages_cells_count = &mut write_info.split_pages_cells_count.borrow_mut(); + split_pages_cells_count.clear(); + let mut last_page_cells_count = 0; + let mut last_page_cells_size = 0; + let content_usable_space = usable_space - page_copy.header_size(); + for scratch_cell in scratch_cells.iter() { + let cell_size = scratch_cell.len() + 2; // + cell pointer size (u16) + if last_page_cells_size + cell_size > content_usable_space + || 2 * last_page_cells_size > content_usable_space + { + split_pages_cells_count.push(last_page_cells_count); + last_page_cells_count = 0; + last_page_cells_size = 0; + } + last_page_cells_count += 1; + last_page_cells_size += cell_size; + assert!(last_page_cells_size <= content_usable_space); + } + split_pages_cells_count.push(last_page_cells_count); + let new_pages_count = split_pages_cells_count.len(); + + debug!( + "splitting left={} new_pages={}, cells_count={:?}", + current_page.get().id, + new_pages_count - 1, + split_pages_cells_count + ); + *write_info.rightmost_pointer.borrow_mut() = page_copy.rightmost_pointer(); write_info.page_copy.replace(Some(page_copy)); - // allocate new pages and move cells to those new pages - // split procedure let page = current_page.get().contents.as_mut().unwrap(); + let page_type = page.page_type(); assert!( - matches!( - page.page_type(), - PageType::TableLeaf | PageType::TableInterior - ), - "indexes still not supported " + matches!(page_type, PageType::TableLeaf | PageType::TableInterior), + "indexes still not supported" ); - let right_page = self.allocate_page(page.page_type(), 0); - let right_page_id = right_page.get().id; - - write_info.new_pages.borrow_mut().clear(); - write_info.new_pages.borrow_mut().push(current_page.clone()); - write_info.new_pages.borrow_mut().push(right_page.clone()); - - debug!( - "splitting left={} right={}", - current_page.get().id, - right_page_id - ); + write_info.split_pages.borrow_mut().clear(); + write_info.split_pages.borrow_mut().push(current_page); + // allocate new pages + for _ in 1..new_pages_count { + let new_page = self.allocate_page(page_type, 0); + write_info.split_pages.borrow_mut().push(new_page); + } (WriteState::BalanceGetParentPage, Ok(CursorResult::Ok(()))) } @@ -1211,7 +1239,7 @@ impl BTreeCursor { BTreeCell::TableInteriorCell(interior) => { interior._left_child_page as usize == current_idx } - _ => unreachable!("Parent should always be a "), + _ => unreachable!("Parent should always be an interior page"), }; if found { let (start, _len) = parent_contents.cell_get_raw_region( @@ -1226,23 +1254,21 @@ impl BTreeCursor { } let write_info = self.state.write_info().unwrap(); - let mut new_pages = write_info.new_pages.borrow_mut(); + let mut split_pages = write_info.split_pages.borrow_mut(); + let split_pages_len = split_pages.len(); let scratch_cells = write_info.scratch_cells.borrow(); // reset pages - for page in new_pages.iter() { + for page in split_pages.iter() { assert!(page.is_dirty()); let contents = page.get().contents.as_mut().unwrap(); contents.write_u16(PAGE_HEADER_OFFSET_FIRST_FREEBLOCK, 0); contents.write_u16(PAGE_HEADER_OFFSET_CELL_COUNT, 0); - let db_header = RefCell::borrow(&self.pager.db_header); - let cell_content_area_start = - db_header.page_size - db_header.reserved_space as u16; contents.write_u16( PAGE_HEADER_OFFSET_CELL_CONTENT_AREA, - cell_content_area_start, + self.usable_space() as u16, ); contents.write_u8(PAGE_HEADER_OFFSET_FRAGMENTED_BYTES_COUNT, 0); @@ -1251,29 +1277,17 @@ impl BTreeCursor { } } - // distribute cells - let new_pages_len = new_pages.len(); - let cells_per_page = scratch_cells.len() / new_pages.len(); let mut current_cell_index = 0_usize; - let mut divider_cells_index = Vec::new(); /* index to scratch cells that will be used as dividers in order */ + /* index to scratch cells that will be used as dividers in order */ + let mut divider_cells_index = Vec::with_capacity(split_pages.len()); - debug!( - "balance_leaf::distribute(cells={}, cells_per_page={})", - scratch_cells.len(), - cells_per_page - ); + debug!("balance_leaf::distribute(cells={})", scratch_cells.len()); - for (i, page) in new_pages.iter_mut().enumerate() { + for (i, page) in split_pages.iter_mut().enumerate() { let page_id = page.get().id; let contents = page.get().contents.as_mut().unwrap(); - let last_page = i == new_pages_len - 1; - let cells_to_copy = if last_page { - // last cells is remaining pages if division was odd - scratch_cells.len() - current_cell_index - } else { - cells_per_page - }; + let cells_to_copy = write_info.split_pages_cells_count.borrow()[i]; debug!( "balance_leaf::distribute(page={}, cells_to_copy={})", page_id, cells_to_copy @@ -1289,6 +1303,7 @@ impl BTreeCursor { divider_cells_index.push(current_cell_index + cells_to_copy - 1); current_cell_index += cells_to_copy; } + let is_leaf = { let page = self.stack.top(); let page = page.get().contents.as_ref().unwrap(); @@ -1297,10 +1312,10 @@ impl BTreeCursor { // update rightmost pointer for each page if we are in interior page if !is_leaf { - for page in new_pages.iter_mut().take(new_pages_len - 1) { + for page in split_pages.iter_mut().take(split_pages_len - 1) { let contents = page.get().contents.as_mut().unwrap(); - assert_eq!(contents.cell_count(), 1); + assert!(contents.cell_count() >= 1); let last_cell = contents.cell_get( contents.cell_count() - 1, self.pager.clone(), @@ -1316,7 +1331,7 @@ impl BTreeCursor { contents.write_u32(PAGE_HEADER_OFFSET_RIGHTMOST_PTR, last_cell_pointer); } // last page right most pointer points to previous right most pointer before splitting - let last_page = new_pages.last().unwrap(); + let last_page = split_pages.last().unwrap(); let last_page_contents = last_page.get().contents.as_mut().unwrap(); last_page_contents.write_u32( PAGE_HEADER_OFFSET_RIGHTMOST_PTR, @@ -1327,7 +1342,7 @@ impl BTreeCursor { // insert dividers in parent // we can consider dividers the first cell of each page starting from the second page for (page_id_index, page) in - new_pages.iter_mut().take(new_pages_len - 1).enumerate() + split_pages.iter_mut().take(split_pages_len - 1).enumerate() { let contents = page.get().contents.as_mut().unwrap(); let divider_cell_index = divider_cells_index[page_id_index]; @@ -1342,38 +1357,23 @@ impl BTreeCursor { self.usable_space(), )?; - if is_leaf { - // create a new divider cell and push - let key = match cell { - BTreeCell::TableLeafCell(leaf) => leaf._rowid, - _ => unreachable!(), - }; - let mut divider_cell = Vec::new(); - divider_cell.extend_from_slice(&(page.get().id as u32).to_be_bytes()); - divider_cell.extend(std::iter::repeat(0).take(9)); - let n = write_varint(&mut divider_cell.as_mut_slice()[4..], key); - divider_cell.truncate(4 + n); - let parent_cell_idx = self.find_cell(parent_contents, key); - self.insert_into_cell( - parent_contents, - divider_cell.as_slice(), - parent_cell_idx, - ); - } else { - // move cell - let key = match cell { - BTreeCell::TableInteriorCell(interior) => interior._rowid, - _ => unreachable!(), - }; - let parent_cell_idx = self.find_cell(contents, key); - self.insert_into_cell(parent_contents, cell_payload, parent_cell_idx); - // self.drop_cell(*page, 0); - } + let key = match cell { + BTreeCell::TableLeafCell(TableLeafCell { _rowid, .. }) + | BTreeCell::TableInteriorCell(TableInteriorCell { _rowid, .. }) => _rowid, + _ => unreachable!(), + }; + + let mut divider_cell = Vec::with_capacity(4 + 9); // 4 - page id, 9 - max length of varint + divider_cell.extend_from_slice(&(page.get().id as u32).to_be_bytes()); + write_varint_to_vec(key, &mut divider_cell); + + let parent_cell_idx = self.find_cell(parent_contents, key); + self.insert_into_cell(parent_contents, ÷r_cell, parent_cell_idx); } { // copy last page id to right pointer - let last_pointer = new_pages.last().unwrap().get().id as u32; + let last_pointer = split_pages.last().unwrap().get().id as u32; parent_contents.write_u32(right_pointer, last_pointer); } self.stack.pop(); @@ -1405,7 +1405,6 @@ impl BTreeCursor { let current_root = self.stack.top(); let current_root_contents = current_root.get().contents.as_ref().unwrap(); - let new_root_page_id = new_root_page.get().id; let new_root_page_contents = new_root_page.get().contents.as_mut().unwrap(); if is_page_1 { // Copy header @@ -1415,12 +1414,14 @@ impl BTreeCursor { .copy_from_slice(¤t_root_buf[0..DATABASE_HEADER_SIZE]); } // point new root right child to previous root - new_root_page_contents - .write_u32(PAGE_HEADER_OFFSET_RIGHTMOST_PTR, new_root_page_id as u32); + new_root_page_contents.write_u32( + PAGE_HEADER_OFFSET_RIGHTMOST_PTR, + current_root.get().id as u32, + ); new_root_page_contents.write_u16(PAGE_HEADER_OFFSET_CELL_COUNT, 0); } - /* swap splitted page buffer with new root buffer so we don't have to update page idx */ + /* swap split page buffer with new root buffer so we don't have to update page idx */ { let (root_id, child_id, child) = { let page_ref = self.stack.top(); @@ -1607,6 +1608,8 @@ impl BTreeCursor { page.write_u16(PAGE_HEADER_OFFSET_CELL_CONTENT_AREA, cbrk as u16); // set free block to 0, unused spaced can be retrieved from gap between cell pointer end and content start page.write_u16(PAGE_HEADER_OFFSET_FIRST_FREEBLOCK, 0); + // set fragmented bytes counter to zero + page.write_u8(PAGE_HEADER_OFFSET_FRAGMENTED_BYTES_COUNT, 0); // set unused space to 0 let first_cell = cloned_page.cell_content_area() as u64; assert!(first_cell <= cbrk); @@ -2137,7 +2140,7 @@ impl BTreeCursor { 1 => PageType::TableLeaf, 2 => PageType::IndexLeaf, _ => unreachable!( - "wrong create table falgs, should be 1 for table and 2 for index, got {}", + "wrong create table flags, should be 1 for table and 2 for index, got {}", flags, ), }; @@ -2362,6 +2365,10 @@ fn to_static_buf(buf: &[u8]) -> &'static [u8] { #[cfg(test)] mod tests { + use rand_chacha::rand_core::RngCore; + use rand_chacha::rand_core::SeedableRng; + use rand_chacha::ChaCha8Rng; + use super::*; use crate::io::{Buffer, Completion, MemoryIO, OpenFlags, IO}; use crate::storage::database::FileStorage; @@ -2371,6 +2378,312 @@ mod tests { use std::cell::RefCell; use std::sync::Arc; + fn validate_btree(pager: Rc, page_idx: usize) -> (usize, bool) { + let cursor = BTreeCursor::new(pager.clone(), page_idx); + let page = pager.read_page(page_idx).unwrap(); + let page = page.get(); + let contents = page.contents.as_ref().unwrap(); + let page_type = contents.page_type(); + let mut previous_key = None; + let mut valid = true; + let mut depth = None; + for cell_idx in 0..contents.cell_count() { + let cell = contents + .cell_get( + cell_idx, + pager.clone(), + cursor.payload_overflow_threshold_max(page_type), + cursor.payload_overflow_threshold_min(page_type), + cursor.usable_space(), + ) + .unwrap(); + let current_depth = match cell { + BTreeCell::TableLeafCell(..) => 1, + BTreeCell::TableInteriorCell(TableInteriorCell { + _left_child_page, .. + }) => { + let (child_depth, child_valid) = + validate_btree(pager.clone(), _left_child_page as usize); + valid &= child_valid; + child_depth + } + _ => panic!("unsupported btree cell: {:?}", cell), + }; + depth = Some(depth.unwrap_or(current_depth + 1)); + if depth != Some(current_depth + 1) { + log::error!("depth is different for child of page {}", page_idx); + valid = false; + } + match cell { + BTreeCell::TableInteriorCell(TableInteriorCell { _rowid, .. }) + | BTreeCell::TableLeafCell(TableLeafCell { _rowid, .. }) => { + if previous_key.is_some() && previous_key.unwrap() >= _rowid { + log::error!( + "keys are in bad order: prev={:?}, current={}", + previous_key, + _rowid + ); + valid = false; + } + previous_key = Some(_rowid); + } + _ => panic!("unsupported btree cell: {:?}", cell), + } + } + if let Some(right) = contents.rightmost_pointer() { + let (right_depth, right_valid) = validate_btree(pager.clone(), right as usize); + valid &= right_valid; + depth = Some(depth.unwrap_or(right_depth + 1)); + if depth != Some(right_depth + 1) { + log::error!("depth is different for child of page {}", page_idx); + valid = false; + } + } + (depth.unwrap(), valid) + } + + fn format_btree(pager: Rc, page_idx: usize, depth: usize) -> String { + let cursor = BTreeCursor::new(pager.clone(), page_idx); + let page = pager.read_page(page_idx).unwrap(); + let page = page.get(); + let contents = page.contents.as_ref().unwrap(); + let page_type = contents.page_type(); + let mut current = Vec::new(); + let mut child = Vec::new(); + for cell_idx in 0..contents.cell_count() { + let cell = contents + .cell_get( + cell_idx, + pager.clone(), + cursor.payload_overflow_threshold_max(page_type), + cursor.payload_overflow_threshold_min(page_type), + cursor.usable_space(), + ) + .unwrap(); + match cell { + BTreeCell::TableInteriorCell(cell) => { + current.push(format!( + "node[rowid:{}, ptr(<=):{}]", + cell._rowid, cell._left_child_page + )); + child.push(format_btree( + pager.clone(), + cell._left_child_page as usize, + depth + 2, + )); + } + BTreeCell::TableLeafCell(cell) => { + current.push(format!( + "leaf[rowid:{}, len(payload):{}, overflow:{}]", + cell._rowid, + cell._payload.len(), + cell.first_overflow_page.is_some() + )); + } + _ => panic!("unsupported btree cell: {:?}", cell), + } + } + if let Some(rightmost) = contents.rightmost_pointer() { + child.push(format_btree(pager.clone(), rightmost as usize, depth + 2)); + } + let current = format!( + "{}-page:{}, ptr(right):{}\n{}+cells:{}", + " ".repeat(depth), + page_idx, + contents.rightmost_pointer().unwrap_or(0), + " ".repeat(depth), + current.join(", ") + ); + if child.is_empty() { + current + } else { + current + "\n" + &child.join("\n") + } + } + + fn empty_btree() -> (Rc, usize) { + let db_header = DatabaseHeader::default(); + let page_size = db_header.page_size as usize; + + #[allow(clippy::arc_with_non_send_sync)] + let io: Arc = Arc::new(MemoryIO::new().unwrap()); + let io_file = io.open_file("test.db", OpenFlags::Create, false).unwrap(); + let page_io = Rc::new(FileStorage::new(io_file)); + + let buffer_pool = Rc::new(BufferPool::new(db_header.page_size as usize)); + let wal_shared = WalFileShared::open_shared(&io, "test.wal", db_header.page_size).unwrap(); + let wal_file = WalFile::new(io.clone(), page_size, wal_shared, buffer_pool.clone()); + let wal = Rc::new(RefCell::new(wal_file)); + + let page_cache = Arc::new(parking_lot::RwLock::new(DumbLruPageCache::new(10))); + let pager = { + let db_header = Rc::new(RefCell::new(db_header.clone())); + Pager::finish_open(db_header, page_io, wal, io, page_cache, buffer_pool).unwrap() + }; + let pager = Rc::new(pager); + let page1 = pager.allocate_page().unwrap(); + btree_init_page(&page1, PageType::TableLeaf, &db_header, 0); + (pager, page1.get().id) + } + + #[test] + pub fn btree_insert_fuzz_ex() { + for sequence in [ + &[ + (777548915, 3364), + (639157228, 3796), + (709175417, 1214), + (390824637, 210), + (906124785, 1481), + (197677875, 1305), + (457946262, 3734), + (956825466, 592), + (835875722, 1334), + (649214013, 1250), + (531143011, 1788), + (765057993, 2351), + (510007766, 1349), + (884516059, 822), + (81604840, 2545), + ] + .as_slice(), + &[ + (293471650, 2452), + (163608869, 627), + (544576229, 464), + (705823748, 3441), + ] + .as_slice(), + &[ + (987283511, 2924), + (261851260, 1766), + (343847101, 1657), + (315844794, 572), + ] + .as_slice(), + &[ + (987283511, 2924), + (261851260, 1766), + (343847101, 1657), + (315844794, 572), + (649272840, 1632), + (723398505, 3140), + (334416967, 3874), + ] + .as_slice(), + ] { + let (pager, root_page) = empty_btree(); + let mut cursor = BTreeCursor::new(pager.clone(), root_page); + for (key, size) in sequence.iter() { + let key = OwnedValue::Integer(*key); + let value = Record::new(vec![OwnedValue::Blob(Rc::new(vec![0; *size]))]); + log::info!("insert key:{}", key); + cursor.insert(&key, &value, false).unwrap(); + log::info!( + "=========== btree ===========\n{}\n\n", + format_btree(pager.clone(), root_page, 0) + ); + } + for (key, _) in sequence.iter() { + let seek_key = SeekKey::TableRowId(*key as u64); + assert!( + matches!( + cursor.seek(seek_key, SeekOp::EQ).unwrap(), + CursorResult::Ok(true) + ), + "key {} is not found", + key + ); + } + } + } + + fn rng_from_time() -> (ChaCha8Rng, u64) { + let seed = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + let rng = ChaCha8Rng::seed_from_u64(seed); + (rng, seed) + } + + fn btree_insert_fuzz_run( + attempts: usize, + inserts: usize, + size: impl Fn(&mut ChaCha8Rng) -> usize, + ) { + let (mut rng, seed) = rng_from_time(); + log::info!("super seed: {}", seed); + for _ in 0..attempts { + let (pager, root_page) = empty_btree(); + let mut cursor = BTreeCursor::new(pager.clone(), root_page); + let mut keys = Vec::new(); + let seed = rng.next_u64(); + log::info!("seed: {}", seed); + let mut rng = ChaCha8Rng::seed_from_u64(seed); + for insert_id in 0..inserts { + let size = size(&mut rng); + let key = (rng.next_u64() % (1 << 30)) as i64; + keys.push(key); + log::info!( + "INSERT INTO t VALUES ({}, randomblob({})); -- {}", + key, + size, + insert_id + ); + let key = OwnedValue::Integer(key); + let value = Record::new(vec![OwnedValue::Blob(Rc::new(vec![0; size]))]); + cursor.insert(&key, &value, false).unwrap(); + } + log::info!( + "=========== btree ===========\n{}\n\n", + format_btree(pager.clone(), root_page, 0) + ); + if matches!(validate_btree(pager.clone(), root_page), (_, false)) { + panic!("invalid btree"); + } + for key in keys.iter() { + let seek_key = SeekKey::TableRowId(*key as u64); + assert!( + matches!( + cursor.seek(seek_key, SeekOp::EQ).unwrap(), + CursorResult::Ok(true) + ), + "key {} is not found", + key + ); + } + } + } + + #[test] + pub fn btree_insert_fuzz_run_equal_size() { + for size in 1..8 { + log::info!("======= size:{} =======", size); + btree_insert_fuzz_run(2, 1024, |_| size); + } + } + + #[test] + pub fn btree_insert_fuzz_run_random() { + btree_insert_fuzz_run(128, 16, |rng| (rng.next_u32() % 4096) as usize); + } + + #[test] + pub fn btree_insert_fuzz_run_small() { + btree_insert_fuzz_run(1, 1024, |rng| (rng.next_u32() % 128) as usize); + } + + #[test] + pub fn btree_insert_fuzz_run_big() { + btree_insert_fuzz_run(64, 32, |rng| 3 * 1024 + (rng.next_u32() % 1024) as usize); + } + + #[test] + pub fn btree_insert_fuzz_run_overflow() { + btree_insert_fuzz_run(64, 32, |rng| (rng.next_u32() % 32 * 1024) as usize); + } + #[allow(clippy::arc_with_non_send_sync)] fn setup_test_env(database_size: u32) -> (Rc, Rc>) { let page_size = 512; @@ -2401,7 +2714,7 @@ mod tests { } let write_complete = Box::new(|_| {}); - let c = Rc::new(Completion::Write(WriteCompletion::new(write_complete))); + let c = Completion::Write(WriteCompletion::new(write_complete)); page_io.write_page(1, buf.clone(), c).unwrap(); let wal_shared = WalFileShared::open_shared(&io, "test.wal", page_size).unwrap(); @@ -2449,7 +2762,7 @@ mod tests { drop_fn, ))); let write_complete = Box::new(|_| {}); - let c = Rc::new(Completion::Write(WriteCompletion::new(write_complete))); + let c = Completion::Write(WriteCompletion::new(write_complete)); pager .page_io .write_page(current_page as usize, buf.clone(), c)?; diff --git a/core/storage/database.rs b/core/storage/database.rs index e59519f38..97bb85721 100644 --- a/core/storage/database.rs +++ b/core/storage/database.rs @@ -1,4 +1,6 @@ -use crate::{error::LimboError, io::Completion, Buffer, Result}; +#[cfg(feature = "fs")] +use crate::error::LimboError; +use crate::{io::Completion, Buffer, Result}; use std::{cell::RefCell, rc::Rc}; /// DatabaseStorage is an interface a database file that consists of pages. @@ -7,14 +9,10 @@ use std::{cell::RefCell, rc::Rc}; /// the storage medium. A database can either be a file on disk, like in SQLite, /// or something like a remote page server service. pub trait DatabaseStorage { - fn read_page(&self, page_idx: usize, c: Rc) -> Result<()>; - fn write_page( - &self, - page_idx: usize, - buffer: Rc>, - c: Rc, - ) -> Result<()>; - fn sync(&self, c: Rc) -> Result<()>; + fn read_page(&self, page_idx: usize, c: Completion) -> Result<()>; + fn write_page(&self, page_idx: usize, buffer: Rc>, c: Completion) + -> Result<()>; + fn sync(&self, c: Completion) -> Result<()>; } #[cfg(feature = "fs")] @@ -24,9 +22,9 @@ pub struct FileStorage { #[cfg(feature = "fs")] impl DatabaseStorage for FileStorage { - fn read_page(&self, page_idx: usize, c: Rc) -> Result<()> { - let r = match c.as_ref() { - Completion::Read(r) => r, + fn read_page(&self, page_idx: usize, c: Completion) -> Result<()> { + let r = match c { + Completion::Read(ref r) => r, _ => unreachable!(), }; let size = r.buf().len(); @@ -43,7 +41,7 @@ impl DatabaseStorage for FileStorage { &self, page_idx: usize, buffer: Rc>, - c: Rc, + c: Completion, ) -> Result<()> { let buffer_size = buffer.borrow().len(); assert!(buffer_size >= 512); @@ -54,7 +52,7 @@ impl DatabaseStorage for FileStorage { Ok(()) } - fn sync(&self, c: Rc) -> Result<()> { + fn sync(&self, c: Completion) -> Result<()> { self.file.sync(c) } } diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 4963fa669..cda8c0c51 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -128,7 +128,7 @@ pub struct DatabaseHeader { text_encoding: u32, /// The "user version" as read and set by the user_version pragma. - user_version: u32, + pub user_version: u32, /// True (non-zero) for incremental-vacuum mode. False (zero) otherwise. incremental_vacuum_enabled: u32, @@ -232,7 +232,7 @@ impl Default for DatabaseHeader { default_page_cache_size: 500, // pages vacuum_mode_largest_root_page: 0, text_encoding: 1, // utf-8 - user_version: 1, + user_version: 0, incremental_vacuum_enabled: 0, application_id: 0, reserved_for_expansion: [0; 20], @@ -253,8 +253,8 @@ pub fn begin_read_database_header( let header = header.clone(); finish_read_database_header(buf, header).unwrap(); }); - let c = Rc::new(Completion::Read(ReadCompletion::new(buf, complete))); - page_io.read_page(1, c.clone())?; + let c = Completion::Read(ReadCompletion::new(buf, complete)); + page_io.read_page(1, c)?; Ok(result) } @@ -313,7 +313,7 @@ pub fn begin_write_database_header(header: &DatabaseHeader, pager: &Pager) -> Re let drop_fn = Rc::new(|_buf| {}); let buf = Rc::new(RefCell::new(Buffer::allocate(512, drop_fn))); - let c = Rc::new(Completion::Read(ReadCompletion::new(buf, read_complete))); + let c = Completion::Read(ReadCompletion::new(buf, read_complete)); page_source.read_page(1, c)?; // run get header block pager.io.run_once()?; @@ -327,7 +327,7 @@ pub fn begin_write_database_header(header: &DatabaseHeader, pager: &Pager) -> Re // finish_read_database_header(buf, header).unwrap(); }); - let c = Rc::new(Completion::Write(WriteCompletion::new(write_complete))); + let c = Completion::Write(WriteCompletion::new(write_complete)); page_source.write_page(0, buffer_to_copy, c)?; Ok(()) @@ -362,7 +362,7 @@ pub fn write_header_to_buf(buf: &mut [u8], header: &DatabaseHeader) { } #[repr(u8)] -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum PageType { IndexInterior = 2, TableInterior = 5, @@ -675,8 +675,8 @@ pub fn begin_read_page( page.set_error(); } }); - let c = Rc::new(Completion::Read(ReadCompletion::new(buf, complete))); - page_io.read_page(page_idx, c.clone())?; + let c = Completion::Read(ReadCompletion::new(buf, complete)); + page_io.read_page(page_idx, c)?; Ok(()) } @@ -733,7 +733,7 @@ pub fn begin_write_btree_page( } }) }; - let c = Rc::new(Completion::Write(WriteCompletion::new(write_complete))); + let c = Completion::Write(WriteCompletion::new(write_complete)); page_source.write_page(page_id, buffer.clone(), c)?; Ok(()) } @@ -746,7 +746,7 @@ pub fn begin_sync(page_io: Rc, syncing: Rc>) *syncing.borrow_mut() = false; }), }); - page_io.sync(Rc::new(completion))?; + page_io.sync(completion)?; Ok(()) } @@ -1129,11 +1129,9 @@ pub fn write_varint(buf: &mut [u8], value: u64) -> usize { } pub fn write_varint_to_vec(value: u64, payload: &mut Vec) { - let mut varint: Vec = vec![0; 9]; - let n = write_varint(&mut varint.as_mut_slice()[0..9], value); - write_varint(&mut varint, value); - varint.truncate(n); - payload.extend_from_slice(&varint); + let mut varint = [0u8; 9]; + let n = write_varint(&mut varint, value); + payload.extend_from_slice(&varint[0..n]); } pub fn begin_read_wal_header(io: &Rc) -> Result>> { @@ -1145,7 +1143,7 @@ pub fn begin_read_wal_header(io: &Rc) -> Result> let header = header.clone(); finish_read_wal_header(buf, header).unwrap(); }); - let c = Rc::new(Completion::Read(ReadCompletion::new(buf, complete))); + let c = Completion::Read(ReadCompletion::new(buf, complete)); io.pread(0, c)?; Ok(result) } @@ -1187,7 +1185,7 @@ pub fn begin_read_wal_frame( let frame = frame.clone(); finish_read_page(2, buf, frame).unwrap(); }); - let c = Rc::new(Completion::Read(ReadCompletion::new(buf, complete))); + let c = Completion::Read(ReadCompletion::new(buf, complete)); io.pread(offset, c)?; Ok(()) } @@ -1262,7 +1260,7 @@ pub fn begin_write_wal_frame( } }) }; - let c = Rc::new(Completion::Write(WriteCompletion::new(write_complete))); + let c = Completion::Write(WriteCompletion::new(write_complete)); io.pwrite(offset, buffer.clone(), c)?; Ok(checksums) } @@ -1295,7 +1293,7 @@ pub fn begin_write_wal_header(io: &Rc, header: &WalHeader) -> Result<( } }) }; - let c = Rc::new(Completion::Write(WriteCompletion::new(write_complete))); + let c = Completion::Write(WriteCompletion::new(write_complete)); io.pwrite(0, buffer.clone(), c)?; Ok(()) } @@ -1420,7 +1418,7 @@ mod tests { #[case(&[], SerialType::ConstInt0, OwnedValue::Integer(0))] #[case(&[], SerialType::ConstInt1, OwnedValue::Integer(1))] #[case(&[1, 2, 3], SerialType::Blob(3), OwnedValue::Blob(vec![1, 2, 3].into()))] - #[case(&[65, 66, 67], SerialType::String(3), OwnedValue::build_text("ABC".to_string().into()))] + #[case(&[65, 66, 67], SerialType::String(3), OwnedValue::build_text("ABC"))] fn test_read_value( #[case] buf: &[u8], #[case] serial_type: SerialType, diff --git a/core/storage/wal.rs b/core/storage/wal.rs index e42fde2b6..fc5f68845 100644 --- a/core/storage/wal.rs +++ b/core/storage/wal.rs @@ -630,7 +630,7 @@ impl Wal for WalFile { *syncing.borrow_mut() = false; }), }); - shared.file.sync(Rc::new(completion))?; + shared.file.sync(completion)?; } self.sync_state.replace(SyncState::Syncing); Ok(CheckpointStatus::IO) diff --git a/core/translate/delete.rs b/core/translate/delete.rs index ffad33d73..b5ce85fdc 100644 --- a/core/translate/delete.rs +++ b/core/translate/delete.rs @@ -13,7 +13,7 @@ pub fn translate_delete( query_mode: QueryMode, schema: &Schema, tbl_name: &QualifiedName, - where_clause: Option, + where_clause: Option>, limit: Option>, syms: &SymbolTable, ) -> Result { @@ -35,7 +35,7 @@ pub fn translate_delete( pub fn prepare_delete_plan( schema: &Schema, tbl_name: &QualifiedName, - where_clause: Option, + where_clause: Option>, limit: Option>, ) -> Result { let table = match schema.get_table(tbl_name.name.0.as_str()) { @@ -53,7 +53,12 @@ pub fn prepare_delete_plan( let mut where_predicates = vec![]; // Parse the WHERE clause - parse_where(where_clause, &table_references, None, &mut where_predicates)?; + parse_where( + where_clause.map(|e| *e), + &table_references, + None, + &mut where_predicates, + )?; // Parse the LIMIT/OFFSET clause let (resolved_limit, resolved_offset) = limit.map_or(Ok((None, None)), |l| parse_limit(*l))?; diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 8ddb580df..a3a71e2c5 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -159,6 +159,7 @@ macro_rules! expect_arguments_min { }}; } +#[allow(unused_macros)] macro_rules! expect_arguments_even { ( $args:expr, @@ -172,7 +173,7 @@ macro_rules! expect_arguments_even { ); }; // The only function right now that requires an even number is `json_object` and it allows - // to have no arguments, so thats why in this macro we do not bail with teh `function with no arguments` error + // to have no arguments, so thats why in this macro we do not bail with the `function with no arguments` error args }}; } @@ -236,8 +237,10 @@ pub fn translate_condition_expr( )?; } ast::Expr::Binary(lhs, op, rhs) => { - let lhs_reg = translate_and_mark(program, Some(referenced_tables), lhs, resolver)?; - let rhs_reg = translate_and_mark(program, Some(referenced_tables), rhs, resolver)?; + let lhs_reg = program.alloc_register(); + let rhs_reg = program.alloc_register(); + translate_and_mark(program, Some(referenced_tables), lhs, lhs_reg, resolver)?; + translate_and_mark(program, Some(referenced_tables), rhs, rhs_reg, resolver)?; match op { ast::Operator::Greater => { emit_cmp_insn!(program, condition_metadata, Gt, Le, lhs_reg, rhs_reg) @@ -416,16 +419,17 @@ pub fn translate_condition_expr( let cur_reg = program.alloc_register(); match op { ast::LikeOperator::Like | ast::LikeOperator::Glob => { - let pattern_reg = program.alloc_register(); + let start_reg = program.alloc_registers(2); let mut constant_mask = 0; - let _ = translate_and_mark(program, Some(referenced_tables), lhs, resolver); - let _ = translate_expr( + translate_and_mark( program, Some(referenced_tables), - rhs, - pattern_reg, + lhs, + start_reg + 1, resolver, )?; + let _ = + translate_expr(program, Some(referenced_tables), rhs, start_reg, resolver)?; if matches!(rhs.as_ref(), ast::Expr::Literal(_)) { program.mark_last_insn_constant(); constant_mask = 1; @@ -437,7 +441,7 @@ pub fn translate_condition_expr( }; program.emit_insn(Insn::Function { constant_mask, - start_reg: pattern_reg, + start_reg, dest: cur_reg, func: FuncCtx { func: Func::Scalar(func), @@ -475,7 +479,7 @@ pub fn translate_condition_expr( ); } else { crate::bail_parse_error!( - "parenthesized condtional should have exactly one expression" + "parenthesized conditional should have exactly one expression" ); } } @@ -791,7 +795,8 @@ pub fn translate_expr( lhs: base_reg, rhs: expr_reg, target_pc: next_case_label, - flags: CmpInsFlags::default(), + // A NULL result is considered untrue when evaluating WHEN terms. + flags: CmpInsFlags::default().jump_if_null(), }), // CASE WHEN 0 THEN 0 ELSE 1 becomes ifnot 0 branch to next clause None => program.emit_insn(Insn::IfNot { @@ -833,15 +838,14 @@ pub fn translate_expr( } ast::Expr::Cast { expr, type_name } => { let type_name = type_name.as_ref().unwrap(); // TODO: why is this optional? - let reg_expr = program.alloc_register(); + let reg_expr = program.alloc_registers(2); translate_expr(program, referenced_tables, expr, reg_expr, resolver)?; - let reg_type = program.alloc_register(); program.emit_insn(Insn::String8 { // we make a comparison against uppercase static strs in the affinity() function, // so we need to make sure we're comparing against the uppercase version, // and it's better to do this once instead of every time we check affinity value: type_name.name.to_uppercase(), - dest: reg_type, + dest: reg_expr + 1, }); program.mark_last_insn_constant(); program.emit_insn(Insn::Function { @@ -1002,16 +1006,23 @@ pub fn translate_expr( ) } JsonFunc::JsonRemove => { + let start_reg = + program.alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1)); if let Some(args) = args { - for arg in args.iter() { + for (i, arg) in args.iter().enumerate() { // register containing result of each argument expression - let _ = - translate_and_mark(program, referenced_tables, arg, resolver)?; + translate_and_mark( + program, + referenced_tables, + arg, + start_reg + i, + resolver, + )?; } } program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -1336,11 +1347,17 @@ pub fn translate_expr( | ScalarFunc::Soundex | ScalarFunc::ZeroBlob => { let args = expect_arguments_exact!(args, 1, srf); - let reg = - translate_and_mark(program, referenced_tables, &args[0], resolver)?; + let start_reg = program.alloc_register(); + translate_and_mark( + program, + referenced_tables, + &args[0], + start_reg, + resolver, + )?; program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: reg, + start_reg, dest: target_register, func: func_ctx, }); @@ -1349,11 +1366,17 @@ pub fn translate_expr( #[cfg(not(target_family = "wasm"))] ScalarFunc::LoadExtension => { let args = expect_arguments_exact!(args, 1, srf); - let reg = - translate_and_mark(program, referenced_tables, &args[0], resolver)?; + let start_reg = program.alloc_register(); + translate_and_mark( + program, + referenced_tables, + &args[0], + start_reg, + resolver, + )?; program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: reg, + start_reg, dest: target_register, func: func_ctx, }); @@ -1376,20 +1399,23 @@ pub fn translate_expr( Ok(target_register) } ScalarFunc::Date | ScalarFunc::DateTime => { + let start_reg = program + .alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1)); if let Some(args) = args { - for arg in args.iter() { + for (i, arg) in args.iter().enumerate() { // register containing result of each argument expression - let _ = translate_and_mark( + translate_and_mark( program, referenced_tables, arg, + start_reg + i, resolver, )?; } } program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -1456,11 +1482,17 @@ pub fn translate_expr( } else { crate::bail_parse_error!("hex function with no arguments",); }; - let regs = - translate_and_mark(program, referenced_tables, &args[0], resolver)?; + let start_reg = program.alloc_register(); + translate_and_mark( + program, + referenced_tables, + &args[0], + start_reg, + resolver, + )?; program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: regs, + start_reg, dest: target_register, func: func_ctx, }); @@ -1494,20 +1526,23 @@ pub fn translate_expr( Ok(target_register) } ScalarFunc::Time => { + let start_reg = program + .alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1)); if let Some(args) = args { - for arg in args.iter() { + for (i, arg) in args.iter().enumerate() { // register containing result of each argument expression - let _ = translate_and_mark( + translate_and_mark( program, referenced_tables, arg, + start_reg + i, resolver, )?; } } program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -1516,7 +1551,7 @@ pub fn translate_expr( ScalarFunc::TotalChanges => { if args.is_some() { crate::bail_parse_error!( - "{} fucntion with more than 0 arguments", + "{} function with more than 0 arguments", srf.to_string() ); } @@ -1536,12 +1571,19 @@ pub fn translate_expr( | ScalarFunc::Unhex => { let args = expect_arguments_max!(args, 2, srf); - for arg in args.iter() { - translate_and_mark(program, referenced_tables, arg, resolver)?; + let start_reg = program.alloc_registers(args.len()); + for (i, arg) in args.iter().enumerate() { + translate_and_mark( + program, + referenced_tables, + arg, + start_reg + i, + resolver, + )?; } program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -1558,13 +1600,20 @@ pub fn translate_expr( } else { crate::bail_parse_error!("min function with no arguments"); }; - for arg in args { - translate_and_mark(program, referenced_tables, arg, resolver)?; + let start_reg = program.alloc_registers(args.len()); + for (i, arg) in args.iter().enumerate() { + translate_and_mark( + program, + referenced_tables, + arg, + start_reg + i, + resolver, + )?; } program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -1581,13 +1630,20 @@ pub fn translate_expr( } else { crate::bail_parse_error!("max function with no arguments"); }; - for arg in args { - translate_and_mark(program, referenced_tables, arg, resolver)?; + let start_reg = program.alloc_registers(args.len()); + for (i, arg) in args.iter().enumerate() { + translate_and_mark( + program, + referenced_tables, + arg, + start_reg + i, + resolver, + )?; } program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -1724,20 +1780,23 @@ pub fn translate_expr( Ok(target_register) } ScalarFunc::StrfTime => { + let start_reg = program + .alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1)); if let Some(args) = args { - for arg in args.iter() { + for (i, arg) in args.iter().enumerate() { // register containing result of each argument expression - let _ = translate_and_mark( + translate_and_mark( program, referenced_tables, arg, + start_reg + i, resolver, )?; } } program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -1770,11 +1829,17 @@ pub fn translate_expr( MathFuncArity::Unary => { let args = expect_arguments_exact!(args, 1, math_func); - let reg = - translate_and_mark(program, referenced_tables, &args[0], resolver)?; + let start_reg = program.alloc_register(); + translate_and_mark( + program, + referenced_tables, + &args[0], + start_reg, + resolver, + )?; program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: reg, + start_reg, dest: target_register, func: func_ctx, }); @@ -2164,14 +2229,14 @@ pub fn translate_and_mark( program: &mut ProgramBuilder, referenced_tables: Option<&[TableReference]>, expr: &ast::Expr, + target_register: usize, resolver: &Resolver, -) -> Result { - let target_register = program.alloc_register(); +) -> Result<()> { translate_expr(program, referenced_tables, expr, target_register, resolver)?; if matches!(expr, ast::Expr::Literal(_)) { program.mark_last_insn_constant(); } - Ok(target_register) + Ok(()) } /// Sanitaizes a string literal by removing single quote at front and back diff --git a/core/translate/main_loop.rs b/core/translate/main_loop.rs index 3b918e9ea..32c2a2d2d 100644 --- a/core/translate/main_loop.rs +++ b/core/translate/main_loop.rs @@ -401,10 +401,10 @@ pub fn open_loop( program.resolve_label(loop_start, program.offset()); // TODO: We are currently only handling ascending indexes. - // For conditions like index_key > 10, we have already seeked to the first key greater than 10, and can just scan forward. + // For conditions like index_key > 10, we have already sought to the first key greater than 10, and can just scan forward. // For conditions like index_key < 10, we are at the beginning of the index, and will scan forward and emit IdxGE(10) with a conditional jump to the end. - // For conditions like index_key = 10, we have already seeked to the first key greater than or equal to 10, and can just scan forward and emit IdxGT(10) with a conditional jump to the end. - // For conditions like index_key >= 10, we have already seeked to the first key greater than or equal to 10, and can just scan forward. + // For conditions like index_key = 10, we have already sought to the first key greater than or equal to 10, and can just scan forward and emit IdxGT(10) with a conditional jump to the end. + // For conditions like index_key >= 10, we have already sought to the first key greater than or equal to 10, and can just scan forward. // For conditions like index_key <= 10, we are at the beginning of the index, and will scan forward and emit IdxGT(10) with a conditional jump to the end. // For conditions like index_key != 10, TODO. probably the optimal way is not to use an index at all. // diff --git a/core/translate/mod.rs b/core/translate/mod.rs index 79791866b..5f9e19866 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -34,6 +34,7 @@ use crate::{bail_parse_error, Connection, LimboError, Result, SymbolTable}; use insert::translate_insert; use select::translate_select; use sqlite3_parser::ast::{self, fmt::ToTokens}; +use sqlite3_parser::ast::{Delete, Insert}; use std::cell::RefCell; use std::fmt::Display; use std::rc::{Rc, Weak}; @@ -51,7 +52,7 @@ pub fn translate( let mut change_cnt_on = false; let program = match stmt { - ast::Stmt::AlterTable(_, _) => bail_parse_error!("ALTER TABLE not supported yet"), + ast::Stmt::AlterTable(_) => bail_parse_error!("ALTER TABLE not supported yet"), ast::Stmt::Analyze(_) => bail_parse_error!("ANALYZE not supported yet"), ast::Stmt::Attach { .. } => bail_parse_error!("ATTACH not supported yet"), ast::Stmt::Begin(_, _) => bail_parse_error!("BEGIN not supported yet"), @@ -67,19 +68,20 @@ pub fn translate( bail_parse_error!("TEMPORARY table not supported yet"); } - translate_create_table(query_mode, tbl_name, body, if_not_exists, schema)? + translate_create_table(query_mode, tbl_name, *body, if_not_exists, schema)? } ast::Stmt::CreateTrigger { .. } => bail_parse_error!("CREATE TRIGGER not supported yet"), ast::Stmt::CreateView { .. } => bail_parse_error!("CREATE VIEW not supported yet"), ast::Stmt::CreateVirtualTable { .. } => { bail_parse_error!("CREATE VIRTUAL TABLE not supported yet") } - ast::Stmt::Delete { - tbl_name, - where_clause, - limit, - .. - } => { + ast::Stmt::Delete(delete) => { + let Delete { + tbl_name, + where_clause, + limit, + .. + } = *delete; change_cnt_on = true; translate_delete(query_mode, schema, &tbl_name, where_clause, limit, syms)? } @@ -92,7 +94,7 @@ pub fn translate( query_mode, &schema, &name, - body, + body.map(|b| *b), database_header.clone(), pager, )?, @@ -103,14 +105,15 @@ pub fn translate( ast::Stmt::Select(select) => translate_select(query_mode, schema, *select, syms)?, ast::Stmt::Update { .. } => bail_parse_error!("UPDATE not supported yet"), ast::Stmt::Vacuum(_, _) => bail_parse_error!("VACUUM not supported yet"), - ast::Stmt::Insert { - with, - or_conflict, - tbl_name, - columns, - body, - returning, - } => { + ast::Stmt::Insert(insert) => { + let Insert { + with, + or_conflict, + tbl_name, + columns, + body, + returning, + } = *insert; change_cnt_on = true; translate_insert( query_mode, @@ -493,7 +496,7 @@ fn translate_create_table( program.resolve_label(parse_schema_label, program.offset()); // TODO: SetCookie // - // TODO: remove format, it sucks for performance but is convinient + // TODO: remove format, it sucks for performance but is convenient let parse_schema_where_clause = format!("tbl_name = '{}' AND type != 'trigger'", tbl_name); program.emit_insn(Insn::ParseSchema { db: sqlite_schema_cursor_id, diff --git a/core/translate/planner.rs b/core/translate/planner.rs index 311458f9f..5d09bbadd 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -1,7 +1,7 @@ use super::{ plan::{ - Aggregate, JoinInfo, Operation, Plan, ResultSetColumn, SelectQueryType, TableReference, - WhereTerm, + Aggregate, JoinInfo, Operation, Plan, ResultSetColumn, SelectPlan, SelectQueryType, + TableReference, WhereTerm, }, select::prepare_select_plan, SymbolTable, @@ -13,7 +13,9 @@ use crate::{ vdbe::BranchOffset, Result, VirtualTable, }; -use sqlite3_parser::ast::{self, Expr, FromClause, JoinType, Limit, UnaryOperator}; +use sqlite3_parser::ast::{ + self, Expr, FromClause, JoinType, Limit, Materialized, UnaryOperator, With, +}; pub const ROWID: &str = "rowid"; @@ -278,46 +280,92 @@ pub fn bind_column_references( } } -fn parse_from_clause_table( +fn parse_from_clause_table<'a>( schema: &Schema, table: ast::SelectTable, - cur_table_index: usize, + scope: &mut Scope<'a>, syms: &SymbolTable, -) -> Result { +) -> Result<()> { match table { ast::SelectTable::Table(qualified_name, maybe_alias, _) => { let normalized_qualified_name = normalize_ident(qualified_name.name.0.as_str()); - let Some(table) = schema.get_table(&normalized_qualified_name) else { - crate::bail_parse_error!("Table {} not found", normalized_qualified_name); + // Check if the FROM clause table is referring to a CTE in the current scope. + if let Some(cte) = scope + .ctes + .iter() + .find(|cte| cte.name == normalized_qualified_name) + { + // CTE can be rewritten as a subquery. + // TODO: find a way not to clone the CTE plan here. + let cte_table = + TableReference::new_subquery(cte.name.clone(), cte.plan.clone(), None); + scope.tables.push(cte_table); + return Ok(()); }; - let alias = maybe_alias - .map(|a| match a { - ast::As::As(id) => id, - ast::As::Elided(id) => id, - }) - .map(|a| a.0); - Ok(TableReference { - op: Operation::Scan { iter_dir: None }, - table: Table::BTree(table.clone()), - identifier: alias.unwrap_or(normalized_qualified_name), - join_info: None, - }) + // Check if our top level schema has this table. + if let Some(table) = schema.get_table(&normalized_qualified_name) { + let alias = maybe_alias + .map(|a| match a { + ast::As::As(id) => id, + ast::As::Elided(id) => id, + }) + .map(|a| a.0); + scope.tables.push(TableReference { + op: Operation::Scan { iter_dir: None }, + table: Table::BTree(table.clone()), + identifier: alias.unwrap_or(normalized_qualified_name), + join_info: None, + }); + return Ok(()); + }; + + // Check if the outer query scope has this table. + if let Some(outer_scope) = scope.parent { + if let Some(table_ref_idx) = outer_scope + .tables + .iter() + .position(|t| t.identifier == normalized_qualified_name) + { + // TODO: avoid cloning the table reference here. + scope.tables.push(outer_scope.tables[table_ref_idx].clone()); + return Ok(()); + } + if let Some(cte) = outer_scope + .ctes + .iter() + .find(|cte| cte.name == normalized_qualified_name) + { + // TODO: avoid cloning the CTE plan here. + let cte_table = + TableReference::new_subquery(cte.name.clone(), cte.plan.clone(), None); + scope.tables.push(cte_table); + return Ok(()); + } + } + + crate::bail_parse_error!("Table {} not found", normalized_qualified_name); } ast::SelectTable::Select(subselect, maybe_alias) => { - let Plan::Select(mut subplan) = prepare_select_plan(schema, *subselect, syms)? else { + let Plan::Select(mut subplan) = + prepare_select_plan(schema, *subselect, syms, Some(scope))? + else { unreachable!(); }; subplan.query_type = SelectQueryType::Subquery { yield_reg: usize::MAX, // will be set later in bytecode emission coroutine_implementation_start: BranchOffset::Placeholder, // will be set later in bytecode emission }; + let cur_table_index = scope.tables.len(); let identifier = maybe_alias .map(|a| match a { ast::As::As(id) => id.0.clone(), ast::As::Elided(id) => id.0.clone(), }) .unwrap_or(format!("subquery_{}", cur_table_index)); - Ok(TableReference::new_subquery(identifier, subplan, None)) + scope + .tables + .push(TableReference::new_subquery(identifier, subplan, None)); + Ok(()) } ast::SelectTable::TableCall(qualified_name, maybe_args, maybe_alias) => { let normalized_name = &normalize_ident(qualified_name.name.0.as_str()); @@ -332,7 +380,7 @@ fn parse_from_clause_table( }) .unwrap_or(normalized_name.to_string()); - Ok(TableReference { + scope.tables.push(TableReference { op: Operation::Scan { iter_dir: None }, join_info: None, table: Table::Virtual( @@ -346,32 +394,131 @@ fn parse_from_clause_table( ) .into(), identifier: alias.clone(), - }) + }); + Ok(()) } _ => todo!(), } } -pub fn parse_from( +/// A scope is a list of tables that are visible to the current query. +/// It is used to resolve table references in the FROM clause. +/// To resolve table references that are potentially ambiguous, the resolution +/// first looks at schema tables and tables in the current scope (which currently just means CTEs in the current query), +/// and only after that looks at whether a table from an outer (upper) query level matches. +/// +/// For example: +/// +/// WITH nested AS (SELECT foo FROM bar) +/// WITH sub AS (SELECT foo FROM bar) +/// SELECT * FROM sub +/// +/// 'sub' would preferentially refer to the 'foo' column from the 'bar' table in the catalog. +/// With an explicit reference like: +/// +/// SELECT nested.foo FROM sub +/// +/// 'nested.foo' would refer to the 'foo' column from the 'nested' CTE. +/// +/// TODO: we should probably use Scope in all of our identifier resolution, because it allows for e.g. +/// WITH users AS (SELECT * FROM products) SELECT * FROM users <-- returns products, even if there is a table named 'users' in the catalog! +/// +/// Currently we are treating Schema as a first-class object in identifier resolution, when in reality +/// be part of the 'Scope' struct. +pub struct Scope<'a> { + /// The tables that are explicitly present in the current query, including catalog tables and CTEs. + tables: Vec, + ctes: Vec, + /// The parent scope, if any. For example, a second CTE has access to the first CTE via the parent scope. + parent: Option<&'a Scope<'a>>, +} + +pub struct Cte { + /// The name of the CTE. + name: String, + /// The query plan for the CTE. + /// Currently we only support SELECT queries in CTEs. + plan: SelectPlan, +} + +pub fn parse_from<'a>( schema: &Schema, mut from: Option, syms: &SymbolTable, + with: Option, out_where_clause: &mut Vec, + outer_scope: Option<&'a Scope<'a>>, ) -> Result> { if from.as_ref().and_then(|f| f.select.as_ref()).is_none() { return Ok(vec![]); } + let mut scope = Scope { + tables: vec![], + ctes: vec![], + parent: outer_scope, + }; + + if let Some(with) = with { + if with.recursive { + crate::bail_parse_error!("Recursive CTEs are not yet supported"); + } + for cte in with.ctes { + if cte.materialized == Materialized::Yes { + crate::bail_parse_error!("Materialized CTEs are not yet supported"); + } + if cte.columns.is_some() { + crate::bail_parse_error!("CTE columns are not yet supported"); + } + + // Check if normalized name conflicts with catalog tables or other CTEs + // TODO: sqlite actually allows overriding a catalog table with a CTE. + // We should carry over the 'Scope' struct to all of our identifier resolution. + let cte_name_normalized = normalize_ident(&cte.tbl_name.0); + if schema.get_table(&cte_name_normalized).is_some() { + crate::bail_parse_error!( + "CTE name {} conflicts with catalog table name", + cte.tbl_name.0 + ); + } + if scope + .tables + .iter() + .any(|t| t.identifier == cte_name_normalized) + { + crate::bail_parse_error!("CTE name {} conflicts with table name", cte.tbl_name.0); + } + if scope.ctes.iter().any(|c| c.name == cte_name_normalized) { + crate::bail_parse_error!("duplicate WITH table name {}", cte.tbl_name.0); + } + + // CTE can refer to other CTEs that came before it, plus any schema tables or tables in the outer scope. + let cte_plan = prepare_select_plan(schema, *cte.select, syms, Some(&scope))?; + let Plan::Select(mut cte_plan) = cte_plan else { + crate::bail_parse_error!("Only SELECT queries are currently supported in CTEs"); + }; + // CTE can be rewritten as a subquery. + cte_plan.query_type = SelectQueryType::Subquery { + yield_reg: usize::MAX, // will be set later in bytecode emission + coroutine_implementation_start: BranchOffset::Placeholder, // will be set later in bytecode emission + }; + scope.ctes.push(Cte { + name: cte_name_normalized, + plan: cte_plan, + }); + } + } + let mut from_owned = std::mem::take(&mut from).unwrap(); let select_owned = *std::mem::take(&mut from_owned.select).unwrap(); let joins_owned = std::mem::take(&mut from_owned.joins).unwrap_or_default(); - let mut tables = vec![parse_from_clause_table(schema, select_owned, 0, syms)?]; + parse_from_clause_table(schema, select_owned, &mut scope, syms)?; for join in joins_owned.into_iter() { - parse_join(schema, join, syms, &mut tables, out_where_clause)?; + parse_join(schema, join, syms, &mut scope, out_where_clause)?; } - Ok(tables) + Ok(scope.tables) } pub fn parse_where( @@ -451,11 +598,11 @@ fn get_rightmost_table_referenced_in_expr<'a>(predicate: &'a ast::Expr) -> Resul Ok(max_table_idx) } -fn parse_join( +fn parse_join<'a>( schema: &Schema, join: ast::JoinedSelectTable, syms: &SymbolTable, - tables: &mut Vec, + scope: &mut Scope<'a>, out_where_clause: &mut Vec, ) -> Result<()> { let ast::JoinedSelectTable { @@ -464,9 +611,7 @@ fn parse_join( constraint, } = join; - let cur_table_index = tables.len(); - let table = parse_from_clause_table(schema, table, cur_table_index, syms)?; - tables.push(table); + parse_from_clause_table(schema, table, scope, syms)?; let (outer, natural) = match join_operator { ast::JoinOperator::TypedJoin(Some(join_type)) => { @@ -484,15 +629,15 @@ fn parse_join( } let constraint = if natural { - assert!(tables.len() >= 2); - let rightmost_table = tables.last().unwrap(); + assert!(scope.tables.len() >= 2); + let rightmost_table = scope.tables.last().unwrap(); // NATURAL JOIN is first transformed into a USING join with the common columns let right_cols = rightmost_table.columns(); let mut distinct_names: Option = None; // TODO: O(n^2) maybe not great for large tables or big multiway joins for right_col in right_cols.iter() { let mut found_match = false; - for left_table in tables.iter().take(tables.len() - 1) { + for left_table in scope.tables.iter().take(scope.tables.len() - 1) { for left_col in left_table.columns().iter() { if left_col.name == right_col.name { if let Some(distinct_names) = distinct_names.as_mut() { @@ -530,10 +675,10 @@ fn parse_join( let mut preds = vec![]; break_predicate_at_and_boundaries(expr, &mut preds); for predicate in preds.iter_mut() { - bind_column_references(predicate, tables, None)?; + bind_column_references(predicate, &scope.tables, None)?; } for pred in preds { - let cur_table_idx = tables.len() - 1; + let cur_table_idx = scope.tables.len() - 1; let eval_at_loop = if outer { cur_table_idx } else { @@ -550,10 +695,10 @@ fn parse_join( // USING join is replaced with a list of equality predicates for distinct_name in distinct_names.iter() { let name_normalized = normalize_ident(distinct_name.0.as_str()); - let cur_table_idx = tables.len() - 1; - let left_tables = &tables[..cur_table_idx]; + let cur_table_idx = scope.tables.len() - 1; + let left_tables = &scope.tables[..cur_table_idx]; assert!(!left_tables.is_empty()); - let right_table = tables.last().unwrap(); + let right_table = scope.tables.last().unwrap(); let mut left_col = None; for (left_table_idx, left_table) in left_tables.iter().enumerate() { left_col = left_table @@ -620,9 +765,9 @@ fn parse_join( } } - assert!(tables.len() >= 2); - let last_idx = tables.len() - 1; - let rightmost_table = tables.get_mut(last_idx).unwrap(); + assert!(scope.tables.len() >= 2); + let last_idx = scope.tables.len() - 1; + let rightmost_table = scope.tables.get_mut(last_idx).unwrap(); rightmost_table.join_info = Some(JoinInfo { outer, using }); Ok(()) diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index ebc854442..e31e732ce 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -11,7 +11,7 @@ use crate::storage::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE}; use crate::storage::wal::CheckpointMode; use crate::util::normalize_ident; use crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode}; -use crate::vdbe::insn::Insn; +use crate::vdbe::insn::{Cookie, Insn}; use crate::vdbe::BranchOffset; use crate::{bail_parse_error, Pager}; use std::str::FromStr; @@ -148,6 +148,10 @@ fn update_pragma( query_pragma(PragmaName::PageCount, schema, None, header, program)?; Ok(()) } + PragmaName::UserVersion => { + // TODO: Implement updating user_version + todo!("updating user_version not yet implemented") + } PragmaName::TableInfo => { // because we need control over the write parameter for the transaction, // this should be unreachable. We have to force-call query_pragma before @@ -241,6 +245,15 @@ fn query_pragma( } } } + PragmaName::UserVersion => { + program.emit_transaction(false); + program.emit_insn(Insn::ReadCookie { + db: 0, + dest: register, + cookie: Cookie::UserVersion, + }); + program.emit_result_row(register, 1); + } } Ok(()) diff --git a/core/translate/select.rs b/core/translate/select.rs index 2a055afd2..5b212cdd7 100644 --- a/core/translate/select.rs +++ b/core/translate/select.rs @@ -1,5 +1,6 @@ use super::emitter::emit_program; use super::plan::{select_star, Operation, Search, SelectQueryType}; +use super::planner::Scope; use crate::function::{AggFunc, ExtFunc, Func}; use crate::translate::optimizer::optimize_plan; use crate::translate::plan::{Aggregate, Direction, GroupBy, Plan, ResultSetColumn, SelectPlan}; @@ -11,8 +12,8 @@ use crate::util::normalize_ident; use crate::vdbe::builder::{ProgramBuilderOpts, QueryMode}; use crate::SymbolTable; use crate::{schema::Schema, vdbe::builder::ProgramBuilder, Result}; -use sqlite3_parser::ast::ResultColumn; use sqlite3_parser::ast::{self}; +use sqlite3_parser::ast::{ResultColumn, SelectInner}; pub fn translate_select( query_mode: QueryMode, @@ -20,7 +21,7 @@ pub fn translate_select( select: ast::Select, syms: &SymbolTable, ) -> Result { - let mut select_plan = prepare_select_plan(schema, select, syms)?; + let mut select_plan = prepare_select_plan(schema, select, syms, None)?; optimize_plan(&mut select_plan, schema)?; let Plan::Select(ref select) = select_plan else { panic!("select_plan is not a SelectPlan"); @@ -36,19 +37,21 @@ pub fn translate_select( Ok(program) } -pub fn prepare_select_plan( +pub fn prepare_select_plan<'a>( schema: &Schema, select: ast::Select, syms: &SymbolTable, + outer_scope: Option<&'a Scope<'a>>, ) -> Result { match *select.body.select { - ast::OneSelect::Select { - mut columns, - from, - where_clause, - group_by, - .. - } => { + ast::OneSelect::Select(select_inner) => { + let SelectInner { + mut columns, + from, + where_clause, + group_by, + .. + } = *select_inner; let col_count = columns.len(); if col_count == 0 { crate::bail_parse_error!("SELECT without columns is not allowed"); @@ -56,8 +59,11 @@ pub fn prepare_select_plan( let mut where_predicates = vec![]; + let with = select.with; + // Parse the FROM clause into a vec of TableReferences. Fold all the join conditions expressions into the WHERE clause. - let table_references = parse_from(schema, from, syms, &mut where_predicates)?; + let table_references = + parse_from(schema, from, syms, with, &mut where_predicates, outer_scope)?; // Preallocate space for the result columns let result_columns = Vec::with_capacity( @@ -305,7 +311,7 @@ pub fn prepare_select_plan( exprs: group_by.exprs, having: if let Some(having) = group_by.having { let mut predicates = vec![]; - break_predicate_at_and_boundaries(having, &mut predicates); + break_predicate_at_and_boundaries(*having, &mut predicates); for expr in predicates.iter_mut() { bind_column_references( expr, diff --git a/core/types.rs b/core/types.rs index ae31314c1..7eee76e94 100644 --- a/core/types.rs +++ b/core/types.rs @@ -56,17 +56,17 @@ pub struct Text { impl Text { pub fn from_str>(value: S) -> Self { - Self::new(Rc::new(value.into())) + Self::new(&value.into()) } - pub fn new(value: Rc) -> Self { + pub fn new(value: &str) -> Self { Self { value: Rc::new(value.as_bytes().to_vec()), subtype: TextSubtype::Text, } } - pub fn json(value: Rc) -> Self { + pub fn json(value: &str) -> Self { Self { value: Rc::new(value.as_bytes().to_vec()), subtype: TextSubtype::Json, @@ -91,7 +91,7 @@ pub enum OwnedValue { impl OwnedValue { // A helper function that makes building a text OwnedValue easier. - pub fn build_text(text: Rc) -> Self { + pub fn build_text(text: &str) -> Self { Self::Text(Text::new(text)) } @@ -114,7 +114,7 @@ impl OwnedValue { } pub fn from_text(text: &str) -> Self { - OwnedValue::Text(Text::new(Rc::new(text.to_string()))) + OwnedValue::Text(Text::new(text)) } pub fn value_type(&self) -> OwnedValueType { @@ -242,7 +242,7 @@ impl OwnedValue { let Some(text) = v.to_text() else { return Ok(OwnedValue::Null); }; - Ok(OwnedValue::build_text(Rc::new(text.to_string()))) + Ok(OwnedValue::build_text(text)) } ExtValueType::Blob => { let Some(blob) = v.to_blob() else { @@ -381,22 +381,22 @@ impl std::ops::Add for OwnedValue { (Self::Float(float_left), Self::Float(float_right)) => { Self::Float(float_left + float_right) } - (Self::Text(string_left), Self::Text(string_right)) => Self::build_text(Rc::new( - string_left.as_str().to_string() + &string_right.as_str(), - )), - (Self::Text(string_left), Self::Integer(int_right)) => Self::build_text(Rc::new( - string_left.as_str().to_string() + &int_right.to_string(), - )), + (Self::Text(string_left), Self::Text(string_right)) => { + Self::build_text(&(string_left.as_str().to_string() + string_right.as_str())) + } + (Self::Text(string_left), Self::Integer(int_right)) => { + Self::build_text(&(string_left.as_str().to_string() + &int_right.to_string())) + } (Self::Integer(int_left), Self::Text(string_right)) => { - Self::build_text(Rc::new(int_left.to_string() + &string_right.as_str())) + Self::build_text(&(int_left.to_string() + string_right.as_str())) } (Self::Text(string_left), Self::Float(float_right)) => { let string_right = Self::Float(float_right).to_string(); - Self::build_text(Rc::new(string_left.as_str().to_string() + &string_right)) + Self::build_text(&(string_left.as_str().to_string() + &string_right)) } (Self::Float(float_left), Self::Text(string_right)) => { let string_left = Self::Float(float_left).to_string(); - Self::build_text(Rc::new(string_left + &string_right.as_str())) + Self::build_text(&(string_left + string_right.as_str())) } (lhs, Self::Null) => lhs, (Self::Null, rhs) => rhs, @@ -522,7 +522,7 @@ impl<'a> FromValue<'a> for &'a str { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Record { - pub values: Vec, + values: Vec, } impl Record { @@ -534,6 +534,22 @@ impl Record { pub fn count(&self) -> usize { self.values.len() } + + pub fn last_value(&self) -> Option<&OwnedValue> { + self.values.last() + } + + pub fn get_values(&self) -> &Vec { + &self.values + } + + pub fn get_value(&self, idx: usize) -> &OwnedValue { + &self.values[idx] + } + + pub fn len(&self) -> usize { + self.values.len() + } } const I8_LOW: i64 = -128; @@ -727,6 +743,7 @@ impl Cursor { } } +#[derive(Debug)] pub enum CursorResult { Ok(T), IO, @@ -864,8 +881,8 @@ mod tests { #[test] fn test_serialize_text() { - let text = Rc::new("hello".to_string()); - let record = Record::new(vec![OwnedValue::Text(Text::new(text.clone()))]); + let text = "hello"; + let record = Record::new(vec![OwnedValue::Text(Text::new(text))]); let mut buf = Vec::new(); record.serialize(&mut buf); @@ -902,12 +919,12 @@ mod tests { #[test] fn test_serialize_mixed_types() { - let text = Rc::new("test".to_string()); + let text = "test"; let record = Record::new(vec![ OwnedValue::Null, OwnedValue::Integer(42), OwnedValue::Float(3.15), - OwnedValue::Text(Text::new(text.clone())), + OwnedValue::Text(Text::new(text)), ]); let mut buf = Vec::new(); record.serialize(&mut buf); diff --git a/core/util.rs b/core/util.rs index 654951700..0d16f7794 100644 --- a/core/util.rs +++ b/core/util.rs @@ -499,7 +499,7 @@ pub mod tests { } #[test] - fn test_expressions_equivalent_multiplicaiton() { + fn test_expressions_equivalent_multiplication() { let expr1 = Expr::Binary( Box::new(Expr::Literal(Literal::Numeric("42.0".to_string()))), Multiply, diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index 67f0f2749..68d0b2f4a 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -20,7 +20,8 @@ pub struct ProgramBuilder { insns: Vec, // for temporarily storing instructions that will be put after Transaction opcode constant_insns: Vec, - next_insn_label: Option, + // Vector of labels which must be assigned to next emitted instruction + next_insn_labels: Vec, // Cursors that are referenced by the program. Indexed by CursorID. pub cursor_ref: Vec<(Option, CursorType)>, /// A vector where index=label number, value=resolved offset. Resolved in build(). @@ -68,7 +69,7 @@ impl ProgramBuilder { next_free_register: 1, next_free_cursor_id: 0, insns: Vec::with_capacity(opts.approx_num_insns), - next_insn_label: None, + next_insn_labels: Vec::with_capacity(2), cursor_ref: Vec::with_capacity(opts.num_cursors), constant_insns: Vec::new(), label_to_resolved_offset: Vec::with_capacity(opts.approx_num_labels), @@ -109,12 +110,9 @@ impl ProgramBuilder { } pub fn emit_insn(&mut self, insn: Insn) { - if let Some(label) = self.next_insn_label { - self.label_to_resolved_offset.insert( - label.to_label_value() as usize, - Some(self.insns.len() as InsnReference), - ); - self.next_insn_label = None; + for label in self.next_insn_labels.drain(..) { + self.label_to_resolved_offset[label.to_label_value() as usize] = + Some(self.insns.len() as InsnReference); } self.insns.push(insn); } @@ -215,7 +213,7 @@ impl ProgramBuilder { // Useful when you know you need to jump to "the next part", but the exact offset is unknowable // at the time of emitting the instruction. pub fn preassign_label_to_next_insn(&mut self, label: BranchOffset) { - self.next_insn_label = Some(label); + self.next_insn_labels.push(label); } pub fn resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset) { diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 6f55d5dc1..79c96a44e 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -17,7 +17,7 @@ pub fn insn_to_str( 0, target_pc.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("Start at {}", target_pc.to_debug_int()), ), @@ -26,7 +26,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}]+r[{}]", dest, lhs, rhs), ), @@ -35,7 +35,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}]-r[{}]", dest, lhs, rhs), ), @@ -44,7 +44,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}]*r[{}]", dest, lhs, rhs), ), @@ -53,7 +53,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}]/r[{}]", dest, lhs, rhs), ), @@ -62,7 +62,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}]&r[{}]", dest, lhs, rhs), ), @@ -71,7 +71,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}]|r[{}]", dest, lhs, rhs), ), @@ -80,7 +80,7 @@ pub fn insn_to_str( *reg as i32, *dest as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=~r[{}]", dest, reg), ), @@ -93,7 +93,7 @@ pub fn insn_to_str( *database as i32, *dest as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=~r[{}]", dest, database), ), @@ -102,7 +102,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}]%r[{}]", dest, lhs, rhs), ), @@ -111,7 +111,7 @@ pub fn insn_to_str( 0, *dest as i32, dest_end.map_or(0, |end| end as i32), - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, dest_end.map_or(format!("r[{}]=NULL", dest), |end| { format!("r[{}..{}]=NULL", dest, end) @@ -122,7 +122,7 @@ pub fn insn_to_str( *cursor_id as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("Set cursor {} to a (pseudo) NULL row", cursor_id), ), @@ -131,7 +131,7 @@ pub fn insn_to_str( *reg as i32, target_pc.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]!=NULL -> goto {}", reg, target_pc.to_debug_int()), ), @@ -144,7 +144,7 @@ pub fn insn_to_str( *start_reg_a as i32, *start_reg_b as i32, *count as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "r[{}..{}]==r[{}..{}]", @@ -163,7 +163,7 @@ pub fn insn_to_str( target_pc_lt.to_debug_int(), target_pc_eq.to_debug_int(), target_pc_gt.to_debug_int(), - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -176,7 +176,7 @@ pub fn insn_to_str( *source_reg as i32, *dest_reg as i32, *count as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "r[{}..{}]=r[{}..{}]", @@ -195,7 +195,7 @@ pub fn insn_to_str( *reg as i32, target_pc.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "r[{}]>0 -> r[{}]-={}, goto {}", @@ -215,7 +215,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, target_pc.to_debug_int(), - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "if r[{}]==r[{}] goto {}", @@ -234,7 +234,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, target_pc.to_debug_int(), - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "if r[{}]!=r[{}] goto {}", @@ -253,7 +253,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, target_pc.to_debug_int(), - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("if r[{}]r[{}] goto {}", lhs, rhs, target_pc.to_debug_int()), ), @@ -300,7 +300,7 @@ pub fn insn_to_str( *lhs as i32, *rhs as i32, target_pc.to_debug_int(), - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "if r[{}]>=r[{}] goto {}", @@ -318,7 +318,7 @@ pub fn insn_to_str( *reg as i32, target_pc.to_debug_int(), *jump_if_null as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("if r[{}] goto {}", reg, target_pc.to_debug_int()), ), @@ -331,7 +331,7 @@ pub fn insn_to_str( *reg as i32, target_pc.to_debug_int(), *jump_if_null as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("if !r[{}] goto {}", reg, target_pc.to_debug_int()), ), @@ -343,7 +343,7 @@ pub fn insn_to_str( *cursor_id as i32, *root_page as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "table={}, root={}", @@ -359,7 +359,7 @@ pub fn insn_to_str( 0, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -368,7 +368,7 @@ pub fn insn_to_str( *cursor_id as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -377,7 +377,7 @@ pub fn insn_to_str( 0, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -391,7 +391,7 @@ pub fn insn_to_str( *cursor_id as i32, pc_if_empty.to_debug_int(), *arg_count as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -404,7 +404,7 @@ pub fn insn_to_str( *cursor_id as i32, *column as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -416,7 +416,7 @@ pub fn insn_to_str( *cursor_id as i32, pc_if_next.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -429,7 +429,7 @@ pub fn insn_to_str( *cursor_id as i32, *content_reg as i32, *num_fields as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("{} columns in r[{}]", num_fields, content_reg), ), @@ -438,7 +438,7 @@ pub fn insn_to_str( *cursor_id as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -450,7 +450,7 @@ pub fn insn_to_str( *cursor_id as i32, pc_if_empty.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "Rewind table {}", @@ -487,7 +487,7 @@ pub fn insn_to_str( *cursor_id as i32, *column as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "r[{}]={}.{}", @@ -508,7 +508,7 @@ pub fn insn_to_str( *start_reg as i32, *count as i32, *dest_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "r[{}]=mkrec(r[{}..{}])", @@ -522,7 +522,7 @@ pub fn insn_to_str( *start_reg as i32, *count as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, if *count == 1 { format!("output=r[{}]", start_reg) @@ -535,7 +535,7 @@ pub fn insn_to_str( *cursor_id as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -547,7 +547,7 @@ pub fn insn_to_str( *cursor_id as i32, pc_if_next.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -559,7 +559,7 @@ pub fn insn_to_str( *err_code as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -568,7 +568,7 @@ pub fn insn_to_str( 0, *write as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -577,7 +577,7 @@ pub fn insn_to_str( 0, target_pc.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -589,7 +589,7 @@ pub fn insn_to_str( *return_reg as i32, target_pc.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -598,7 +598,7 @@ pub fn insn_to_str( *return_reg as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -607,7 +607,7 @@ pub fn insn_to_str( *value as i32, *dest as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]={}", dest, value), ), @@ -625,7 +625,7 @@ pub fn insn_to_str( *register as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -634,7 +634,7 @@ pub fn insn_to_str( 0, *dest as i32, 0, - OwnedValue::build_text(Rc::new(value.clone())), + OwnedValue::build_text(value), 0, format!("r[{}]='{}'", dest, value), ), @@ -657,7 +657,7 @@ pub fn insn_to_str( *cursor_id as i32, *dest as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "r[{}]={}.rowid", @@ -677,7 +677,7 @@ pub fn insn_to_str( *cursor_id as i32, *src_reg as i32, target_pc.to_debug_int(), - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "if (r[{}]!={}.rowid) goto {}", @@ -697,7 +697,7 @@ pub fn insn_to_str( *index_cursor_id as i32, *table_cursor_id as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -712,7 +712,7 @@ pub fn insn_to_str( *cursor_id as i32, target_pc.to_debug_int(), *start_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -727,7 +727,7 @@ pub fn insn_to_str( *cursor_id as i32, target_pc.to_debug_int(), *start_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -741,7 +741,7 @@ pub fn insn_to_str( *cursor_id as i32, target_pc.to_debug_int(), *start_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -755,7 +755,7 @@ pub fn insn_to_str( *cursor_id as i32, target_pc.to_debug_int(), *start_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -764,7 +764,7 @@ pub fn insn_to_str( *reg as i32, target_pc.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("if (--r[{}]==0) goto {}", reg, target_pc.to_debug_int()), ), @@ -778,7 +778,7 @@ pub fn insn_to_str( 0, *col as i32, *acc_reg as i32, - OwnedValue::build_text(Rc::new(func.to_string().into())), + OwnedValue::build_text(func.to_string()), 0, format!("accum=r[{}] step(r[{}])", *acc_reg, *col), ), @@ -787,7 +787,7 @@ pub fn insn_to_str( 0, *register as i32, 0, - OwnedValue::build_text(Rc::new(func.to_string().into())), + OwnedValue::build_text(func.to_string()), 0, format!("accum=r[{}]", *register), ), @@ -798,7 +798,7 @@ pub fn insn_to_str( } => { let _p4 = String::new(); let to_print: Vec = order - .values + .get_values() .iter() .map(|v| match v { OwnedValue::Integer(i) => { @@ -816,11 +816,7 @@ pub fn insn_to_str( *cursor_id as i32, *columns as i32, 0, - OwnedValue::build_text(Rc::new(format!( - "k({},{})", - order.values.len(), - to_print.join(",") - ))), + OwnedValue::build_text(&(format!("k({},{})", order.len(), to_print.join(",")))), 0, format!("cursor={}", cursor_id), ) @@ -834,7 +830,7 @@ pub fn insn_to_str( *cursor_id as i32, *dest_reg as i32, *pseudo_cursor as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=data", dest_reg), ), @@ -858,7 +854,7 @@ pub fn insn_to_str( *cursor_id as i32, pc_if_empty.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -870,7 +866,7 @@ pub fn insn_to_str( *cursor_id as i32, pc_if_next.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -884,7 +880,7 @@ pub fn insn_to_str( *constant_mask, *start_reg as i32, *dest as i32, - OwnedValue::build_text(Rc::new(func.func.to_string())), + OwnedValue::build_text(&func.func.to_string()), 0, if func.arg_count == 0 { format!("r[{}]=func()", dest) @@ -908,7 +904,7 @@ pub fn insn_to_str( *yield_reg as i32, jump_on_definition.to_debug_int(), start_offset.to_debug_int(), - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -917,7 +913,7 @@ pub fn insn_to_str( *yield_reg as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -929,7 +925,7 @@ pub fn insn_to_str( *yield_reg as i32, end_offset.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -943,7 +939,7 @@ pub fn insn_to_str( *cursor as i32, *record_reg as i32, *key_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), *flag as u16, "".to_string(), ), @@ -952,7 +948,7 @@ pub fn insn_to_str( *cursor_id as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -961,7 +957,7 @@ pub fn insn_to_str( *cursor_id as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -970,7 +966,7 @@ pub fn insn_to_str( *cursor_id as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -983,7 +979,7 @@ pub fn insn_to_str( *cursor as i32, *rowid_reg as i32, *prev_largest_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -992,7 +988,7 @@ pub fn insn_to_str( *reg as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1001,7 +997,7 @@ pub fn insn_to_str( *reg as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1014,7 +1010,7 @@ pub fn insn_to_str( *cursor as i32, target_pc.to_debug_int(), *rowid_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1027,7 +1023,7 @@ pub fn insn_to_str( *limit_reg as i32, *combined_reg as i32, *offset_reg as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "if r[{}]>0 then r[{}]=r[{}]+max(0,r[{}]) else r[{}]=(-1)", @@ -1042,7 +1038,7 @@ pub fn insn_to_str( *cursor_id as i32, *root_page as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1051,7 +1047,7 @@ pub fn insn_to_str( 0, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1064,7 +1060,7 @@ pub fn insn_to_str( *src_reg as i32, *dst_reg as i32, *amount as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}]", dst_reg, src_reg), ), @@ -1073,7 +1069,7 @@ pub fn insn_to_str( *db as i32, *root as i32, *flags as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=root iDb={} flags={}", root, db, flags), ), @@ -1082,7 +1078,7 @@ pub fn insn_to_str( *cursor_id as i32, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1091,7 +1087,7 @@ pub fn insn_to_str( 0, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1100,7 +1096,7 @@ pub fn insn_to_str( *reg as i32, target_pc.to_debug_int(), 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("if (r[{}]==NULL) goto {}", reg, target_pc.to_debug_int()), ), @@ -1109,7 +1105,7 @@ pub fn insn_to_str( *db as i32, 0, 0, - OwnedValue::build_text(Rc::new(where_clause.clone())), + OwnedValue::build_text(where_clause), 0, where_clause.clone(), ), @@ -1118,7 +1114,7 @@ pub fn insn_to_str( 0, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1127,7 +1123,7 @@ pub fn insn_to_str( 0, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1136,7 +1132,7 @@ pub fn insn_to_str( 0, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, "".to_string(), ), @@ -1145,7 +1141,7 @@ pub fn insn_to_str( *rhs as i32, *lhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}] >> r[{}]", dest, lhs, rhs), ), @@ -1154,7 +1150,7 @@ pub fn insn_to_str( *rhs as i32, *lhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}] << r[{}]", dest, lhs, rhs), ), @@ -1163,7 +1159,7 @@ pub fn insn_to_str( usize::from(*index) as i32, *dest as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=parameter({})", *dest, *index), ), @@ -1172,7 +1168,7 @@ pub fn insn_to_str( *rg1 as i32, *dest as i32, *rg2 as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!( "((r[{}]=NULL)|(r[{}]=NULL)) ? r[{}]=NULL : r[{}]=0", @@ -1184,7 +1180,7 @@ pub fn insn_to_str( *reg as i32, *dest as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=!r[{}]", dest, reg), ), @@ -1193,7 +1189,7 @@ pub fn insn_to_str( *rhs as i32, *lhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=r[{}] + r[{}]", dest, lhs, rhs), ), @@ -1202,7 +1198,7 @@ pub fn insn_to_str( *rhs as i32, *lhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=(r[{}] && r[{}])", dest, lhs, rhs), ), @@ -1211,7 +1207,7 @@ pub fn insn_to_str( *rhs as i32, *lhs as i32, *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, format!("r[{}]=(r[{}] || r[{}])", dest, lhs, rhs), ), @@ -1220,7 +1216,7 @@ pub fn insn_to_str( 0, 0, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), 0, String::new(), ), @@ -1229,7 +1225,16 @@ pub fn insn_to_str( *db as i32, *dest as i32, 0, - OwnedValue::build_text(Rc::new("".to_string())), + OwnedValue::build_text(""), + 0, + "".to_string(), + ), + Insn::ReadCookie { db, dest, cookie } => ( + "ReadCookie", + *db as i32, + *dest as i32, + *cookie as i32, + OwnedValue::build_text(""), 0, "".to_string(), ), diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 2d673b018..e58de109c 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -1,5 +1,4 @@ use std::num::NonZero; -use std::rc::Rc; use super::{AggFunc, BranchOffset, CursorID, FuncCtx, PageIdx}; use crate::storage::wal::CheckpointMode; @@ -640,6 +639,29 @@ pub enum Insn { db: usize, dest: usize, }, + /// Read cookie number P3 from database P1 and write it into register P2 + ReadCookie { + db: usize, + dest: usize, + cookie: Cookie, + }, +} + +// TODO: Add remaining cookies. +#[derive(Description, Debug, Clone, Copy)] +pub enum Cookie { + /// The schema cookie. + SchemaVersion = 1, + /// The schema format number. Supported schema formats are 1, 2, 3, and 4. + DatabaseFormat = 2, + /// Default page cache size. + DefaultPageCacheSize = 3, + /// The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise. + LargestRootPageNumber = 4, + /// The database text encoding. A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be. + DatabaseTextEncoding = 5, + /// The "user version" as read and set by the user_version pragma. + UserVersion = 6, } fn cast_text_to_numerical(value: &str) -> OwnedValue { @@ -985,56 +1007,56 @@ pub fn exec_boolean_not(mut reg: &OwnedValue) -> OwnedValue { pub fn exec_concat(lhs: &OwnedValue, rhs: &OwnedValue) -> OwnedValue { match (lhs, rhs) { (OwnedValue::Text(lhs_text), OwnedValue::Text(rhs_text)) => { - OwnedValue::build_text(Rc::new(lhs_text.as_str().to_string() + &rhs_text.as_str())) + OwnedValue::build_text(&(lhs_text.as_str().to_string() + rhs_text.as_str())) } - (OwnedValue::Text(lhs_text), OwnedValue::Integer(rhs_int)) => OwnedValue::build_text( - Rc::new(lhs_text.as_str().to_string() + &rhs_int.to_string()), + (OwnedValue::Text(lhs_text), OwnedValue::Integer(rhs_int)) => { + OwnedValue::build_text(&(lhs_text.as_str().to_string() + &rhs_int.to_string())) + } + (OwnedValue::Text(lhs_text), OwnedValue::Float(rhs_float)) => { + OwnedValue::build_text(&(lhs_text.as_str().to_string() + &rhs_float.to_string())) + } + (OwnedValue::Text(lhs_text), OwnedValue::Agg(rhs_agg)) => OwnedValue::build_text( + (lhs_text.as_str().to_string() + &rhs_agg.final_value().to_string()).as_str(), ), - (OwnedValue::Text(lhs_text), OwnedValue::Float(rhs_float)) => OwnedValue::build_text( - Rc::new(lhs_text.as_str().to_string() + &rhs_float.to_string()), - ), - (OwnedValue::Text(lhs_text), OwnedValue::Agg(rhs_agg)) => OwnedValue::build_text(Rc::new( - lhs_text.as_str().to_string() + &rhs_agg.final_value().to_string(), - )), (OwnedValue::Integer(lhs_int), OwnedValue::Text(rhs_text)) => { - OwnedValue::build_text(Rc::new(lhs_int.to_string() + rhs_text.as_str())) + OwnedValue::build_text(&(lhs_int.to_string() + rhs_text.as_str())) } (OwnedValue::Integer(lhs_int), OwnedValue::Integer(rhs_int)) => { - OwnedValue::build_text(Rc::new(lhs_int.to_string() + &rhs_int.to_string())) + OwnedValue::build_text(&(lhs_int.to_string() + &rhs_int.to_string())) } (OwnedValue::Integer(lhs_int), OwnedValue::Float(rhs_float)) => { - OwnedValue::build_text(Rc::new(lhs_int.to_string() + &rhs_float.to_string())) + OwnedValue::build_text(&(lhs_int.to_string() + &rhs_float.to_string())) + } + (OwnedValue::Integer(lhs_int), OwnedValue::Agg(rhs_agg)) => { + OwnedValue::build_text(&(lhs_int.to_string() + &rhs_agg.final_value().to_string())) } - (OwnedValue::Integer(lhs_int), OwnedValue::Agg(rhs_agg)) => OwnedValue::build_text( - Rc::new(lhs_int.to_string() + &rhs_agg.final_value().to_string()), - ), (OwnedValue::Float(lhs_float), OwnedValue::Text(rhs_text)) => { - OwnedValue::build_text(Rc::new(lhs_float.to_string() + rhs_text.as_str())) + OwnedValue::build_text(&(lhs_float.to_string() + rhs_text.as_str())) } (OwnedValue::Float(lhs_float), OwnedValue::Integer(rhs_int)) => { - OwnedValue::build_text(Rc::new(lhs_float.to_string() + &rhs_int.to_string())) + OwnedValue::build_text(&(lhs_float.to_string() + &rhs_int.to_string())) } (OwnedValue::Float(lhs_float), OwnedValue::Float(rhs_float)) => { - OwnedValue::build_text(Rc::new(lhs_float.to_string() + &rhs_float.to_string())) + OwnedValue::build_text(&(lhs_float.to_string() + &rhs_float.to_string())) + } + (OwnedValue::Float(lhs_float), OwnedValue::Agg(rhs_agg)) => { + OwnedValue::build_text(&(lhs_float.to_string() + &rhs_agg.final_value().to_string())) } - (OwnedValue::Float(lhs_float), OwnedValue::Agg(rhs_agg)) => OwnedValue::build_text( - Rc::new(lhs_float.to_string() + &rhs_agg.final_value().to_string()), - ), - (OwnedValue::Agg(lhs_agg), OwnedValue::Text(rhs_text)) => OwnedValue::build_text(Rc::new( - lhs_agg.final_value().to_string() + rhs_text.as_str(), - )), - (OwnedValue::Agg(lhs_agg), OwnedValue::Integer(rhs_int)) => OwnedValue::build_text( - Rc::new(lhs_agg.final_value().to_string() + &rhs_int.to_string()), + (OwnedValue::Agg(lhs_agg), OwnedValue::Text(rhs_text)) => { + OwnedValue::build_text(&(lhs_agg.final_value().to_string() + rhs_text.as_str())) + } + (OwnedValue::Agg(lhs_agg), OwnedValue::Integer(rhs_int)) => { + OwnedValue::build_text(&(lhs_agg.final_value().to_string() + &rhs_int.to_string())) + } + (OwnedValue::Agg(lhs_agg), OwnedValue::Float(rhs_float)) => { + OwnedValue::build_text(&(lhs_agg.final_value().to_string() + &rhs_float.to_string())) + } + (OwnedValue::Agg(lhs_agg), OwnedValue::Agg(rhs_agg)) => OwnedValue::build_text( + &(lhs_agg.final_value().to_string() + &rhs_agg.final_value().to_string()), ), - (OwnedValue::Agg(lhs_agg), OwnedValue::Float(rhs_float)) => OwnedValue::build_text( - Rc::new(lhs_agg.final_value().to_string() + &rhs_float.to_string()), - ), - (OwnedValue::Agg(lhs_agg), OwnedValue::Agg(rhs_agg)) => OwnedValue::build_text(Rc::new( - lhs_agg.final_value().to_string() + &rhs_agg.final_value().to_string(), - )), (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, (OwnedValue::Blob(_), _) | (_, OwnedValue::Blob(_)) => { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 3ccfdc1e3..d60a70e1e 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -55,7 +55,7 @@ use crate::{resolve_ext_path, Connection, Result, TransactionState, DATABASE_VER use insn::{ exec_add, exec_and, exec_bit_and, exec_bit_not, exec_bit_or, exec_boolean_not, exec_concat, exec_divide, exec_multiply, exec_or, exec_remainder, exec_shift_left, exec_shift_right, - exec_subtract, + exec_subtract, Cookie, }; use likeop::{construct_like_escape_arg, exec_glob, exec_like_with_escape}; use rand::distributions::{Distribution, Uniform}; @@ -1020,7 +1020,7 @@ impl Program { state.registers[*dest] = if cursor.get_null_flag() { OwnedValue::Null } else { - record.values[*column].clone() + record.get_value(*column).clone() }; } else { state.registers[*dest] = OwnedValue::Null; @@ -1029,7 +1029,7 @@ impl Program { CursorType::Sorter => { let cursor = get_cursor_as_sorter_mut(&mut cursors, *cursor_id); if let Some(record) = cursor.record() { - state.registers[*dest] = record.values[*column].clone(); + state.registers[*dest] = record.get_value(*column).clone(); } else { state.registers[*dest] = OwnedValue::Null; } @@ -1037,7 +1037,7 @@ impl Program { CursorType::Pseudo(_) => { let cursor = get_cursor_as_pseudo_mut(&mut cursors, *cursor_id); if let Some(record) = cursor.record() { - state.registers[*dest] = record.values[*column].clone(); + state.registers[*dest] = record.get_value(*column).clone(); } else { state.registers[*dest] = OwnedValue::Null; } @@ -1234,7 +1234,7 @@ impl Program { state.pc += 1; } Insn::String8 { value, dest } => { - state.registers[*dest] = OwnedValue::build_text(Rc::new(value.into())); + state.registers[*dest] = OwnedValue::build_text(value); state.pc += 1; } Insn::Blob { value, dest } => { @@ -1403,8 +1403,8 @@ impl Program { make_owned_record(&state.registers, start_reg, num_regs); if let Some(ref idx_record) = *cursor.record()? { // omit the rowid from the idx_record, which is the last value - if idx_record.values[..idx_record.values.len() - 1] - >= *record_from_regs.values + if idx_record.get_values()[..idx_record.len() - 1] + >= record_from_regs.get_values()[..] { state.pc = target_pc.to_offset_int(); } else { @@ -1427,8 +1427,8 @@ impl Program { make_owned_record(&state.registers, start_reg, num_regs); if let Some(ref idx_record) = *cursor.record()? { // omit the rowid from the idx_record, which is the last value - if idx_record.values[..idx_record.values.len() - 1] - > *record_from_regs.values + if idx_record.get_values()[..idx_record.len() - 1] + > record_from_regs.get_values()[..] { state.pc = target_pc.to_offset_int(); } else { @@ -1511,11 +1511,9 @@ impl Program { } } } - AggFunc::GroupConcat | AggFunc::StringAgg => { - OwnedValue::Agg(Box::new(AggContext::GroupConcat( - OwnedValue::build_text(Rc::new("".to_string())), - ))) - } + AggFunc::GroupConcat | AggFunc::StringAgg => OwnedValue::Agg(Box::new( + AggContext::GroupConcat(OwnedValue::build_text("")), + )), AggFunc::External(func) => match func.as_ref() { ExtFunc::Aggregate { init, @@ -1744,7 +1742,7 @@ impl Program { order, } => { let order = order - .values + .get_values() .iter() .map(|v| match v { OwnedValue::Integer(i) => *i == 0, @@ -2146,19 +2144,31 @@ impl Program { } ScalarFunc::Trim => { let reg_value = state.registers[*start_reg].clone(); - let pattern_value = state.registers.get(*start_reg + 1).cloned(); + let pattern_value = if func.arg_count == 2 { + state.registers.get(*start_reg + 1).cloned() + } else { + None + }; let result = exec_trim(®_value, pattern_value); state.registers[*dest] = result; } ScalarFunc::LTrim => { let reg_value = state.registers[*start_reg].clone(); - let pattern_value = state.registers.get(*start_reg + 1).cloned(); + let pattern_value = if func.arg_count == 2 { + state.registers.get(*start_reg + 1).cloned() + } else { + None + }; let result = exec_ltrim(®_value, pattern_value); state.registers[*dest] = result; } ScalarFunc::RTrim => { let reg_value = state.registers[*start_reg].clone(); - let pattern_value = state.registers.get(*start_reg + 1).cloned(); + let pattern_value = if func.arg_count == 2 { + state.registers.get(*start_reg + 1).cloned() + } else { + None + }; let result = exec_rtrim(®_value, pattern_value); state.registers[*dest] = result; } @@ -2222,18 +2232,15 @@ impl Program { } ScalarFunc::JulianDay => { if *start_reg == 0 { - let julianday: String = exec_julianday( - &OwnedValue::build_text(Rc::new("now".to_string())), - )?; - state.registers[*dest] = - OwnedValue::build_text(Rc::new(julianday)); + let julianday: String = + exec_julianday(&OwnedValue::build_text("now"))?; + state.registers[*dest] = OwnedValue::build_text(&julianday); } else { let datetime_value = &state.registers[*start_reg]; let julianday = exec_julianday(datetime_value); match julianday { Ok(time) => { - state.registers[*dest] = - OwnedValue::build_text(Rc::new(time)) + state.registers[*dest] = OwnedValue::build_text(&time) } Err(e) => { return Err(LimboError::ParseError(format!( @@ -2246,18 +2253,15 @@ impl Program { } ScalarFunc::UnixEpoch => { if *start_reg == 0 { - let unixepoch: String = exec_unixepoch( - &OwnedValue::build_text(Rc::new("now".to_string())), - )?; - state.registers[*dest] = - OwnedValue::build_text(Rc::new(unixepoch)); + let unixepoch: String = + exec_unixepoch(&OwnedValue::build_text("now"))?; + state.registers[*dest] = OwnedValue::build_text(&unixepoch); } else { let datetime_value = &state.registers[*start_reg]; let unixepoch = exec_unixepoch(datetime_value); match unixepoch { Ok(time) => { - state.registers[*dest] = - OwnedValue::build_text(Rc::new(time)) + state.registers[*dest] = OwnedValue::build_text(&time) } Err(e) => { return Err(LimboError::ParseError(format!( @@ -2272,7 +2276,7 @@ impl Program { let version_integer: i64 = DATABASE_VERSION.get().unwrap().parse()?; let version = execute_sqlite_version(version_integer); - state.registers[*dest] = OwnedValue::build_text(Rc::new(version)); + state.registers[*dest] = OwnedValue::build_text(&version); } ScalarFunc::SqliteSourceId => { let src_id = format!( @@ -2280,7 +2284,7 @@ impl Program { info::build::BUILT_TIME_SQLITE, info::build::GIT_COMMIT_HASH.unwrap_or("unknown") ); - state.registers[*dest] = OwnedValue::build_text(Rc::new(src_id)); + state.registers[*dest] = OwnedValue::build_text(&src_id); } ScalarFunc::Replace => { assert_eq!(arg_count, 3); @@ -2425,7 +2429,7 @@ impl Program { let pc: u32 = pc .try_into() .unwrap_or_else(|_| panic!("EndCoroutine: pc overflow: {}", pc)); - state.pc = pc - 1; // yield jump is always next to yield. Here we substract 1 to go back to yield instruction + state.pc = pc - 1; // yield jump is always next to yield. Here we subtract 1 to go back to yield instruction } else { unreachable!(); } @@ -2652,7 +2656,7 @@ impl Program { todo!("temp databases not implemented yet"); } // SQLite returns "0" on an empty database, and 2 on the first insertion, - // so we'll mimick that behavior. + // so we'll mimic that behavior. let mut pages = pager.db_header.borrow().database_size.into(); if pages == 1 { pages = 0; @@ -2675,6 +2679,18 @@ impl Program { parse_schema_rows(Some(stmt), &mut schema, conn.pager.io.clone())?; state.pc += 1; } + Insn::ReadCookie { db, dest, cookie } => { + if *db > 0 { + // TODO: implement temp databases + todo!("temp databases not implemented yet"); + } + let cookie_value = match cookie { + Cookie::UserVersion => pager.db_header.borrow().user_version.into(), + cookie => todo!("{cookie:?} is not yet implement for ReadCookie"), + }; + state.registers[*dest] = OwnedValue::Integer(cookie_value); + state.pc += 1; + } Insn::ShiftRight { lhs, rhs, dest } => { state.registers[*dest] = exec_shift_right(&state.registers[*lhs], &state.registers[*rhs]); @@ -2829,7 +2845,7 @@ fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&In fn exec_lower(reg: &OwnedValue) -> Option { match reg { - OwnedValue::Text(t) => Some(OwnedValue::build_text(Rc::new(t.as_str().to_lowercase()))), + OwnedValue::Text(t) => Some(OwnedValue::build_text(&t.as_str().to_lowercase())), t => Some(t.to_owned()), } } @@ -2858,7 +2874,7 @@ fn exec_octet_length(reg: &OwnedValue) -> OwnedValue { fn exec_upper(reg: &OwnedValue) -> Option { match reg { - OwnedValue::Text(t) => Some(OwnedValue::build_text(Rc::new(t.as_str().to_uppercase()))), + OwnedValue::Text(t) => Some(OwnedValue::build_text(&t.as_str().to_uppercase())), t => Some(t.to_owned()), } } @@ -2876,7 +2892,7 @@ fn exec_concat_strings(registers: &[OwnedValue]) -> OwnedValue { OwnedValue::Record(_) => unreachable!(), } } - OwnedValue::build_text(Rc::new(result)) + OwnedValue::build_text(&result) } fn exec_concat_ws(registers: &[OwnedValue]) -> OwnedValue { @@ -2904,7 +2920,7 @@ fn exec_concat_ws(registers: &[OwnedValue]) -> OwnedValue { } } - OwnedValue::build_text(Rc::new(result)) + OwnedValue::build_text(&result) } fn exec_sign(reg: &OwnedValue) -> Option { @@ -2949,15 +2965,15 @@ fn exec_sign(reg: &OwnedValue) -> Option { /// Generates the Soundex code for a given word pub fn exec_soundex(reg: &OwnedValue) -> OwnedValue { let s = match reg { - OwnedValue::Null => return OwnedValue::build_text(Rc::new("?000".to_string())), + OwnedValue::Null => return OwnedValue::build_text("?000"), OwnedValue::Text(s) => { // return ?000 if non ASCII alphabet character is found if !s.as_str().chars().all(|c| c.is_ascii_alphabetic()) { - return OwnedValue::build_text(Rc::new("?000".to_string())); + return OwnedValue::build_text("?000"); } s.clone() } - _ => return OwnedValue::build_text(Rc::new("?000".to_string())), // For unsupported types, return NULL + _ => return OwnedValue::build_text("?000"), // For unsupported types, return NULL }; // Remove numbers and spaces @@ -2968,7 +2984,7 @@ pub fn exec_soundex(reg: &OwnedValue) -> OwnedValue { .collect::() .replace(" ", ""); if word.is_empty() { - return OwnedValue::build_text(Rc::new("0000".to_string())); + return OwnedValue::build_text("0000"); } let soundex_code = |c| match c { @@ -3034,7 +3050,7 @@ pub fn exec_soundex(reg: &OwnedValue) -> OwnedValue { // Retain the first 4 characters and convert to uppercase result.truncate(4); - OwnedValue::build_text(Rc::new(result.to_uppercase())) + OwnedValue::build_text(&result.to_uppercase()) } fn exec_abs(reg: &OwnedValue) -> Result { @@ -3082,7 +3098,7 @@ fn exec_randomblob(reg: &OwnedValue) -> OwnedValue { fn exec_quote(value: &OwnedValue) -> OwnedValue { match value { - OwnedValue::Null => OwnedValue::build_text(OwnedValue::Null.to_string().into()), + OwnedValue::Null => OwnedValue::build_text(&OwnedValue::Null.to_string()), OwnedValue::Integer(_) | OwnedValue::Float(_) => value.to_owned(), OwnedValue::Blob(_) => todo!(), OwnedValue::Text(s) => { @@ -3091,12 +3107,15 @@ fn exec_quote(value: &OwnedValue) -> OwnedValue { for c in s.as_str().chars() { if c == '\0' { break; + } else if c == '\'' { + quoted.push('\''); + quoted.push(c); } else { quoted.push(c); } } quoted.push('\''); - OwnedValue::build_text(Rc::new(quoted)) + OwnedValue::build_text("ed) } _ => OwnedValue::Null, // For unsupported types, return NULL } @@ -3113,7 +3132,7 @@ fn exec_char(values: Vec) -> OwnedValue { } }) .collect(); - OwnedValue::build_text(Rc::new(result)) + OwnedValue::build_text(&result) } fn construct_like_regex(pattern: &str) -> Regex { @@ -3196,7 +3215,7 @@ fn exec_substring( let str_len = str.as_str().len(); if start > str_len { - return OwnedValue::build_text(Rc::new("".to_string())); + return OwnedValue::build_text(""); } let start_idx = start - 1; @@ -3207,19 +3226,19 @@ fn exec_substring( }; let substring = &str.as_str()[start_idx..end.min(str_len)]; - OwnedValue::build_text(Rc::new(substring.to_string())) + OwnedValue::build_text(substring) } else if let (OwnedValue::Text(str), OwnedValue::Integer(start)) = (str_value, start_value) { let start = *start as usize; let str_len = str.as_str().len(); if start > str_len { - return OwnedValue::build_text(Rc::new("".to_string())); + return OwnedValue::build_text(""); } let start_idx = start - 1; let substring = &str.as_str()[start_idx..str_len]; - OwnedValue::build_text(Rc::new(substring.to_string())) + OwnedValue::build_text(substring) } else { OwnedValue::Null } @@ -3264,11 +3283,11 @@ fn exec_instr(reg: &OwnedValue, pattern: &OwnedValue) -> OwnedValue { fn exec_typeof(reg: &OwnedValue) -> OwnedValue { match reg { - OwnedValue::Null => OwnedValue::build_text(Rc::new("null".to_string())), - OwnedValue::Integer(_) => OwnedValue::build_text(Rc::new("integer".to_string())), - OwnedValue::Float(_) => OwnedValue::build_text(Rc::new("real".to_string())), - OwnedValue::Text(_) => OwnedValue::build_text(Rc::new("text".to_string())), - OwnedValue::Blob(_) => OwnedValue::build_text(Rc::new("blob".to_string())), + OwnedValue::Null => OwnedValue::build_text("null"), + OwnedValue::Integer(_) => OwnedValue::build_text("integer"), + OwnedValue::Float(_) => OwnedValue::build_text("real"), + OwnedValue::Text(_) => OwnedValue::build_text("text"), + OwnedValue::Blob(_) => OwnedValue::build_text("blob"), OwnedValue::Agg(ctx) => exec_typeof(ctx.final_value()), OwnedValue::Record(_) => unimplemented!(), } @@ -3281,7 +3300,7 @@ fn exec_hex(reg: &OwnedValue) -> OwnedValue { | OwnedValue::Float(_) | OwnedValue::Blob(_) => { let text = reg.to_string(); - OwnedValue::build_text(Rc::new(hex::encode_upper(text))) + OwnedValue::build_text(&hex::encode_upper(text)) } _ => OwnedValue::Null, } @@ -3365,15 +3384,11 @@ fn exec_trim(reg: &OwnedValue, pattern: Option) -> OwnedValue { (reg, Some(pattern)) => match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { let pattern_chars: Vec = pattern.to_string().chars().collect(); - OwnedValue::build_text(Rc::new( - reg.to_string().trim_matches(&pattern_chars[..]).to_string(), - )) + OwnedValue::build_text(reg.to_string().trim_matches(&pattern_chars[..])) } _ => reg.to_owned(), }, - (OwnedValue::Text(t), None) => { - OwnedValue::build_text(Rc::new(t.as_str().trim().to_string())) - } + (OwnedValue::Text(t), None) => OwnedValue::build_text(t.as_str().trim()), (reg, _) => reg.to_owned(), } } @@ -3384,17 +3399,11 @@ fn exec_ltrim(reg: &OwnedValue, pattern: Option) -> OwnedValue { (reg, Some(pattern)) => match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { let pattern_chars: Vec = pattern.to_string().chars().collect(); - OwnedValue::build_text(Rc::new( - reg.to_string() - .trim_start_matches(&pattern_chars[..]) - .to_string(), - )) + OwnedValue::build_text(reg.to_string().trim_start_matches(&pattern_chars[..])) } _ => reg.to_owned(), }, - (OwnedValue::Text(t), None) => { - OwnedValue::build_text(Rc::new(t.as_str().trim_start().to_string())) - } + (OwnedValue::Text(t), None) => OwnedValue::build_text(t.as_str().trim_start()), (reg, _) => reg.to_owned(), } } @@ -3405,17 +3414,11 @@ fn exec_rtrim(reg: &OwnedValue, pattern: Option) -> OwnedValue { (reg, Some(pattern)) => match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { let pattern_chars: Vec = pattern.to_string().chars().collect(); - OwnedValue::build_text(Rc::new( - reg.to_string() - .trim_end_matches(&pattern_chars[..]) - .to_string(), - )) + OwnedValue::build_text(reg.to_string().trim_end_matches(&pattern_chars[..])) } _ => reg.to_owned(), }, - (OwnedValue::Text(t), None) => { - OwnedValue::build_text(Rc::new(t.as_str().trim_end().to_string())) - } + (OwnedValue::Text(t), None) => OwnedValue::build_text(t.as_str().trim_end()), (reg, _) => reg.to_owned(), } } @@ -3458,7 +3461,7 @@ fn exec_cast(value: &OwnedValue, datatype: &str) -> OwnedValue { Affinity::Text => { // Convert everything to text representation // TODO: handle encoding and whatever sqlite3_snprintf does - OwnedValue::build_text(Rc::new(value.to_string())) + OwnedValue::build_text(&value.to_string()) } Affinity::Real => match value { OwnedValue::Blob(b) => { @@ -3484,7 +3487,7 @@ fn exec_cast(value: &OwnedValue, datatype: &str) -> OwnedValue { // then the result is the greatest possible signed integer and if the REAL is less than the least possible signed integer (-9223372036854775808) // then the result is the least possible signed integer. OwnedValue::Float(f) => { - let i = f.floor() as i128; + let i = f.trunc() as i128; if i > i64::MAX as i128 { OwnedValue::Integer(i64::MAX) } else if i < i64::MIN as i128 { @@ -3535,7 +3538,7 @@ fn exec_replace(source: &OwnedValue, pattern: &OwnedValue, replacement: &OwnedVa let result = source .as_str() .replace(pattern.as_str(), replacement.as_str()); - OwnedValue::build_text(Rc::new(result)) + OwnedValue::build_text(&result) } _ => unreachable!("text cast should never fail"), } @@ -3795,7 +3798,7 @@ mod tests { #[test] fn test_length() { - let input_str = OwnedValue::build_text(Rc::new(String::from("bob"))); + let input_str = OwnedValue::build_text("bob"); let expected_len = OwnedValue::Integer(3); assert_eq!(exec_length(&input_str), expected_len); @@ -3814,60 +3817,57 @@ mod tests { #[test] fn test_quote() { - let input = OwnedValue::build_text(Rc::new(String::from("abc\0edf"))); - let expected = OwnedValue::build_text(Rc::new(String::from("'abc'"))); + let input = OwnedValue::build_text("abc\0edf"); + let expected = OwnedValue::build_text("'abc'"); assert_eq!(exec_quote(&input), expected); let input = OwnedValue::Integer(123); let expected = OwnedValue::Integer(123); assert_eq!(exec_quote(&input), expected); - let input = OwnedValue::build_text(Rc::new(String::from("hello''world"))); - let expected = OwnedValue::build_text(Rc::new(String::from("'hello''world'"))); + let input = OwnedValue::build_text("hello''world"); + let expected = OwnedValue::build_text("'hello''''world'"); assert_eq!(exec_quote(&input), expected); } #[test] fn test_typeof() { let input = OwnedValue::Null; - let expected: OwnedValue = OwnedValue::build_text(Rc::new("null".to_string())); + let expected: OwnedValue = OwnedValue::build_text("null"); assert_eq!(exec_typeof(&input), expected); let input = OwnedValue::Integer(123); - let expected: OwnedValue = OwnedValue::build_text(Rc::new("integer".to_string())); + let expected: OwnedValue = OwnedValue::build_text("integer"); assert_eq!(exec_typeof(&input), expected); let input = OwnedValue::Float(123.456); - let expected: OwnedValue = OwnedValue::build_text(Rc::new("real".to_string())); + let expected: OwnedValue = OwnedValue::build_text("real"); assert_eq!(exec_typeof(&input), expected); - let input = OwnedValue::build_text(Rc::new("hello".to_string())); - let expected: OwnedValue = OwnedValue::build_text(Rc::new("text".to_string())); + let input = OwnedValue::build_text("hello"); + let expected: OwnedValue = OwnedValue::build_text("text"); assert_eq!(exec_typeof(&input), expected); let input = OwnedValue::Blob(Rc::new("limbo".as_bytes().to_vec())); - let expected: OwnedValue = OwnedValue::build_text(Rc::new("blob".to_string())); + let expected: OwnedValue = OwnedValue::build_text("blob"); assert_eq!(exec_typeof(&input), expected); let input = OwnedValue::Agg(Box::new(AggContext::Sum(OwnedValue::Integer(123)))); - let expected = OwnedValue::build_text(Rc::new("integer".to_string())); + let expected = OwnedValue::build_text("integer"); assert_eq!(exec_typeof(&input), expected); } #[test] fn test_unicode() { assert_eq!( - exec_unicode(&OwnedValue::build_text(Rc::new("a".to_string()))), + exec_unicode(&OwnedValue::build_text("a")), OwnedValue::Integer(97) ); assert_eq!( - exec_unicode(&OwnedValue::build_text(Rc::new("😊".to_string()))), + exec_unicode(&OwnedValue::build_text("😊")), OwnedValue::Integer(128522) ); - assert_eq!( - exec_unicode(&OwnedValue::build_text(Rc::new("".to_string()))), - OwnedValue::Null - ); + assert_eq!(exec_unicode(&OwnedValue::build_text("")), OwnedValue::Null); assert_eq!( exec_unicode(&OwnedValue::Integer(23)), OwnedValue::Integer(50) @@ -3897,17 +3897,11 @@ mod tests { assert_eq!(exec_min(input_int_vec.clone()), OwnedValue::Integer(-1)); assert_eq!(exec_max(input_int_vec.clone()), OwnedValue::Integer(10)); - let str1 = OwnedValue::build_text(Rc::new(String::from("A"))); - let str2 = OwnedValue::build_text(Rc::new(String::from("z"))); + let str1 = OwnedValue::build_text("A"); + let str2 = OwnedValue::build_text("z"); let input_str_vec = vec![&str2, &str1]; - assert_eq!( - exec_min(input_str_vec.clone()), - OwnedValue::build_text(Rc::new(String::from("A"))) - ); - assert_eq!( - exec_max(input_str_vec.clone()), - OwnedValue::build_text(Rc::new(String::from("z"))) - ); + assert_eq!(exec_min(input_str_vec.clone()), OwnedValue::build_text("A")); + assert_eq!(exec_max(input_str_vec.clone()), OwnedValue::build_text("z")); let input_null_vec = vec![&OwnedValue::Null, &OwnedValue::Null]; assert_eq!(exec_min(input_null_vec.clone()), OwnedValue::Null); @@ -3917,102 +3911,102 @@ mod tests { assert_eq!(exec_min(input_mixed_vec.clone()), OwnedValue::Integer(10)); assert_eq!( exec_max(input_mixed_vec.clone()), - OwnedValue::build_text(Rc::new(String::from("A"))) + OwnedValue::build_text("A") ); } #[test] fn test_trim() { - let input_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice "))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("Bob and Alice"))); + let input_str = OwnedValue::build_text(" Bob and Alice "); + let expected_str = OwnedValue::build_text("Bob and Alice"); assert_eq!(exec_trim(&input_str, None), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice "))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from("Bob and"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("Alice"))); + let input_str = OwnedValue::build_text(" Bob and Alice "); + let pattern_str = OwnedValue::build_text("Bob and"); + let expected_str = OwnedValue::build_text("Alice"); assert_eq!(exec_trim(&input_str, Some(pattern_str)), expected_str); } #[test] fn test_ltrim() { - let input_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice "))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("Bob and Alice "))); + let input_str = OwnedValue::build_text(" Bob and Alice "); + let expected_str = OwnedValue::build_text("Bob and Alice "); assert_eq!(exec_ltrim(&input_str, None), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice "))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from("Bob and"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("Alice "))); + let input_str = OwnedValue::build_text(" Bob and Alice "); + let pattern_str = OwnedValue::build_text("Bob and"); + let expected_str = OwnedValue::build_text("Alice "); assert_eq!(exec_ltrim(&input_str, Some(pattern_str)), expected_str); } #[test] fn test_rtrim() { - let input_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice "))); - let expected_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice"))); + let input_str = OwnedValue::build_text(" Bob and Alice "); + let expected_str = OwnedValue::build_text(" Bob and Alice"); assert_eq!(exec_rtrim(&input_str, None), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice "))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from("Bob and"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice"))); + let input_str = OwnedValue::build_text(" Bob and Alice "); + let pattern_str = OwnedValue::build_text("Bob and"); + let expected_str = OwnedValue::build_text(" Bob and Alice"); assert_eq!(exec_rtrim(&input_str, Some(pattern_str)), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from(" Bob and Alice "))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from("and Alice"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from(" Bob"))); + let input_str = OwnedValue::build_text(" Bob and Alice "); + let pattern_str = OwnedValue::build_text("and Alice"); + let expected_str = OwnedValue::build_text(" Bob"); assert_eq!(exec_rtrim(&input_str, Some(pattern_str)), expected_str); } #[test] fn test_soundex() { - let input_str = OwnedValue::build_text(Rc::new(String::from("Pfister"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("P236"))); + let input_str = OwnedValue::build_text("Pfister"); + let expected_str = OwnedValue::build_text("P236"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("husobee"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("H210"))); + let input_str = OwnedValue::build_text("husobee"); + let expected_str = OwnedValue::build_text("H210"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("Tymczak"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("T522"))); + let input_str = OwnedValue::build_text("Tymczak"); + let expected_str = OwnedValue::build_text("T522"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("Ashcraft"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("A261"))); + let input_str = OwnedValue::build_text("Ashcraft"); + let expected_str = OwnedValue::build_text("A261"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("Robert"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("R163"))); + let input_str = OwnedValue::build_text("Robert"); + let expected_str = OwnedValue::build_text("R163"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("Rupert"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("R163"))); + let input_str = OwnedValue::build_text("Rupert"); + let expected_str = OwnedValue::build_text("R163"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("Rubin"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("R150"))); + let input_str = OwnedValue::build_text("Rubin"); + let expected_str = OwnedValue::build_text("R150"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("Kant"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("K530"))); + let input_str = OwnedValue::build_text("Kant"); + let expected_str = OwnedValue::build_text("K530"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("Knuth"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("K530"))); + let input_str = OwnedValue::build_text("Knuth"); + let expected_str = OwnedValue::build_text("K530"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("x"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("X000"))); + let input_str = OwnedValue::build_text("x"); + let expected_str = OwnedValue::build_text("X000"); assert_eq!(exec_soundex(&input_str), expected_str); - let input_str = OwnedValue::build_text(Rc::new(String::from("闪电五连鞭"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("?000"))); + let input_str = OwnedValue::build_text("闪电五连鞭"); + let expected_str = OwnedValue::build_text("?000"); assert_eq!(exec_soundex(&input_str), expected_str); } #[test] fn test_upper_case() { - let input_str = OwnedValue::build_text(Rc::new(String::from("Limbo"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("LIMBO"))); + let input_str = OwnedValue::build_text("Limbo"); + let expected_str = OwnedValue::build_text("LIMBO"); assert_eq!(exec_upper(&input_str).unwrap(), expected_str); let input_int = OwnedValue::Integer(10); @@ -4022,8 +4016,8 @@ mod tests { #[test] fn test_lower_case() { - let input_str = OwnedValue::build_text(Rc::new(String::from("Limbo"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("limbo"))); + let input_str = OwnedValue::build_text("Limbo"); + let expected_str = OwnedValue::build_text("limbo"); assert_eq!(exec_lower(&input_str).unwrap(), expected_str); let input_int = OwnedValue::Integer(10); @@ -4033,38 +4027,38 @@ mod tests { #[test] fn test_hex() { - let input_str = OwnedValue::build_text(Rc::new("limbo".to_string())); - let expected_val = OwnedValue::build_text(Rc::new(String::from("6C696D626F"))); + let input_str = OwnedValue::build_text("limbo"); + let expected_val = OwnedValue::build_text("6C696D626F"); assert_eq!(exec_hex(&input_str), expected_val); let input_int = OwnedValue::Integer(100); - let expected_val = OwnedValue::build_text(Rc::new(String::from("313030"))); + let expected_val = OwnedValue::build_text("313030"); assert_eq!(exec_hex(&input_int), expected_val); let input_float = OwnedValue::Float(12.34); - let expected_val = OwnedValue::build_text(Rc::new(String::from("31322E3334"))); + let expected_val = OwnedValue::build_text("31322E3334"); assert_eq!(exec_hex(&input_float), expected_val); } #[test] fn test_unhex() { - let input = OwnedValue::build_text(Rc::new(String::from("6f"))); + let input = OwnedValue::build_text("6f"); let expected = OwnedValue::Blob(Rc::new(vec![0x6f])); assert_eq!(exec_unhex(&input, None), expected); - let input = OwnedValue::build_text(Rc::new(String::from("6f"))); + let input = OwnedValue::build_text("6f"); let expected = OwnedValue::Blob(Rc::new(vec![0x6f])); assert_eq!(exec_unhex(&input, None), expected); - let input = OwnedValue::build_text(Rc::new(String::from("611"))); + let input = OwnedValue::build_text("611"); let expected = OwnedValue::Null; assert_eq!(exec_unhex(&input, None), expected); - let input = OwnedValue::build_text(Rc::new(String::from(""))); + let input = OwnedValue::build_text(""); let expected = OwnedValue::Blob(Rc::new(vec![])); assert_eq!(exec_unhex(&input, None), expected); - let input = OwnedValue::build_text(Rc::new(String::from("61x"))); + let input = OwnedValue::build_text("61x"); let expected = OwnedValue::Null; assert_eq!(exec_unhex(&input, None), expected); @@ -4086,7 +4080,7 @@ mod tests { assert_eq!(exec_abs(&float_negative_reg).unwrap(), float_positive_reg); assert_eq!( - exec_abs(&OwnedValue::build_text(Rc::new(String::from("a")))).unwrap(), + exec_abs(&OwnedValue::build_text("a")).unwrap(), OwnedValue::Float(0.0) ); assert_eq!(exec_abs(&OwnedValue::Null).unwrap(), OwnedValue::Null); @@ -4099,19 +4093,16 @@ mod tests { fn test_char() { assert_eq!( exec_char(vec![OwnedValue::Integer(108), OwnedValue::Integer(105)]), - OwnedValue::build_text(Rc::new("li".to_string())) - ); - assert_eq!( - exec_char(vec![]), - OwnedValue::build_text(Rc::new("".to_string())) + OwnedValue::build_text("li") ); + assert_eq!(exec_char(vec![]), OwnedValue::build_text("")); assert_eq!( exec_char(vec![OwnedValue::Null]), - OwnedValue::build_text(Rc::new("".to_string())) + OwnedValue::build_text("") ); assert_eq!( - exec_char(vec![OwnedValue::build_text(Rc::new("a".to_string()))]), - OwnedValue::build_text(Rc::new("".to_string())) + exec_char(vec![OwnedValue::build_text("a")]), + OwnedValue::build_text("") ); } @@ -4182,19 +4173,19 @@ mod tests { expected_len: 1, }, TestCase { - input: OwnedValue::build_text(Rc::new(String::from(""))), + input: OwnedValue::build_text(""), expected_len: 1, }, TestCase { - input: OwnedValue::build_text(Rc::new(String::from("5"))), + input: OwnedValue::build_text("5"), expected_len: 5, }, TestCase { - input: OwnedValue::build_text(Rc::new(String::from("0"))), + input: OwnedValue::build_text("0"), expected_len: 1, }, TestCase { - input: OwnedValue::build_text(Rc::new(String::from("-1"))), + input: OwnedValue::build_text("-1"), expected_len: 1, }, TestCase { @@ -4234,11 +4225,11 @@ mod tests { assert_eq!(exec_round(&input_val, Some(precision_val)), expected_val); let input_val = OwnedValue::Float(123.456); - let precision_val = OwnedValue::build_text(Rc::new(String::from("1"))); + let precision_val = OwnedValue::build_text("1"); let expected_val = OwnedValue::Float(123.5); assert_eq!(exec_round(&input_val, Some(precision_val)), expected_val); - let input_val = OwnedValue::build_text(Rc::new(String::from("123.456"))); + let input_val = OwnedValue::build_text("123.456"); let precision_val = OwnedValue::Integer(2); let expected_val = OwnedValue::Float(123.46); assert_eq!(exec_round(&input_val, Some(precision_val)), expected_val); @@ -4292,8 +4283,8 @@ mod tests { ); assert_eq!( exec_nullif( - &OwnedValue::build_text(Rc::new("limbo".to_string())), - &OwnedValue::build_text(Rc::new("limbo".to_string())) + &OwnedValue::build_text("limbo"), + &OwnedValue::build_text("limbo") ), OwnedValue::Null ); @@ -4308,55 +4299,55 @@ mod tests { ); assert_eq!( exec_nullif( - &OwnedValue::build_text(Rc::new("limbo".to_string())), - &OwnedValue::build_text(Rc::new("limb".to_string())) + &OwnedValue::build_text("limbo"), + &OwnedValue::build_text("limb") ), - OwnedValue::build_text(Rc::new("limbo".to_string())) + OwnedValue::build_text("limbo") ); } #[test] fn test_substring() { - let str_value = OwnedValue::build_text(Rc::new("limbo".to_string())); + let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(1); let length_value = OwnedValue::Integer(3); - let expected_val = OwnedValue::build_text(Rc::new(String::from("lim"))); + let expected_val = OwnedValue::build_text("lim"); assert_eq!( exec_substring(&str_value, &start_value, &length_value), expected_val ); - let str_value = OwnedValue::build_text(Rc::new("limbo".to_string())); + let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(1); let length_value = OwnedValue::Integer(10); - let expected_val = OwnedValue::build_text(Rc::new(String::from("limbo"))); + let expected_val = OwnedValue::build_text("limbo"); assert_eq!( exec_substring(&str_value, &start_value, &length_value), expected_val ); - let str_value = OwnedValue::build_text(Rc::new("limbo".to_string())); + let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(10); let length_value = OwnedValue::Integer(3); - let expected_val = OwnedValue::build_text(Rc::new(String::from(""))); + let expected_val = OwnedValue::build_text(""); assert_eq!( exec_substring(&str_value, &start_value, &length_value), expected_val ); - let str_value = OwnedValue::build_text(Rc::new("limbo".to_string())); + let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(3); let length_value = OwnedValue::Null; - let expected_val = OwnedValue::build_text(Rc::new(String::from("mbo"))); + let expected_val = OwnedValue::build_text("mbo"); assert_eq!( exec_substring(&str_value, &start_value, &length_value), expected_val ); - let str_value = OwnedValue::build_text(Rc::new("limbo".to_string())); + let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(10); let length_value = OwnedValue::Null; - let expected_val = OwnedValue::build_text(Rc::new(String::from(""))); + let expected_val = OwnedValue::build_text(""); assert_eq!( exec_substring(&str_value, &start_value, &length_value), expected_val @@ -4365,43 +4356,43 @@ mod tests { #[test] fn test_exec_instr() { - let input = OwnedValue::build_text(Rc::new(String::from("limbo"))); - let pattern = OwnedValue::build_text(Rc::new(String::from("im"))); + let input = OwnedValue::build_text("limbo"); + let pattern = OwnedValue::build_text("im"); let expected = OwnedValue::Integer(2); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from("limbo"))); - let pattern = OwnedValue::build_text(Rc::new(String::from("limbo"))); + let input = OwnedValue::build_text("limbo"); + let pattern = OwnedValue::build_text("limbo"); let expected = OwnedValue::Integer(1); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from("limbo"))); - let pattern = OwnedValue::build_text(Rc::new(String::from("o"))); + let input = OwnedValue::build_text("limbo"); + let pattern = OwnedValue::build_text("o"); let expected = OwnedValue::Integer(5); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from("liiiiimbo"))); - let pattern = OwnedValue::build_text(Rc::new(String::from("ii"))); + let input = OwnedValue::build_text("liiiiimbo"); + let pattern = OwnedValue::build_text("ii"); let expected = OwnedValue::Integer(2); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from("limbo"))); - let pattern = OwnedValue::build_text(Rc::new(String::from("limboX"))); + let input = OwnedValue::build_text("limbo"); + let pattern = OwnedValue::build_text("limboX"); let expected = OwnedValue::Integer(0); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from("limbo"))); - let pattern = OwnedValue::build_text(Rc::new(String::from(""))); + let input = OwnedValue::build_text("limbo"); + let pattern = OwnedValue::build_text(""); let expected = OwnedValue::Integer(1); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from(""))); - let pattern = OwnedValue::build_text(Rc::new(String::from("limbo"))); + let input = OwnedValue::build_text(""); + let pattern = OwnedValue::build_text("limbo"); let expected = OwnedValue::Integer(0); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from(""))); - let pattern = OwnedValue::build_text(Rc::new(String::from(""))); + let input = OwnedValue::build_text(""); + let pattern = OwnedValue::build_text(""); let expected = OwnedValue::Integer(1); assert_eq!(exec_instr(&input, &pattern), expected); @@ -4410,13 +4401,13 @@ mod tests { let expected = OwnedValue::Null; assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from("limbo"))); + let input = OwnedValue::build_text("limbo"); let pattern = OwnedValue::Null; let expected = OwnedValue::Null; assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Null; - let pattern = OwnedValue::build_text(Rc::new(String::from("limbo"))); + let pattern = OwnedValue::build_text("limbo"); let expected = OwnedValue::Null; assert_eq!(exec_instr(&input, &pattern), expected); @@ -4441,7 +4432,7 @@ mod tests { assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Float(12.34); - let pattern = OwnedValue::build_text(Rc::new(String::from("."))); + let pattern = OwnedValue::build_text("."); let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); @@ -4456,11 +4447,11 @@ mod tests { assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Blob(Rc::new(vec![0x61, 0x62, 0x63, 0x64, 0x65])); - let pattern = OwnedValue::build_text(Rc::new(String::from("cd"))); + let pattern = OwnedValue::build_text("cd"); let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::build_text(Rc::new(String::from("abcde"))); + let input = OwnedValue::build_text("abcde"); let pattern = OwnedValue::Blob(Rc::new(vec![0x63, 0x64])); let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); @@ -4496,19 +4487,19 @@ mod tests { let expected = Some(OwnedValue::Integer(-1)); assert_eq!(exec_sign(&input), expected); - let input = OwnedValue::build_text(Rc::new("abc".to_string())); + let input = OwnedValue::build_text("abc"); let expected = Some(OwnedValue::Null); assert_eq!(exec_sign(&input), expected); - let input = OwnedValue::build_text(Rc::new("42".to_string())); + let input = OwnedValue::build_text("42"); let expected = Some(OwnedValue::Integer(1)); assert_eq!(exec_sign(&input), expected); - let input = OwnedValue::build_text(Rc::new("-42".to_string())); + let input = OwnedValue::build_text("-42"); let expected = Some(OwnedValue::Integer(-1)); assert_eq!(exec_sign(&input), expected); - let input = OwnedValue::build_text(Rc::new("0".to_string())); + let input = OwnedValue::build_text("0"); let expected = Some(OwnedValue::Integer(0)); assert_eq!(exec_sign(&input), expected); @@ -4551,15 +4542,15 @@ mod tests { let expected = OwnedValue::Blob(Rc::new(vec![])); assert_eq!(exec_zeroblob(&input), expected); - let input = OwnedValue::build_text(Rc::new("5".to_string())); + let input = OwnedValue::build_text("5"); let expected = OwnedValue::Blob(Rc::new(vec![0; 5])); assert_eq!(exec_zeroblob(&input), expected); - let input = OwnedValue::build_text(Rc::new("-5".to_string())); + let input = OwnedValue::build_text("-5"); let expected = OwnedValue::Blob(Rc::new(vec![])); assert_eq!(exec_zeroblob(&input), expected); - let input = OwnedValue::build_text(Rc::new("text".to_string())); + let input = OwnedValue::build_text("text"); let expected = OwnedValue::Blob(Rc::new(vec![])); assert_eq!(exec_zeroblob(&input), expected); @@ -4581,101 +4572,101 @@ mod tests { #[test] fn test_replace() { - let input_str = OwnedValue::build_text(Rc::new(String::from("bob"))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from("b"))); - let replace_str = OwnedValue::build_text(Rc::new(String::from("a"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("aoa"))); + let input_str = OwnedValue::build_text("bob"); + let pattern_str = OwnedValue::build_text("b"); + let replace_str = OwnedValue::build_text("a"); + let expected_str = OwnedValue::build_text("aoa"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bob"))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from("b"))); - let replace_str = OwnedValue::build_text(Rc::new(String::from(""))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("o"))); + let input_str = OwnedValue::build_text("bob"); + let pattern_str = OwnedValue::build_text("b"); + let replace_str = OwnedValue::build_text(""); + let expected_str = OwnedValue::build_text("o"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bob"))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from("b"))); - let replace_str = OwnedValue::build_text(Rc::new(String::from("abc"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("abcoabc"))); + let input_str = OwnedValue::build_text("bob"); + let pattern_str = OwnedValue::build_text("b"); + let replace_str = OwnedValue::build_text("abc"); + let expected_str = OwnedValue::build_text("abcoabc"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bob"))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from("a"))); - let replace_str = OwnedValue::build_text(Rc::new(String::from("b"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("bob"))); + let input_str = OwnedValue::build_text("bob"); + let pattern_str = OwnedValue::build_text("a"); + let replace_str = OwnedValue::build_text("b"); + let expected_str = OwnedValue::build_text("bob"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bob"))); - let pattern_str = OwnedValue::build_text(Rc::new(String::from(""))); - let replace_str = OwnedValue::build_text(Rc::new(String::from("a"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("bob"))); + let input_str = OwnedValue::build_text("bob"); + let pattern_str = OwnedValue::build_text(""); + let replace_str = OwnedValue::build_text("a"); + let expected_str = OwnedValue::build_text("bob"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bob"))); + let input_str = OwnedValue::build_text("bob"); let pattern_str = OwnedValue::Null; - let replace_str = OwnedValue::build_text(Rc::new(String::from("a"))); + let replace_str = OwnedValue::build_text("a"); let expected_str = OwnedValue::Null; assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bo5"))); + let input_str = OwnedValue::build_text("bo5"); let pattern_str = OwnedValue::Integer(5); - let replace_str = OwnedValue::build_text(Rc::new(String::from("a"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("boa"))); + let replace_str = OwnedValue::build_text("a"); + let expected_str = OwnedValue::build_text("boa"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bo5.0"))); + let input_str = OwnedValue::build_text("bo5.0"); let pattern_str = OwnedValue::Float(5.0); - let replace_str = OwnedValue::build_text(Rc::new(String::from("a"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("boa"))); + let replace_str = OwnedValue::build_text("a"); + let expected_str = OwnedValue::build_text("boa"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bo5"))); + let input_str = OwnedValue::build_text("bo5"); let pattern_str = OwnedValue::Float(5.0); - let replace_str = OwnedValue::build_text(Rc::new(String::from("a"))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("bo5"))); + let replace_str = OwnedValue::build_text("a"); + let expected_str = OwnedValue::build_text("bo5"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); - let input_str = OwnedValue::build_text(Rc::new(String::from("bo5.0"))); + let input_str = OwnedValue::build_text("bo5.0"); let pattern_str = OwnedValue::Float(5.0); let replace_str = OwnedValue::Float(6.0); - let expected_str = OwnedValue::build_text(Rc::new(String::from("bo6.0"))); + let expected_str = OwnedValue::build_text("bo6.0"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); // todo: change this test to use (0.1 + 0.2) instead of 0.3 when decimals are implemented. - let input_str = OwnedValue::build_text(Rc::new(String::from("tes3"))); + let input_str = OwnedValue::build_text("tes3"); let pattern_str = OwnedValue::Integer(3); let replace_str = OwnedValue::Agg(Box::new(AggContext::Sum(OwnedValue::Float(0.3)))); - let expected_str = OwnedValue::build_text(Rc::new(String::from("tes0.3"))); + let expected_str = OwnedValue::build_text("tes0.3"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str diff --git a/core/vdbe/sorter.rs b/core/vdbe/sorter.rs index eecc42e04..60f839b1c 100644 --- a/core/vdbe/sorter.rs +++ b/core/vdbe/sorter.rs @@ -27,8 +27,8 @@ impl Sorter { pub fn sort(&mut self) { self.records.sort_by(|a, b| { let cmp_by_idx = |idx: usize, ascending: bool| { - let a = &a.values[idx]; - let b = &b.values[idx]; + let a = &a.get_value(idx); + let b = &b.get_value(idx); if ascending { a.cmp(b) } else { @@ -56,6 +56,6 @@ impl Sorter { } pub fn insert(&mut self, record: &Record) { - self.records.push(Record::new(record.values.to_vec())); + self.records.push(Record::new(record.get_values().to_vec())); } } diff --git a/core/vector/mod.rs b/core/vector/mod.rs index a1c002fdc..60e365f94 100644 --- a/core/vector/mod.rs +++ b/core/vector/mod.rs @@ -56,16 +56,12 @@ pub fn vector_extract(args: &[OwnedValue]) -> Result { }; if blob.is_empty() { - return Ok(OwnedValue::Text(crate::types::Text::new(std::rc::Rc::new( - "[]".to_string(), - )))); + return Ok(OwnedValue::build_text("[]")); } let vector_type = vector_type(blob)?; let vector = vector_deserialize(vector_type, blob)?; - Ok(OwnedValue::Text(crate::types::Text::new(std::rc::Rc::new( - vector_to_text(&vector), - )))) + Ok(OwnedValue::build_text(&vector_to_text(&vector))) } pub fn vector_distance_cos(args: &[OwnedValue]) -> Result { diff --git a/docs/internals.md b/docs/internals.md index 0063abf5d..fe35fa524 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -39,9 +39,48 @@ interface to parse the statement and generate a bytecode program, a step called preparing a statement. When a statement is prepared, it can be executed using the `sqlite3_step()` function. -To inspect the bytecode program for a SQL statement, you can use the -`EXPLAIN` command in the shell. For our example SQL statement, the bytecode -looks as follows: +To illustrate the different components of Limbo, we can look at the sequence +diagram of a query from the CLI to the bytecode virtual machine (VDBE): + +```mermaid +sequenceDiagram + +participant main as cli/main +participant Database as core/lib/Database +participant Connection as core/lib/Connection +participant Parser as sql/mod/Parser +participant translate as translate/mod +participant Statement as core/lib/Statement +participant Program as vdbe/mod/Program + +main->>Database: open_file +Database->>main: Connection +main->>Connection: query(sql) +Note left of Parser: Parser uses vendored sqlite3-parser +Connection->>Parser: next() +Note left of Parser: Passes the SQL query to Parser + +Parser->>Connection: Cmd::Stmt (ast/mod.rs) + +Note right of translate: Translates SQL statement into bytecode +Connection->>translate:translate(stmt) + +translate->>Connection: Program + +Connection->>main: Ok(Some(Rows { Statement })) + +note right of main: a Statement with
a reference to Program is returned + +main->>Statement: step() +Statement->>Program: step() +Note left of Program: Program executes bytecode instructions
See https://www.sqlite.org/opcode.html +Program->>Statement: StepResult +Statement->>main: StepResult +``` + +To drill down into more specifics, we inspect the bytecode program for a SQL +statement using the `EXPLAIN` command in the shell. For our example SQL +statement, the bytecode looks as follows: ``` limbo> EXPLAIN SELECT 'hello, world'; @@ -67,12 +106,22 @@ constant `'hello, world'` to register `r[1]`. The `ResultRow` instruction produces a SQL query result using contents of `r[1]`. Finally, the program terminates with the `Halt` instruction. + ## Frontend ### Parser +The parser is the module in the front end that processes SQLite query language input data, transforming it into an abstract syntax tree (AST) for further processing. The parser is an in-tree fork of [lemon-rs](https://github.com/gwenn/lemon-rs), which in turn is a port of SQLite parser into Rust. The emitted AST is handed over to the code generation steps to turn the AST into virtual machine programs. + ### Code generator +The code generator module takes AST as input and produces virtual machine programs representing executable SQL statements. At high-level, code generation works as follows: + +1. `JOIN` clauses are transformed into equivalent `WHERE` clauses, which simplifies code generation. +2. `WHERE` clauses are mapped into bytecode loops +3. `ORDER BY` causes the bytecode program to pass result rows to a sorter before returned to the application. +4. `GROUP BY` also causes the bytecode programs to pass result rows to an aggregation function before results are returned to the application. + ### Query optimizer ## Virtual Machine diff --git a/docs/internals/typical_query.md b/docs/internals/typical_query.md deleted file mode 100644 index d4d1c841f..000000000 --- a/docs/internals/typical_query.md +++ /dev/null @@ -1,37 +0,0 @@ -The following sequence diagram shows the typical flow of a query from the CLI to the [VDBE](https://www.sqlite.org/opcode.html). - -```mermaid -sequenceDiagram - -participant main as cli/main -participant Database as core/lib/Database -participant Connection as core/lib/Connection -participant Parser as sql/mod/Parser -participant translate as translate/mod -participant Statement as core/lib/Statement -participant Program as vdbe/mod/Program - -main->>Database: open_file -Database->>main: Connection -main->>Connection: query(sql) -Note left of Parser: Parser uses vendored sqlite3-parser -Connection->>Parser: next() -Note left of Parser: Passes the SQL query to Parser - -Parser->>Connection: Cmd::Stmt (ast/mod.rs) - -Note right of translate: Translates SQL statement into bytecode -Connection->>translate:translate(stmt) - -translate->>Connection: Program - -Connection->>main: Ok(Some(Rows { Statement })) - -note right of main: a Statement with
a reference to Program is returned - -main->>Statement: step() -Statement->>Program: step() -Note left of Program: Program executes bytecode instructions
See https://www.sqlite.org/opcode.html -Program->>Statement: StepResult -Statement->>main: StepResult -``` diff --git a/extensions/core/Cargo.toml b/extensions/core/Cargo.toml index 3194bcadb..7dc60b498 100644 --- a/extensions/core/Cargo.toml +++ b/extensions/core/Cargo.toml @@ -12,5 +12,4 @@ default = [] static = [] [dependencies] -log = "0.4.20" limbo_macros = { path = "../../macros" } diff --git a/extensions/regexp/Cargo.toml b/extensions/regexp/Cargo.toml index c8288e601..9702db5f1 100644 --- a/extensions/regexp/Cargo.toml +++ b/extensions/regexp/Cargo.toml @@ -17,7 +17,6 @@ crate-type = ["cdylib", "lib"] [dependencies] limbo_ext = { path = "../core", features = ["static"] } regex = "1.11.1" -log = "0.4.20" [target.'cfg(not(target_family = "wasm"))'.dependencies] mimalloc = { version = "*", default-features = false } diff --git a/extensions/series/Cargo.toml b/extensions/series/Cargo.toml index 823e95472..33f45cff2 100644 --- a/extensions/series/Cargo.toml +++ b/extensions/series/Cargo.toml @@ -15,7 +15,6 @@ crate-type = ["cdylib", "lib"] [dependencies] limbo_ext = { path = "../core", features = ["static"] } -log = "0.4.20" [target.'cfg(not(target_family = "wasm"))'.dependencies] mimalloc = { version = "*", default-features = false } diff --git a/extensions/time/src/lib.rs b/extensions/time/src/lib.rs index 5c4d5a383..2e1b8efd2 100644 --- a/extensions/time/src/lib.rs +++ b/extensions/time/src/lib.rs @@ -816,7 +816,7 @@ fn time_until(args: &[Value]) -> Value { time_sub_internal(t, now) } -// Rouding +// Rounding #[scalar(name = "time_trunc", alias = "date_trunc")] fn time_trunc(args: &[Value]) -> Value { diff --git a/simulator/generation/mod.rs b/simulator/generation/mod.rs index ac7defd54..565f65297 100644 --- a/simulator/generation/mod.rs +++ b/simulator/generation/mod.rs @@ -33,11 +33,11 @@ pub trait ArbitraryFromMaybe { } /// Frequency is a helper function for composing different generators with different frequency -/// of occurences. +/// of occurrences. /// The type signature for the `N` parameter is a bit complex, but it /// roughly corresponds to a type that can be summed, compared, subtracted and sampled, which are /// the operations we require for the implementation. -// todo: switch to a simpler type signature that can accomodate all integer and float types, which +// todo: switch to a simpler type signature that can accommodate all integer and float types, which // should be enough for our purposes. pub(crate) fn frequency< 'a, @@ -61,7 +61,7 @@ pub(crate) fn frequency< unreachable!() } -/// one_of is a helper function for composing different generators with equal probability of occurence. +/// one_of is a helper function for composing different generators with equal probability of occurrence. pub(crate) fn one_of<'a, T, R: Rng>(choices: Vec T + 'a>>, rng: &mut R) -> T { let index = rng.gen_range(0..choices.len()); choices[index](rng) diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index d06cdfff3..9f17bb06c 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -47,9 +47,9 @@ impl InteractionPlan { .map(|i| i.interactions()) .collect::>(); - let (mut i, mut j1, mut j2) = (0, 0, 0); + let (mut i, mut j) = (0, 0); - while i < interactions.len() && j1 < plan.len() { + while i < interactions.len() && j < plan.len() { if interactions[i].starts_with("-- begin") || interactions[i].starts_with("-- end") || interactions[i].is_empty() @@ -58,28 +58,30 @@ impl InteractionPlan { continue; } - if interactions[i].contains(plan[j1][j2].to_string().as_str()) { - i += 1; - if j2 + 1 < plan[j1].len() { - j2 += 1; - } else { - j1 += 1; - j2 = 0; - } - } else { - plan[j1].remove(j2); + // interactions[i] is the i'th line in the human readable plan + // plan[j][k] is the k'th interaction in the j'th property + let mut k = 0; - if plan[j1].is_empty() { - plan.remove(j1); - j2 = 0; + while k < plan[j].len() { + if i >= interactions.len() { + let _ = plan.split_off(j + 1); + let _ = plan[j].split_off(k); + break; + } + + if interactions[i].contains(plan[j][k].to_string().as_str()) { + i += 1; + k += 1; + } else { + plan[j].remove(k); } } - } - if j1 < plan.len() { - if j2 < plan[j1].len() { - let _ = plan[j1].split_off(j2); + + if plan[j].is_empty() { + plan.remove(j); + } else { + j += 1; } - let _ = plan.split_off(j1); } plan @@ -454,7 +456,7 @@ impl Interaction { StepResult::Row => { let row = rows.row().unwrap(); let mut r = Vec::new(); - for el in &row.values { + for el in row.get_values() { let v = el.to_value(); let v = match v { limbo_core::Value::Null => Value::Null, diff --git a/simulator/main.rs b/simulator/main.rs index 82c39b809..e8a5b34cf 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -233,6 +233,10 @@ fn run_simulator( }) .collect::>(); + // Write the shrunk plan to a file + let mut f = std::fs::File::create(&paths.shrunk_plan).unwrap(); + f.write_all(shrunk_plans[0].to_string().as_bytes()).unwrap(); + let last_execution = Arc::new(Mutex::new(*last_execution)); let shrunk = SandboxedResult::from( @@ -270,11 +274,6 @@ fn run_simulator( log::error!("shrinking failed, the error was not properly reproduced"); } } - - // Write the shrunk plan to a file - let shrunk_plan = std::fs::read(&paths.shrunk_plan).unwrap(); - let mut f = std::fs::File::create(&paths.shrunk_plan).unwrap(); - f.write_all(&shrunk_plan).unwrap(); } } } @@ -430,7 +429,7 @@ fn setup_simulation( let mut env = SimulatorEnv::new(seed, cli_opts, db_path); // todo: the loading works correctly because of a hacky decision - // Rigth now, the plan generation is the only point we use the rng, so the environment doesn't + // Right now, the plan generation is the only point we use the rng, so the environment doesn't // even need it. In the future, especially with multi-connections and multi-threading, we might // use the RNG for more things such as scheduling, so this assumption will fail. When that happens, // we'll need to reachitect this logic by saving and loading RNG state. diff --git a/simulator/runner/cli.rs b/simulator/runner/cli.rs index 93a14849f..aa3697b27 100644 --- a/simulator/runner/cli.rs +++ b/simulator/runner/cli.rs @@ -64,7 +64,7 @@ impl SimulatorCLI { return Err("Minimum size cannot be greater than maximum size".to_string()); } - // Make sure uncompatible options are not set + // Make sure incompatible options are not set if self.shrink && self.doublecheck { return Err("Cannot use shrink and doublecheck at the same time".to_string()); } diff --git a/simulator/runner/env.rs b/simulator/runner/env.rs index 2813b80e8..83f5180d3 100644 --- a/simulator/runner/env.rs +++ b/simulator/runner/env.rs @@ -43,7 +43,7 @@ impl SimulatorEnv { let opts = SimulatorOpts { ticks: rng.gen_range(cli_opts.minimum_size..=cli_opts.maximum_size), max_connections: 1, // TODO: for now let's use one connection as we didn't implement - // correct transactions procesing + // correct transactions processing max_tables: rng.gen_range(0..128), create_percent, read_percent, diff --git a/simulator/runner/execution.rs b/simulator/runner/execution.rs index 6544928a1..6342dff3a 100644 --- a/simulator/runner/execution.rs +++ b/simulator/runner/execution.rs @@ -62,6 +62,7 @@ pub(crate) fn execute_plans( ) -> ExecutionResult { let mut history = ExecutionHistory::new(); let now = std::time::Instant::now(); + env.clear_poison(); let mut env = env.lock().unwrap(); for _tick in 0..env.opts.ticks { // Pick the connection to interact with diff --git a/simulator/runner/file.rs b/simulator/runner/file.rs index 7a514ea1e..6d73af505 100644 --- a/simulator/runner/file.rs +++ b/simulator/runner/file.rs @@ -74,7 +74,7 @@ impl File for SimulatorFile { self.inner.unlock_file() } - fn pread(&self, pos: usize, c: Rc) -> Result<()> { + fn pread(&self, pos: usize, c: limbo_core::Completion) -> Result<()> { *self.nr_pread_calls.borrow_mut() += 1; if *self.fault.borrow() { *self.nr_pread_faults.borrow_mut() += 1; @@ -89,7 +89,7 @@ impl File for SimulatorFile { &self, pos: usize, buffer: Rc>, - c: Rc, + c: limbo_core::Completion, ) -> Result<()> { *self.nr_pwrite_calls.borrow_mut() += 1; if *self.fault.borrow() { @@ -101,7 +101,7 @@ impl File for SimulatorFile { self.inner.pwrite(pos, buffer, c) } - fn sync(&self, c: Rc) -> Result<()> { + fn sync(&self, c: limbo_core::Completion) -> Result<()> { *self.nr_sync_calls.borrow_mut() += 1; self.inner.sync(c) } diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index 440715849..acace7259 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -442,7 +442,7 @@ pub unsafe extern "C" fn sqlite3_expanded_sql(_stmt: *mut sqlite3_stmt) -> *mut pub unsafe extern "C" fn sqlite3_data_count(stmt: *mut sqlite3_stmt) -> ffi::c_int { let stmt = &*stmt; let row = stmt.stmt.row().unwrap(); - row.values.len() as ffi::c_int + row.len() as ffi::c_int } #[no_mangle] @@ -635,7 +635,7 @@ pub unsafe extern "C" fn sqlite3_column_text( Some(row) => row, None => return std::ptr::null(), }; - match row.values.get(idx as usize).map(|v| v.to_value()) { + match row.get_values().get(idx as usize).map(|v| v.to_value()) { Some(limbo_core::Value::Text(text)) => text.as_bytes().as_ptr(), _ => std::ptr::null(), } diff --git a/testing/coalesce.test b/testing/coalesce.test index a8ecdc0e9..4a748c1ad 100755 --- a/testing/coalesce.test +++ b/testing/coalesce.test @@ -11,6 +11,18 @@ do_execsql_test coalesce-2 { select coalesce(NULL, NULL, 1); } {1} +do_execsql_test coalesce-nested { + select coalesce(NULL, coalesce(NULL, NULL)); +} {} + +do_execsql_test coalesce-nested-2 { + select coalesce(NULL, coalesce(NULL, 2)); + select coalesce(NULL, coalesce(1, 2)); + select coalesce(0, coalesce(1, 2)); +} {2 +1 +0} + do_execsql_test coalesce-null { select coalesce(NULL, NULL, NULL); } {} diff --git a/testing/json.test b/testing/json.test index 0bfec6247..7a1890369 100755 --- a/testing/json.test +++ b/testing/json.test @@ -759,7 +759,7 @@ do_execsql_test json-patch-add-all-dup-keys-from-patch { '{"z":{}, "z":5, "z":100}' ); } {{{"x":100,"x":200,"z":100}}} -do_execsql_test json-patch-first-occurance-patch { +do_execsql_test json-patch-first-occurrence-patch { select json_patch('{"x":100,"x":200}','{"x":{}, "x":5, "x":100}'); } {{{"x":100,"x":200}}} do_execsql_test json-patch-complex-nested-dup-keys { diff --git a/testing/pragma.test b/testing/pragma.test index 436ad48e1..c478c032c 100755 --- a/testing/pragma.test +++ b/testing/pragma.test @@ -41,3 +41,11 @@ do_execsql_test_on_specific_db ":memory:" pragma-page-count-table { CREATE TABLE foo(bar); PRAGMA page_count } {2} + +do_execsql_test_on_specific_db "testing/testing_user_version_10.db" pragma-user-version-user-set { + PRAGMA user_version +} {10} + +do_execsql_test_on_specific_db ":memory:" pragma-user-version-default { + PRAGMA user_version +} {0} \ No newline at end of file diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index f04fa1765..47cca09d4 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -407,7 +407,7 @@ do_execsql_test length-text { SELECT length('limbo'); } {5} -do_execsql_test lenght-text-utf8-chars { +do_execsql_test length-text-utf8-chars { SELECT length('ąłóżźć'); } {6} @@ -431,7 +431,7 @@ do_execsql_test octet-length-text { SELECT length('limbo'); } {5} -do_execsql_test octet-lenght-text-utf8-chars { +do_execsql_test octet-length-text-utf8-chars { SELECT octet_length('ąłóżźć'); } {12} @@ -631,6 +631,10 @@ do_execsql_test quote-string { SELECT quote('limbo') } {'limbo'} +do_execsql_test quote-escape { + SELECT quote('''quote''') +} {'''quote'''} + do_execsql_test quote-null { SELECT quote(null) } {NULL} @@ -740,6 +744,21 @@ do_execsql_test cast-float-to-integer { SELECT CAST(123.45 AS INTEGER); } {123} +do_execsql_test cast-float-to-integer-rounding { + SELECT CAST(0.6 AS INTEGER); + SELECT CAST(1.0 AS INTEGER); + SELECT CAST(1.6 AS INTEGER); + SELECT CAST(-0.6 AS INTEGER); + SELECT CAST(-1.0 AS INTEGER); + SELECT CAST(-1.6 AS INTEGER); +} {0 +1 +1 +0 +-1 +-1 +} + do_execsql_test cast-large-float-to-integer { SELECT CAST(9223372036854775808.0 AS INTEGER); } {9223372036854775807} diff --git a/testing/select.test b/testing/select.test index 934c4e879..60f2e5bc2 100755 --- a/testing/select.test +++ b/testing/select.test @@ -108,6 +108,14 @@ do_execsql_test select_base_case_else { select case 1 when 0 then 'zero' when 1 then 'one' else 'two' end; } {one} +do_execsql_test select_base_case_null_result { + select case NULL when 0 then 'first' else 'second' end; + select case NULL when NULL then 'first' else 'second' end; + select case 0 when 0 then 'first' else 'second' end; +} {second +second +first} + do_execsql_test select_base_case_noelse_null { select case 'null else' when 0 then 0 when 1 then 1 end; } {} diff --git a/testing/subquery.test b/testing/subquery.test index 4754d1563..3eb2e3326 100644 --- a/testing/subquery.test +++ b/testing/subquery.test @@ -10,6 +10,14 @@ do_execsql_test subquery-inner-filter { ) sub; } {hat!!!} +do_execsql_test subquery-inner-filter-cte { + with sub as ( + select concat(name, '!!!') as loud_hat + from products where name = 'hat' + ) + select sub.loud_hat from sub; +} {hat!!!} + do_execsql_test subquery-outer-filter { select sub.loud_hat from ( select concat(name, '!!!') as loud_hat @@ -17,6 +25,14 @@ do_execsql_test subquery-outer-filter { ) sub where sub.loud_hat = 'hat!!!' } {hat!!!} +do_execsql_test subquery-outer-filter-cte { + with sub as ( + select concat(name, '!!!') as loud_hat + from products + ) + select sub.loud_hat from sub where sub.loud_hat = 'hat!!!' +} {hat!!!} + do_execsql_test subquery-without-alias { select loud_hat from ( select concat(name, '!!!') as loud_hat @@ -24,30 +40,66 @@ do_execsql_test subquery-without-alias { ); } {hat!!!} +do_execsql_test subquery-without-alias-cte { + with cte as ( + select concat(name, '!!!') as loud_hat + from products where name = 'hat' + ) + select loud_hat from cte; +} {hat!!!} + do_execsql_test subquery-no-alias-on-col { select price from ( select * from products where name = 'hat' ) } {79.0} +do_execsql_test subquery-no-alias-on-col-cte { + with cte as ( + select * from products where name = 'hat' + ) + select price from cte +} {79.0} + do_execsql_test subquery-no-alias-on-col-named { select price from ( select price from products where name = 'hat' ) } {79.0} +do_execsql_test subquery-no-alias-on-col-named-cte { + with cte as ( + select price from products where name = 'hat' + ) + select price from cte +} {79.0} + do_execsql_test subquery-select-star { select * from ( select price, price + 1.0, name from products where name = 'hat' ) } {79.0|80.0|hat} +do_execsql_test subquery-select-star-cte { + with cte as ( + select price, price + 1.0, name from products where name = 'hat' + ) + select * from cte +} {79.0|80.0|hat} + do_execsql_test subquery-select-table-star { select sub.* from ( select price, price + 1.0, name from products where name = 'hat' ) sub } {79.0|80.0|hat} +do_execsql_test subquery-select-table-star-cte { + with sub as ( + select price, price + 1.0, name from products where name = 'hat' + ) + select sub.* from sub +} {79.0|80.0|hat} + do_execsql_test nested-subquery { select sub.loudest_hat from ( select upper(nested_sub.loud_hat) as loudest_hat from ( @@ -57,6 +109,17 @@ do_execsql_test nested-subquery { ) sub; } {HAT!!!} +do_execsql_test nested-subquery-cte { + with nested_sub as ( + select concat(name, '!!!') as loud_hat + from products where name = 'hat' + ), + sub as ( + select upper(nested_sub.loud_hat) as loudest_hat from nested_sub + ) + select sub.loudest_hat from sub; +} {HAT!!!} + do_execsql_test subquery-orderby-limit { select upper(sub.loud_name) as loudest_name from ( @@ -69,6 +132,18 @@ do_execsql_test subquery-orderby-limit { BOOTS!!! CAP!!!} +do_execsql_test subquery-orderby-limit-cte { + with sub as ( + select concat(name, '!!!') as loud_name + from products + order by name + limit 3 + ) + select upper(sub.loud_name) as loudest_name from sub; +} {ACCESSORIES!!! +BOOTS!!! +CAP!!!} + do_execsql_test table-join-subquery { select sub.product_name, p.name from products p join ( @@ -77,6 +152,16 @@ do_execsql_test table-join-subquery { ) sub on p.name = sub.product_name where p.name = 'hat' } {hat|hat} +do_execsql_test table-join-subquery-cte { + with sub as ( + select name as product_name + from products + ) + select sub.product_name, p.name + from products p join sub on p.name = sub.product_name + where p.name = 'hat' +} {hat|hat} + do_execsql_test subquery-join-table { select sub.product_name, p.name from ( @@ -85,6 +170,16 @@ do_execsql_test subquery-join-table { ) sub join products p on sub.product_name = p.name where sub.product_name = 'hat' } {hat|hat} +do_execsql_test subquery-join-table-cte { + with sub as ( + select name as product_name + from products + ) + select sub.product_name, p.name + from sub join products p on sub.product_name = p.name + where sub.product_name = 'hat' +} {hat|hat} + do_execsql_test subquery-join-subquery { select sub1.sus_name, sub2.truthful_name from ( @@ -98,6 +193,21 @@ do_execsql_test subquery-join-subquery { ) sub2; } {"cap|no cap"} +do_execsql_test subquery-join-subquery-cte { + with sub1 as ( + select name as sus_name + from products + where name = 'cap' + ), + sub2 as ( + select concat('no ', name) as truthful_name + from products + where name = 'cap' + ) + select sub1.sus_name, sub2.truthful_name + from sub1 join sub2; +} {"cap|no cap"} + do_execsql_test select-star-table-subquery { select * from products p join ( @@ -107,6 +217,16 @@ do_execsql_test select-star-table-subquery { ) sub on p.name = sub.name; } {1|hat|79.0|hat|79.0} +do_execsql_test select-star-table-subquery-cte { + with sub as ( + select name, price + from products + where name = 'hat' + ) + select * + from products p join sub on p.name = sub.name; +} {1|hat|79.0|hat|79.0} + do_execsql_test select-star-subquery-table { select * from ( @@ -116,6 +236,16 @@ do_execsql_test select-star-subquery-table { ) sub join products p on sub.name = p.name; } {hat|79.0|1|hat|79.0} +do_execsql_test select-star-subquery-table-cte { + with sub as ( + select name, price + from products + where name = 'hat' + ) + select * + from sub join products p on sub.name = p.name; +} {hat|79.0|1|hat|79.0} + do_execsql_test select-star-subquery-subquery { select * from ( @@ -129,6 +259,20 @@ do_execsql_test select-star-subquery-subquery { ) sub2 on sub1.price = sub2.price; } {hat|79.0|79.0} +do_execsql_test select-star-subquery-subquery-cte { + with sub1 as ( + select name, price + from products + where name = 'hat' + ), + sub2 as ( + select price + from products + where name = 'hat' + ) + select * + from sub1 join sub2 on sub1.price = sub2.price; +} {hat|79.0|79.0} do_execsql_test subquery-inner-grouping { select is_jennifer, person_count @@ -139,6 +283,16 @@ do_execsql_test subquery-inner-grouping { } {1|151 0|9849} +do_execsql_test subquery-inner-grouping-cte { + with cte as ( + select first_name = 'Jennifer' as is_jennifer, count(1) as person_count from users + group by first_name = 'Jennifer' + ) + select is_jennifer, person_count + from cte order by person_count asc +} {1|151 +0|9849} + do_execsql_test subquery-outer-grouping { select is_jennifer, count(1) as person_count from ( @@ -147,6 +301,15 @@ do_execsql_test subquery-outer-grouping { } {1|151 0|9849} +do_execsql_test subquery-outer-grouping-cte { + with cte as ( + select first_name = 'Jennifer' as is_jennifer from users + ) + select is_jennifer, count(1) as person_count + from cte group by is_jennifer order by count(1) asc +} {1|151 +0|9849} + do_execsql_test subquery-join-using-with-outer-limit { SELECT p.name, sub.funny_name FROM products p @@ -159,6 +322,19 @@ do_execsql_test subquery-join-using-with-outer-limit { cap|cap-lol shirt|shirt-lol"} +do_execsql_test subquery-join-using-with-outer-limit-cte { + WITH sub AS ( + select id, concat(name, '-lol') as funny_name + from products + ) + SELECT p.name, sub.funny_name + FROM products p + JOIN sub USING (id) + LIMIT 3; +} {"hat|hat-lol +cap|cap-lol +shirt|shirt-lol"} + do_execsql_test subquery-join-using-with-inner-limit { SELECT p.name, sub.funny_name FROM products p @@ -171,6 +347,19 @@ do_execsql_test subquery-join-using-with-inner-limit { cap|cap-lol shirt|shirt-lol"} +do_execsql_test subquery-join-using-with-inner-limit-cte { + WITH sub AS ( + select id, concat(name, '-lol') as funny_name + from products + limit 3 + ) + SELECT p.name, sub.funny_name + FROM products p + JOIN sub USING (id); +} {"hat|hat-lol +cap|cap-lol +shirt|shirt-lol"} + do_execsql_test subquery-join-using-with-both-limits { SELECT p.name, sub.funny_name FROM products p @@ -183,6 +372,19 @@ do_execsql_test subquery-join-using-with-both-limits { } {"hat|hat-lol cap|cap-lol"} +do_execsql_test subquery-join-using-with-both-limits-cte { + WITH sub AS ( + select id, concat(name, '-lol') as funny_name + from products + limit 3 + ) + SELECT p.name, sub.funny_name + FROM products p + JOIN sub USING (id) + LIMIT 2; +} {"hat|hat-lol +cap|cap-lol"} + do_execsql_test subquery-containing-join { select foo, bar from ( @@ -191,4 +393,21 @@ do_execsql_test subquery-containing-join { ) limit 3; } {hat|Jamie cap|Cindy -shirt|Tommy} \ No newline at end of file +shirt|Tommy} + +do_execsql_test subquery-containing-join-cte { + with cte as ( + select p.name as foo, u.first_name as bar + from products p join users u using (id) + ) + select foo, bar + from cte limit 3; +} {hat|Jamie +cap|Cindy +shirt|Tommy} + +do_execsql_test subquery-ignore-unused-cte { + with unused as (select last_name from users), + sub as (select first_name from users where first_name = 'Jamie' limit 1) + select * from sub; +} {Jamie} diff --git a/testing/testing_user_version_10.db b/testing/testing_user_version_10.db new file mode 100644 index 000000000..d51d013f1 Binary files /dev/null and b/testing/testing_user_version_10.db differ diff --git a/tests/Cargo.toml b/tests/Cargo.toml index f3e67d73a..a17c58910 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -16,11 +16,8 @@ path = "integration/mod.rs" [dependencies] anyhow = "1.0.75" -clap = { version = "4.5", features = ["derive"] } -dirs = "5.0.1" env_logger = "0.10.1" limbo_core = { path = "../core" } -rustyline = "12.0.0" rusqlite = { version = "0.29", features = ["bundled"] } tempfile = "3.0.7" log = "0.4.22" diff --git a/tests/integration/functions/test_function_rowid.rs b/tests/integration/functions/test_function_rowid.rs index e3f5124df..3f5b5fdf8 100644 --- a/tests/integration/functions/test_function_rowid.rs +++ b/tests/integration/functions/test_function_rowid.rs @@ -30,7 +30,7 @@ fn test_last_insert_rowid_basic() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - if let Value::Integer(id) = row.values[0].to_value() { + if let Value::Integer(id) = row.get_value(0).to_value() { assert_eq!(id, 1, "First insert should have rowid 1"); } } @@ -66,7 +66,7 @@ fn test_last_insert_rowid_basic() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - if let Value::Integer(id) = row.values[0].to_value() { + if let Value::Integer(id) = row.get_value(0).to_value() { last_id = id; } } @@ -112,7 +112,7 @@ fn test_integer_primary_key() -> anyhow::Result<()> { match select_query.step()? { StepResult::Row => { let row = select_query.row().unwrap(); - if let Value::Integer(id) = row.values[0].to_value() { + if let Value::Integer(id) = row.get_value(0).to_value() { rowids.push(id); } } diff --git a/tests/integration/fuzz/mod.rs b/tests/integration/fuzz/mod.rs index 8179dfde4..a9bc88360 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -56,7 +56,7 @@ mod tests { r => panic!("unexpected result {:?}: expecting single row", r), } }; - row.values + row.get_values() .iter() .map(|x| match x.to_value() { limbo_core::Value::Null => rusqlite::types::Value::Null, @@ -163,6 +163,9 @@ mod tests { "SELECT ((NULL) IS NOT TRUE <= ((NOT (FALSE))))", "SELECT ifnull(0, NOT 0)", "SELECT like('a%', 'a') = 1", + "SELECT CASE ( NULL < NULL ) WHEN ( 0 ) THEN ( NULL ) ELSE ( 2.0 ) END;", + "SELECT (COALESCE(0, COALESCE(0, 0)));", + "SELECT CAST((1 > 0) AS INTEGER);", ] { let limbo = limbo_exec_row(&limbo_conn, query); let sqlite = sqlite_exec_row(&sqlite_conn, query); @@ -174,6 +177,113 @@ mod tests { } } + #[test] + pub fn string_expression_fuzz_run() { + let _ = env_logger::try_init(); + let g = GrammarGenerator::new(); + let (expr, expr_builder) = g.create_handle(); + let (bin_op, bin_op_builder) = g.create_handle(); + let (scalar, scalar_builder) = g.create_handle(); + let (paren, paren_builder) = g.create_handle(); + + paren_builder + .concat("") + .push_str("(") + .push(expr) + .push_str(")") + .build(); + + bin_op_builder + .concat(" ") + .push(expr) + .push(g.create().choice().options_str(["||"]).build()) + .push(expr) + .build(); + + scalar_builder + .choice() + .option( + g.create() + .concat("") + .push_str("char(") + .push( + g.create() + .concat("") + .push_symbol(rand_int(65..91)) + .repeat(1..8, ", ") + .build(), + ) + .push_str(")") + .build(), + ) + .option( + g.create() + .concat("") + .push( + g.create() + .choice() + .options_str(["ltrim", "rtrim", "trim"]) + .build(), + ) + .push_str("(") + .push(g.create().concat("").push(expr).repeat(2..3, ", ").build()) + .push_str(")") + .build(), + ) + .option( + g.create() + .concat("") + .push( + g.create() + .choice() + .options_str([ + "ltrim", "rtrim", "lower", "upper", "quote", "hex", "trim", + ]) + .build(), + ) + .push_str("(") + .push(expr) + .push_str(")") + .build(), + ) + .build(); + + expr_builder + .choice() + .option_w(bin_op, 1.0) + .option_w(paren, 1.0) + .option_w(scalar, 1.0) + .option( + g.create() + .concat("") + .push_str("'") + .push_symbol(rand_str("", 2)) + .push_str("'") + .build(), + ) + .build(); + + let sql = g.create().concat(" ").push_str("SELECT").push(expr).build(); + + let db = TempDatabase::new_empty(); + let limbo_conn = db.connect_limbo(); + let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); + + let (mut rng, seed) = rng_from_time(); + log::info!("seed: {}", seed); + for _ in 0..1024 { + let query = g.generate(&mut rng, sql, 50); + log::info!("query: {}", query); + let limbo = limbo_exec_row(&limbo_conn, &query); + let sqlite = sqlite_exec_row(&sqlite_conn, &query); + assert_eq!( + limbo, sqlite, + "query: {}, limbo: {:?}, sqlite: {:?}", + query, limbo, sqlite + ); + } + } + #[test] pub fn logical_expression_fuzz_run() { let _ = env_logger::try_init(); @@ -209,15 +319,86 @@ mod tests { .push(expr) .build(); + let (like_pattern, like_pattern_builder) = g.create_handle(); + like_pattern_builder + .choice() + .option_str("%") + .option_str("_") + .option_symbol(rand_str("", 1)) + .repeat(1..10, "") + .build(); + + let (glob_pattern, glob_pattern_builder) = g.create_handle(); + glob_pattern_builder + .choice() + .option_str("*") + .option_str("**") + .option_str("A") + .option_str("B") + .repeat(1..10, "") + .build(); + + let (coalesce_expr, coalesce_expr_builder) = g.create_handle(); + coalesce_expr_builder + .concat("") + .push_str("COALESCE(") + .push(g.create().concat("").push(expr).repeat(2..5, ",").build()) + .push_str(")") + .build(); + + let (cast_expr, cast_expr_builder) = g.create_handle(); + cast_expr_builder + .concat(" ") + .push_str("CAST ( (") + .push(expr) + .push_str(") AS ") + // cast to INTEGER/REAL/TEXT types can be added when Limbo will use proper equality semantic between values (e.g. 1 = 1.0) + .push(g.create().choice().options_str(["NUMERIC"]).build()) + .push_str(")") + .build(); + + let (case_expr, case_expr_builder) = g.create_handle(); + case_expr_builder + .concat(" ") + .push_str("CASE (") + .push(expr) + .push_str(")") + .push( + g.create() + .concat(" ") + .push_str("WHEN (") + .push(expr) + .push_str(") THEN (") + .push(expr) + .push_str(")") + .repeat(1..5, " ") + .build(), + ) + .push_str("ELSE (") + .push(expr) + .push_str(") END") + .build(); + scalar_builder .choice() + .option(coalesce_expr) .option( g.create() .concat("") .push_str("like('") - .push_symbol(rand_str("", 2)) + .push(like_pattern) .push_str("', '") - .push_symbol(rand_str("", 2)) + .push(like_pattern) + .push_str("')") + .build(), + ) + .option( + g.create() + .concat("") + .push_str("glob('") + .push(glob_pattern) + .push_str("', '") + .push(glob_pattern) .push_str("')") .build(), ) @@ -247,11 +428,13 @@ mod tests { expr_builder .choice() - .option_w(unary_infix_op, 1.0) - .option_w(bin_op, 1.0) - .option_w(paren, 1.0) - .option_w(scalar, 1.0) - // unfortunatelly, sqlite behaves weirdly when IS operator is used with TRUE/FALSE constants + .option_w(cast_expr, 1.0) + .option_w(case_expr, 1.0) + .option_w(unary_infix_op, 2.0) + .option_w(bin_op, 3.0) + .option_w(paren, 2.0) + .option_w(scalar, 4.0) + // unfortunately, sqlite behaves weirdly when IS operator is used with TRUE/FALSE constants // e.g. 8 IS TRUE == 1 (although 8 = TRUE == 0) // so, we do not use TRUE/FALSE constants as they will produce diff with sqlite results .options_str(["1", "0", "NULL", "2.0", "1.5", "-0.5", "-2.0", "(1 / 0)"]) diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index 9d29ef35c..5e99524ab 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -1,6 +1,5 @@ mod common; mod functions; mod fuzz; -mod pragma; mod query_processing; mod wal; diff --git a/tests/integration/pragma/mod.rs b/tests/integration/pragma/mod.rs deleted file mode 100644 index 1070d3011..000000000 --- a/tests/integration/pragma/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod test_pragma_stmts; diff --git a/tests/integration/pragma/test_pragma_stmts.rs b/tests/integration/pragma/test_pragma_stmts.rs deleted file mode 100644 index 11a831d37..000000000 --- a/tests/integration/pragma/test_pragma_stmts.rs +++ /dev/null @@ -1,40 +0,0 @@ -/// rexpect does not work on Windows. -/// https://github.com/rust-cli/rexpect/issues/11 -#[cfg(not(target_os = "windows"))] -mod tests { - use assert_cmd::cargo::cargo_bin; - use rexpect::error::*; - use rexpect::session::{spawn_command, PtySession}; - use std::process; - - #[test] - fn test_pragma_journal_mode_wal() -> Result<(), Error> { - let mut child = spawn_command(run_cli(), Some(1000))?; - child.exp_regex("limbo>")?; // skip everything until limbo cursor appear - child.exp_regex(".?")?; - child.send_line("pragma journal_mode;")?; - child.exp_string("wal")?; - quit(&mut child) - } - - #[test] - fn test_pragma_wal_checkpoint() -> Result<(), Error> { - let mut child = spawn_command(run_cli(), Some(1000))?; - child.exp_regex("limbo>")?; // skip everything until limbo cursor appear - child.exp_regex(".?")?; - child.send_line("pragma wal_checkpoint;")?; - child.exp_string("0|0|0")?; - quit(&mut child) - } - - fn quit(child: &mut PtySession) -> Result<(), Error> { - child.send_line(".quit")?; - child.exp_eof()?; - Ok(()) - } - - fn run_cli() -> process::Command { - let bin_path = cargo_bin("limbo"); - process::Command::new(bin_path) - } -} diff --git a/tests/integration/query_processing/test_read_path.rs b/tests/integration/query_processing/test_read_path.rs index 82c29539e..8f84d4b9c 100644 --- a/tests/integration/query_processing/test_read_path.rs +++ b/tests/integration/query_processing/test_read_path.rs @@ -15,7 +15,7 @@ fn test_statement_reset_bind() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - assert_eq!(row.values[0].to_value(), Value::Integer(1)); + assert_eq!(row.get_value(0).to_value(), Value::Integer(1)); } StepResult::IO => tmp_db.io.run_once()?, _ => break, @@ -30,7 +30,7 @@ fn test_statement_reset_bind() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - assert_eq!(row.values[0].to_value(), Value::Integer(2)); + assert_eq!(row.get_value(0).to_value(), Value::Integer(2)); } StepResult::IO => tmp_db.io.run_once()?, _ => break, @@ -63,23 +63,23 @@ fn test_statement_bind() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - if let Value::Text(s) = row.values[0].to_value() { + if let Value::Text(s) = row.get_value(0).to_value() { assert_eq!(s, "hello") } - if let Value::Text(s) = row.values[1].to_value() { + if let Value::Text(s) = row.get_value(1).to_value() { assert_eq!(s, "hello") } - if let Value::Integer(i) = row.values[2].to_value() { + if let Value::Integer(i) = row.get_value(2).to_value() { assert_eq!(i, 42) } - if let Value::Blob(v) = row.values[3].to_value() { + if let Value::Blob(v) = row.get_value(3).to_value() { assert_eq!(v, &vec![0x1 as u8, 0x2, 0x3]) } - if let Value::Float(f) = row.values[4].to_value() { + if let Value::Float(f) = row.get_value(4).to_value() { assert_eq!(f, 0.5) } } diff --git a/tests/integration/query_processing/test_write_path.rs b/tests/integration/query_processing/test_write_path.rs index 1f05d714f..d1b8c50d6 100644 --- a/tests/integration/query_processing/test_write_path.rs +++ b/tests/integration/query_processing/test_write_path.rs @@ -43,8 +43,8 @@ fn test_simple_overflow_page() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.values[0].to_value(); - let text = row.values[1].to_value(); + let first_value = row.get_value(0).to_value(); + let text = row.get_value(1).to_value(); let id = match first_value { Value::Integer(i) => i as i32, Value::Float(f) => f as i32, @@ -118,8 +118,8 @@ fn test_sequential_overflow_page() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.values[0].to_value(); - let text = row.values[1].to_value(); + let first_value = row.get_value(0).to_value(); + let text = row.get_value(1).to_value(); let id = match first_value { Value::Integer(i) => i as i32, Value::Float(f) => f as i32, @@ -190,7 +190,7 @@ fn test_sequential_write() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.values.first().expect("missing id"); + let first_value = row.get_values().first().expect("missing id"); let id = match first_value.to_value() { Value::Integer(i) => i as i32, Value::Float(f) => f as i32, @@ -256,7 +256,7 @@ fn test_regression_multi_row_insert() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.values.first().expect("missing id"); + let first_value = row.get_values().first().expect("missing id"); let id = match first_value.to_value() { Value::Float(f) => f as i32, _ => panic!("expected float"), @@ -302,7 +302,7 @@ fn test_statement_reset() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - assert_eq!(row.values[0].to_value(), Value::Integer(1)); + assert_eq!(row.get_value(0).to_value(), Value::Integer(1)); break; } StepResult::IO => tmp_db.io.run_once()?, @@ -316,7 +316,7 @@ fn test_statement_reset() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - assert_eq!(row.values[0].to_value(), Value::Integer(1)); + assert_eq!(row.get_value(0).to_value(), Value::Integer(1)); break; } StepResult::IO => tmp_db.io.run_once()?, @@ -366,7 +366,7 @@ fn test_wal_checkpoint() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.values[0].to_value(); + let first_value = row.get_value(0).to_value(); let id = match first_value { Value::Integer(i) => i as i32, Value::Float(f) => f as i32, @@ -430,7 +430,7 @@ fn test_wal_restart() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.values[0].to_value(); + let first_value = row.get_value(0).to_value(); let count = match first_value { Value::Integer(i) => i, _ => unreachable!(), diff --git a/tests/integration/wal/test_wal.rs b/tests/integration/wal/test_wal.rs index 994c5d529..d3d3005ce 100644 --- a/tests/integration/wal/test_wal.rs +++ b/tests/integration/wal/test_wal.rs @@ -44,7 +44,7 @@ pub(crate) fn execute_and_get_strings( match step_result { StepResult::Row => { let row = stmt.row().unwrap(); - for el in &row.values { + for el in row.get_values() { result.push(format!("{el}")); } } @@ -72,7 +72,7 @@ pub(crate) fn execute_and_get_ints( match step_result { StepResult::Row => { let row = stmt.row().unwrap(); - for value in &row.values { + for value in row.get_values() { let value = value.to_value(); let out = match value { Value::Integer(i) => i, diff --git a/vendored/sqlite3-parser/src/lexer/sql/test.rs b/vendored/sqlite3-parser/src/lexer/sql/test.rs index 2ba066bdd..c0923403b 100644 --- a/vendored/sqlite3-parser/src/lexer/sql/test.rs +++ b/vendored/sqlite3-parser/src/lexer/sql/test.rs @@ -3,7 +3,7 @@ use fallible_iterator::FallibleIterator; use super::{Error, Parser}; use crate::parser::ast::fmt::ToTokens; use crate::parser::{ - ast::{Cmd, Name, ParameterInfo, QualifiedName, Stmt}, + ast::{Cmd, ParameterInfo, Stmt}, ParserError, }; @@ -73,20 +73,12 @@ fn vtab_args() -> Result<(), Error> { body TEXT CHECK(length(body)<10240) );"; let r = parse_cmd(sql); - let Cmd::Stmt(Stmt::CreateVirtualTable { - tbl_name: QualifiedName { - name: Name(tbl_name), - .. - }, - module_name: Name(module_name), - args: Some(args), - .. - }) = r - else { + let Cmd::Stmt(Stmt::CreateVirtualTable(create_virtual_table)) = r else { panic!("unexpected AST") }; - assert_eq!(tbl_name, "mail"); - assert_eq!(module_name, "fts3"); + assert_eq!(create_virtual_table.tbl_name.name, "mail"); + assert_eq!(create_virtual_table.module_name.0, "fts3"); + let args = create_virtual_table.args.as_ref().unwrap(); assert_eq!(args.len(), 2); assert_eq!(args[0], "subject VARCHAR(256) NOT NULL"); assert_eq!(args[1], "body TEXT CHECK(length(body)<10240)"); diff --git a/vendored/sqlite3-parser/src/parser/ast/check.rs b/vendored/sqlite3-parser/src/parser/ast/check.rs index e1e0eecd3..3df2c1c97 100644 --- a/vendored/sqlite3-parser/src/parser/ast/check.rs +++ b/vendored/sqlite3-parser/src/parser/ast/check.rs @@ -56,20 +56,29 @@ impl Stmt { /// Like `sqlite3_column_count` but more limited pub fn column_count(&self) -> ColumnCount { match self { - Self::Delete { - returning: Some(returning), - .. - } => column_count(returning), - Self::Insert { - returning: Some(returning), - .. - } => column_count(returning), + Self::Delete(delete) => { + let Delete { returning, .. } = &**delete; + match returning { + Some(returning) => column_count(returning), + None => ColumnCount::None, + } + } + Self::Insert(insert) => { + let Insert { returning, .. } = &**insert; + match returning { + Some(returning) => column_count(returning), + None => ColumnCount::None, + } + } Self::Pragma(..) => ColumnCount::Dynamic, Self::Select(s) => s.column_count(), - Self::Update { - returning: Some(returning), - .. - } => column_count(returning), + Self::Update(update) => { + let Update { returning, .. } = &**update; + match returning { + Some(returning) => column_count(returning), + None => ColumnCount::None, + } + } _ => ColumnCount::None, } } @@ -94,22 +103,28 @@ impl Stmt { /// check for extra rules pub fn check(&self) -> Result<(), ParserError> { match self { - Self::AlterTable(old_name, AlterTableBody::RenameTo(new_name)) => { - if *new_name == old_name.name { - return Err(custom_err!( - "there is already another table or index with this name: {}", - new_name - )); - } - Ok(()) - } - Self::AlterTable(.., AlterTableBody::AddColumn(cd)) => { - for c in cd { - if let ColumnConstraint::PrimaryKey { .. } = c { - return Err(custom_err!("Cannot add a PRIMARY KEY column")); - } else if let ColumnConstraint::Unique(..) = c { - return Err(custom_err!("Cannot add a UNIQUE column")); + Self::AlterTable(alter_table) => { + let (old_name, body) = &**alter_table; + match body { + AlterTableBody::RenameTo(new_name) => { + if *new_name == old_name.name { + return Err(custom_err!( + "there is already another table or index with this name: {}", + new_name + )); + } } + AlterTableBody::AddColumn(cd) => { + for c in cd { + if let ColumnConstraint::PrimaryKey { .. } = c { + return Err(custom_err!("Cannot add a PRIMARY KEY column")); + } + if let ColumnConstraint::Unique(..) = c { + return Err(custom_err!("Cannot add a UNIQUE column")); + } + } + } + _ => {} } Ok(()) } @@ -153,31 +168,47 @@ impl Stmt { _ => Ok(()), } } - Self::Delete { - order_by: Some(_), - limit: None, - .. - } => Err(custom_err!("ORDER BY without LIMIT on DELETE")), - Self::Insert { - columns: Some(columns), - body: InsertBody::Select(select, ..), - .. - } => match select.body.select.column_count() { - ColumnCount::Fixed(n) if n != columns.len() => { - Err(custom_err!("{} values for {} columns", n, columns.len())) + Self::Delete(delete) => { + let Delete { + order_by, limit, .. + } = &**delete; + if let Some(_) = order_by { + if limit.is_none() { + return Err(custom_err!("ORDER BY without LIMIT on DELETE")); + } } - _ => Ok(()), - }, - Self::Insert { - columns: Some(columns), - body: InsertBody::DefaultValues, - .. - } => Err(custom_err!("0 values for {} columns", columns.len())), - Self::Update { - order_by: Some(_), - limit: None, - .. - } => Err(custom_err!("ORDER BY without LIMIT on UPDATE")), + Ok(()) + } + Self::Insert(insert) => { + let Insert { columns, body, .. } = &**insert; + if columns.is_none() { + return Ok(()); + } + let columns = columns.as_ref().unwrap(); + match &*body { + InsertBody::Select(select, ..) => match select.body.select.column_count() { + ColumnCount::Fixed(n) if n != columns.len() => { + Err(custom_err!("{} values for {} columns", n, columns.len())) + } + _ => Ok(()), + }, + InsertBody::DefaultValues => { + Err(custom_err!("0 values for {} columns", columns.len())) + } + } + } + Self::Update(update) => { + let Update { + order_by, limit, .. + } = &**update; + if let Some(_) = order_by { + if limit.is_none() { + return Err(custom_err!("ORDER BY without LIMIT on UPDATE")); + } + } + + Ok(()) + } _ => Ok(()), } } @@ -295,7 +326,10 @@ impl OneSelect { /// Like `sqlite3_column_count` but more limited pub fn column_count(&self) -> ColumnCount { match self { - Self::Select { columns, .. } => column_count(columns), + Self::Select(select) => { + let SelectInner { columns, .. } = &**select; + column_count(columns) + } Self::Values(values) => { assert!(!values.is_empty()); // TODO Validate ColumnCount::Fixed(values[0].len()) diff --git a/vendored/sqlite3-parser/src/parser/ast/fmt.rs b/vendored/sqlite3-parser/src/parser/ast/fmt.rs index 34a7fa3f0..3c264d607 100644 --- a/vendored/sqlite3-parser/src/parser/ast/fmt.rs +++ b/vendored/sqlite3-parser/src/parser/ast/fmt.rs @@ -119,7 +119,8 @@ impl Display for Cmd { impl ToTokens for Stmt { fn to_tokens(&self, s: &mut S) -> Result<(), S::Error> { match self { - Self::AlterTable(tbl_name, body) => { + Self::AlterTable(alter_table) => { + let (tbl_name, body) = &**alter_table; s.append(TK_ALTER, None)?; s.append(TK_TABLE, None)?; tbl_name.to_tokens(s)?; @@ -211,17 +212,18 @@ impl ToTokens for Stmt { tbl_name.to_tokens(s)?; body.to_tokens(s) } - Self::CreateTrigger { - temporary, - if_not_exists, - trigger_name, - time, - event, - tbl_name, - for_each_row, - when_clause, - commands, - } => { + Self::CreateTrigger(trigger) => { + let CreateTrigger { + temporary, + if_not_exists, + trigger_name, + time, + event, + tbl_name, + for_each_row, + when_clause, + commands, + } = &**trigger; s.append(TK_CREATE, None)?; if *temporary { s.append(TK_TEMP, None)?; @@ -281,12 +283,13 @@ impl ToTokens for Stmt { s.append(TK_AS, None)?; select.to_tokens(s) } - Self::CreateVirtualTable { - if_not_exists, - tbl_name, - module_name, - args, - } => { + Self::CreateVirtualTable(create_virtual_table) => { + let CreateVirtualTable { + if_not_exists, + tbl_name, + module_name, + args, + } = &**create_virtual_table; s.append(TK_CREATE, None)?; s.append(TK_VIRTUAL, None)?; s.append(TK_TABLE, None)?; @@ -304,15 +307,16 @@ impl ToTokens for Stmt { } s.append(TK_RP, None) } - Self::Delete { - with, - tbl_name, - indexed, - where_clause, - returning, - order_by, - limit, - } => { + Self::Delete(delete) => { + let Delete { + with, + tbl_name, + indexed, + where_clause, + returning, + order_by, + limit, + } = &**delete; if let Some(with) = with { with.to_tokens(s)?; } @@ -392,14 +396,15 @@ impl ToTokens for Stmt { } view_name.to_tokens(s) } - Self::Insert { - with, - or_conflict, - tbl_name, - columns, - body, - returning, - } => { + Self::Insert(insert) => { + let Insert { + with, + or_conflict, + tbl_name, + columns, + body, + returning, + } = &**insert; if let Some(with) = with { with.to_tokens(s)?; } @@ -465,18 +470,19 @@ impl ToTokens for Stmt { name.to_tokens(s) } Self::Select(select) => select.to_tokens(s), - Self::Update { - with, - or_conflict, - tbl_name, - indexed, - sets, - from, - where_clause, - returning, - order_by, - limit, - } => { + Self::Update(update) => { + let Update { + with, + or_conflict, + tbl_name, + indexed, + sets, + from, + where_clause, + returning, + order_by, + limit, + } = &**update; if let Some(with) = with { with.to_tokens(s)?; } @@ -891,14 +897,15 @@ impl Display for CompoundOperator { impl ToTokens for OneSelect { fn to_tokens(&self, s: &mut S) -> Result<(), S::Error> { match self { - Self::Select { - distinctness, - columns, - from, - where_clause, - group_by, - window_clause, - } => { + Self::Select(select) => { + let SelectInner { + distinctness, + columns, + from, + where_clause, + group_by, + window_clause, + } = &**select; s.append(TK_SELECT, None)?; if let Some(ref distinctness) = distinctness { distinctness.to_tokens(s)?; @@ -1649,13 +1656,14 @@ impl ToTokens for TriggerEvent { impl ToTokens for TriggerCmd { fn to_tokens(&self, s: &mut S) -> Result<(), S::Error> { match self { - Self::Update { - or_conflict, - tbl_name, - sets, - from, - where_clause, - } => { + Self::Update(update) => { + let TriggerCmdUpdate { + or_conflict, + tbl_name, + sets, + from, + where_clause, + } = &**update; s.append(TK_UPDATE, None)?; if let Some(or_conflict) = or_conflict { s.append(TK_OR, None)?; @@ -1674,14 +1682,15 @@ impl ToTokens for TriggerCmd { } Ok(()) } - Self::Insert { - or_conflict, - tbl_name, - col_names, - select, - upsert, - returning, - } => { + Self::Insert(insert) => { + let TriggerCmdInsert { + or_conflict, + tbl_name, + col_names, + select, + upsert, + returning, + } = &**insert; if let Some(ResolveType::Replace) = or_conflict { s.append(TK_REPLACE, None)?; } else { @@ -1708,14 +1717,11 @@ impl ToTokens for TriggerCmd { } Ok(()) } - Self::Delete { - tbl_name, - where_clause, - } => { + Self::Delete(delete) => { s.append(TK_DELETE, None)?; s.append(TK_FROM, None)?; - tbl_name.to_tokens(s)?; - if let Some(where_clause) = where_clause { + delete.tbl_name.to_tokens(s)?; + if let Some(where_clause) = &delete.where_clause { s.append(TK_WHERE, None)?; where_clause.to_tokens(s)?; } diff --git a/vendored/sqlite3-parser/src/parser/ast/mod.rs b/vendored/sqlite3-parser/src/parser/ast/mod.rs index f149322b3..294fb7553 100644 --- a/vendored/sqlite3-parser/src/parser/ast/mod.rs +++ b/vendored/sqlite3-parser/src/parser/ast/mod.rs @@ -71,18 +71,18 @@ pub(crate) enum ExplainKind { #[derive(Clone, Debug, PartialEq, Eq)] pub enum Stmt { /// `ALTER TABLE`: table name, body - AlterTable(QualifiedName, AlterTableBody), + AlterTable(Box<(QualifiedName, AlterTableBody)>), /// `ANALYSE`: object name Analyze(Option), /// `ATTACH DATABASE` Attach { /// filename // TODO distinction between ATTACH and ATTACH DATABASE - expr: Expr, + expr: Box, /// schema name - db_name: Expr, + db_name: Box, /// password - key: Option, + key: Option>, }, /// `BEGIN`: tx type, tx name Begin(Option, Option), @@ -95,13 +95,13 @@ pub enum Stmt { /// `IF NOT EXISTS` if_not_exists: bool, /// index name - idx_name: QualifiedName, + idx_name: Box, /// table name tbl_name: Name, /// indexed columns or expressions columns: Vec, /// partial index - where_clause: Option, + where_clause: Option>, }, /// `CREATE TABLE` CreateTable { @@ -112,29 +112,10 @@ pub enum Stmt { /// table name tbl_name: QualifiedName, /// table body - body: CreateTableBody, + body: Box, }, /// `CREATE TRIGGER` - CreateTrigger { - /// `TEMPORARY` - temporary: bool, - /// `IF NOT EXISTS` - if_not_exists: bool, - /// trigger name - trigger_name: QualifiedName, - /// `BEFORE`/`AFTER`/`INSTEAD OF` - time: Option, - /// `DELETE`/`INSERT`/`UPDATE` - event: TriggerEvent, - /// table name - tbl_name: QualifiedName, - /// `FOR EACH ROW` - for_each_row: bool, - /// `WHEN` - when_clause: Option, - /// statements - commands: Vec, - }, + CreateTrigger(Box), /// `CREATE VIEW` CreateView { /// `TEMPORARY` @@ -149,35 +130,11 @@ pub enum Stmt { select: Box), /// `UPDATE` - Update { - /// CTE - with: Option, - /// `OR` - or_conflict: Option, - /// table name - tbl_name: QualifiedName, - /// `INDEXED` - indexed: Option, - /// `SET` assignments - sets: Vec, - /// `FROM` - from: Option, - /// `WHERE` clause - where_clause: Option, - /// `RETURNING` - returning: Option>, - /// `ORDER BY` - order_by: Option>, - /// `LIMIT` - limit: Option>, - }, + Update(Box), /// `VACUUM`: database name, into expr - Vacuum(Option, Option), + Vacuum(Option, Option>), +} + +/// `CREATE VIRTUAL TABLE` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CreateVirtualTable { + /// `IF NOT EXISTS` + pub if_not_exists: bool, + /// table name + pub tbl_name: QualifiedName, + /// module name + pub module_name: Name, + /// args + pub args: Option>, // TODO smol str +} + +/// `CREATE TRIGGER +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CreateTrigger { + /// `TEMPORARY` + pub temporary: bool, + /// `IF NOT EXISTS` + pub if_not_exists: bool, + /// trigger name + pub trigger_name: QualifiedName, + /// `BEFORE`/`AFTER`/`INSTEAD OF` + pub time: Option, + /// `DELETE`/`INSERT`/`UPDATE` + pub event: TriggerEvent, + /// table name + pub tbl_name: QualifiedName, + /// `FOR EACH ROW` + pub for_each_row: bool, + /// `WHEN` + pub when_clause: Option, + /// statements + pub commands: Vec, +} + +/// `INSERT` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Insert { + /// CTE + pub with: Option, + /// `OR` + pub or_conflict: Option, // TODO distinction between REPLACE and INSERT OR REPLACE + /// table name + pub tbl_name: QualifiedName, + /// `COLUMNS` + pub columns: Option, + /// `VALUES` or `SELECT` + pub body: InsertBody, + /// `RETURNING` + pub returning: Option>, +} + +/// `UPDATE` clause +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Update { + /// CTE + pub with: Option, + /// `OR` + pub or_conflict: Option, + /// table name + pub tbl_name: QualifiedName, + /// `INDEXED` + pub indexed: Option, + /// `SET` assignments + pub sets: Vec, + /// `FROM` + pub from: Option, + /// `WHERE` clause + pub where_clause: Option>, + /// `RETURNING` + pub returning: Option>, + /// `ORDER BY` + pub order_by: Option>, + /// `LIMIT` + pub limit: Option>, +} + +/// `DELETE` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Delete { + /// CTE + pub with: Option, + /// `FROM` table name + pub tbl_name: QualifiedName, + /// `INDEXED` + pub indexed: Option, + /// `WHERE` clause + pub where_clause: Option>, + /// `RETURNING` + pub returning: Option>, + /// `ORDER BY` + pub order_by: Option>, + /// `LIMIT` + pub limit: Option>, } /// SQL expression @@ -771,24 +791,28 @@ pub enum CompoundOperator { #[derive(Clone, Debug, PartialEq, Eq)] pub enum OneSelect { /// `SELECT` - Select { - /// `DISTINCT` - distinctness: Option, - /// columns - columns: Vec, - /// `FROM` clause - from: Option, - /// `WHERE` clause - where_clause: Option, - /// `GROUP BY` - group_by: Option, - /// `WINDOW` definition - window_clause: Option>, - }, + Select(Box), /// `VALUES` Values(Vec>), } +#[derive(Clone, Debug, PartialEq, Eq)] +/// `SELECT` core +pub struct SelectInner { + /// `DISTINCT` + pub distinctness: Option, + /// columns + pub columns: Vec, + /// `FROM` clause + pub from: Option, + /// `WHERE` clause + pub where_clause: Option, + /// `GROUP BY` + pub group_by: Option, + /// `WINDOW` definition + pub window_clause: Option>, +} + /// `SELECT` ... `FROM` clause // https://sqlite.org/syntax/join-clause.html #[derive(Clone, Debug, PartialEq, Eq)] @@ -1005,7 +1029,7 @@ pub struct GroupBy { /// expressions pub exprs: Vec, /// `HAVING` - pub having: Option, // HAVING clause on a non-aggregate query + pub having: Option>, // HAVING clause on a non-aggregate query } /// identifier or one of several keywords or `INDEXED` @@ -1598,6 +1622,8 @@ pub enum PragmaName { PageCount, /// returns information about the columns of a table TableInfo, + /// Returns the user version of the database file. + UserVersion, /// trigger a checkpoint to run on database(s) if WAL is enabled WalCheckpoint, } @@ -1632,44 +1658,56 @@ pub enum TriggerEvent { #[derive(Clone, Debug, PartialEq, Eq)] pub enum TriggerCmd { /// `UPDATE` - Update { - /// `OR` - or_conflict: Option, - /// table name - tbl_name: Name, - /// `SET` assignments - sets: Vec, - /// `FROM` - from: Option, - /// `WHERE` clause - where_clause: Option, - }, + Update(Box), /// `INSERT` - Insert { - /// `OR` - or_conflict: Option, - /// table name - tbl_name: Name, - /// `COLUMNS` - col_names: Option, - /// `SELECT` or `VALUES` - select: Box), } +/// `UPDATE` trigger command +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TriggerCmdUpdate { + /// `OR` + pub or_conflict: Option, + /// table name + pub tbl_name: Name, + /// `SET` assignments + pub sets: Vec, + /// `FROM` + pub from: Option, + /// `WHERE` clause + pub where_clause: Option, +} + +/// `INSERT` trigger command +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TriggerCmdInsert { + /// `OR` + pub or_conflict: Option, + /// table name + pub tbl_name: Name, + /// `COLUMNS` + pub col_names: Option, + /// `SELECT` or `VALUES` + pub select: Box