├── migrations └── 20210509094817_create_user_table.sql ├── src └── bin │ ├── common.rs │ ├── 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 ├── Cargo.toml ├── Makefile ├── commons.py ├── busy_loop.py ├── LICENSE ├── naive.py ├── sqlite3_opt.py ├── naive_batched.py ├── sqlite3_opt_batched.py ├── .gitignore ├── README.md ├── threaded_batched.py ├── 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 | -------------------------------------------------------------------------------- /src/bin/common.rs: -------------------------------------------------------------------------------- 1 | use fastrand; 2 | 3 | pub fn get_random_age() -> i8 { 4 | let vs: Vec = vec![5, 10, 15]; 5 | vs[ fastrand::usize(..vs.len()) ] 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() -> String { 17 | format!("{:06}", fastrand::u32(0..999_999)) 18 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | mod common; 9 | 10 | fn faker(count: i64) { 11 | let min_batch_size = 1_000_000; 12 | for _ in 0..(count / min_batch_size) { 13 | let with_area = common::get_random_bool(); 14 | let mut current_batch = Vec::<(String, i8, i8)>::new(); 15 | for _ in 0..min_batch_size { 16 | if with_area { 17 | current_batch.push(( 18 | common::get_random_area_code(), 19 | common::get_random_age(), 20 | common::get_random_active(), 21 | )); 22 | } else { 23 | current_batch.push(( 24 | "".parse().unwrap(), 25 | common::get_random_age(), 26 | common::get_random_active(), 27 | )); 28 | } 29 | } 30 | } 31 | } 32 | 33 | fn main() { 34 | faker(100_000_000); 35 | } 36 | -------------------------------------------------------------------------------- /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/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 | mod common; 12 | 13 | fn faker(count: i64) { 14 | let min_batch_size = 1_000_000; 15 | for _ in 0..(count / min_batch_size) { 16 | let with_area = common::get_random_bool(); 17 | let mut current_batch = Vec::<(String, i8, i8)>::new(); 18 | for _ in 0..min_batch_size { 19 | if with_area { 20 | current_batch.push(( 21 | common::get_random_area_code(), 22 | common::get_random_age(), 23 | common::get_random_active(), 24 | )); 25 | } else { 26 | current_batch.push(( 27 | "".parse().unwrap(), 28 | common::get_random_age(), 29 | common::get_random_active(), 30 | )); 31 | } 32 | } 33 | } 34 | } 35 | 36 | fn multi_producers() { 37 | let cpu_count = num_cpus::get(); 38 | let total_rows = 100_000_000; 39 | let each_producer_count = (total_rows / cpu_count) as i64; 40 | let mut handles = Vec::with_capacity(cpu_count); 41 | for _ in 0..cpu_count { 42 | handles.push(thread::spawn(move || faker(each_producer_count.clone()))) 43 | } 44 | for t in handles { 45 | t.join().unwrap(); 46 | } 47 | } 48 | 49 | fn main() { 50 | multi_producers() 51 | } 52 | -------------------------------------------------------------------------------- /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 rusqlite::{params, Connection}; 10 | 11 | mod common; 12 | 13 | fn faker(mut conn: Connection, count: i64) { 14 | let tx = conn.transaction().unwrap(); 15 | for _ in 0..count { 16 | let with_area = common::get_random_bool(); 17 | let age = common::get_random_age(); 18 | let is_active = common::get_random_active(); 19 | if with_area { 20 | let area_code = common::get_random_area_code(); 21 | tx.execute( 22 | "INSERT INTO user VALUES (NULL, ?, ?, ?)", 23 | params![area_code, age, is_active], 24 | ) 25 | .unwrap(); 26 | } else { 27 | tx.execute( 28 | "INSERT INTO user VALUES (NULL, NULL, ?, ?)", 29 | params![age, is_active], 30 | ) 31 | .unwrap(); 32 | } 33 | } 34 | tx.commit().unwrap(); 35 | } 36 | 37 | fn main() { 38 | let conn = Connection::open("basic.db").unwrap(); 39 | conn.execute_batch( 40 | "PRAGMA journal_mode = OFF; 41 | PRAGMA synchronous = 0; 42 | PRAGMA cache_size = 1000000; 43 | PRAGMA locking_mode = EXCLUSIVE; 44 | PRAGMA temp_store = MEMORY;", 45 | ) 46 | .expect("PRAGMA"); 47 | conn.execute( 48 | "CREATE TABLE IF NOT EXISTS user ( 49 | id INTEGER not null primary key, 50 | area CHAR(6), 51 | age INTEGER not null, 52 | active INTEGER not null)", 53 | [], 54 | ) 55 | .unwrap(); 56 | faker(conn, 100_000_000) 57 | } 58 | -------------------------------------------------------------------------------- /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 | mod 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 | mod 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, 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 | mod 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) 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 | -------------------------------------------------------------------------------- /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 | 30 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 | Sun Aug 8 13:52:32 IST 2021 [RUST] basic_batched.rs (100_000_000) inserts 39 | 40 | real 0m32.424s 41 | user 0m30.826s 42 | sys 0m2.272s 43 | 44 | Sun Aug 8 13:53:06 IST 2021 [RUST] threaded_batched.rs (100_000_000) inserts 45 | 46 | real 0m30.094s 47 | user 0m42.704s 48 | sys 0m3.877s 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 | 90 | 91 | ## Contributing 92 | 93 | 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. 94 | 95 | ## License 96 | 97 | Released under MIT License. Check `LICENSE` file more info. 98 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | mod 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, 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 | mod common; 13 | 14 | fn faker_wrapper(mut conn: Connection, count: i64) { 15 | let tx = conn.transaction().unwrap(); 16 | faker(&tx, count); 17 | tx.commit().unwrap(); 18 | } 19 | 20 | fn faker(tx: &Transaction, count: i64) { 21 | // that is, we will batch 50 inserts of rows at once 22 | let min_batch_size: i64 = 50; 23 | if count < min_batch_size { 24 | panic!("count cant be less than min batch size"); 25 | } 26 | 27 | // the way this works is 28 | // 1. we build a prepared statement and cache it so that it can be re-used. We build two of those 29 | // one for insertions with area and another for without area code. 30 | // 31 | // 2. Then we will build the parameters which can be passed to these prepared statements. 32 | // 3. Execute 33 | // 4. ??? 34 | // 5. Profit 35 | 36 | // we will build parameters to pass to prepared statements 37 | // jeez, refactor this! 38 | let mut with_area_params = " (NULL, ?, ?, ?),".repeat(min_batch_size as usize); 39 | with_area_params.pop(); 40 | let with_area_params = with_area_params.as_str(); 41 | let mut without_area_params = " (NULL, NULL, ?, ?),".repeat(min_batch_size as usize); 42 | without_area_params.pop(); 43 | let without_area_params = without_area_params.as_str(); 44 | let st1 = format!("INSERT INTO user VALUES {}", with_area_params); 45 | let st2 = format!("INSERT INTO user VALUES {}", without_area_params); 46 | 47 | let mut stmt_with_area = tx.prepare_cached(st1.as_str()).unwrap(); 48 | let mut stmt = tx.prepare_cached(st2.as_str()).unwrap(); 49 | for _ in 0..(count / min_batch_size) { 50 | let with_area = common::get_random_bool(); 51 | let age = common::get_random_age(); 52 | let is_active = common::get_random_active(); 53 | let mut param_values: Vec<_> = Vec::new(); 54 | if with_area { 55 | // lets prepare the batch 56 | let mut vector = Vec::<(String, i8, i8)>::new(); 57 | for _ in 0..min_batch_size { 58 | let area_code = common::get_random_area_code(); 59 | vector.push((area_code, age, is_active)); 60 | } 61 | for batch in vector.iter() { 62 | param_values.push(&batch.0 as &dyn ToSql); 63 | param_values.push(&batch.1 as &dyn ToSql); 64 | param_values.push(&batch.2 as &dyn ToSql); 65 | } 66 | stmt_with_area.execute(&*param_values).unwrap(); 67 | } else { 68 | // lets prepare the batch 69 | let mut vector = Vec::<(i8, i8)>::new(); 70 | for _ in 0..min_batch_size { 71 | vector.push((age, is_active)); 72 | } 73 | for batch in vector.iter() { 74 | param_values.push(&batch.0 as &dyn ToSql); 75 | param_values.push(&batch.1 as &dyn ToSql); 76 | } 77 | stmt.execute(&*param_values).unwrap(); 78 | } 79 | } 80 | } 81 | 82 | fn main() { 83 | let conn = Connection::open("basic_batched.db").unwrap(); 84 | conn.execute_batch( 85 | "PRAGMA journal_mode = OFF; 86 | PRAGMA synchronous = 0; 87 | PRAGMA cache_size = 1000000; 88 | PRAGMA locking_mode = EXCLUSIVE; 89 | PRAGMA temp_store = MEMORY;", 90 | ) 91 | .expect("PRAGMA"); 92 | conn.execute( 93 | "CREATE TABLE IF NOT EXISTS user ( 94 | id INTEGER not null primary key, 95 | area CHAR(6), 96 | age INTEGER not null, 97 | active INTEGER not null)", 98 | [], 99 | ) 100 | .unwrap(); 101 | faker_wrapper(conn, 100_000_000) 102 | } 103 | -------------------------------------------------------------------------------- /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 | mod common; 17 | 18 | static MIN_BATCH_SIZE: i64 = 50; 19 | 20 | enum ParamValues { 21 | WithArea(Vec<(String, i8, i8)>), 22 | WithoutArea(Vec<(i8, i8)>), 23 | } 24 | 25 | fn consumer(rx: Receiver) { 26 | let mut conn = Connection::open("threaded_batched.db").unwrap(); 27 | conn.execute_batch( 28 | "PRAGMA journal_mode = OFF; 29 | PRAGMA synchronous = 0; 30 | PRAGMA cache_size = 1000000; 31 | PRAGMA locking_mode = EXCLUSIVE; 32 | PRAGMA temp_store = MEMORY;", 33 | ) 34 | .expect("PRAGMA"); 35 | conn.execute( 36 | "CREATE TABLE IF NOT EXISTS user ( 37 | id INTEGER not null primary key, 38 | area CHAR(6), 39 | age INTEGER not null, 40 | active INTEGER not null)", 41 | [], 42 | ) 43 | .unwrap(); 44 | let tx = conn.transaction().unwrap(); 45 | { 46 | // TODO: refactor and DRY from basic_batched 47 | // jeez, refactor this! 48 | // this is very similar to the code from basic_batched, check that file to understand 49 | // whats happening here. 50 | let mut with_area_params = " (NULL, ?, ?, ?),".repeat(MIN_BATCH_SIZE as usize); 51 | with_area_params.pop(); 52 | let with_area_params = with_area_params.as_str(); 53 | let mut without_area_params = " (NULL, NULL, ?, ?),".repeat(MIN_BATCH_SIZE as usize); 54 | without_area_params.pop(); 55 | let without_area_params = without_area_params.as_str(); 56 | let st1 = format!("INSERT INTO user VALUES {}", with_area_params); 57 | let st2 = format!("INSERT INTO user VALUES {}", without_area_params); 58 | 59 | let mut stmt_with_area = tx.prepare_cached(st1.as_str()).unwrap(); 60 | let mut stmt_without_area = tx.prepare_cached(st2.as_str()).unwrap(); 61 | for param_values in rx { 62 | let mut row_values: Vec<&dyn ToSql> = Vec::new(); 63 | match param_values { 64 | ParamValues::WithArea(values) => { 65 | for batch in values.iter() { 66 | row_values.push(&batch.0 as &dyn ToSql); 67 | row_values.push(&batch.1 as &dyn ToSql); 68 | row_values.push(&batch.2 as &dyn ToSql); 69 | } 70 | stmt_with_area.execute(&*row_values).unwrap(); 71 | } 72 | ParamValues::WithoutArea(values) => { 73 | for batch in values.iter() { 74 | row_values.push(&batch.0 as &dyn ToSql); 75 | row_values.push(&batch.1 as &dyn ToSql); 76 | } 77 | stmt_without_area.execute(&*row_values).unwrap(); 78 | } 79 | } 80 | } 81 | } 82 | tx.commit().unwrap(); 83 | } 84 | 85 | fn producer(tx: Sender, count: i64) { 86 | if count < MIN_BATCH_SIZE { 87 | panic!("count cant be less than min batch size"); 88 | } 89 | for _ in 0..(count / MIN_BATCH_SIZE) { 90 | let with_area = common::get_random_bool(); 91 | let age = common::get_random_age(); 92 | let is_active = common::get_random_active(); 93 | let mut param_values: Vec<_> = Vec::new(); 94 | if with_area { 95 | // lets prepare the batch 96 | let mut vector = Vec::<(String, i8, i8)>::new(); 97 | for _ in 0..MIN_BATCH_SIZE { 98 | let area_code = common::get_random_area_code(); 99 | vector.push((area_code, age, is_active)); 100 | } 101 | for batch in vector.iter() { 102 | param_values.push(&batch.0 as &dyn ToSql); 103 | param_values.push(&batch.1 as &dyn ToSql); 104 | param_values.push(&batch.2 as &dyn ToSql); 105 | } 106 | // send the values 107 | tx.send(ParamValues::WithArea(vector)).unwrap(); 108 | } else { 109 | // lets prepare the batch 110 | let mut vector = Vec::<(i8, i8)>::new(); 111 | for _ in 0..MIN_BATCH_SIZE { 112 | vector.push((age, is_active)); 113 | } 114 | for batch in vector.iter() { 115 | param_values.push(&batch.0 as &dyn ToSql); 116 | param_values.push(&batch.1 as &dyn ToSql); 117 | } 118 | // send the values 119 | tx.send(ParamValues::WithoutArea(vector)).unwrap(); 120 | } 121 | } 122 | } 123 | 124 | fn main() { 125 | // setup the DB and tables 126 | let (tx, rx): (Sender, Receiver) = mpsc::channel(); 127 | // lets launch the consumer 128 | let consumer_handle = thread::spawn(|| consumer(rx)); 129 | 130 | let cpu_count = num_cpus::get(); 131 | let total_rows = 100_000_000; 132 | let each_producer_count = (total_rows / cpu_count) as i64; 133 | let mut handles = Vec::with_capacity(cpu_count); 134 | for _ in 0..cpu_count { 135 | let thread_tx = tx.clone(); 136 | handles.push(thread::spawn(move || { 137 | producer(thread_tx, each_producer_count.clone()) 138 | })) 139 | } 140 | for t in handles { 141 | t.join().unwrap(); 142 | } 143 | drop(tx); 144 | // wait till consumer is exited 145 | consumer_handle.join().unwrap(); 146 | } 147 | -------------------------------------------------------------------------------- /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 | "tokio", 223 | ] 224 | 225 | [[package]] 226 | name = "fastrand" 227 | version = "1.5.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" 230 | dependencies = [ 231 | "instant", 232 | ] 233 | 234 | [[package]] 235 | name = "foreign-types" 236 | version = "0.3.2" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 239 | dependencies = [ 240 | "foreign-types-shared", 241 | ] 242 | 243 | [[package]] 244 | name = "foreign-types-shared" 245 | version = "0.1.1" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 248 | 249 | [[package]] 250 | name = "form_urlencoded" 251 | version = "1.0.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 254 | dependencies = [ 255 | "matches", 256 | "percent-encoding", 257 | ] 258 | 259 | [[package]] 260 | name = "funty" 261 | version = "1.1.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" 264 | 265 | [[package]] 266 | name = "futures" 267 | version = "0.3.14" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" 270 | dependencies = [ 271 | "futures-channel", 272 | "futures-core", 273 | "futures-executor", 274 | "futures-io", 275 | "futures-sink", 276 | "futures-task", 277 | "futures-util", 278 | ] 279 | 280 | [[package]] 281 | name = "futures-channel" 282 | version = "0.3.14" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" 285 | dependencies = [ 286 | "futures-core", 287 | "futures-sink", 288 | ] 289 | 290 | [[package]] 291 | name = "futures-core" 292 | version = "0.3.14" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" 295 | 296 | [[package]] 297 | name = "futures-executor" 298 | version = "0.3.14" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" 301 | dependencies = [ 302 | "futures-core", 303 | "futures-task", 304 | "futures-util", 305 | ] 306 | 307 | [[package]] 308 | name = "futures-io" 309 | version = "0.3.14" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" 312 | 313 | [[package]] 314 | name = "futures-macro" 315 | version = "0.3.14" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" 318 | dependencies = [ 319 | "proc-macro-hack", 320 | "proc-macro2", 321 | "quote", 322 | "syn", 323 | ] 324 | 325 | [[package]] 326 | name = "futures-sink" 327 | version = "0.3.14" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" 330 | 331 | [[package]] 332 | name = "futures-task" 333 | version = "0.3.14" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" 336 | 337 | [[package]] 338 | name = "futures-util" 339 | version = "0.3.14" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" 342 | dependencies = [ 343 | "futures-channel", 344 | "futures-core", 345 | "futures-io", 346 | "futures-macro", 347 | "futures-sink", 348 | "futures-task", 349 | "memchr", 350 | "pin-project-lite", 351 | "pin-utils", 352 | "proc-macro-hack", 353 | "proc-macro-nested", 354 | "slab", 355 | ] 356 | 357 | [[package]] 358 | name = "generic-array" 359 | version = "0.14.4" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 362 | dependencies = [ 363 | "typenum", 364 | "version_check", 365 | ] 366 | 367 | [[package]] 368 | name = "getrandom" 369 | version = "0.2.2" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 372 | dependencies = [ 373 | "cfg-if", 374 | "libc", 375 | "wasi", 376 | ] 377 | 378 | [[package]] 379 | name = "hashbrown" 380 | version = "0.9.1" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 383 | dependencies = [ 384 | "ahash 0.4.7", 385 | ] 386 | 387 | [[package]] 388 | name = "hashbrown" 389 | version = "0.11.2" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 392 | dependencies = [ 393 | "ahash 0.7.2", 394 | ] 395 | 396 | [[package]] 397 | name = "hashlink" 398 | version = "0.6.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" 401 | dependencies = [ 402 | "hashbrown 0.9.1", 403 | ] 404 | 405 | [[package]] 406 | name = "hashlink" 407 | version = "0.7.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" 410 | dependencies = [ 411 | "hashbrown 0.11.2", 412 | ] 413 | 414 | [[package]] 415 | name = "heck" 416 | version = "0.3.2" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" 419 | dependencies = [ 420 | "unicode-segmentation", 421 | ] 422 | 423 | [[package]] 424 | name = "hermit-abi" 425 | version = "0.1.18" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 428 | dependencies = [ 429 | "libc", 430 | ] 431 | 432 | [[package]] 433 | name = "hex" 434 | version = "0.4.3" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 437 | 438 | [[package]] 439 | name = "idna" 440 | version = "0.2.3" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 443 | dependencies = [ 444 | "matches", 445 | "unicode-bidi", 446 | "unicode-normalization", 447 | ] 448 | 449 | [[package]] 450 | name = "instant" 451 | version = "0.1.9" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 454 | dependencies = [ 455 | "cfg-if", 456 | ] 457 | 458 | [[package]] 459 | name = "itoa" 460 | version = "0.4.7" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 463 | 464 | [[package]] 465 | name = "js-sys" 466 | version = "0.3.50" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" 469 | dependencies = [ 470 | "wasm-bindgen", 471 | ] 472 | 473 | [[package]] 474 | name = "lazy_static" 475 | version = "1.4.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 478 | 479 | [[package]] 480 | name = "lexical-core" 481 | version = "0.7.6" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 484 | dependencies = [ 485 | "arrayvec", 486 | "bitflags", 487 | "cfg-if", 488 | "ryu", 489 | "static_assertions", 490 | ] 491 | 492 | [[package]] 493 | name = "libc" 494 | version = "0.2.94" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" 497 | 498 | [[package]] 499 | name = "libsqlite3-sys" 500 | version = "0.22.2" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" 503 | dependencies = [ 504 | "cc", 505 | "pkg-config", 506 | "vcpkg", 507 | ] 508 | 509 | [[package]] 510 | name = "lock_api" 511 | version = "0.4.4" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" 514 | dependencies = [ 515 | "scopeguard", 516 | ] 517 | 518 | [[package]] 519 | name = "log" 520 | version = "0.4.14" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 523 | dependencies = [ 524 | "cfg-if", 525 | ] 526 | 527 | [[package]] 528 | name = "maplit" 529 | version = "1.0.2" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 532 | 533 | [[package]] 534 | name = "matches" 535 | version = "0.1.8" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 538 | 539 | [[package]] 540 | name = "memchr" 541 | version = "2.4.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 544 | 545 | [[package]] 546 | name = "mio" 547 | version = "0.7.11" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" 550 | dependencies = [ 551 | "libc", 552 | "log", 553 | "miow", 554 | "ntapi", 555 | "winapi", 556 | ] 557 | 558 | [[package]] 559 | name = "miow" 560 | version = "0.3.7" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 563 | dependencies = [ 564 | "winapi", 565 | ] 566 | 567 | [[package]] 568 | name = "native-tls" 569 | version = "0.2.7" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" 572 | dependencies = [ 573 | "lazy_static", 574 | "libc", 575 | "log", 576 | "openssl", 577 | "openssl-probe", 578 | "openssl-sys", 579 | "schannel", 580 | "security-framework", 581 | "security-framework-sys", 582 | "tempfile", 583 | ] 584 | 585 | [[package]] 586 | name = "nom" 587 | version = "6.1.2" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" 590 | dependencies = [ 591 | "bitvec", 592 | "funty", 593 | "lexical-core", 594 | "memchr", 595 | "version_check", 596 | ] 597 | 598 | [[package]] 599 | name = "ntapi" 600 | version = "0.3.6" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 603 | dependencies = [ 604 | "winapi", 605 | ] 606 | 607 | [[package]] 608 | name = "num-traits" 609 | version = "0.2.14" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 612 | dependencies = [ 613 | "autocfg", 614 | ] 615 | 616 | [[package]] 617 | name = "num_cpus" 618 | version = "1.13.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 621 | dependencies = [ 622 | "hermit-abi", 623 | "libc", 624 | ] 625 | 626 | [[package]] 627 | name = "once_cell" 628 | version = "1.7.2" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" 631 | 632 | [[package]] 633 | name = "opaque-debug" 634 | version = "0.3.0" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 637 | 638 | [[package]] 639 | name = "openssl" 640 | version = "0.10.34" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "6d7830286ad6a3973c0f1d9b73738f69c76b739301d0229c4b96501695cbe4c8" 643 | dependencies = [ 644 | "bitflags", 645 | "cfg-if", 646 | "foreign-types", 647 | "libc", 648 | "once_cell", 649 | "openssl-sys", 650 | ] 651 | 652 | [[package]] 653 | name = "openssl-probe" 654 | version = "0.1.2" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 657 | 658 | [[package]] 659 | name = "openssl-sys" 660 | version = "0.9.63" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "b6b0d6fb7d80f877617dfcb014e605e2b5ab2fb0afdf27935219bb6bd984cb98" 663 | dependencies = [ 664 | "autocfg", 665 | "cc", 666 | "libc", 667 | "pkg-config", 668 | "vcpkg", 669 | ] 670 | 671 | [[package]] 672 | name = "parking_lot" 673 | version = "0.11.1" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 676 | dependencies = [ 677 | "instant", 678 | "lock_api", 679 | "parking_lot_core", 680 | ] 681 | 682 | [[package]] 683 | name = "parking_lot_core" 684 | version = "0.8.3" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 687 | dependencies = [ 688 | "cfg-if", 689 | "instant", 690 | "libc", 691 | "redox_syscall", 692 | "smallvec", 693 | "winapi", 694 | ] 695 | 696 | [[package]] 697 | name = "percent-encoding" 698 | version = "2.1.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 701 | 702 | [[package]] 703 | name = "pin-project-lite" 704 | version = "0.2.6" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" 707 | 708 | [[package]] 709 | name = "pin-utils" 710 | version = "0.1.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 713 | 714 | [[package]] 715 | name = "pkg-config" 716 | version = "0.3.19" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 719 | 720 | [[package]] 721 | name = "ppv-lite86" 722 | version = "0.2.10" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 725 | 726 | [[package]] 727 | name = "proc-macro-hack" 728 | version = "0.5.19" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 731 | 732 | [[package]] 733 | name = "proc-macro-nested" 734 | version = "0.1.7" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 737 | 738 | [[package]] 739 | name = "proc-macro2" 740 | version = "1.0.26" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" 743 | dependencies = [ 744 | "unicode-xid", 745 | ] 746 | 747 | [[package]] 748 | name = "quote" 749 | version = "1.0.9" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 752 | dependencies = [ 753 | "proc-macro2", 754 | ] 755 | 756 | [[package]] 757 | name = "radium" 758 | version = "0.5.3" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" 761 | 762 | [[package]] 763 | name = "rand" 764 | version = "0.8.3" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 767 | dependencies = [ 768 | "libc", 769 | "rand_chacha", 770 | "rand_core", 771 | "rand_hc", 772 | ] 773 | 774 | [[package]] 775 | name = "rand_chacha" 776 | version = "0.3.0" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 779 | dependencies = [ 780 | "ppv-lite86", 781 | "rand_core", 782 | ] 783 | 784 | [[package]] 785 | name = "rand_core" 786 | version = "0.6.2" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 789 | dependencies = [ 790 | "getrandom", 791 | ] 792 | 793 | [[package]] 794 | name = "rand_hc" 795 | version = "0.3.0" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 798 | dependencies = [ 799 | "rand_core", 800 | ] 801 | 802 | [[package]] 803 | name = "redox_syscall" 804 | version = "0.2.8" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" 807 | dependencies = [ 808 | "bitflags", 809 | ] 810 | 811 | [[package]] 812 | name = "regex" 813 | version = "1.5.4" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 816 | dependencies = [ 817 | "aho-corasick", 818 | "memchr", 819 | "regex-syntax", 820 | ] 821 | 822 | [[package]] 823 | name = "regex-syntax" 824 | version = "0.6.25" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 827 | 828 | [[package]] 829 | name = "remove_dir_all" 830 | version = "0.5.3" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 833 | dependencies = [ 834 | "winapi", 835 | ] 836 | 837 | [[package]] 838 | name = "rusqlite" 839 | version = "0.25.3" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "57adcf67c8faaf96f3248c2a7b419a0dbc52ebe36ba83dd57fe83827c1ea4eb3" 842 | dependencies = [ 843 | "bitflags", 844 | "fallible-iterator", 845 | "fallible-streaming-iterator", 846 | "hashlink 0.7.0", 847 | "libsqlite3-sys", 848 | "memchr", 849 | "smallvec", 850 | ] 851 | 852 | [[package]] 853 | name = "ryu" 854 | version = "1.0.5" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 857 | 858 | [[package]] 859 | name = "schannel" 860 | version = "0.1.19" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 863 | dependencies = [ 864 | "lazy_static", 865 | "winapi", 866 | ] 867 | 868 | [[package]] 869 | name = "scopeguard" 870 | version = "1.1.0" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 873 | 874 | [[package]] 875 | name = "security-framework" 876 | version = "2.2.0" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" 879 | dependencies = [ 880 | "bitflags", 881 | "core-foundation", 882 | "core-foundation-sys", 883 | "libc", 884 | "security-framework-sys", 885 | ] 886 | 887 | [[package]] 888 | name = "security-framework-sys" 889 | version = "2.2.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" 892 | dependencies = [ 893 | "core-foundation-sys", 894 | "libc", 895 | ] 896 | 897 | [[package]] 898 | name = "sha2" 899 | version = "0.9.4" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "d8f6b75b17576b792bef0db1bcc4b8b8bcdf9506744cf34b974195487af6cff2" 902 | dependencies = [ 903 | "block-buffer", 904 | "cfg-if", 905 | "cpufeatures", 906 | "digest", 907 | "opaque-debug", 908 | ] 909 | 910 | [[package]] 911 | name = "signal-hook-registry" 912 | version = "1.3.0" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" 915 | dependencies = [ 916 | "libc", 917 | ] 918 | 919 | [[package]] 920 | name = "slab" 921 | version = "0.4.3" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" 924 | 925 | [[package]] 926 | name = "smallvec" 927 | version = "1.6.1" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 930 | 931 | [[package]] 932 | name = "sqlformat" 933 | version = "0.1.6" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "6d86e3c77ff882a828346ba401a7ef4b8e440df804491c6064fe8295765de71c" 936 | dependencies = [ 937 | "lazy_static", 938 | "maplit", 939 | "nom", 940 | "regex", 941 | "unicode_categories", 942 | ] 943 | 944 | [[package]] 945 | name = "sqlx" 946 | version = "0.5.2" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "d582b9bc04ec6c03084196efc42c2226b018e9941f03ee62bd88921d500917c0" 949 | dependencies = [ 950 | "sqlx-core", 951 | "sqlx-macros", 952 | ] 953 | 954 | [[package]] 955 | name = "sqlx-core" 956 | version = "0.5.2" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "de52d1d473cebb2abb79c886ef6a8023e965e34c0676a99cfeac2cc7f0fde4c1" 959 | dependencies = [ 960 | "ahash 0.7.2", 961 | "atoi", 962 | "bitflags", 963 | "byteorder", 964 | "bytes", 965 | "crc", 966 | "crossbeam-channel", 967 | "crossbeam-queue", 968 | "crossbeam-utils", 969 | "either", 970 | "futures-channel", 971 | "futures-core", 972 | "futures-util", 973 | "hashlink 0.6.0", 974 | "hex", 975 | "itoa", 976 | "libc", 977 | "libsqlite3-sys", 978 | "log", 979 | "memchr", 980 | "once_cell", 981 | "parking_lot", 982 | "percent-encoding", 983 | "sha2", 984 | "smallvec", 985 | "sqlformat", 986 | "sqlx-rt", 987 | "stringprep", 988 | "thiserror", 989 | "tokio-stream", 990 | "url", 991 | "whoami", 992 | ] 993 | 994 | [[package]] 995 | name = "sqlx-macros" 996 | version = "0.5.2" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "1a40f0be97e704d3fbf059e7e3333c3735639146a72d586c5534c70e79da88a4" 999 | dependencies = [ 1000 | "dotenv", 1001 | "either", 1002 | "futures", 1003 | "heck", 1004 | "proc-macro2", 1005 | "quote", 1006 | "sha2", 1007 | "sqlx-core", 1008 | "sqlx-rt", 1009 | "syn", 1010 | "url", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "sqlx-rt" 1015 | version = "0.5.2" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "b6ae97ab05063ed515cdc23d90253213aa24dda0a288c5ec079af3d10f9771bc" 1018 | dependencies = [ 1019 | "native-tls", 1020 | "once_cell", 1021 | "tokio", 1022 | "tokio-native-tls", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "static_assertions" 1027 | version = "1.1.0" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1030 | 1031 | [[package]] 1032 | name = "stringprep" 1033 | version = "0.1.2" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 1036 | dependencies = [ 1037 | "unicode-bidi", 1038 | "unicode-normalization", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "syn" 1043 | version = "1.0.72" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" 1046 | dependencies = [ 1047 | "proc-macro2", 1048 | "quote", 1049 | "unicode-xid", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "tap" 1054 | version = "1.0.1" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 1057 | 1058 | [[package]] 1059 | name = "tempfile" 1060 | version = "3.2.0" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 1063 | dependencies = [ 1064 | "cfg-if", 1065 | "libc", 1066 | "rand", 1067 | "redox_syscall", 1068 | "remove_dir_all", 1069 | "winapi", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "thiserror" 1074 | version = "1.0.24" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" 1077 | dependencies = [ 1078 | "thiserror-impl", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "thiserror-impl" 1083 | version = "1.0.24" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" 1086 | dependencies = [ 1087 | "proc-macro2", 1088 | "quote", 1089 | "syn", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "tinyvec" 1094 | version = "1.2.0" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" 1097 | dependencies = [ 1098 | "tinyvec_macros", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "tinyvec_macros" 1103 | version = "0.1.0" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1106 | 1107 | [[package]] 1108 | name = "tokio" 1109 | version = "1.5.0" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" 1112 | dependencies = [ 1113 | "autocfg", 1114 | "bytes", 1115 | "libc", 1116 | "memchr", 1117 | "mio", 1118 | "num_cpus", 1119 | "once_cell", 1120 | "parking_lot", 1121 | "pin-project-lite", 1122 | "signal-hook-registry", 1123 | "tokio-macros", 1124 | "winapi", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "tokio-macros" 1129 | version = "1.1.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" 1132 | dependencies = [ 1133 | "proc-macro2", 1134 | "quote", 1135 | "syn", 1136 | ] 1137 | 1138 | [[package]] 1139 | name = "tokio-native-tls" 1140 | version = "0.3.0" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1143 | dependencies = [ 1144 | "native-tls", 1145 | "tokio", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "tokio-stream" 1150 | version = "0.1.5" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0" 1153 | dependencies = [ 1154 | "futures-core", 1155 | "pin-project-lite", 1156 | "tokio", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "typenum" 1161 | version = "1.13.0" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" 1164 | 1165 | [[package]] 1166 | name = "unicode-bidi" 1167 | version = "0.3.5" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" 1170 | dependencies = [ 1171 | "matches", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "unicode-normalization" 1176 | version = "0.1.17" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" 1179 | dependencies = [ 1180 | "tinyvec", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "unicode-segmentation" 1185 | version = "1.7.1" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 1188 | 1189 | [[package]] 1190 | name = "unicode-xid" 1191 | version = "0.2.2" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1194 | 1195 | [[package]] 1196 | name = "unicode_categories" 1197 | version = "0.1.1" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 1200 | 1201 | [[package]] 1202 | name = "url" 1203 | version = "2.2.2" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1206 | dependencies = [ 1207 | "form_urlencoded", 1208 | "idna", 1209 | "matches", 1210 | "percent-encoding", 1211 | ] 1212 | 1213 | [[package]] 1214 | name = "vcpkg" 1215 | version = "0.2.12" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" 1218 | 1219 | [[package]] 1220 | name = "version_check" 1221 | version = "0.9.3" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1224 | 1225 | [[package]] 1226 | name = "wasi" 1227 | version = "0.10.2+wasi-snapshot-preview1" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1230 | 1231 | [[package]] 1232 | name = "wasm-bindgen" 1233 | version = "0.2.73" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" 1236 | dependencies = [ 1237 | "cfg-if", 1238 | "wasm-bindgen-macro", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "wasm-bindgen-backend" 1243 | version = "0.2.73" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" 1246 | dependencies = [ 1247 | "bumpalo", 1248 | "lazy_static", 1249 | "log", 1250 | "proc-macro2", 1251 | "quote", 1252 | "syn", 1253 | "wasm-bindgen-shared", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "wasm-bindgen-macro" 1258 | version = "0.2.73" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" 1261 | dependencies = [ 1262 | "quote", 1263 | "wasm-bindgen-macro-support", 1264 | ] 1265 | 1266 | [[package]] 1267 | name = "wasm-bindgen-macro-support" 1268 | version = "0.2.73" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" 1271 | dependencies = [ 1272 | "proc-macro2", 1273 | "quote", 1274 | "syn", 1275 | "wasm-bindgen-backend", 1276 | "wasm-bindgen-shared", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "wasm-bindgen-shared" 1281 | version = "0.2.73" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" 1284 | 1285 | [[package]] 1286 | name = "web-sys" 1287 | version = "0.3.50" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" 1290 | dependencies = [ 1291 | "js-sys", 1292 | "wasm-bindgen", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "whoami" 1297 | version = "1.1.2" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "4abacf325c958dfeaf1046931d37f2a901b6dfe0968ee965a29e94c6766b2af6" 1300 | dependencies = [ 1301 | "wasm-bindgen", 1302 | "web-sys", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "winapi" 1307 | version = "0.3.9" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1310 | dependencies = [ 1311 | "winapi-i686-pc-windows-gnu", 1312 | "winapi-x86_64-pc-windows-gnu", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "winapi-i686-pc-windows-gnu" 1317 | version = "0.4.0" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1320 | 1321 | [[package]] 1322 | name = "winapi-x86_64-pc-windows-gnu" 1323 | version = "0.4.0" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1326 | 1327 | [[package]] 1328 | name = "wyz" 1329 | version = "0.2.0" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" 1332 | --------------------------------------------------------------------------------