├── migrations └── 20210509094817_create_user_table.sql ├── Cargo.toml ├── Makefile ├── commons.py ├── busy_loop.py ├── src ├── bin │ ├── busy.rs │ ├── threaded_busy.rs │ ├── basic.rs │ ├── basic_prep.rs │ ├── basic_batched_wp.rs │ ├── basic_async.rs │ ├── threaded_str_batched.rs │ ├── basic_batched.rs │ └── threaded_batched.rs └── lib.rs ├── LICENSE ├── naive.py ├── sqlite3_opt.py ├── naive_batched.py ├── sqlite3_opt_batched.py ├── .gitignore ├── threaded_batched.py ├── README.md ├── bench.sh └── Cargo.lock /migrations/20210509094817_create_user_table.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | create table IF NOT EXISTS user 3 | ( 4 | id INTEGER not null primary key, 5 | area CHAR(6), 6 | age INTEGER not null, 7 | active INTEGER not null 8 | ); 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fast-sqlite3-inserts" 3 | version = "0.1.0" 4 | authors = ["avi "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [dependencies] 9 | sqlx = { version = "0.5.2", features = ["runtime-tokio-native-tls", "sqlite"]} 10 | tokio = {version = "1.5.0", features = ["full"]} 11 | rand = "0.8.3" 12 | fastrand = "1" 13 | num_cpus = "1.0" 14 | rusqlite = "0.25.3" 15 | tinystr = "0.4.10" 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # runs each of the scripts one after another, prints the measurements to stdout 2 | .SILENT: 3 | 4 | export TZ := ":Asia/Kolkata" 5 | 6 | busy-python: 7 | echo 8 | echo "$$(date)" "[PYTHON] busy_loop.py (100_000_000) iterations" 9 | time python3 busy_loop.py; 10 | 11 | busy-pypy: 12 | echo 13 | echo "$$(date)" "[PYPY] busy_loop.py (100_000_000) iterations" 14 | time pypy3 busy_loop.py; 15 | 16 | busy-rust: 17 | cargo build --release --quiet --bin busy 18 | echo 19 | echo "$$(date)" "[RUST] busy.rs (100_000_000) iterations" 20 | time ./target/release/busy; 21 | 22 | busy-rust-thread: 23 | cargo build --release --quiet --bin threaded_busy 24 | echo 25 | echo "$$(date)" "[RUST] threaded_busy.rs (100_000_000) iterations" 26 | time ./target/release/threaded_busy; 27 | 28 | busy-py-all: busy-python busy-pypy 29 | 30 | busy-rust-all: busy-rust busy-rust-thread 31 | 32 | busy-all: busy-py-all busy-rust-all 33 | -------------------------------------------------------------------------------- /commons.py: -------------------------------------------------------------------------------- 1 | """all the common helper methods used by the Python script 2 | """ 3 | import random 4 | import sqlite3 5 | 6 | 7 | def create_table(con: sqlite3.Connection): 8 | con.execute(""" 9 | create table IF NOT EXISTS user 10 | ( 11 | id INTEGER not null primary key, 12 | area CHAR(6), 13 | age INTEGER not null, 14 | active INTEGER not null 15 | ) 16 | """) 17 | 18 | 19 | def get_random_area_code() -> str: 20 | return str(random.randint(100000, 999999)) 21 | 22 | # Slightly faster. The doc for random.sample mentions: 23 | # To choose a sample from a range of integers, use a range() object as an argument. This is especially fast and space efficient for sampling from a large population: sample(range(10000000), k=60). 24 | def get_random_age() -> int: 25 | return random.choice(range(5,16,5)) 26 | 27 | def get_random_active() -> int: 28 | return random.getrandbits(1) 29 | 30 | def get_random_bool() -> bool: 31 | return bool(random.getrandbits(1)) -------------------------------------------------------------------------------- /busy_loop.py: -------------------------------------------------------------------------------- 1 | """ busy loop 2 | 3 | This code does not really do anything, just runs two for loops. It has no SQL code. The idea was to measure how 4 | much time python spending just to run a for loop, generating data. 5 | """ 6 | 7 | import sqlite3 8 | 9 | from commons import get_random_age, get_random_active, get_random_bool, get_random_area_code, create_table 10 | 11 | 12 | def faker(count=100_000): 13 | min_batch_size = 1_000_000 14 | for _ in range(int(count / min_batch_size)): 15 | with_area = get_random_bool() 16 | current_batch = [] 17 | for _ in range(min_batch_size): 18 | age = get_random_age() 19 | active = get_random_active() 20 | # switch for area code 21 | if with_area: 22 | # random 6 digit number 23 | area = get_random_area_code() 24 | current_batch.append((area, age, active)) 25 | else: 26 | current_batch.append((age, active)) 27 | 28 | 29 | def main(): 30 | faker(count=100_000_000) 31 | 32 | 33 | if __name__ == '__main__': 34 | main() 35 | -------------------------------------------------------------------------------- /src/bin/busy.rs: -------------------------------------------------------------------------------- 1 | //! busy loop 2 | //! 3 | //! This code does not really do anything, just runs two for loops. It has no SQL code. The idea was to measure how much 4 | //! time rust spending just to run a for loop, generating data. 5 | //! 6 | //! next: threaded_busy.rs 7 | 8 | use crate::common::AreaCode; 9 | use fast_sqlite3_inserts as common; 10 | 11 | fn faker(count: i64) { 12 | let min_batch_size = 1_000_000; 13 | for _ in 0..(count / min_batch_size) { 14 | let with_area = common::get_random_bool(); 15 | let mut current_batch = Vec::<(Option, i8, i8)>::new(); 16 | for _ in 0..min_batch_size { 17 | if with_area { 18 | current_batch.push(( 19 | Some(common::get_random_area_code()), 20 | common::get_random_age(), 21 | common::get_random_active(), 22 | )); 23 | } else { 24 | current_batch.push((None, common::get_random_age(), common::get_random_active())); 25 | } 26 | } 27 | } 28 | } 29 | 30 | fn main() { 31 | faker(100_000_000); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Avinash Sajjanshetty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /naive.py: -------------------------------------------------------------------------------- 1 | """ naive version 2 | 3 | This is the first version I came up with. This is very close to how I would write in a day to day life. 4 | 5 | This has no SQLite optimisations and no batching. I just have a long for loop where it loops over 10M 6 | and inserts the rows one by one. 7 | 8 | next: naive_batched.py 9 | """ 10 | 11 | import sqlite3 12 | 13 | from commons import get_random_age, get_random_active, get_random_bool, get_random_area_code, create_table 14 | 15 | DB_NAME = "naive.db" 16 | 17 | 18 | def faker(con: sqlite3.Connection, count=100_000): 19 | for _ in range(count): 20 | age = get_random_age() 21 | active = get_random_active() 22 | # switch for area code 23 | if get_random_bool(): 24 | # random 6 digit number 25 | area = get_random_area_code() 26 | con.execute('INSERT INTO user VALUES (NULL,?,?,?)', (area, age, active)) 27 | else: 28 | con.execute('INSERT INTO user VALUES (NULL,NULL,?,?)', (age, active)) 29 | con.commit() 30 | 31 | 32 | def main(): 33 | con = sqlite3.connect(DB_NAME, isolation_level=None) 34 | con.execute('PRAGMA journal_mode = WAL;') 35 | create_table(con) 36 | faker(con, count=10_000_000) 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /sqlite3_opt.py: -------------------------------------------------------------------------------- 1 | """ naive, sqlite3 optimised. 2 | 3 | This version builds from naive.py, but this one includes SQLite3 optimisations. However, there is no batching involved and we have 4 | a single loop of 100M. 5 | 6 | previous: naive_batched.py 7 | next: sqlite3_opt_batched.py 8 | """ 9 | 10 | import sqlite3 11 | 12 | from commons import get_random_age, get_random_active, get_random_bool, get_random_area_code, create_table 13 | 14 | DB_NAME = "sqlite3_opt.db" 15 | 16 | 17 | def faker(con: sqlite3.Connection, count=100_000): 18 | con.execute('BEGIN') 19 | for _ in range(count): 20 | age = get_random_age() 21 | active = get_random_active() 22 | # switch for area code 23 | if get_random_bool(): 24 | # random 6 digit number 25 | area = get_random_area_code() 26 | con.execute('INSERT INTO user VALUES (NULL,?,?,?)', (area, age, active)) 27 | else: 28 | con.execute('INSERT INTO user VALUES (NULL,NULL,?,?)', (age, active)) 29 | con.commit() 30 | 31 | 32 | def main(): 33 | con = sqlite3.connect(DB_NAME, isolation_level=None) 34 | con.execute('PRAGMA journal_mode = OFF;') 35 | con.execute('PRAGMA synchronous = 0;') 36 | con.execute('PRAGMA cache_size = 1000000;') # give it a GB 37 | con.execute('PRAGMA locking_mode = EXCLUSIVE;') 38 | con.execute('PRAGMA temp_store = MEMORY;') 39 | create_table(con) 40 | faker(con, count=100_000_000) 41 | 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /naive_batched.py: -------------------------------------------------------------------------------- 1 | """ naive batched 2 | 3 | This version builds from naive.py, here I added batching. So instead of one 10M for loop, here we insert rows in a batches of 4 | 100K. This has no SQLite optimisations either. 5 | 6 | previous: naive.py 7 | next: sqlite3_opt.py 8 | """ 9 | 10 | import sqlite3 11 | 12 | from commons import get_random_age, get_random_active, get_random_bool, get_random_area_code, create_table 13 | 14 | DB_NAME = "naive_batched.db" 15 | 16 | 17 | def faker(con: sqlite3.Connection, count=100_000): 18 | min_batch_size = 1_00_000 19 | for _ in range(int(count / min_batch_size)): 20 | with_area = get_random_bool() 21 | current_batch = [] 22 | for _ in range(min_batch_size): 23 | age = get_random_age() 24 | active = get_random_active() 25 | if with_area: 26 | area = get_random_area_code() 27 | current_batch.append((area, age, active)) 28 | else: 29 | current_batch.append((age, active)) 30 | 31 | if with_area: 32 | con.executemany('INSERT INTO user VALUES (NULL,?,?,?)', current_batch) 33 | else: 34 | con.executemany('INSERT INTO user VALUES (NULL,NULL,?,?)', current_batch) 35 | con.commit() 36 | 37 | 38 | def main(): 39 | con = sqlite3.connect(DB_NAME, isolation_level=None) 40 | con.execute('PRAGMA journal_mode = WAL;') 41 | create_table(con) 42 | faker(con, count=10_000_000) 43 | 44 | 45 | if __name__ == '__main__': 46 | main() 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use rusqlite::{types::ToSqlOutput, ToSql}; 2 | use tinystr::TinyStr8; 3 | 4 | pub fn get_random_age() -> i8 { 5 | [5, 10, 15][fastrand::usize(0..3)] 6 | } 7 | 8 | pub fn get_random_active() -> i8 { 9 | fastrand::bool().into() 10 | } 11 | 12 | pub fn get_random_bool() -> bool { 13 | fastrand::bool() 14 | } 15 | 16 | pub fn get_random_area_code() -> AreaCode { 17 | let n = fastrand::u32(0..=999_999); 18 | let buffer = format_6digits_number(n); 19 | TinyStr8::from_bytes(&buffer).map(AreaCode).unwrap() 20 | } 21 | 22 | /// Formats a number that is between 0 and 999_999, 23 | /// the number will be padded with `0`s. 24 | pub fn format_6digits_number(mut n: u32) -> [u8; 6] { 25 | let mut buffer = [b'0'; 6]; 26 | let mut i = buffer.len() - 1; 27 | while i < buffer.len() { 28 | buffer[i] = (n % 10) as u8 + b'0'; 29 | n = n / 10; 30 | i = i.wrapping_sub(1); 31 | } 32 | buffer 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn formatting() { 41 | for n in 0..=999_999 { 42 | let output = format_6digits_number(n); 43 | let expected = format!("{:06}", n); 44 | assert_eq!(output, expected.as_bytes()); 45 | } 46 | } 47 | } 48 | 49 | pub struct AreaCode(TinyStr8); 50 | 51 | impl AreaCode { 52 | pub fn as_str(&self) -> &str { 53 | self.0.as_str() 54 | } 55 | } 56 | 57 | impl ToSql for AreaCode { 58 | fn to_sql(&self) -> rusqlite::Result> { 59 | Ok(ToSqlOutput::from(self.0.as_str())) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/bin/threaded_busy.rs: -------------------------------------------------------------------------------- 1 | //! busy loop but threaded. 2 | //! 3 | //! This code does not really do anything, just runs two for loops. It has no SQL code. The idea was to measure how much 4 | //! time rust spending just to run a for loop, generating data. This builds upon busy.rs and uses multiple threads. 5 | //! 6 | //! previous: busy.rs 7 | 8 | use std::thread; 9 | extern crate num_cpus; 10 | 11 | use crate::common::AreaCode; 12 | use fast_sqlite3_inserts as common; 13 | 14 | fn faker(count: i64) { 15 | let min_batch_size = 1_000_000; 16 | for _ in 0..(count / min_batch_size) { 17 | let with_area = common::get_random_bool(); 18 | let mut current_batch = Vec::<(Option, i8, i8)>::new(); 19 | for _ in 0..min_batch_size { 20 | if with_area { 21 | current_batch.push(( 22 | Some(common::get_random_area_code()), 23 | common::get_random_age(), 24 | common::get_random_active(), 25 | )); 26 | } else { 27 | current_batch.push((None, common::get_random_age(), common::get_random_active())); 28 | } 29 | } 30 | } 31 | } 32 | 33 | fn multi_producers() { 34 | let cpu_count = num_cpus::get(); 35 | let total_rows = 100_000_000; 36 | let each_producer_count = (total_rows / cpu_count) as i64; 37 | let mut handles = Vec::with_capacity(cpu_count); 38 | for _ in 0..cpu_count { 39 | handles.push(thread::spawn(move || faker(each_producer_count.clone()))) 40 | } 41 | for t in handles { 42 | t.join().unwrap(); 43 | } 44 | } 45 | 46 | fn main() { 47 | multi_producers() 48 | } 49 | -------------------------------------------------------------------------------- /sqlite3_opt_batched.py: -------------------------------------------------------------------------------- 1 | """ batched, sqlite3 optimised. 2 | 3 | This version builds from naive_batched.py, but this one includes SQLite3 optimisations. This one also has batching, so this gives 4 | us the best performance in CPython / PyPy. 5 | 6 | previous: sqlite3_opt.py 7 | next: thread_batched.py 8 | """ 9 | 10 | import sqlite3 11 | 12 | from commons import get_random_age, get_random_active, get_random_bool, get_random_area_code, create_table 13 | 14 | DB_NAME = "sqlite3_opt_batched.db" 15 | 16 | 17 | def faker(con: sqlite3.Connection, count=100_000): 18 | min_batch_size = 1_000_000 19 | con.execute('BEGIN') 20 | for _ in range(int(count / min_batch_size)): 21 | with_area = get_random_bool() 22 | current_batch = [] 23 | for _ in range(min_batch_size): 24 | age = get_random_age() 25 | active = get_random_active() 26 | # switch for area code 27 | if with_area: 28 | # random 6 digit number 29 | area = get_random_area_code() 30 | current_batch.append((area, age, active)) 31 | else: 32 | current_batch.append((age, active)) 33 | if with_area: 34 | con.executemany('INSERT INTO user VALUES (NULL,?,?,?)', current_batch) 35 | else: 36 | con.executemany('INSERT INTO user VALUES (NULL,NULL,?,?)', current_batch) 37 | con.commit() 38 | 39 | 40 | def main(): 41 | con = sqlite3.connect(DB_NAME, isolation_level=None) 42 | con.execute('PRAGMA journal_mode = OFF;') 43 | con.execute('PRAGMA synchronous = 0;') 44 | con.execute('PRAGMA cache_size = 1000000;') # give it a GB 45 | con.execute('PRAGMA locking_mode = EXCLUSIVE;') 46 | con.execute('PRAGMA temp_store = MEMORY;') 47 | create_table(con) 48 | faker(con, count=100_000_000) 49 | 50 | 51 | if __name__ == '__main__': 52 | main() 53 | -------------------------------------------------------------------------------- /src/bin/basic.rs: -------------------------------------------------------------------------------- 1 | //! naive version 2 | //! 3 | //! This is the first Rust version I came up with. This builds from the Python versions, so I have 4 | //! included all the SQLite optimisations. However, there is no batching involved, I just have a 5 | //! long for loop where it loops over 100M and inserts the rows one by one. 6 | //! 7 | //! next: basic_async.rs 8 | 9 | use fast_sqlite3_inserts as common; 10 | use rusqlite::{params, Connection}; 11 | 12 | fn faker(mut conn: Connection, count: i64) { 13 | let tx = conn.transaction().unwrap(); 14 | for _ in 0..count { 15 | let with_area = common::get_random_bool(); 16 | let age = common::get_random_age(); 17 | let is_active = common::get_random_active(); 18 | if with_area { 19 | let area_code = common::get_random_area_code(); 20 | tx.execute( 21 | "INSERT INTO user VALUES (NULL, ?, ?, ?)", 22 | params![area_code, age, is_active], 23 | ) 24 | .unwrap(); 25 | } else { 26 | tx.execute( 27 | "INSERT INTO user VALUES (NULL, NULL, ?, ?)", 28 | params![age, is_active], 29 | ) 30 | .unwrap(); 31 | } 32 | } 33 | tx.commit().unwrap(); 34 | } 35 | 36 | fn main() { 37 | let conn = Connection::open("basic.db").unwrap(); 38 | conn.execute_batch( 39 | "PRAGMA journal_mode = OFF; 40 | PRAGMA synchronous = 0; 41 | PRAGMA cache_size = 1000000; 42 | PRAGMA locking_mode = EXCLUSIVE; 43 | PRAGMA temp_store = MEMORY;", 44 | ) 45 | .expect("PRAGMA"); 46 | conn.execute( 47 | "CREATE TABLE IF NOT EXISTS user ( 48 | id INTEGER not null primary key, 49 | area CHAR(6), 50 | age INTEGER not null, 51 | active INTEGER not null)", 52 | [], 53 | ) 54 | .unwrap(); 55 | faker(conn, 100_000_000) 56 | } 57 | -------------------------------------------------------------------------------- /src/bin/basic_prep.rs: -------------------------------------------------------------------------------- 1 | //! prepared statements 2 | //! 3 | //! This is very similar to basic.rs, just that it uses prepared statements. We have a long for loop 4 | //! of 100M and insert each row one by one. 5 | //! 6 | //! previous: basic_async.rs 7 | //! next: basic_batched.rs 8 | 9 | use rusqlite::{params, Connection, Transaction}; 10 | 11 | use fast_sqlite3_inserts as common; 12 | 13 | fn faker_wrapper(mut conn: Connection, count: i64) { 14 | let tx = conn.transaction().unwrap(); 15 | faker(&tx, count); 16 | tx.commit().unwrap(); 17 | } 18 | 19 | fn faker(tx: &Transaction, count: i64) { 20 | let mut stmt_with_area = tx 21 | .prepare_cached("INSERT INTO user VALUES (?, ?, ?, ?)") 22 | .unwrap(); 23 | let mut stmt = tx 24 | .prepare_cached("INSERT INTO user VALUES (?, NULL, ?, ?)") 25 | .unwrap(); 26 | let mut pk: i64 = 1; 27 | for _ in 0..count { 28 | let with_area = common::get_random_bool(); 29 | let age = common::get_random_age(); 30 | let is_active = common::get_random_active(); 31 | if with_area { 32 | let area_code = common::get_random_area_code(); 33 | stmt_with_area 34 | .execute(params![pk, area_code, age, is_active]) 35 | .unwrap(); 36 | } else { 37 | stmt.execute(params![pk, age, is_active]).unwrap(); 38 | } 39 | pk += 1; 40 | } 41 | } 42 | 43 | fn main() { 44 | let conn = Connection::open("basic_prep.db").unwrap(); 45 | conn.execute_batch( 46 | "PRAGMA journal_mode = OFF; 47 | PRAGMA synchronous = 0; 48 | PRAGMA cache_size = 1000000; 49 | PRAGMA locking_mode = EXCLUSIVE; 50 | PRAGMA temp_store = MEMORY;", 51 | ) 52 | .expect("PRAGMA"); 53 | conn.execute( 54 | "CREATE TABLE IF NOT EXISTS user ( 55 | id INTEGER not null primary key, 56 | area CHAR(6), 57 | age INTEGER not null, 58 | active INTEGER not null)", 59 | [], 60 | ) 61 | .unwrap(); 62 | faker_wrapper(conn, 100_000_000) 63 | } 64 | -------------------------------------------------------------------------------- /src/bin/basic_batched_wp.rs: -------------------------------------------------------------------------------- 1 | //! string as prepared statement 2 | //! 3 | //! I have no idea why I suffixed this file with `_wp.rs` 4 | //! 5 | //! Here I do a batch insertion where each batch being 1M in size. Then I insert all of them with 6 | //! a single insert statement. I thought this was a fun experiment to try, lol. 7 | //! 8 | //! previous: basic_batched.rs 9 | //! next: threaded_batched.rs 10 | 11 | use rusqlite::Connection; 12 | 13 | use fast_sqlite3_inserts as common; 14 | 15 | fn faker(mut conn: Connection, count: i64) { 16 | let tx = conn.transaction().unwrap(); 17 | let min_batch_size = 1_000_000; 18 | for _ in 0..(count / min_batch_size) { 19 | let mut stmt = "INSERT INTO user VALUES".to_owned(); 20 | let with_area = common::get_random_bool(); 21 | let age = common::get_random_age(); 22 | let is_active = common::get_random_active(); 23 | for _ in 0..min_batch_size { 24 | if with_area { 25 | let area_code = common::get_random_area_code(); 26 | let params = format!(" (NULL, {}, {}, {}),", area_code.as_str(), age, is_active); 27 | stmt.push_str(¶ms); 28 | } else { 29 | let params = format!(" (NULL, NULL, {}, {}),", age, is_active); 30 | stmt.push_str(¶ms); 31 | } 32 | } 33 | // at the end, we end up a with string which looks like: 34 | // 35 | // INSERT INTO user VALUES (NULL, NULL, 5, 1), (NULL, 1, 5, 1), (NULL, 1, 5, 1), 36 | // 37 | // Notice the `,` at the, which we will replace it with `;` 38 | stmt.pop(); 39 | stmt.push(';'); 40 | tx.execute(&*stmt, []).unwrap(); 41 | } 42 | tx.commit().unwrap(); 43 | } 44 | 45 | fn main() { 46 | let conn = Connection::open("basic_batched_wp.db").unwrap(); 47 | conn.execute_batch( 48 | "PRAGMA journal_mode = OFF; 49 | PRAGMA synchronous = 0; 50 | PRAGMA cache_size = 1000000; 51 | PRAGMA locking_mode = EXCLUSIVE; 52 | PRAGMA temp_store = MEMORY;", 53 | ) 54 | .expect("PRAGMA"); 55 | conn.execute( 56 | "CREATE TABLE IF NOT EXISTS user ( 57 | id INTEGER not null primary key, 58 | area CHAR(6), 59 | age INTEGER not null, 60 | active INTEGER not null)", 61 | [], 62 | ) 63 | .unwrap(); 64 | faker(conn, 100_000_000) 65 | } 66 | -------------------------------------------------------------------------------- /src/bin/basic_async.rs: -------------------------------------------------------------------------------- 1 | //! naive but async version 2 | //! 3 | //! This is very similar to basic.rs, just that it is asynchronous. I also wanted to try out sqlx 4 | //! and rest of all the examples are sync and uses rusqlite 5 | //! 6 | //! previous: basic.rs 7 | //! next: basic_prep.rs 8 | 9 | use std::str::FromStr; 10 | 11 | use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqliteSynchronous}; 12 | use sqlx::{ConnectOptions, Connection, Executor, SqliteConnection, Statement}; 13 | 14 | use fast_sqlite3_inserts as common; 15 | 16 | async fn faker(mut conn: SqliteConnection, count: i64) -> Result<(), sqlx::Error> { 17 | let mut tx = conn.begin().await?; 18 | let stmt_with_area = tx 19 | .prepare("INSERT INTO user VALUES (NULL, ?, ?, ?)") 20 | .await?; 21 | let stmt = tx 22 | .prepare("INSERT INTO user VALUES (NULL, NULL, ?, ?)") 23 | .await?; 24 | for _ in 0..count { 25 | let with_area = common::get_random_bool(); 26 | let age = common::get_random_age(); 27 | let is_active = common::get_random_active(); 28 | if with_area { 29 | let area_code = common::get_random_area_code(); 30 | stmt_with_area 31 | .query() 32 | .bind(area_code.as_str()) 33 | .bind(age) 34 | .bind(is_active) 35 | .execute(&mut tx) 36 | .await?; 37 | } else { 38 | stmt.query() 39 | .bind(age) 40 | .bind(is_active) 41 | .execute(&mut tx) 42 | .await?; 43 | } 44 | } 45 | tx.commit().await?; 46 | Ok(()) 47 | } 48 | 49 | #[tokio::main] 50 | async fn main() -> Result<(), sqlx::Error> { 51 | let mut conn = SqliteConnectOptions::from_str("basic_async.db") 52 | .unwrap() 53 | .create_if_missing(true) 54 | .journal_mode(SqliteJournalMode::Off) 55 | .synchronous(SqliteSynchronous::Off) 56 | .connect() 57 | .await?; 58 | conn.execute("PRAGMA cache_size = 1000000;").await?; 59 | conn.execute("PRAGMA locking_mode = EXCLUSIVE;").await?; 60 | conn.execute("PRAGMA temp_store = MEMORY;").await?; 61 | conn.execute( 62 | "CREATE TABLE IF NOT EXISTS user ( 63 | id INTEGER not null primary key, 64 | area CHAR(6), 65 | age INTEGER not null, 66 | active INTEGER not null);", 67 | ) 68 | .await?; 69 | faker(conn, 100_000_000).await?; 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Ignore all database files 2 | *.db 3 | *.db-shm 4 | *.db-wal 5 | 6 | ### All Python Stuff 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | pip-wheel-metadata/ 30 | share/python-wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | MANIFEST 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | ### All Go Stuff 138 | # Binaries for programs and plugins 139 | *.exe 140 | *.exe~ 141 | *.dll 142 | *.so 143 | *.dylib 144 | 145 | # Test binary, built with `go test -c` 146 | *.test 147 | 148 | # Output of the go coverage tool, specifically when used with LiteIDE 149 | *.out 150 | 151 | ### All Rust Stuff 152 | # Generated by Cargo 153 | # will have compiled files and executables 154 | /debug 155 | /target 156 | 157 | # These are backup files generated by rustfmt 158 | **/*.rs.bk 159 | -------------------------------------------------------------------------------- /threaded_batched.py: -------------------------------------------------------------------------------- 1 | """ batched, sqlite3 optimised and multi threaded version. 2 | 3 | This version builds from sqlite3_opt_batched.py, but this one is multithreaded and probabaly the complex variant of all python ones. 4 | 5 | We have a queue, spawn a single writer thread which consumes from queue and writes to SQLite. Then we spawn few more producer threads 6 | which generate the data, push to queue. 7 | 8 | previous: sqlite3_opt_batched.py 9 | """ 10 | 11 | import queue 12 | import sqlite3 13 | import threading 14 | import multiprocessing 15 | from typing import List 16 | 17 | from commons import get_random_age, get_random_active, get_random_bool, get_random_area_code, create_table 18 | 19 | DB_NAME = "threaded_batched.db" 20 | 21 | q = queue.Queue() 22 | 23 | 24 | def consumer(): 25 | con = sqlite3.connect(DB_NAME, isolation_level=None) 26 | con.execute('PRAGMA journal_mode = OFF;') 27 | con.execute('PRAGMA synchronous = 0;') 28 | con.execute('PRAGMA cache_size = 1000000;') # give it a GB 29 | con.execute('PRAGMA locking_mode = EXCLUSIVE;') 30 | con.execute('PRAGMA temp_store = MEMORY;') 31 | create_table(con) 32 | 33 | while True: 34 | item = q.get() 35 | stmt, batch = item 36 | con.execute('BEGIN') 37 | con.executemany(stmt, batch) 38 | con.commit() 39 | q.task_done() 40 | 41 | 42 | def producer(count: int): 43 | min_batch_size = 1_000_000 44 | for _ in range(int(count / min_batch_size)): 45 | with_area = get_random_bool() 46 | current_batch = [] 47 | for _ in range(min_batch_size): 48 | age = get_random_age() 49 | active = get_random_active() 50 | # switch for area code 51 | if with_area: 52 | # random 6 digit number 53 | area = get_random_area_code() 54 | current_batch.append((area, age, active)) 55 | else: 56 | current_batch.append((age, active)) 57 | if with_area: 58 | q.put(('INSERT INTO user VALUES (NULL,?,?,?)', current_batch)) 59 | else: 60 | q.put(('INSERT INTO user VALUES (NULL,NULL,?,?)', current_batch)) 61 | 62 | 63 | def main(): 64 | total_rows = 100_000_000 65 | # start the consumer. Marks this thread as daemon thread. Our main / program exits only 66 | # when the consumer thread has returned 67 | # https://docs.python.org/3.8/library/threading.html#thread-objects 68 | threading.Thread(target=consumer, daemon=True).start() 69 | 70 | # we would want to launch as many as producers, so we will take the max CPU value 71 | # and launch as many. We keep two threads, one for main and one for consumer. 72 | max_producers = multiprocessing.cpu_count() - 2 73 | 74 | # how many rows each producer should produce 75 | each_producer_count = int(total_rows / max_producers) 76 | 77 | producer_threads: List[threading.Thread] = [threading.Thread( 78 | target=producer, args=(each_producer_count,)) for _ in range(max_producers)] 79 | 80 | for p in producer_threads: 81 | p.start() 82 | 83 | for p in producer_threads: 84 | p.join() 85 | 86 | q.join() 87 | 88 | 89 | if __name__ == '__main__': 90 | main() 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fast SQLite Inserts 2 | 3 | To find out the fastest way to create an SQLite DB with one billion random rows. 4 | 5 | Read this blog post for the more context - [Towards Inserting One Billion Rows in SQLite Under A Minute](https://avi.im/blag/2021/fast-sqlite-inserts/) 6 | 7 | ## Leaderboard 8 | 9 | (for 100M insertions) 10 | 11 | Variant | Time 12 | ------------- | ------------- 13 | Rust | 23 seconds 14 | PyPy | 126 seconds 15 | CPython | 210 seconds 16 | 17 | ## Current Benchmark 18 | 19 | ### Python 20 | 21 | These are the current fastest CPython and PyPy numbers. 22 | 23 | ```shell 24 | $ ./bench.sh 25 | 26 | Sat May 8 19:42:44 IST 2021 [PYTHON] running sqlite3_opt_batched.py (100_000_000) inserts 27 | 517.53 real 508.24 user 7.35 sys 28 | 29 | Sat May 8 20:03:04 IST 2021 [PYPY] running sqlite3_opt_batched.py (100_000_000) inserts 30 | 159.70 real 153.46 user 5.81 sys 31 | ``` 32 | 33 | ### Rust 34 | 35 | These are the current fastest Rust numbers 36 | 37 | ``` 38 | Mon Nov 22 18:47:26 IST 2021 [RUST] basic_batched.rs (100_000_000) inserts 39 | 40 | real 0m23.826s 41 | user 0m21.685s 42 | sys 0m2.057s 43 | 44 | Mon Nov 22 18:47:50 IST 2021 [RUST] threaded_batched.rs (100_000_000) inserts 45 | 46 | real 0m23.070s 47 | user 0m27.512s 48 | sys 0m2.465s 49 | ``` 50 | 51 | ### In Memory 52 | 53 | Instead of writing to disk, I used a `:memory:` DB, these are the numbers 54 | 55 | ``` 56 | Mon May 10 17:40:39 IST 2021 [RUST] basic_batched.rs (100_000_000) inserts 57 | 31.38 real 30.55 user 0.56 sys 58 | 59 | Mon May 10 17:39:39 IST 2021 [RUST] threaded_batched.rs (100_000_000) inserts 60 | 28.94 real 45.02 user 2.03 sys 61 | ``` 62 | 63 | ### Busy loop time 64 | 65 | The amount of time these scripts were taking in just to run the for loops (and no SQL insertion) 66 | 67 | ``` 68 | $ ./busy.sh 69 | 70 | Sun May 9 13:16:01 IST 2021 [PYTHON] busy_loop.py (100_000_000) iterations 71 | 351.14 real 347.53 user 3.39 sys 72 | 73 | Sun May 9 13:21:52 IST 2021 [PYPY] busy_loop.py (100_000_000) iterations 74 | 81.58 real 77.73 user 3.80 sys 75 | 76 | Sun May 9 13:23:14 IST 2021 [RUST] busy.rs (100_000_000) iterations 77 | 17.97 real 16.29 user 1.67 sys 78 | 79 | Sun May 9 13:23:32 IST 2021 [RUST] threaded_busy.rs (100_000_000) iterations 80 | 7.18 real 42.52 user 7.20 sys 81 | ``` 82 | 83 | ## Community Contributions 84 | 85 | | PR | Author | Result | 86 | |---|---|---| 87 | | [#2](https://github.com/avinassh/fast-sqlite3-inserts/pull/2) | [captn3m0](https://github.com/captn3m0) | Reduced the CPython running time by half (from 7.5 minutes to 3.5 minute) | 88 | | [#12](https://github.com/avinassh/fast-sqlite3-inserts/pull/12) | [red15](https://github.com/red15) | saved 2s from Rust's running time (bringing it to 30s) | 89 | | [#19](https://github.com/avinassh/fast-sqlite3-inserts/pull/19) | [kerollmops](https://github.com/Kerollmops) | saved 5s from Rust's running time (bringing it to 23s) | 90 | 91 | 92 | ## Contributing 93 | 94 | All contributions are welcome. If you have any ideas on increasing the performance, feel free to submit a PR. You may also check the current open issues to work on. 95 | 96 | ## License 97 | 98 | Released under MIT License. Check `LICENSE` file more info. 99 | -------------------------------------------------------------------------------- /src/bin/threaded_str_batched.rs: -------------------------------------------------------------------------------- 1 | //! batched, prepared statements and also threaded 2 | //! 3 | //! This version is a combination of threaded_batched.rs and basic_batched_wp.rs, so we have threads to 4 | //! handle writing to SQLite and generating data. The difference is, we build a long string to make 5 | //! a single insertion. Checking `basic_batched_wp.rs` would help 6 | //! 7 | //! previous: threaded_batched.rs 8 | 9 | use rusqlite::Connection; 10 | use std::sync::mpsc; 11 | use std::sync::mpsc::{Receiver, Sender}; 12 | use std::thread; 13 | 14 | use fast_sqlite3_inserts as common; 15 | 16 | fn consumer(rx: Receiver) { 17 | let mut conn = Connection::open("threaded_str_batched.db").unwrap(); 18 | conn.execute_batch( 19 | "PRAGMA journal_mode = OFF; 20 | PRAGMA synchronous = 0; 21 | PRAGMA cache_size = 1000000; 22 | PRAGMA locking_mode = EXCLUSIVE; 23 | PRAGMA temp_store = MEMORY;", 24 | ) 25 | .expect("PRAGMA"); 26 | conn.execute( 27 | "CREATE TABLE IF NOT EXISTS user ( 28 | id INTEGER not null primary key, 29 | area CHAR(6), 30 | age INTEGER not null, 31 | active INTEGER not null)", 32 | [], 33 | ) 34 | .unwrap(); 35 | let tx = conn.transaction().unwrap(); 36 | for stmt in rx { 37 | tx.execute(&*stmt, []).unwrap(); 38 | } 39 | tx.commit().unwrap(); 40 | } 41 | 42 | fn producer(tx: Sender, count: i64) { 43 | let min_batch_size = 1_000_000; 44 | for _ in 0..(count / min_batch_size) { 45 | let mut stmt = "INSERT INTO user VALUES".to_owned(); 46 | let with_area = common::get_random_bool(); 47 | let age = common::get_random_age(); 48 | let is_active = common::get_random_active(); 49 | for _ in 0..min_batch_size { 50 | if with_area { 51 | let area_code = common::get_random_area_code(); 52 | let params = format!(" (NULL, {}, {}, {}),", area_code.as_str(), age, is_active); 53 | stmt.push_str(¶ms); 54 | } else { 55 | let params = format!(" (NULL, NULL, {}, {}),", age, is_active); 56 | stmt.push_str(¶ms); 57 | } 58 | } 59 | // at the end, we end up a with string which looks like: 60 | // 61 | // INSERT INTO user VALUES (NULL, NULL, 5, 1), (NULL, 1, 5, 1), (NULL, 1, 5, 1), 62 | // 63 | // Notice the `,` at the, which we will replace it with `;` 64 | stmt.pop(); 65 | stmt.push(';'); 66 | tx.send(stmt).unwrap(); 67 | } 68 | } 69 | 70 | fn main() { 71 | let (tx, rx): (Sender, Receiver) = mpsc::channel(); 72 | 73 | // lets launch the consumer 74 | let consumer_handle = thread::spawn(move || consumer(rx)); 75 | 76 | let cpu_count = num_cpus::get(); 77 | let total_rows = 100_000_000; 78 | let each_producer_count = (total_rows / cpu_count) as i64; 79 | let mut handles = Vec::with_capacity(cpu_count); 80 | for _ in 0..cpu_count { 81 | let thread_tx = tx.clone(); 82 | handles.push(thread::spawn(move || { 83 | producer(thread_tx, each_producer_count.clone()) 84 | })) 85 | } 86 | for t in handles { 87 | t.join().unwrap(); 88 | } 89 | drop(tx); 90 | // wait till consumer is exited 91 | consumer_handle.join().unwrap(); 92 | } 93 | -------------------------------------------------------------------------------- /src/bin/basic_batched.rs: -------------------------------------------------------------------------------- 1 | //! batched and prepared statements 2 | //! 3 | //! This builds upon basic_prep, however we do batched insertions. Each batch is is of size 50. 4 | //! 5 | //! This is second fastest version in rust. 6 | //! 7 | //! previous: basic_prep.rs 8 | //! next: basic_batched_wp.rs 9 | 10 | use rusqlite::{Connection, ToSql, Transaction}; 11 | 12 | use crate::common::AreaCode; 13 | use fast_sqlite3_inserts as common; 14 | 15 | fn faker_wrapper(mut conn: Connection, count: i64) { 16 | let tx = conn.transaction().unwrap(); 17 | faker(&tx, count); 18 | tx.commit().unwrap(); 19 | } 20 | 21 | fn faker(tx: &Transaction, count: i64) { 22 | // that is, we will batch 50 inserts of rows at once 23 | let min_batch_size: i64 = 50; 24 | if count < min_batch_size { 25 | panic!("count cant be less than min batch size"); 26 | } 27 | 28 | // the way this works is 29 | // 1. we build a prepared statement and cache it so that it can be re-used. We build two of those 30 | // one for insertions with area and another for without area code. 31 | // 32 | // 2. Then we will build the parameters which can be passed to these prepared statements. 33 | // 3. Execute 34 | // 4. ??? 35 | // 5. Profit 36 | 37 | // we will build parameters to pass to prepared statements 38 | // jeez, refactor this! 39 | let mut with_area_params = " (NULL, ?, ?, ?),".repeat(min_batch_size as usize); 40 | with_area_params.pop(); 41 | let with_area_params = with_area_params.as_str(); 42 | let mut without_area_params = " (NULL, NULL, ?, ?),".repeat(min_batch_size as usize); 43 | without_area_params.pop(); 44 | let without_area_params = without_area_params.as_str(); 45 | let st1 = format!("INSERT INTO user VALUES {}", with_area_params); 46 | let st2 = format!("INSERT INTO user VALUES {}", without_area_params); 47 | 48 | let mut stmt_with_area = tx.prepare_cached(st1.as_str()).unwrap(); 49 | let mut stmt = tx.prepare_cached(st2.as_str()).unwrap(); 50 | for _ in 0..(count / min_batch_size) { 51 | let with_area = common::get_random_bool(); 52 | let age = common::get_random_age(); 53 | let is_active = common::get_random_active(); 54 | let mut param_values: Vec<_> = Vec::new(); 55 | if with_area { 56 | // lets prepare the batch 57 | let mut vector = Vec::<(AreaCode, i8, i8)>::new(); 58 | for _ in 0..min_batch_size { 59 | let area_code = common::get_random_area_code(); 60 | vector.push((area_code, age, is_active)); 61 | } 62 | for batch in vector.iter() { 63 | param_values.push(&batch.0 as &dyn ToSql); 64 | param_values.push(&batch.1 as &dyn ToSql); 65 | param_values.push(&batch.2 as &dyn ToSql); 66 | } 67 | stmt_with_area.execute(&*param_values).unwrap(); 68 | } else { 69 | // lets prepare the batch 70 | let mut vector = Vec::<(i8, i8)>::new(); 71 | for _ in 0..min_batch_size { 72 | vector.push((age, is_active)); 73 | } 74 | for batch in vector.iter() { 75 | param_values.push(&batch.0 as &dyn ToSql); 76 | param_values.push(&batch.1 as &dyn ToSql); 77 | } 78 | stmt.execute(&*param_values).unwrap(); 79 | } 80 | } 81 | } 82 | 83 | fn main() { 84 | let conn = Connection::open("basic_batched.db").unwrap(); 85 | conn.execute_batch( 86 | "PRAGMA journal_mode = OFF; 87 | PRAGMA synchronous = 0; 88 | PRAGMA cache_size = 1000000; 89 | PRAGMA locking_mode = EXCLUSIVE; 90 | PRAGMA temp_store = MEMORY;", 91 | ) 92 | .expect("PRAGMA"); 93 | conn.execute( 94 | "CREATE TABLE IF NOT EXISTS user ( 95 | id INTEGER not null primary key, 96 | area CHAR(6), 97 | age INTEGER not null, 98 | active INTEGER not null)", 99 | [], 100 | ) 101 | .unwrap(); 102 | faker_wrapper(conn, 100_000_000) 103 | } 104 | -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # runs each of the scripts one after another, prints the measurements to stdout 3 | export TZ=":Asia/Kolkata" 4 | 5 | # benching naive version 6 | rm -rf naive.db naive.db-shm naive.db-wal 7 | echo "$(date)" "[PYTHON] running naive.py (10_000_000) inserts" 8 | time python3 naive.py 9 | # lets verify the data exists 10 | if [[ $(sqlite3 naive.db "select count(*) from user";) != 10000000 ]]; then 11 | echo "data verification failed" 12 | fi 13 | 14 | # benching naive batched 15 | rm -rf naive_batched.db naive_batched.db-shm naive_batched.db-wal 16 | echo 17 | echo "$(date)" "[PYTHON] running naive_batched.py (10_000_000) inserts" 18 | time python3 naive_batched.py 19 | if [[ $(sqlite3 naive_batched.db "select count(*) from user";) != 10000000 ]]; then 20 | echo "data verification failed" 21 | fi 22 | 23 | # benching sqlite3 optimized 24 | rm -rf sqlite3_opt.db sqlite3_opt.db-shm sqlite3_opt.db-wal 25 | echo 26 | echo "$(date)" "[PYTHON] running sqlite3_opt.py (100_000_000) inserts" 27 | time python3 sqlite3_opt.py 28 | if [[ $(sqlite3 sqlite3_opt.db "select count(*) from user";) != 100000000 ]]; then 29 | echo "data verification failed" 30 | fi 31 | 32 | # benching sqlite3 optimized on PYPY 33 | rm -rf sqlite3_opt.db sqlite3_opt.db-shm sqlite3_opt.db-wal 34 | echo 35 | echo "$(date)" "[PYPY] running sqlite3_opt.py (100_000_000) inserts" 36 | time pypy3 sqlite3_opt.py 37 | if [[ $(sqlite3 sqlite3_opt.db "select count(*) from user";) != 100000000 ]]; then 38 | echo "data verification failed" 39 | fi 40 | 41 | # benching sqlite3 optimized and batched 42 | rm -rf sqlite3_opt_batched.db sqlite3_opt_batched.db-shm sqlite3_opt_batched.db-wal 43 | echo 44 | echo "$(date)" "[PYTHON] running sqlite3_opt_batched.py (100_000_000) inserts" 45 | time python3 sqlite3_opt_batched.py 46 | if [[ $(sqlite3 sqlite3_opt_batched.db "select count(*) from user";) != 100000000 ]]; then 47 | echo "data verification failed" 48 | fi 49 | 50 | # benching sqlite3 optimized, batched and threaded 51 | rm -rf threaded_batched.db threaded_batched.db-shm threaded_batched.db-wal 52 | echo 53 | echo "$(date)" "[PYTHON] running threaded_batched.py (100_000_000) inserts" 54 | time python3 threaded_batched.py 55 | # this will fail anyways 56 | if [[ $(sqlite3 threaded_batched.db "select count(*) from user";) != 100000000 ]]; then 57 | echo "data verification failed" 58 | fi 59 | 60 | # benching sqlite3 optimized, batched, single threaded but on pypy 61 | rm -rf sqlite3_opt_batched.db sqlite3_opt_batched.db-shm sqlite3_opt_batched.db-wal 62 | echo 63 | echo "$(date)" "[PYPY] running sqlite3_opt_batched.py (100_000_000) inserts" 64 | time pypy3 sqlite3_opt_batched.py 65 | if [[ $(sqlite3 sqlite3_opt_batched.db "select count(*) from user";) != 100000000 ]]; then 66 | echo "data verification failed" 67 | fi 68 | 69 | # benching sqlite3 optimized, batched, threaded but on pypy 70 | rm -rf threaded_batched.db threaded_batched.db-shm threaded_batched.db-wal 71 | echo 72 | echo "$(date)" "[PYPY] running threaded_batched.py (100_000_000) inserts" 73 | time pypy3 threaded_batched.py 74 | # this will fail anyways 75 | if [[ $(sqlite3 threaded_batched.db "select count(*) from user";) != 100000000 ]]; then 76 | echo "data verification failed" 77 | fi 78 | 79 | # benching with all prev sqlite optimisations, but on rust with sqlx async 80 | rm -rf basic_async.db basic_async.db-shm basic_async.db-wal 81 | cargo build --release --quiet --bin basic_async 82 | echo "$(date)" "[RUST] basic_async.rs (100_000_000) inserts" 83 | time ./target/release/basic_async 84 | 85 | # benching with all prev sqlite optimisations, but on rust with rusqlite 86 | rm -rf basic.db basic.db-shm basic.db-wal 87 | cargo build --release --quiet --bin basic 88 | echo "$(date)" "[RUST] basic.rs (100_000_000) inserts" 89 | time ./target/release/basic 90 | 91 | # benching with all prev sqlite optimisations, but on rust with rusqlite with batched inserts where 92 | # each batch is a really large ass string 93 | rm -rf basic_batched_wp.db basic_batched_wp.db-shm basic_batched_wp.db-wal 94 | cargo build --release --quiet --bin basic_batched_wp 95 | echo "$(date)" "[RUST] basic_batched_wp.rs (100_000_000) inserts" 96 | time ./target/release/basic_batched_wp 97 | 98 | # just like the previous version, so really bad. 99 | rm -rf threaded_str_batched.db threaded_str_batched.db-shm threaded_str_batched.db-wal 100 | cargo build --release --quiet --bin threaded_str_batched 101 | echo "$(date)" "[RUST] threaded_str_batched.rs (100_000_000) inserts" 102 | time ./target/release/threaded_str_batched 103 | 104 | 105 | # benching with all prev sqlite optimisations, but on rust with rusqlite with inserts where 106 | # each batch is a proper prepared statement 107 | rm -rf basic_prep.db basic_prep.db-shm basic_prep.db-wal 108 | cargo build --release --quiet --bin basic_prep 109 | echo "$(date)" "[RUST] basic_prep.rs (100_000_000) inserts" 110 | time ./target/release/basic_prep 111 | 112 | # benching with all prev sqlite optimisations, but on rust with rusqlite with batched inserts where 113 | # each batch is a proper prepared statement 114 | rm -rf basic_batched.db basic_batched.db-shm basic_batched.db-wal 115 | cargo build --release --quiet --bin basic_batched 116 | echo "$(date)" "[RUST] basic_batched.rs (100_000_000) inserts" 117 | time ./target/release/basic_batched 118 | 119 | # previous version but threaded 120 | rm -rf threaded_batched.db threaded_batched.db-shm threaded_batched.db-wal 121 | cargo build --release --quiet --bin threaded_batched 122 | echo "$(date)" "[RUST] threaded_batched.rs (100_000_000) inserts" 123 | time ./target/release/threaded_batched 124 | -------------------------------------------------------------------------------- /src/bin/threaded_batched.rs: -------------------------------------------------------------------------------- 1 | //! batched, prepared statements and also threaded 2 | //! 3 | //! This builds upon basic_batched version and very similar to the python counterpart `threaded_batched.py` 4 | //! 5 | //! We have a channel, spawn a single writer thread which consumes from queue and writes to SQLite. 6 | //! Then we spawn few more producer threads which generate the data, push to channel. 7 | //! 8 | //! previous: basic_batched.rs 9 | //! next: threaded_str_batched.rs 10 | 11 | use rusqlite::{Connection, ToSql}; 12 | use std::sync::mpsc; 13 | use std::sync::mpsc::{Receiver, Sender}; 14 | use std::thread; 15 | 16 | use crate::common::AreaCode; 17 | use fast_sqlite3_inserts as common; 18 | 19 | static MIN_BATCH_SIZE: i64 = 50; 20 | 21 | enum ParamValues { 22 | WithArea(Vec<(AreaCode, i8, i8)>), 23 | WithoutArea(Vec<(i8, i8)>), 24 | } 25 | 26 | fn consumer(rx: Receiver) { 27 | let mut conn = Connection::open("threaded_batched.db").unwrap(); 28 | conn.execute_batch( 29 | "PRAGMA journal_mode = OFF; 30 | PRAGMA synchronous = 0; 31 | PRAGMA cache_size = 1000000; 32 | PRAGMA locking_mode = EXCLUSIVE; 33 | PRAGMA temp_store = MEMORY;", 34 | ) 35 | .expect("PRAGMA"); 36 | conn.execute( 37 | "CREATE TABLE IF NOT EXISTS user ( 38 | id INTEGER not null primary key, 39 | area CHAR(6), 40 | age INTEGER not null, 41 | active INTEGER not null)", 42 | [], 43 | ) 44 | .unwrap(); 45 | let tx = conn.transaction().unwrap(); 46 | { 47 | // TODO: refactor and DRY from basic_batched 48 | // jeez, refactor this! 49 | // this is very similar to the code from basic_batched, check that file to understand 50 | // whats happening here. 51 | let mut with_area_params = " (NULL, ?, ?, ?),".repeat(MIN_BATCH_SIZE as usize); 52 | with_area_params.pop(); 53 | let with_area_params = with_area_params.as_str(); 54 | let mut without_area_params = " (NULL, NULL, ?, ?),".repeat(MIN_BATCH_SIZE as usize); 55 | without_area_params.pop(); 56 | let without_area_params = without_area_params.as_str(); 57 | let st1 = format!("INSERT INTO user VALUES {}", with_area_params); 58 | let st2 = format!("INSERT INTO user VALUES {}", without_area_params); 59 | 60 | let mut stmt_with_area = tx.prepare_cached(st1.as_str()).unwrap(); 61 | let mut stmt_without_area = tx.prepare_cached(st2.as_str()).unwrap(); 62 | for param_values in rx { 63 | let mut row_values: Vec<&dyn ToSql> = Vec::new(); 64 | match param_values { 65 | ParamValues::WithArea(values) => { 66 | for batch in values.iter() { 67 | row_values.push(&batch.0 as &dyn ToSql); 68 | row_values.push(&batch.1 as &dyn ToSql); 69 | row_values.push(&batch.2 as &dyn ToSql); 70 | } 71 | stmt_with_area.execute(&*row_values).unwrap(); 72 | } 73 | ParamValues::WithoutArea(values) => { 74 | for batch in values.iter() { 75 | row_values.push(&batch.0 as &dyn ToSql); 76 | row_values.push(&batch.1 as &dyn ToSql); 77 | } 78 | stmt_without_area.execute(&*row_values).unwrap(); 79 | } 80 | } 81 | } 82 | } 83 | tx.commit().unwrap(); 84 | } 85 | 86 | fn producer(tx: Sender, count: i64) { 87 | if count < MIN_BATCH_SIZE { 88 | panic!("count cant be less than min batch size"); 89 | } 90 | for _ in 0..(count / MIN_BATCH_SIZE) { 91 | let with_area = common::get_random_bool(); 92 | let age = common::get_random_age(); 93 | let is_active = common::get_random_active(); 94 | let mut param_values: Vec<_> = Vec::new(); 95 | if with_area { 96 | // lets prepare the batch 97 | let mut vector = Vec::<(AreaCode, i8, i8)>::new(); 98 | for _ in 0..MIN_BATCH_SIZE { 99 | let area_code = common::get_random_area_code(); 100 | vector.push((area_code, age, is_active)); 101 | } 102 | for batch in vector.iter() { 103 | param_values.push(&batch.0 as &dyn ToSql); 104 | param_values.push(&batch.1 as &dyn ToSql); 105 | param_values.push(&batch.2 as &dyn ToSql); 106 | } 107 | // send the values 108 | tx.send(ParamValues::WithArea(vector)).unwrap(); 109 | } else { 110 | // lets prepare the batch 111 | let mut vector = Vec::<(i8, i8)>::new(); 112 | for _ in 0..MIN_BATCH_SIZE { 113 | vector.push((age, is_active)); 114 | } 115 | for batch in vector.iter() { 116 | param_values.push(&batch.0 as &dyn ToSql); 117 | param_values.push(&batch.1 as &dyn ToSql); 118 | } 119 | // send the values 120 | tx.send(ParamValues::WithoutArea(vector)).unwrap(); 121 | } 122 | } 123 | } 124 | 125 | fn main() { 126 | // setup the DB and tables 127 | let (tx, rx): (Sender, Receiver) = mpsc::channel(); 128 | // lets launch the consumer 129 | let consumer_handle = thread::spawn(|| consumer(rx)); 130 | 131 | let cpu_count = num_cpus::get(); 132 | let total_rows = 100_000_000; 133 | let each_producer_count = (total_rows / cpu_count) as i64; 134 | let mut handles = Vec::with_capacity(cpu_count); 135 | for _ in 0..cpu_count { 136 | let thread_tx = tx.clone(); 137 | handles.push(thread::spawn(move || { 138 | producer(thread_tx, each_producer_count.clone()) 139 | })) 140 | } 141 | for t in handles { 142 | t.join().unwrap(); 143 | } 144 | drop(tx); 145 | // wait till consumer is exited 146 | consumer_handle.join().unwrap(); 147 | } 148 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.4.7" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" 10 | 11 | [[package]] 12 | name = "ahash" 13 | version = "0.7.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "7f200cbb1e856866d9eade941cf3aa0c5d7dd36f74311c4273b494f4ef036957" 16 | dependencies = [ 17 | "getrandom", 18 | "once_cell", 19 | "version_check", 20 | ] 21 | 22 | [[package]] 23 | name = "aho-corasick" 24 | version = "0.7.18" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 27 | dependencies = [ 28 | "memchr", 29 | ] 30 | 31 | [[package]] 32 | name = "arrayvec" 33 | version = "0.5.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 36 | 37 | [[package]] 38 | name = "atoi" 39 | version = "0.4.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" 42 | dependencies = [ 43 | "num-traits", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.0.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 51 | 52 | [[package]] 53 | name = "bitflags" 54 | version = "1.2.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 57 | 58 | [[package]] 59 | name = "bitvec" 60 | version = "0.19.5" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" 63 | dependencies = [ 64 | "funty", 65 | "radium", 66 | "tap", 67 | "wyz", 68 | ] 69 | 70 | [[package]] 71 | name = "block-buffer" 72 | version = "0.9.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 75 | dependencies = [ 76 | "generic-array", 77 | ] 78 | 79 | [[package]] 80 | name = "build_const" 81 | version = "0.2.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" 84 | 85 | [[package]] 86 | name = "bumpalo" 87 | version = "3.6.1" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" 90 | 91 | [[package]] 92 | name = "byteorder" 93 | version = "1.4.3" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 96 | 97 | [[package]] 98 | name = "bytes" 99 | version = "1.0.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 102 | 103 | [[package]] 104 | name = "cc" 105 | version = "1.0.67" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" 108 | 109 | [[package]] 110 | name = "cfg-if" 111 | version = "1.0.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 114 | 115 | [[package]] 116 | name = "core-foundation" 117 | version = "0.9.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" 120 | dependencies = [ 121 | "core-foundation-sys", 122 | "libc", 123 | ] 124 | 125 | [[package]] 126 | name = "core-foundation-sys" 127 | version = "0.8.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" 130 | 131 | [[package]] 132 | name = "cpufeatures" 133 | version = "0.1.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "dec1028182c380cc45a2e2c5ec841134f2dfd0f8f5f0a5bcd68004f81b5efdf4" 136 | dependencies = [ 137 | "libc", 138 | ] 139 | 140 | [[package]] 141 | name = "crc" 142 | version = "1.8.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" 145 | dependencies = [ 146 | "build_const", 147 | ] 148 | 149 | [[package]] 150 | name = "crossbeam-channel" 151 | version = "0.5.1" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 154 | dependencies = [ 155 | "cfg-if", 156 | "crossbeam-utils", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-queue" 161 | version = "0.3.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" 164 | dependencies = [ 165 | "cfg-if", 166 | "crossbeam-utils", 167 | ] 168 | 169 | [[package]] 170 | name = "crossbeam-utils" 171 | version = "0.8.4" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" 174 | dependencies = [ 175 | "autocfg", 176 | "cfg-if", 177 | "lazy_static", 178 | ] 179 | 180 | [[package]] 181 | name = "digest" 182 | version = "0.9.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 185 | dependencies = [ 186 | "generic-array", 187 | ] 188 | 189 | [[package]] 190 | name = "dotenv" 191 | version = "0.15.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 194 | 195 | [[package]] 196 | name = "either" 197 | version = "1.6.1" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 200 | 201 | [[package]] 202 | name = "fallible-iterator" 203 | version = "0.2.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 206 | 207 | [[package]] 208 | name = "fallible-streaming-iterator" 209 | version = "0.1.9" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 212 | 213 | [[package]] 214 | name = "fast-sqlite3-inserts" 215 | version = "0.1.0" 216 | dependencies = [ 217 | "fastrand", 218 | "num_cpus", 219 | "rand", 220 | "rusqlite", 221 | "sqlx", 222 | "tinystr", 223 | "tokio", 224 | ] 225 | 226 | [[package]] 227 | name = "fastrand" 228 | version = "1.5.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" 231 | dependencies = [ 232 | "instant", 233 | ] 234 | 235 | [[package]] 236 | name = "foreign-types" 237 | version = "0.3.2" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 240 | dependencies = [ 241 | "foreign-types-shared", 242 | ] 243 | 244 | [[package]] 245 | name = "foreign-types-shared" 246 | version = "0.1.1" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 249 | 250 | [[package]] 251 | name = "form_urlencoded" 252 | version = "1.0.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 255 | dependencies = [ 256 | "matches", 257 | "percent-encoding", 258 | ] 259 | 260 | [[package]] 261 | name = "funty" 262 | version = "1.1.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" 265 | 266 | [[package]] 267 | name = "futures" 268 | version = "0.3.14" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" 271 | dependencies = [ 272 | "futures-channel", 273 | "futures-core", 274 | "futures-executor", 275 | "futures-io", 276 | "futures-sink", 277 | "futures-task", 278 | "futures-util", 279 | ] 280 | 281 | [[package]] 282 | name = "futures-channel" 283 | version = "0.3.14" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" 286 | dependencies = [ 287 | "futures-core", 288 | "futures-sink", 289 | ] 290 | 291 | [[package]] 292 | name = "futures-core" 293 | version = "0.3.14" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" 296 | 297 | [[package]] 298 | name = "futures-executor" 299 | version = "0.3.14" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" 302 | dependencies = [ 303 | "futures-core", 304 | "futures-task", 305 | "futures-util", 306 | ] 307 | 308 | [[package]] 309 | name = "futures-io" 310 | version = "0.3.14" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" 313 | 314 | [[package]] 315 | name = "futures-macro" 316 | version = "0.3.14" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" 319 | dependencies = [ 320 | "proc-macro-hack", 321 | "proc-macro2", 322 | "quote", 323 | "syn", 324 | ] 325 | 326 | [[package]] 327 | name = "futures-sink" 328 | version = "0.3.14" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" 331 | 332 | [[package]] 333 | name = "futures-task" 334 | version = "0.3.14" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" 337 | 338 | [[package]] 339 | name = "futures-util" 340 | version = "0.3.14" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" 343 | dependencies = [ 344 | "futures-channel", 345 | "futures-core", 346 | "futures-io", 347 | "futures-macro", 348 | "futures-sink", 349 | "futures-task", 350 | "memchr", 351 | "pin-project-lite", 352 | "pin-utils", 353 | "proc-macro-hack", 354 | "proc-macro-nested", 355 | "slab", 356 | ] 357 | 358 | [[package]] 359 | name = "generic-array" 360 | version = "0.14.4" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 363 | dependencies = [ 364 | "typenum", 365 | "version_check", 366 | ] 367 | 368 | [[package]] 369 | name = "getrandom" 370 | version = "0.2.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 373 | dependencies = [ 374 | "cfg-if", 375 | "libc", 376 | "wasi", 377 | ] 378 | 379 | [[package]] 380 | name = "hashbrown" 381 | version = "0.9.1" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 384 | dependencies = [ 385 | "ahash 0.4.7", 386 | ] 387 | 388 | [[package]] 389 | name = "hashbrown" 390 | version = "0.11.2" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 393 | dependencies = [ 394 | "ahash 0.7.2", 395 | ] 396 | 397 | [[package]] 398 | name = "hashlink" 399 | version = "0.6.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" 402 | dependencies = [ 403 | "hashbrown 0.9.1", 404 | ] 405 | 406 | [[package]] 407 | name = "hashlink" 408 | version = "0.7.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" 411 | dependencies = [ 412 | "hashbrown 0.11.2", 413 | ] 414 | 415 | [[package]] 416 | name = "heck" 417 | version = "0.3.2" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" 420 | dependencies = [ 421 | "unicode-segmentation", 422 | ] 423 | 424 | [[package]] 425 | name = "hermit-abi" 426 | version = "0.1.18" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 429 | dependencies = [ 430 | "libc", 431 | ] 432 | 433 | [[package]] 434 | name = "hex" 435 | version = "0.4.3" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 438 | 439 | [[package]] 440 | name = "idna" 441 | version = "0.2.3" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 444 | dependencies = [ 445 | "matches", 446 | "unicode-bidi", 447 | "unicode-normalization", 448 | ] 449 | 450 | [[package]] 451 | name = "instant" 452 | version = "0.1.9" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 455 | dependencies = [ 456 | "cfg-if", 457 | ] 458 | 459 | [[package]] 460 | name = "itoa" 461 | version = "0.4.7" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 464 | 465 | [[package]] 466 | name = "js-sys" 467 | version = "0.3.50" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" 470 | dependencies = [ 471 | "wasm-bindgen", 472 | ] 473 | 474 | [[package]] 475 | name = "lazy_static" 476 | version = "1.4.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 479 | 480 | [[package]] 481 | name = "lexical-core" 482 | version = "0.7.6" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 485 | dependencies = [ 486 | "arrayvec", 487 | "bitflags", 488 | "cfg-if", 489 | "ryu", 490 | "static_assertions", 491 | ] 492 | 493 | [[package]] 494 | name = "libc" 495 | version = "0.2.94" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" 498 | 499 | [[package]] 500 | name = "libsqlite3-sys" 501 | version = "0.22.2" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" 504 | dependencies = [ 505 | "cc", 506 | "pkg-config", 507 | "vcpkg", 508 | ] 509 | 510 | [[package]] 511 | name = "lock_api" 512 | version = "0.4.4" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" 515 | dependencies = [ 516 | "scopeguard", 517 | ] 518 | 519 | [[package]] 520 | name = "log" 521 | version = "0.4.14" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 524 | dependencies = [ 525 | "cfg-if", 526 | ] 527 | 528 | [[package]] 529 | name = "maplit" 530 | version = "1.0.2" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 533 | 534 | [[package]] 535 | name = "matches" 536 | version = "0.1.8" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 539 | 540 | [[package]] 541 | name = "memchr" 542 | version = "2.4.0" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 545 | 546 | [[package]] 547 | name = "mio" 548 | version = "0.7.11" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" 551 | dependencies = [ 552 | "libc", 553 | "log", 554 | "miow", 555 | "ntapi", 556 | "winapi", 557 | ] 558 | 559 | [[package]] 560 | name = "miow" 561 | version = "0.3.7" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 564 | dependencies = [ 565 | "winapi", 566 | ] 567 | 568 | [[package]] 569 | name = "native-tls" 570 | version = "0.2.7" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" 573 | dependencies = [ 574 | "lazy_static", 575 | "libc", 576 | "log", 577 | "openssl", 578 | "openssl-probe", 579 | "openssl-sys", 580 | "schannel", 581 | "security-framework", 582 | "security-framework-sys", 583 | "tempfile", 584 | ] 585 | 586 | [[package]] 587 | name = "nom" 588 | version = "6.1.2" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" 591 | dependencies = [ 592 | "bitvec", 593 | "funty", 594 | "lexical-core", 595 | "memchr", 596 | "version_check", 597 | ] 598 | 599 | [[package]] 600 | name = "ntapi" 601 | version = "0.3.6" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 604 | dependencies = [ 605 | "winapi", 606 | ] 607 | 608 | [[package]] 609 | name = "num-traits" 610 | version = "0.2.14" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 613 | dependencies = [ 614 | "autocfg", 615 | ] 616 | 617 | [[package]] 618 | name = "num_cpus" 619 | version = "1.13.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 622 | dependencies = [ 623 | "hermit-abi", 624 | "libc", 625 | ] 626 | 627 | [[package]] 628 | name = "once_cell" 629 | version = "1.8.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 632 | 633 | [[package]] 634 | name = "opaque-debug" 635 | version = "0.3.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 638 | 639 | [[package]] 640 | name = "openssl" 641 | version = "0.10.34" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "6d7830286ad6a3973c0f1d9b73738f69c76b739301d0229c4b96501695cbe4c8" 644 | dependencies = [ 645 | "bitflags", 646 | "cfg-if", 647 | "foreign-types", 648 | "libc", 649 | "once_cell", 650 | "openssl-sys", 651 | ] 652 | 653 | [[package]] 654 | name = "openssl-probe" 655 | version = "0.1.2" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 658 | 659 | [[package]] 660 | name = "openssl-sys" 661 | version = "0.9.63" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "b6b0d6fb7d80f877617dfcb014e605e2b5ab2fb0afdf27935219bb6bd984cb98" 664 | dependencies = [ 665 | "autocfg", 666 | "cc", 667 | "libc", 668 | "pkg-config", 669 | "vcpkg", 670 | ] 671 | 672 | [[package]] 673 | name = "parking_lot" 674 | version = "0.11.1" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 677 | dependencies = [ 678 | "instant", 679 | "lock_api", 680 | "parking_lot_core", 681 | ] 682 | 683 | [[package]] 684 | name = "parking_lot_core" 685 | version = "0.8.3" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 688 | dependencies = [ 689 | "cfg-if", 690 | "instant", 691 | "libc", 692 | "redox_syscall", 693 | "smallvec", 694 | "winapi", 695 | ] 696 | 697 | [[package]] 698 | name = "percent-encoding" 699 | version = "2.1.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 702 | 703 | [[package]] 704 | name = "pin-project-lite" 705 | version = "0.2.6" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" 708 | 709 | [[package]] 710 | name = "pin-utils" 711 | version = "0.1.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 714 | 715 | [[package]] 716 | name = "pkg-config" 717 | version = "0.3.19" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 720 | 721 | [[package]] 722 | name = "ppv-lite86" 723 | version = "0.2.10" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 726 | 727 | [[package]] 728 | name = "proc-macro-hack" 729 | version = "0.5.19" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 732 | 733 | [[package]] 734 | name = "proc-macro-nested" 735 | version = "0.1.7" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 738 | 739 | [[package]] 740 | name = "proc-macro2" 741 | version = "1.0.26" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" 744 | dependencies = [ 745 | "unicode-xid", 746 | ] 747 | 748 | [[package]] 749 | name = "quote" 750 | version = "1.0.9" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 753 | dependencies = [ 754 | "proc-macro2", 755 | ] 756 | 757 | [[package]] 758 | name = "radium" 759 | version = "0.5.3" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" 762 | 763 | [[package]] 764 | name = "rand" 765 | version = "0.8.3" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 768 | dependencies = [ 769 | "libc", 770 | "rand_chacha", 771 | "rand_core", 772 | "rand_hc", 773 | ] 774 | 775 | [[package]] 776 | name = "rand_chacha" 777 | version = "0.3.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 780 | dependencies = [ 781 | "ppv-lite86", 782 | "rand_core", 783 | ] 784 | 785 | [[package]] 786 | name = "rand_core" 787 | version = "0.6.2" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 790 | dependencies = [ 791 | "getrandom", 792 | ] 793 | 794 | [[package]] 795 | name = "rand_hc" 796 | version = "0.3.0" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 799 | dependencies = [ 800 | "rand_core", 801 | ] 802 | 803 | [[package]] 804 | name = "redox_syscall" 805 | version = "0.2.8" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" 808 | dependencies = [ 809 | "bitflags", 810 | ] 811 | 812 | [[package]] 813 | name = "regex" 814 | version = "1.5.4" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 817 | dependencies = [ 818 | "aho-corasick", 819 | "memchr", 820 | "regex-syntax", 821 | ] 822 | 823 | [[package]] 824 | name = "regex-syntax" 825 | version = "0.6.25" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 828 | 829 | [[package]] 830 | name = "remove_dir_all" 831 | version = "0.5.3" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 834 | dependencies = [ 835 | "winapi", 836 | ] 837 | 838 | [[package]] 839 | name = "rusqlite" 840 | version = "0.25.3" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "57adcf67c8faaf96f3248c2a7b419a0dbc52ebe36ba83dd57fe83827c1ea4eb3" 843 | dependencies = [ 844 | "bitflags", 845 | "fallible-iterator", 846 | "fallible-streaming-iterator", 847 | "hashlink 0.7.0", 848 | "libsqlite3-sys", 849 | "memchr", 850 | "smallvec", 851 | ] 852 | 853 | [[package]] 854 | name = "ryu" 855 | version = "1.0.5" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 858 | 859 | [[package]] 860 | name = "schannel" 861 | version = "0.1.19" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 864 | dependencies = [ 865 | "lazy_static", 866 | "winapi", 867 | ] 868 | 869 | [[package]] 870 | name = "scopeguard" 871 | version = "1.1.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 874 | 875 | [[package]] 876 | name = "security-framework" 877 | version = "2.2.0" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" 880 | dependencies = [ 881 | "bitflags", 882 | "core-foundation", 883 | "core-foundation-sys", 884 | "libc", 885 | "security-framework-sys", 886 | ] 887 | 888 | [[package]] 889 | name = "security-framework-sys" 890 | version = "2.2.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" 893 | dependencies = [ 894 | "core-foundation-sys", 895 | "libc", 896 | ] 897 | 898 | [[package]] 899 | name = "sha2" 900 | version = "0.9.4" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "d8f6b75b17576b792bef0db1bcc4b8b8bcdf9506744cf34b974195487af6cff2" 903 | dependencies = [ 904 | "block-buffer", 905 | "cfg-if", 906 | "cpufeatures", 907 | "digest", 908 | "opaque-debug", 909 | ] 910 | 911 | [[package]] 912 | name = "signal-hook-registry" 913 | version = "1.3.0" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" 916 | dependencies = [ 917 | "libc", 918 | ] 919 | 920 | [[package]] 921 | name = "slab" 922 | version = "0.4.3" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" 925 | 926 | [[package]] 927 | name = "smallvec" 928 | version = "1.6.1" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 931 | 932 | [[package]] 933 | name = "sqlformat" 934 | version = "0.1.6" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "6d86e3c77ff882a828346ba401a7ef4b8e440df804491c6064fe8295765de71c" 937 | dependencies = [ 938 | "lazy_static", 939 | "maplit", 940 | "nom", 941 | "regex", 942 | "unicode_categories", 943 | ] 944 | 945 | [[package]] 946 | name = "sqlx" 947 | version = "0.5.2" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "d582b9bc04ec6c03084196efc42c2226b018e9941f03ee62bd88921d500917c0" 950 | dependencies = [ 951 | "sqlx-core", 952 | "sqlx-macros", 953 | ] 954 | 955 | [[package]] 956 | name = "sqlx-core" 957 | version = "0.5.2" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "de52d1d473cebb2abb79c886ef6a8023e965e34c0676a99cfeac2cc7f0fde4c1" 960 | dependencies = [ 961 | "ahash 0.7.2", 962 | "atoi", 963 | "bitflags", 964 | "byteorder", 965 | "bytes", 966 | "crc", 967 | "crossbeam-channel", 968 | "crossbeam-queue", 969 | "crossbeam-utils", 970 | "either", 971 | "futures-channel", 972 | "futures-core", 973 | "futures-util", 974 | "hashlink 0.6.0", 975 | "hex", 976 | "itoa", 977 | "libc", 978 | "libsqlite3-sys", 979 | "log", 980 | "memchr", 981 | "once_cell", 982 | "parking_lot", 983 | "percent-encoding", 984 | "sha2", 985 | "smallvec", 986 | "sqlformat", 987 | "sqlx-rt", 988 | "stringprep", 989 | "thiserror", 990 | "tokio-stream", 991 | "url", 992 | "whoami", 993 | ] 994 | 995 | [[package]] 996 | name = "sqlx-macros" 997 | version = "0.5.2" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "1a40f0be97e704d3fbf059e7e3333c3735639146a72d586c5534c70e79da88a4" 1000 | dependencies = [ 1001 | "dotenv", 1002 | "either", 1003 | "futures", 1004 | "heck", 1005 | "proc-macro2", 1006 | "quote", 1007 | "sha2", 1008 | "sqlx-core", 1009 | "sqlx-rt", 1010 | "syn", 1011 | "url", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "sqlx-rt" 1016 | version = "0.5.2" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "b6ae97ab05063ed515cdc23d90253213aa24dda0a288c5ec079af3d10f9771bc" 1019 | dependencies = [ 1020 | "native-tls", 1021 | "once_cell", 1022 | "tokio", 1023 | "tokio-native-tls", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "static_assertions" 1028 | version = "1.1.0" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1031 | 1032 | [[package]] 1033 | name = "stringprep" 1034 | version = "0.1.2" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 1037 | dependencies = [ 1038 | "unicode-bidi", 1039 | "unicode-normalization", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "syn" 1044 | version = "1.0.72" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" 1047 | dependencies = [ 1048 | "proc-macro2", 1049 | "quote", 1050 | "unicode-xid", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "tap" 1055 | version = "1.0.1" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 1058 | 1059 | [[package]] 1060 | name = "tempfile" 1061 | version = "3.2.0" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 1064 | dependencies = [ 1065 | "cfg-if", 1066 | "libc", 1067 | "rand", 1068 | "redox_syscall", 1069 | "remove_dir_all", 1070 | "winapi", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "thiserror" 1075 | version = "1.0.24" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" 1078 | dependencies = [ 1079 | "thiserror-impl", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "thiserror-impl" 1084 | version = "1.0.24" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" 1087 | dependencies = [ 1088 | "proc-macro2", 1089 | "quote", 1090 | "syn", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "tinystr" 1095 | version = "0.4.10" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "01e8700bce17d12d9653596139d85039dd63b8728839a9f2e0c540d5301ea0cd" 1098 | dependencies = [ 1099 | "tinystr-macros", 1100 | "tinystr-raw", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "tinystr-macros" 1105 | version = "0.2.0" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "5f65be51117c325c2b58eec529be7a0857d11527a9029973b58810a4c63e77a6" 1108 | dependencies = [ 1109 | "tinystr-raw", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "tinystr-raw" 1114 | version = "0.1.3" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "10f87ef8b0485e4efff5cac95608adc3251e412fef6039ecd56c5618c8003895" 1117 | 1118 | [[package]] 1119 | name = "tinyvec" 1120 | version = "1.2.0" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" 1123 | dependencies = [ 1124 | "tinyvec_macros", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "tinyvec_macros" 1129 | version = "0.1.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1132 | 1133 | [[package]] 1134 | name = "tokio" 1135 | version = "1.5.0" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" 1138 | dependencies = [ 1139 | "autocfg", 1140 | "bytes", 1141 | "libc", 1142 | "memchr", 1143 | "mio", 1144 | "num_cpus", 1145 | "once_cell", 1146 | "parking_lot", 1147 | "pin-project-lite", 1148 | "signal-hook-registry", 1149 | "tokio-macros", 1150 | "winapi", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "tokio-macros" 1155 | version = "1.1.0" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" 1158 | dependencies = [ 1159 | "proc-macro2", 1160 | "quote", 1161 | "syn", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "tokio-native-tls" 1166 | version = "0.3.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1169 | dependencies = [ 1170 | "native-tls", 1171 | "tokio", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "tokio-stream" 1176 | version = "0.1.5" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0" 1179 | dependencies = [ 1180 | "futures-core", 1181 | "pin-project-lite", 1182 | "tokio", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "typenum" 1187 | version = "1.13.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" 1190 | 1191 | [[package]] 1192 | name = "unicode-bidi" 1193 | version = "0.3.5" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" 1196 | dependencies = [ 1197 | "matches", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "unicode-normalization" 1202 | version = "0.1.17" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" 1205 | dependencies = [ 1206 | "tinyvec", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "unicode-segmentation" 1211 | version = "1.7.1" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 1214 | 1215 | [[package]] 1216 | name = "unicode-xid" 1217 | version = "0.2.2" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1220 | 1221 | [[package]] 1222 | name = "unicode_categories" 1223 | version = "0.1.1" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 1226 | 1227 | [[package]] 1228 | name = "url" 1229 | version = "2.2.2" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1232 | dependencies = [ 1233 | "form_urlencoded", 1234 | "idna", 1235 | "matches", 1236 | "percent-encoding", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "vcpkg" 1241 | version = "0.2.12" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" 1244 | 1245 | [[package]] 1246 | name = "version_check" 1247 | version = "0.9.3" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1250 | 1251 | [[package]] 1252 | name = "wasi" 1253 | version = "0.10.2+wasi-snapshot-preview1" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1256 | 1257 | [[package]] 1258 | name = "wasm-bindgen" 1259 | version = "0.2.73" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" 1262 | dependencies = [ 1263 | "cfg-if", 1264 | "wasm-bindgen-macro", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "wasm-bindgen-backend" 1269 | version = "0.2.73" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" 1272 | dependencies = [ 1273 | "bumpalo", 1274 | "lazy_static", 1275 | "log", 1276 | "proc-macro2", 1277 | "quote", 1278 | "syn", 1279 | "wasm-bindgen-shared", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "wasm-bindgen-macro" 1284 | version = "0.2.73" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" 1287 | dependencies = [ 1288 | "quote", 1289 | "wasm-bindgen-macro-support", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "wasm-bindgen-macro-support" 1294 | version = "0.2.73" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" 1297 | dependencies = [ 1298 | "proc-macro2", 1299 | "quote", 1300 | "syn", 1301 | "wasm-bindgen-backend", 1302 | "wasm-bindgen-shared", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "wasm-bindgen-shared" 1307 | version = "0.2.73" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" 1310 | 1311 | [[package]] 1312 | name = "web-sys" 1313 | version = "0.3.50" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" 1316 | dependencies = [ 1317 | "js-sys", 1318 | "wasm-bindgen", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "whoami" 1323 | version = "1.1.2" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "4abacf325c958dfeaf1046931d37f2a901b6dfe0968ee965a29e94c6766b2af6" 1326 | dependencies = [ 1327 | "wasm-bindgen", 1328 | "web-sys", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "winapi" 1333 | version = "0.3.9" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1336 | dependencies = [ 1337 | "winapi-i686-pc-windows-gnu", 1338 | "winapi-x86_64-pc-windows-gnu", 1339 | ] 1340 | 1341 | [[package]] 1342 | name = "winapi-i686-pc-windows-gnu" 1343 | version = "0.4.0" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1346 | 1347 | [[package]] 1348 | name = "winapi-x86_64-pc-windows-gnu" 1349 | version = "0.4.0" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1352 | 1353 | [[package]] 1354 | name = "wyz" 1355 | version = "0.2.0" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" 1358 | --------------------------------------------------------------------------------