├── .gitignore ├── readyset_proxysql_scheduler.cnf ├── Cargo.toml ├── .github └── workflows │ └── build.yml ├── src ├── messages.rs ├── main.rs ├── config.rs ├── hosts.rs ├── queries.rs └── proxysql.rs ├── README.md ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | .vscode/ 13 | 14 | # Demo config 15 | config_demo.cnf 16 | -------------------------------------------------------------------------------- /readyset_proxysql_scheduler.cnf: -------------------------------------------------------------------------------- 1 | proxysql_user = 'admin' 2 | proxysql_password = 'admin' 3 | proxysql_host = '127.0.0.1' 4 | proxysql_port = 6032 5 | readyset_user = 'root' 6 | readyset_password = 'root' 7 | source_hostgroup = 11 8 | readyset_hostgroup = 99 9 | warmup_time_s = 60 10 | lock_file = '/tmp/readyset_scheduler.lock' 11 | operation_mode='All' 12 | number_of_queries=10 13 | query_discovery_mode='SumTime' 14 | log_verbosity='Note' -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "readyset_proxysql_scheduler" 3 | version = "0.6.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "*", features = ["derive"] } 10 | mysql = "*" 11 | toml = "0.8.12" 12 | serde = "1.0" 13 | chrono = "0.4.35" 14 | file-guard = "0.2.0" 15 | once_cell = "1.10.0" 16 | 17 | 18 | [package.metadata.generate-rpm] 19 | assets = [ 20 | { source = "target/release/readyset_proxysql_scheduler", dest = "/usr/bin/readyset_proxysql_scheduler", mode = "755" }, 21 | { source = "./readyset_proxysql_scheduler.cnf", dest = "/etc/readyset_proxysql_scheduler.cnf", mode = "644" }, 22 | ] 23 | license = "Apache 2.0" 24 | description = "Readyset ProxySQL Scheduler" 25 | 26 | [package.metadata.deb] 27 | extended-description = """\ 28 | Readyset ProxySQL Scheduler""" 29 | copyright = "2024, ReadySet, Inc." 30 | maintainer = "ReadySet, Inc. " 31 | assets = [ 32 | ["target/release/readyset_proxysql_scheduler", "/usr/bin/readyset_proxysql_scheduler", "755"], 33 | ["./readyset_proxysql_scheduler.cnf", "/etc/readyset_proxysql_scheduler.cnf", "644"], 34 | ] 35 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | 11 | - name: Install pre-requirements 12 | run: sudo curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -q -y 13 | 14 | - name: Check format 15 | run: cargo fmt --check 16 | 17 | - name: Run clippy 18 | run: cargo clippy --workspace --all-targets --all-features -- -W clippy::disallowed_methods -D warnings 19 | 20 | - name: Install cargo-generate-rpm 21 | run: cargo install cargo-generate-rpm 22 | 23 | - name: Install cargo-deb 24 | run: cargo install cargo-deb 25 | 26 | - name: Build 27 | run: cargo build --release 28 | 29 | - name: Generate RPM 30 | run: cargo generate-rpm 31 | 32 | - name: Generate DEB 33 | run: cargo deb 34 | 35 | - name: Save RPM / DEB name 36 | run: | 37 | echo "rpm_name=$(ls target/generate-rpm/)" >> "$GITHUB_ENV" 38 | echo "deb_name=$(ls target/debian/ -I *-stripped)" >> "$GITHUB_ENV" 39 | 40 | - name: Upload RPM 41 | uses: actions/upload-artifact@v4 42 | with: 43 | path: target/generate-rpm/ 44 | name: ${{ env.rpm_name }} 45 | 46 | - name: Upload Deb 47 | uses: actions/upload-artifact@v4 48 | with: 49 | path: target/debian/${{ env.deb_name }} 50 | name: ${{ env.deb_name }} 51 | 52 | release: 53 | if: contains(github.ref, 'tags/v') 54 | runs-on: ubuntu-latest 55 | needs: build 56 | 57 | steps: 58 | - name: Download artifact 59 | uses: actions/download-artifact@v4 60 | with: 61 | path: ./ 62 | merge-multiple: true 63 | - name: List Files 64 | run: | 65 | pwd 66 | ls -lah ./ 67 | - name: Create Release 68 | id: create_release 69 | uses: ncipollo/release-action@v1 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | draft: false 74 | prerelease: false 75 | allowUpdates: true 76 | artifactErrorsFailBuild: false 77 | artifacts: | 78 | *.rpm 79 | *.deb 80 | -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | use std::process; 2 | 3 | use chrono::{DateTime, Local}; 4 | use once_cell::sync::Lazy; 5 | use std::sync::Mutex; 6 | 7 | #[derive(Clone, Copy, serde::Deserialize, Debug, Default, PartialEq, PartialOrd)] 8 | pub enum MessageType { 9 | /// Information message, this will not result in any action 10 | Info, 11 | /// Note message, this will result in some action that changes state 12 | #[default] 13 | Note, 14 | /// Warning message 15 | Warning, 16 | /// Error message 17 | Error, 18 | } 19 | 20 | impl std::fmt::Display for MessageType { 21 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 22 | match self { 23 | MessageType::Info => write!(f, "Info"), 24 | MessageType::Note => write!(f, "Note"), 25 | MessageType::Warning => write!(f, "Warning"), 26 | MessageType::Error => write!(f, "Error"), 27 | } 28 | } 29 | } 30 | 31 | static LOG_VERBOSITY: Lazy> = Lazy::new(|| Mutex::new(MessageType::default())); 32 | 33 | pub fn set_log_verbosity(level: MessageType) { 34 | let mut verbosity = LOG_VERBOSITY.lock().unwrap(); 35 | *verbosity = level; 36 | } 37 | 38 | pub fn get_log_verbosity() -> MessageType { 39 | let verbosity = LOG_VERBOSITY.lock().unwrap(); 40 | *verbosity 41 | } 42 | 43 | fn print_message_with_ts(message: &str, message_type: MessageType) { 44 | let datetime_now: DateTime = Local::now(); 45 | let date_formatted = datetime_now.format("%Y-%m-%d %H:%M:%S"); 46 | let pid = process::id(); 47 | match message_type { 48 | MessageType::Info => { 49 | if MessageType::Info >= get_log_verbosity() { 50 | println!("{} [INFO] Readyset[{}]: {}", date_formatted, pid, message); 51 | } 52 | } 53 | MessageType::Note => { 54 | if MessageType::Note >= get_log_verbosity() { 55 | println!("{} [NOTE] Readyset[{}]: {}", date_formatted, pid, message); 56 | } 57 | } 58 | MessageType::Warning => { 59 | if MessageType::Warning >= get_log_verbosity() { 60 | eprintln!( 61 | "{} [WARNING] Readyset[{}]: {}", 62 | date_formatted, pid, message 63 | ); 64 | } 65 | } 66 | MessageType::Error => { 67 | if MessageType::Error >= get_log_verbosity() { 68 | eprintln!("{} [ERROR] Readyset[{}]: {}", date_formatted, pid, message); 69 | } 70 | } 71 | } 72 | } 73 | 74 | pub fn print_info(message: &str) { 75 | print_message_with_ts(message, MessageType::Info); 76 | } 77 | 78 | pub fn print_note(message: &str) { 79 | print_message_with_ts(message, MessageType::Note); 80 | } 81 | 82 | pub fn print_warning(message: &str) { 83 | print_message_with_ts(message, MessageType::Warning); 84 | } 85 | 86 | pub fn print_error(message: &str) { 87 | print_message_with_ts(message, MessageType::Error); 88 | } 89 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod hosts; 3 | mod messages; 4 | mod proxysql; 5 | mod queries; 6 | 7 | use clap::Parser; 8 | use config::read_config_file; 9 | use file_guard::Lock; 10 | use messages::MessageType; 11 | use mysql::{Conn, OptsBuilder}; 12 | use proxysql::ProxySQL; 13 | use std::fs::OpenOptions; 14 | 15 | /// Readyset ProxySQL Scheduler 16 | /// This tool is used to query ProxySQL Stats tables to find queries that are not yet cached in Readyset and then cache them. 17 | #[derive(Parser, Debug)] 18 | #[command(version, about, long_about = None)] 19 | struct Args { 20 | /// path to the config file 21 | #[arg(long)] 22 | config: String, 23 | /// Dry run mode 24 | #[arg(long)] 25 | dry_run: bool, 26 | } 27 | 28 | fn main() { 29 | let args = Args::parse(); 30 | let config_file = read_config_file(&args.config).expect("Failed to read config file"); 31 | let config = config::parse_config_file(&config_file).expect("Failed to parse config file"); 32 | messages::set_log_verbosity(config.clone().log_verbosity.unwrap_or(MessageType::Note)); 33 | messages::print_info("Running readyset_scheduler"); 34 | let file = match OpenOptions::new() 35 | .read(true) 36 | .write(true) 37 | .create(true) 38 | .truncate(true) 39 | .open( 40 | config 41 | .clone() 42 | .lock_file 43 | .unwrap_or("/tmp/readyset_scheduler.lock".to_string()), 44 | ) { 45 | Ok(file) => file, 46 | Err(err) => { 47 | messages::print_error( 48 | format!( 49 | "Failed to open lock file {}: {}", 50 | config 51 | .lock_file 52 | .unwrap_or("/tmp/readyset_scheduler.lock".to_string()), 53 | err 54 | ) 55 | .as_str(), 56 | ); 57 | std::process::exit(1); 58 | } 59 | }; 60 | 61 | let _guard = match file_guard::try_lock(&file, Lock::Exclusive, 0, 1) { 62 | Ok(guard) => guard, 63 | Err(err) => { 64 | messages::print_error(format!("Failed to acquire lock: {}", err).as_str()); 65 | std::process::exit(1); 66 | } 67 | }; 68 | 69 | let mut proxysql = ProxySQL::new(&config, args.dry_run); 70 | 71 | let running_mode = match config.operation_mode { 72 | Some(mode) => mode, 73 | None => config::OperationMode::All, 74 | }; 75 | 76 | if running_mode == config::OperationMode::HealthCheck 77 | || running_mode == config::OperationMode::All 78 | { 79 | proxysql.health_check(); 80 | } 81 | 82 | // retain only healthy hosts 83 | //hosts.retain_online(); 84 | if running_mode == config::OperationMode::QueryDiscovery 85 | || running_mode == config::OperationMode::All 86 | { 87 | let mut conn = Conn::new( 88 | OptsBuilder::new() 89 | .ip_or_hostname(Some(config.proxysql_host.as_str())) 90 | .tcp_port(config.proxysql_port) 91 | .user(Some(config.proxysql_user.as_str())) 92 | .pass(Some(config.proxysql_password.clone().as_str())) 93 | .prefer_socket(false), 94 | ) 95 | .expect("Failed to create ProxySQL connection"); 96 | let mut query_discovery = queries::QueryDiscovery::new(config); 97 | query_discovery.run(&mut proxysql, &mut conn); 98 | } 99 | 100 | messages::print_info("Finished readyset_scheduler"); 101 | } 102 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{Display, Formatter}, 3 | fs::File, 4 | io::Read, 5 | }; 6 | 7 | use crate::messages::MessageType; 8 | 9 | #[derive(serde::Deserialize, Clone, Copy, PartialEq, PartialOrd, Default, Debug)] 10 | pub enum OperationMode { 11 | HealthCheck, 12 | QueryDiscovery, 13 | #[default] 14 | All, 15 | } 16 | 17 | impl From for OperationMode { 18 | fn from(s: String) -> Self { 19 | match s.to_lowercase().as_str() { 20 | "health_check" => OperationMode::HealthCheck, 21 | "query_discovery" => OperationMode::QueryDiscovery, 22 | "all" => OperationMode::All, 23 | _ => OperationMode::All, 24 | } 25 | } 26 | } 27 | 28 | impl Display for OperationMode { 29 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { 30 | match self { 31 | OperationMode::HealthCheck => write!(f, "health_check"), 32 | OperationMode::QueryDiscovery => write!(f, "query_discovery"), 33 | OperationMode::All => write!(f, "all"), 34 | } 35 | } 36 | } 37 | 38 | #[derive(serde::Deserialize, Clone, Copy, PartialEq, PartialOrd, Default, Debug)] 39 | pub enum QueryDiscoveryMode { 40 | #[default] 41 | CountStar, 42 | SumTime, 43 | SumRowsSent, 44 | MeanTime, 45 | ExecutionTimeDistance, 46 | QueryThroughput, 47 | WorstBestCase, 48 | WorstWorstCase, 49 | DistanceMeanMax, 50 | External, 51 | } 52 | 53 | impl From for QueryDiscoveryMode { 54 | fn from(s: String) -> Self { 55 | match s.to_lowercase().as_str() { 56 | "count_star" => QueryDiscoveryMode::CountStar, 57 | "sum_time" => QueryDiscoveryMode::SumTime, 58 | "sum_rows_sent" => QueryDiscoveryMode::SumRowsSent, 59 | "mean_time" => QueryDiscoveryMode::MeanTime, 60 | "execution_time_distance" => QueryDiscoveryMode::ExecutionTimeDistance, 61 | "query_throughput" => QueryDiscoveryMode::QueryThroughput, 62 | "worst_best_case" => QueryDiscoveryMode::WorstBestCase, 63 | "worst_worst_case" => QueryDiscoveryMode::WorstWorstCase, 64 | "distance_mean_max" => QueryDiscoveryMode::DistanceMeanMax, 65 | "external" => QueryDiscoveryMode::External, 66 | _ => QueryDiscoveryMode::CountStar, 67 | } 68 | } 69 | } 70 | 71 | #[derive(serde::Deserialize, Clone, Debug)] 72 | pub struct Config { 73 | pub proxysql_user: String, 74 | pub proxysql_password: String, 75 | pub proxysql_host: String, 76 | pub proxysql_port: u16, 77 | pub readyset_user: String, 78 | pub readyset_password: String, 79 | pub source_hostgroup: u16, 80 | pub readyset_hostgroup: u16, 81 | pub warmup_time_s: Option, 82 | pub lock_file: Option, 83 | pub operation_mode: Option, 84 | pub number_of_queries: u16, 85 | pub query_discovery_mode: Option, 86 | pub query_discovery_min_execution: Option, 87 | pub query_discovery_min_row_sent: Option, 88 | pub log_verbosity: Option, 89 | } 90 | 91 | pub fn read_config_file(path: &str) -> Result { 92 | let mut file = 93 | File::open(path).unwrap_or_else(|_| panic!("Failed to open config file at path {}", path)); 94 | let mut contents = String::new(); 95 | file.read_to_string(&mut contents)?; 96 | Ok(contents) 97 | } 98 | 99 | pub fn parse_config_file(contents: &str) -> Result { 100 | toml::from_str(contents) 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automatic Query Caching with Readyset ProxySQL Scheduler 2 | Unlock the full potential of your database integrating ReadySet and ProxySQL by automatically analyzing and caching inefficient queries in your workload. Experience optimized performance with zero code changes—your application runs faster, effortlessly. 3 | 4 | 5 | # Workflow 6 | This scheduler executes the following steps: 7 | 8 | 1. Locks an in disk file (configured by `lock_file`) to avoid multiple instances of the scheduler to overlap their execution. 9 | 2. If `mode=(All|HealthCheck)` - Query `mysql_servers` and check all servers that have `comment='Readyset` (case insensitive) and `hostgroup=readyset_hostgroup`. For each server it checks if it can connect to Readyset and validate the output of `Status` and act as follow: 10 | * `Online` - Adjust the server status to `ONLINE` in ProxySQL. 11 | * `Maitenance Mode` - Adjust the server status to `OFFLINE_SOFT` in ProxySQL. 12 | * `Snapshot In Progress` - Adjust the server status to `SHUNNED` in ProxySQL. 13 | 4. If `mode=(All|QueryDiscovery)` Query the table `stats_mysql_query_digest` finding queries executed at `source_hostgroup` by `readyset_user` and validates if each query is supported by Readyset. The rules to order queries are configured by [Query Discovery](#query-discovery) configurations. 14 | 3. If the query is supported it adds a cache in Readyset by executing `CREATE CACHE FROM __query__`. 15 | 4. If `warmup_time_s` is NOT configure, a new query rule will be added redirecting this query to Readyset 16 | 5. If `warmup_time_s` is configured, a new query rule will be added to mirror this query to Readyset. The query will still be redirected to the original hostgroup 17 | 6. Once `warmup_time_s` seconds has elapsed since the query was mirrored, the query rule will be updated to redirect the query to Readyset instead of mirroring. 18 | 19 | 20 | 21 | # Configuration 22 | 23 | Assuming you have your ProxySQL already Configured you will need to create a new hostgroup and add Readyset to this hostgroup: 24 | 25 | ``` 26 | INSERT INTO mysql_servers (hostgroup_id, hostname, port, comment) VALUES (99, '127.0.0.1', 3307, 'Readyset'); 27 | LOAD MYSQL SERVERS TO RUNTIME; 28 | SAVE MYSQL SERVERS TO DISK; 29 | ``` 30 | 31 | *NOTE*: It's required to add `Readyset` as a comment to the server to be able to identify it in the scheduler. 32 | 33 | To configure the scheduler to run execute: 34 | 35 | ``` 36 | INSERT INTO scheduler (active, interval_ms, filename, arg1) VALUES (1, 10000, '/usr/bin/readyset_proxysql_scheduler', '--config=/etc/readyset_proxysql_scheduler.cnf'); 37 | LOAD SCHEDULER TO RUNTIME; 38 | SAVE SCHEDULER TO DISK; 39 | ``` 40 | 41 | Configure `/etc/readyset_proxysql_scheduler.cnf` as follow: 42 | * `proxysql_user` - (Required) - Proxysql admin user 43 | * `proxysql_password` - (Required) - Proxysql admin password 44 | * `proxysql_host` - (Required) - Proxysql admin host 45 | * `proxysql_port` - (Required) - Proxysql admin port 46 | * `readyset_user` - (Required) - Readyset application user 47 | * `readyset_password` - (Required) - Readyset application password 48 | * `source_hostgroup` - (Required) - Hostgroup running your Read workload 49 | * `readyset_hostgroup` - (Required) - Hostgroup where Readyset is configure 50 | * `warmup_time_s` - (Optional) - Time in seconds to mirror a query supported before redirecting the query to Readyset (Default 0 - no mirror) 51 | * `lock_file` - (Optional) - Lock file to prevent two instances of the scheduler to run at the same time (Default '/etc/readyset_scheduler.lock') 52 | * `operation_mode` - (Optional) - Operation mode to run the scheduler. The options are described in [Operation Mode](#operation-mode) (Default All). 53 | * `number_of_queries` - (Optional) - Number of queries to cache in Readyset (Default 10). 54 | * `query_discovery_mode` / `query_discovery_min_execution` / `query_discovery_min_row_sent` - (Optional) - Query Discovery configurations. The options are described in [Query Discovery](#query-discovery) (Default CountStar / 0 / 0). 55 | 56 | 57 | # Query Discovery 58 | The Query Discovery is a set of configuration to find queries that are supported by Readyset. The configurations are defined by the following fields: 59 | 60 | * `query_discovery_mode`: (Optional) - Mode to discover queries to automatically cache in Readyset. The options are described in [Query Discovery Mode](#query-discovery-mode) (Default CountStar). 61 | * `query_discovery_min_execution`: (Optional) - Minimum number of executions of a query to be considered a candidate to be cached (Default 0). 62 | * `query_discovery_min_row_sent`: (Optional) - Minimum number of rows sent by a query to be considered a candidate to be cached (Default 0). 63 | 64 | # Query Discovery Mode 65 | The Query Discovery Mode is a set of possible rules to discover queries to automatically cache in Readyset. The options are: 66 | 67 | 1. `CountStar` - Total Number of Query Executions 68 | * Formula: `total_executions = count_star` 69 | * Description: This metric gives the total number of times the query has been executed. It is valuable for understanding how frequently the query runs. A high count_star value suggests that the query is executed often. 70 | 71 | 2. `SumTime` - Total Time Spent Executing the Query 72 | * Formula: `total_execution_time = sum_time` 73 | * Description: This metric represents the total cumulative time spent (measured in microseconds) executing the query across all its executions. It provides a clear understanding of how much processing time the query is consuming over time. A high total execution time can indicate that the query is either frequently executed or is time-intensive to process. 74 | 75 | 3. `SumRowsSent` - Total Number of Rows Sent by the Query (sum_rows_sent) 76 | * Formula: `total_rows_sent = sum_rows_sent` 77 | * Description: This metric provides the total number of rows sent to the client across all executions of the query. It helps you understand the query’s output volume and the amount of data being transmitted. 78 | 79 | 4. `MeanTime` - Average Query Execution Time (Mean) 80 | * Formula: `mean_time = sum_time / count_star` 81 | * Description: The mean time gives you an idea of the typical performance (measured in microseconds) of the query over all executions. It provides a central tendency of how long the query generally takes to execute. 82 | 83 | 5. `ExecutionTimeDistance` - Time Distance Between Query Executions 84 | * Formula: `execution_time_distance = max_time - min_time` 85 | * Description: This shows the spread between the fastest and slowest executions of the query (measured in microseconds). A large range might indicate variability in system load, input sizes, or external factors affecting performance. 86 | 87 | 6. `QueryThroughput` - Query Throughput 88 | * Formula: `query_throughput = count_star / sum_time` 89 | * Description: This shows how many queries are processed per unit of time (measured in microseconds). It’s useful for understanding system capacity and how efficiently the database is handling the queries. 90 | 91 | 7. `WorstBestCase` - Worst Best-Case Query Performance 92 | * Formula: `worst_case = max(min_time)` 93 | * Description: The min_time metric gives the fastest time the query was ever executed (measured in microseconds). It reflects the best-case performance scenario, which could indicate the query’s performance under optimal conditions. 94 | 95 | 8. `WorstWorstCase` - Worst Worst-Case Query Performance 96 | * Formula: `worst_case = max(max_time)` 97 | * Description: The max_time shows the slowest time the query was executed (measured in microseconds). This can indicate potential bottlenecks or edge cases where the query underperforms, which could be due to larger data sets, locks, or high server load. 98 | 99 | 9. `DistanceMeanMax` - Distance Between Mean Time and Max Time (mean_time vs max_time) 100 | * Formula: `distance_mean_max = max_time - mean_time` 101 | * Description: The distance between the mean execution time and the maximum execution time provides insight into how much slower the worst-case execution is compared to the average (measured in microseconds). A large gap indicates significant variability in query performance, which could be caused by certain executions encountering performance bottlenecks, such as large datasets, locking, or high system load. 102 | 103 | # Operation Mode 104 | The Operation Mode is a set of possible rules to run the scheduler. The options are: 105 | * `All` - Run `HealthCheck` and `QueryDiscovery` operations. 106 | * `HealthCheck` - Run only the health check operation. 107 | * `QueryDiscovery` - Run only the query discovery operation. 108 | -------------------------------------------------------------------------------- /src/hosts.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::Config, queries::Query}; 2 | use core::fmt; 3 | use mysql::{prelude::Queryable, Conn, OptsBuilder}; 4 | use std::time::Duration; 5 | 6 | #[allow(dead_code)] 7 | /// Defines the possible status of a host 8 | #[derive(PartialEq, Clone, Copy)] 9 | pub enum ProxyStatus { 10 | /// backend server is fully operational 11 | Online, 12 | /// backend sever is temporarily taken out of use because of either too many connection errors in a time that was too short, or the replication lag exceeded the allowed threshold 13 | Shunned, 14 | /// when a server is put into OFFLINE_SOFT mode, no new connections are created toward that server, while the existing connections are kept until they are returned to the connection pool or destructed. In other words, connections are kept in use until multiplexing is enabled again, for example when a transaction is completed. This makes it possible to gracefully detach a backend as long as multiplexing is efficient 15 | OfflineSoft, 16 | /// when a server is put into OFFLINE_HARD mode, no new connections are created toward that server and the existing free connections are immediately dropped, while backend connections currently associated with a client session are dropped as soon as the client tries to use them. This is equivalent to deleting the server from a hostgroup. Internally, setting a server in OFFLINE_HARD status is equivalent to deleting the server 17 | OfflineHard, 18 | } 19 | 20 | impl fmt::Display for ProxyStatus { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | match self { 23 | ProxyStatus::Online => write!(f, "ONLINE"), 24 | ProxyStatus::Shunned => write!(f, "SHUNNED"), 25 | ProxyStatus::OfflineSoft => write!(f, "OFFLINE_SOFT"), 26 | ProxyStatus::OfflineHard => write!(f, "OFFLINE_HARD"), 27 | } 28 | } 29 | } 30 | 31 | impl From for ProxyStatus { 32 | fn from(s: String) -> Self { 33 | match s.to_uppercase().as_str() { 34 | "ONLINE" => ProxyStatus::Online, 35 | "SHUNNED" => ProxyStatus::Shunned, 36 | "OFFLINE_SOFT" => ProxyStatus::OfflineSoft, 37 | "OFFLINE_HARD" => ProxyStatus::OfflineHard, 38 | _ => ProxyStatus::Online, 39 | } 40 | } 41 | } 42 | 43 | impl From for ProxyStatus { 44 | fn from(status: ReadysetStatus) -> Self { 45 | match status { 46 | ReadysetStatus::Online => ProxyStatus::Online, 47 | ReadysetStatus::SnapshotInProgress => ProxyStatus::Shunned, 48 | ReadysetStatus::Maintenance => ProxyStatus::OfflineSoft, 49 | ReadysetStatus::Unknown => ProxyStatus::Shunned, 50 | } 51 | } 52 | } 53 | 54 | #[derive(PartialEq, Clone, Copy)] 55 | pub enum ReadysetStatus { 56 | Online, 57 | SnapshotInProgress, 58 | Maintenance, 59 | Unknown, 60 | } 61 | 62 | impl fmt::Display for ReadysetStatus { 63 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 64 | match self { 65 | ReadysetStatus::Online => write!(f, "Online"), 66 | ReadysetStatus::SnapshotInProgress => write!(f, "Snapshot in progress"), 67 | ReadysetStatus::Maintenance => write!(f, "Maintenance mode"), 68 | ReadysetStatus::Unknown => write!(f, "Unknown"), 69 | } 70 | } 71 | } 72 | 73 | impl From for ReadysetStatus { 74 | fn from(s: String) -> Self { 75 | match s.to_lowercase().as_str() { 76 | "online" => ReadysetStatus::Online, 77 | "snapshot in progress" => ReadysetStatus::SnapshotInProgress, 78 | "maintenance mode" => ReadysetStatus::Maintenance, 79 | _ => ReadysetStatus::Unknown, 80 | } 81 | } 82 | } 83 | 84 | /// Represents a Readyset host 85 | pub struct Host { 86 | hostname: String, 87 | port: u16, 88 | proxysql_status: ProxyStatus, 89 | readyset_status: ReadysetStatus, 90 | conn: Option, 91 | } 92 | 93 | impl Host { 94 | /// Creates a new `Host` instance with the given hostname and port. 95 | /// The connection to the host is established during the creation of the instance. 96 | /// If the connection fails, the `conn` field will be `None`. 97 | /// If the connection is successful, the `conn` field will contain the connection. 98 | /// 99 | /// # Arguments 100 | /// 101 | /// * `hostname` - The hostname of the host. 102 | /// * `port` - The port number of the host. 103 | /// 104 | /// # Returns 105 | /// 106 | /// A new `Host` instance. 107 | pub fn new(hostname: String, port: u16, proxysql_status: String, config: &Config) -> Host { 108 | let conn = match Conn::new( 109 | OptsBuilder::new() 110 | .ip_or_hostname(Some(hostname.clone())) 111 | .tcp_port(port) 112 | .user(Some(config.readyset_user.clone())) 113 | .pass(Some(config.readyset_password.clone())) 114 | .prefer_socket(false) 115 | .read_timeout(Some(Duration::from_secs(5))) 116 | .write_timeout(Some(Duration::from_secs(5))) 117 | .tcp_connect_timeout(Some(Duration::from_secs(5))), 118 | ) { 119 | Ok(conn) => conn, 120 | Err(err) => { 121 | eprintln!("Failed to establish connection: {}", err); 122 | return Host { 123 | hostname, 124 | port, 125 | proxysql_status: ProxyStatus::from(proxysql_status), 126 | readyset_status: ReadysetStatus::Unknown, 127 | conn: None, 128 | }; 129 | } 130 | }; 131 | 132 | Host { 133 | hostname, 134 | port, 135 | proxysql_status: ProxyStatus::from(proxysql_status), 136 | readyset_status: ReadysetStatus::Unknown, 137 | conn: Some(conn), 138 | } 139 | } 140 | 141 | /// Gets the hostname of the host. 142 | /// 143 | /// # Returns 144 | /// 145 | /// The hostname of the host. 146 | pub fn get_hostname(&self) -> &String { 147 | &self.hostname 148 | } 149 | 150 | /// Gets the port of the host. 151 | /// 152 | /// # Returns 153 | /// 154 | /// The port of the host. 155 | pub fn get_port(&self) -> u16 { 156 | self.port 157 | } 158 | 159 | /// Gets the proxysql status of the host. 160 | /// 161 | /// # Returns 162 | /// 163 | /// The status of the host. 164 | pub fn get_proxysql_status(&self) -> ProxyStatus { 165 | self.proxysql_status 166 | } 167 | 168 | /// Changes the proxysql status of the host. 169 | /// 170 | /// # Arguments 171 | /// 172 | /// * `status` - The new status of the host. 173 | pub fn change_proxysql_status(&mut self, status: ProxyStatus) { 174 | self.proxysql_status = status; 175 | } 176 | 177 | /// Checks if the host is online in proxysql. 178 | /// 179 | /// # Returns 180 | /// 181 | /// true if the host is online, false otherwise. 182 | pub fn is_proxysql_online(&self) -> bool { 183 | self.proxysql_status == ProxyStatus::Online 184 | } 185 | 186 | /// Gets the readyset status of the host. 187 | /// 188 | /// # Returns 189 | /// 190 | /// The status of the host. 191 | pub fn get_readyset_status(&self) -> ReadysetStatus { 192 | self.readyset_status 193 | } 194 | 195 | /// Checks if the Readyset host is ready to serve traffic. 196 | /// This is done by querying the SHOW READYSET STATUS command. 197 | /// 198 | /// # Returns 199 | /// 200 | /// true if the host is ready, false otherwise. 201 | pub fn check_readyset_is_ready(&mut self) -> Result { 202 | match &mut self.conn { 203 | Some(conn) => { 204 | let result = conn.query("SHOW READYSET STATUS"); 205 | match result { 206 | Ok(rows) => { 207 | let rows: Vec<(String, String)> = rows; 208 | for (field, value) in rows { 209 | if field == "Snapshot Status" && value == "Completed" { 210 | self.readyset_status = ReadysetStatus::Online; 211 | return Ok(ProxyStatus::Online); 212 | } else if field == "Snapshot Status" && value == "In Progress" { 213 | self.readyset_status = ReadysetStatus::SnapshotInProgress; 214 | return Ok(ProxyStatus::Shunned); 215 | } else if field == "Status" { 216 | let status = ReadysetStatus::from(value); 217 | self.readyset_status = status; 218 | return Ok(status.into()); 219 | } 220 | } 221 | self.readyset_status = ReadysetStatus::Unknown; 222 | Ok(ProxyStatus::Shunned) 223 | } 224 | Err(err) => Err(mysql::Error::IoError(std::io::Error::other(format!( 225 | "Failed to execute query: {}", 226 | err 227 | )))), 228 | } 229 | } 230 | None => Err(mysql::Error::IoError(std::io::Error::other( 231 | "Connection to Readyset host is not established", 232 | ))), 233 | } 234 | } 235 | 236 | /// Checks if the host supports the given query. 237 | /// This is done by querying the EXPLAIN CREATE CACHE FROM command. 238 | /// 239 | /// # Arguments 240 | /// 241 | /// * `digest_text` - The digest text of the query. 242 | /// * `schema` - The schema of the query. 243 | /// 244 | /// # Returns 245 | /// 246 | /// true if the host supports the query, false otherwise. 247 | pub fn check_query_support( 248 | &mut self, 249 | digest_text: &String, 250 | schema: &String, 251 | ) -> Result { 252 | match &mut self.conn { 253 | Some(conn) => { 254 | conn.query_drop(format!("USE {}", schema)) 255 | .expect("Failed to use schema"); 256 | let row: Option<(String, String, String)> = 257 | conn.query_first(format!("EXPLAIN CREATE CACHE FROM {}", digest_text))?; 258 | match row { 259 | Some((_, _, value)) => Ok(value == "yes" || value == "cached"), 260 | None => Ok(false), 261 | } 262 | } 263 | None => Ok(false), 264 | } 265 | } 266 | 267 | /// Caches the given query on the host. 268 | /// This is done by executing the CREATE CACHE FROM command. 269 | /// 270 | /// # Arguments 271 | /// 272 | /// * `digest_text` - The digest text of the query. 273 | /// 274 | /// # Returns 275 | /// 276 | /// true if the query was cached successfully, false otherwise. 277 | pub fn cache_query(&mut self, query: &Query) -> Result { 278 | match &mut self.conn { 279 | None => { 280 | return Err(mysql::Error::IoError(std::io::Error::other( 281 | "Connection to Readyset host is not established", 282 | ))) 283 | } 284 | Some(conn) => { 285 | conn.query_drop(format!("USE {}", query.get_schema()))?; 286 | conn.query_drop(format!( 287 | "CREATE CACHE d_{} FROM {}", 288 | query.get_digest(), 289 | query.get_digest_text() 290 | ))?; 291 | } 292 | } 293 | Ok(true) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/queries.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | config::{Config, QueryDiscoveryMode}, 3 | messages, 4 | proxysql::ProxySQL, 5 | }; 6 | use mysql::{prelude::Queryable, Conn}; 7 | 8 | pub struct Query { 9 | digest_text: String, 10 | digest: String, 11 | schema: String, 12 | user: String, 13 | } 14 | 15 | impl Query { 16 | /// This function is used to create a new Query struct. 17 | /// 18 | /// # Arguments 19 | /// 20 | /// * `digest_text` - A string containing the digest text of the query. 21 | /// * `digest` - A string containing the digest of the query. 22 | /// * `schema` - A string containing the schema name of the query. 23 | /// * `user` - A string containing the user that executed the query. 24 | /// 25 | /// # Returns 26 | /// 27 | /// A new Query struct. 28 | fn new(digest_text: String, digest: String, schema: String, user: String) -> Self { 29 | Query { 30 | digest_text, 31 | digest, 32 | schema, 33 | user, 34 | } 35 | } 36 | 37 | /// This function is used to get the digest text of the query. 38 | /// 39 | /// # Returns 40 | /// A string containing the digest text of the query. 41 | pub fn get_digest_text(&self) -> &String { 42 | &self.digest_text 43 | } 44 | 45 | /// This function is used to get the digest of the query. 46 | /// 47 | /// # Returns 48 | /// 49 | /// A string containing the digest of the query. 50 | pub fn get_digest(&self) -> &String { 51 | &self.digest 52 | } 53 | 54 | /// This function is used to get the schema name of the query. 55 | /// 56 | /// # Returns 57 | /// 58 | /// A string containing the schema name of the query. 59 | pub fn get_schema(&self) -> &String { 60 | &self.schema 61 | } 62 | 63 | /// This function is used to get the user that executed the query. 64 | /// 65 | /// # Returns 66 | /// 67 | /// A string containing the user that executed the query. 68 | pub fn get_user(&self) -> &String { 69 | &self.user 70 | } 71 | } 72 | 73 | pub struct QueryDiscovery { 74 | query_discovery_mode: QueryDiscoveryMode, 75 | query_discovery_min_execution: u64, 76 | query_discovery_min_rows_sent: u64, 77 | source_hostgroup: u16, 78 | readyset_user: String, 79 | number_of_queries: u16, 80 | offset: u16, 81 | } 82 | 83 | /// Query Discovery is a feature responsible for discovering queries that are hurting the database performance. 84 | /// The queries are discovered by analyzing the stats_mysql_query_digest table and finding queries that are not cached in ReadySet and are not in the mysql_query_rules table. 85 | /// The query discover is also responsible for promoting the queries from mirror(warmup) to destination. 86 | impl QueryDiscovery { 87 | /// This function is used to create a new QueryDiscovery struct. 88 | /// 89 | /// # Arguments 90 | /// 91 | /// * `query_discovery_mode` - A QueryDiscoveryMode containing the mode to use for query discovery. 92 | /// * `config` - A Config containing the configuration for the query discovery. 93 | /// * `offset` - A u16 containing the offset to use for query discovery. 94 | /// 95 | /// # Returns 96 | /// 97 | /// A new QueryDiscovery struct. 98 | pub fn new(config: Config) -> Self { 99 | QueryDiscovery { 100 | query_discovery_mode: config 101 | .query_discovery_mode 102 | .unwrap_or(QueryDiscoveryMode::CountStar), 103 | query_discovery_min_execution: config.query_discovery_min_execution.unwrap_or(0), 104 | query_discovery_min_rows_sent: config.query_discovery_min_row_sent.unwrap_or(0), 105 | source_hostgroup: config.source_hostgroup, 106 | readyset_user: config.readyset_user.clone(), 107 | number_of_queries: config.number_of_queries, 108 | offset: 0, 109 | } 110 | } 111 | 112 | /// This function is used to generate the query responsible for finding queries that are not cached in ReadySet and are not in the mysql_query_rules table. 113 | /// Queries have to return 3 fields: digest_text, digest, and schema name. 114 | /// 115 | /// # Arguments 116 | /// 117 | /// * `query_discovery_mode` - A QueryDiscoveryMode containing the mode to use for query discovery. 118 | /// 119 | /// # Returns 120 | /// 121 | /// A string containing the query responsible for finding queries that are not cached in ReadySet and are not in the mysql_query_rules table. 122 | fn query_builder(&self) -> String { 123 | let order_by = match self.query_discovery_mode { 124 | QueryDiscoveryMode::SumRowsSent => "s.sum_rows_sent".to_string(), 125 | QueryDiscoveryMode::SumTime => "s.sum_time".to_string(), 126 | QueryDiscoveryMode::MeanTime => "(s.sum_time / s.count_star)".to_string(), 127 | QueryDiscoveryMode::CountStar => "s.count_star".to_string(), 128 | QueryDiscoveryMode::ExecutionTimeDistance => "(s.max_time - s.min_time)".to_string(), 129 | QueryDiscoveryMode::QueryThroughput => "(s.count_star / s.sum_time)".to_string(), 130 | QueryDiscoveryMode::WorstBestCase => "s.min_time".to_string(), 131 | QueryDiscoveryMode::WorstWorstCase => "s.max_time".to_string(), 132 | QueryDiscoveryMode::DistanceMeanMax => { 133 | "(s.max_time - (s.sum_time / s.count_star))".to_string() 134 | } 135 | QueryDiscoveryMode::External => unreachable!("External mode is caught earlier"), 136 | }; 137 | 138 | format!( 139 | "SELECT s.digest_text, s.digest, s.schemaname 140 | FROM stats_mysql_query_digest s 141 | LEFT JOIN mysql_query_rules q 142 | USING(digest) 143 | WHERE s.hostgroup = {} 144 | AND s.username = '{}' 145 | AND s.schemaname NOT IN ('sys', 'information_schema', 'performance_schema', 'mysql') 146 | AND s.digest_text LIKE 'SELECT%FROM%' 147 | AND digest_text NOT LIKE '%?=?%' 148 | AND s.count_star > {} 149 | AND s.sum_rows_sent > {} 150 | AND q.rule_id IS NULL 151 | ORDER BY {} DESC 152 | LIMIT {} OFFSET {}", 153 | self.source_hostgroup, 154 | self.readyset_user, 155 | self.query_discovery_min_execution, 156 | self.query_discovery_min_rows_sent, 157 | order_by, 158 | self.number_of_queries, 159 | self.offset 160 | ) 161 | } 162 | 163 | pub fn run(&mut self, proxysql: &mut ProxySQL, conn: &mut Conn) { 164 | if proxysql.number_of_online_hosts() == 0 { 165 | return; 166 | } 167 | 168 | let mut queries_added_or_change = proxysql.adjust_mirror_rules().unwrap(); 169 | 170 | let mut current_queries_digest: Vec = proxysql.find_queries_routed_to_readyset(); 171 | 172 | let mut more_queries = true; 173 | while more_queries && current_queries_digest.len() < self.number_of_queries as usize { 174 | let queries_to_cache = self.find_queries_to_cache(conn); 175 | more_queries = !queries_to_cache.is_empty(); 176 | for query in queries_to_cache[0..queries_to_cache.len()].iter() { 177 | if current_queries_digest.len() > self.number_of_queries as usize { 178 | break; 179 | } 180 | let digest_text = self.replace_placeholders(query.get_digest_text()); 181 | messages::print_note( 182 | format!("Going to test query support for {}", digest_text).as_str(), 183 | ); 184 | let supported = proxysql 185 | .get_first_online_host() 186 | .unwrap() 187 | .check_query_support(&digest_text, query.get_schema()); // Safe to unwrap because we checked if hosts is empty 188 | match supported { 189 | Ok(true) => { 190 | messages::print_note( 191 | "Query is supported, adding it to proxysql and readyset" 192 | .to_string() 193 | .as_str(), 194 | ); 195 | queries_added_or_change = true; 196 | if !proxysql.dry_run() { 197 | proxysql.get_online_hosts().iter_mut().for_each(|host| { 198 | host.cache_query(query).unwrap_or_else(|_| { 199 | panic!( 200 | "Failed to create readyset cache on host {}:{}", 201 | host.get_hostname(), 202 | host.get_port() 203 | ) 204 | }); 205 | }); 206 | proxysql 207 | .add_as_query_rule(query) 208 | .expect("Failed to add query rule"); 209 | } else { 210 | messages::print_info("Dry run, not adding query"); 211 | } 212 | current_queries_digest.push(query.get_digest().to_string()); 213 | } 214 | Ok(false) => { 215 | messages::print_note("Query is not supported"); 216 | } 217 | Err(err) => { 218 | messages::print_warning( 219 | format!("Failed to check query support: {}", err).as_str(), 220 | ); 221 | } 222 | } 223 | } 224 | self.offset += queries_to_cache.len() as u16; 225 | } 226 | if queries_added_or_change { 227 | proxysql 228 | .load_query_rules() 229 | .expect("Failed to load query rules"); 230 | proxysql 231 | .save_query_rules() 232 | .expect("Failed to save query rules"); 233 | } 234 | } 235 | 236 | /// This function is used to find queries that are not cached in ReadySet and are not in the mysql_query_rules table. 237 | /// 238 | /// # Arguments 239 | /// * `conn` - A reference to a connection to ProxySQL. 240 | /// * `config` - A reference to the configuration struct. 241 | /// 242 | /// # Returns 243 | /// A vector of tuples containing the digest_text, digest, and schema name of the queries that are not cached in ReadySet and are not in the mysql_query_rules table. 244 | fn find_queries_to_cache(&self, con: &mut Conn) -> Vec { 245 | match self.query_discovery_mode { 246 | QueryDiscoveryMode::External => { 247 | todo!("External mode is not implemented yet"); 248 | } 249 | _ => { 250 | let query = self.query_builder(); 251 | let rows: Vec<(String, String, String)> = 252 | con.query(query).expect("Failed to find queries to cache"); 253 | rows.iter() 254 | .map(|(digest_text, digest, schema)| { 255 | Query::new( 256 | self.replace_placeholders(digest_text), 257 | digest.to_string(), 258 | schema.to_string(), 259 | self.readyset_user.clone(), 260 | ) 261 | }) 262 | .collect() 263 | } 264 | } 265 | } 266 | 267 | fn replace_placeholders(&self, query: &str) -> String { 268 | // date placeholder 269 | // multiple placeholders 270 | query.replace("?,?,?,...", "?,?,?").replace("?-?-?", "?") 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/proxysql.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Local}; 2 | use mysql::{prelude::Queryable, Conn, OptsBuilder}; 3 | 4 | use crate::{ 5 | config, 6 | hosts::{Host, ProxyStatus}, 7 | messages, 8 | queries::Query, 9 | }; 10 | 11 | const MIRROR_QUERY_TOKEN: &str = "Mirror by readyset scheduler at"; 12 | const DESTINATION_QUERY_TOKEN: &str = "Added by readyset scheduler at"; 13 | pub struct ProxySQL { 14 | readyset_hostgroup: u16, 15 | warmup_time_s: u16, 16 | conn: mysql::Conn, 17 | hosts: Vec, 18 | dry_run: bool, 19 | } 20 | 21 | impl ProxySQL { 22 | /// This function is used to create a new ProxySQL struct. 23 | /// 24 | /// # Arguments 25 | /// 26 | /// * `config` - A reference to a config::Config containing the configuration for the ProxySQL connection. 27 | /// 28 | /// # Returns 29 | /// 30 | /// A new ProxySQL struct. 31 | pub fn new(config: &config::Config, dry_run: bool) -> Self { 32 | let mut conn = Conn::new( 33 | OptsBuilder::new() 34 | .ip_or_hostname(Some(config.proxysql_host.as_str())) 35 | .tcp_port(config.proxysql_port) 36 | .user(Some(config.proxysql_user.as_str())) 37 | .pass(Some(config.proxysql_password.as_str())) 38 | .prefer_socket(false), 39 | ) 40 | .expect("Failed to create ProxySQL connection"); 41 | 42 | let query = format!( 43 | "SELECT hostname, port, status, comment FROM mysql_servers WHERE hostgroup_id = {} AND status IN ('ONLINE', 'SHUNNED', 'OFFLINE_SOFT')", 44 | config.readyset_hostgroup 45 | ); 46 | let results: Vec<(String, u16, String, String)> = conn.query(query).unwrap(); 47 | let hosts = results 48 | .into_iter() 49 | .filter_map(|(hostname, port, status, comment)| { 50 | if comment.to_lowercase().contains("readyset") { 51 | Some(Host::new(hostname, port, status, config)) 52 | } else { 53 | None 54 | } 55 | }) 56 | .collect::>(); 57 | 58 | ProxySQL { 59 | conn, 60 | readyset_hostgroup: config.readyset_hostgroup, 61 | warmup_time_s: config.warmup_time_s.unwrap_or(0), 62 | hosts, 63 | dry_run, 64 | } 65 | } 66 | 67 | /// This function is used to get the dry_run field. 68 | /// This field is used to indicate if the ProxySQL operations should be executed or not. 69 | /// 70 | /// # Returns 71 | /// 72 | /// A boolean indicating if the ProxySQL operations should be executed or not. 73 | pub fn dry_run(&self) -> bool { 74 | self.dry_run 75 | } 76 | 77 | /// This function is used to add a query rule to ProxySQL. 78 | /// 79 | /// # Arguments 80 | /// 81 | /// * `query` - A reference to a Query containing the query to be added as a rule. 82 | /// 83 | /// # Returns 84 | /// 85 | /// A boolean indicating if the rule was added successfully. 86 | pub fn add_as_query_rule(&mut self, query: &Query) -> Result { 87 | let datetime_now: DateTime = Local::now(); 88 | let date_formatted = datetime_now.format("%Y-%m-%d %H:%M:%S"); 89 | if self.warmup_time_s > 0 { 90 | self.conn.query_drop(format!("INSERT INTO mysql_query_rules (username, mirror_hostgroup, active, digest, apply, comment) VALUES ('{}', {}, 1, '{}', 1, '{}: {}')", query.get_user(), self.readyset_hostgroup, query.get_digest(), MIRROR_QUERY_TOKEN, date_formatted)).expect("Failed to insert into mysql_query_rules"); 91 | messages::print_note("Inserted warm-up rule"); 92 | } else { 93 | self.conn.query_drop(format!("INSERT INTO mysql_query_rules (username, destination_hostgroup, active, digest, apply, comment) VALUES ('{}', {}, 1, '{}', 1, '{}: {}')", query.get_user(), self.readyset_hostgroup, query.get_digest(), DESTINATION_QUERY_TOKEN, date_formatted)).expect("Failed to insert into mysql_query_rules"); 94 | messages::print_note("Inserted destination rule"); 95 | } 96 | Ok(true) 97 | } 98 | 99 | pub fn load_query_rules(&mut self) -> Result { 100 | self.conn 101 | .query_drop("LOAD MYSQL QUERY RULES TO RUNTIME") 102 | .expect("Failed to load query rules"); 103 | Ok(true) 104 | } 105 | pub fn save_query_rules(&mut self) -> Result { 106 | self.conn 107 | .query_drop("SAVE MYSQL QUERY RULES TO DISK") 108 | .expect("Failed to load query rules"); 109 | Ok(true) 110 | } 111 | 112 | /// This function is used to check the current list of queries routed to Readyset. 113 | /// 114 | /// # Arguments 115 | /// * `conn` - A reference to a connection to ProxySQL. 116 | /// 117 | /// # Returns 118 | /// A vector of tuples containing the digest_text, digest, and schemaname of the queries that are currently routed to ReadySet. 119 | pub fn find_queries_routed_to_readyset(&mut self) -> Vec { 120 | let rows: Vec = self 121 | .conn 122 | .query(format!( 123 | "SELECT digest FROM mysql_query_rules WHERE comment LIKE '{}%' OR comment LIKE '{}%'", 124 | MIRROR_QUERY_TOKEN, DESTINATION_QUERY_TOKEN 125 | )) 126 | .expect("Failed to find queries routed to ReadySet"); 127 | rows 128 | } 129 | 130 | /// This function is used to check if any mirror query rule needs to be changed to destination. 131 | /// 132 | /// # Returns 133 | /// 134 | /// A boolean indicating if any mirror query rule was changed to destination. 135 | pub fn adjust_mirror_rules(&mut self) -> Result { 136 | let mut updated_rules = false; 137 | let datetime_now: DateTime = Local::now(); 138 | let tz = datetime_now.format("%z").to_string(); 139 | let date_formatted = datetime_now.format("%Y-%m-%d %H:%M:%S"); 140 | let rows: Vec<(u16, String)> = self.conn.query(format!("SELECT rule_id, comment FROM mysql_query_rules WHERE comment LIKE '{}: ____-__-__ __:__:__';", MIRROR_QUERY_TOKEN)).expect("Failed to select mirror rules"); 141 | for (rule_id, comment) in rows { 142 | let datetime_mirror_str = comment 143 | .split("Mirror by readyset scheduler at:") 144 | .nth(1) 145 | .unwrap_or("") 146 | .trim(); 147 | let datetime_mirror_str = format!("{} {}", datetime_mirror_str, tz); 148 | let datetime_mirror_rule = 149 | DateTime::parse_from_str(datetime_mirror_str.as_str(), "%Y-%m-%d %H:%M:%S %z") 150 | .unwrap_or_else(|_| { 151 | panic!("Failed to parse datetime from comment: {}", comment); 152 | }); 153 | let elapsed = datetime_now 154 | .signed_duration_since(datetime_mirror_rule) 155 | .num_seconds(); 156 | if elapsed > self.warmup_time_s as i64 { 157 | let comment = format!( 158 | "{}\n Added by readyset scheduler at: {}", 159 | comment, date_formatted 160 | ); 161 | self.conn.query_drop(format!("UPDATE mysql_query_rules SET mirror_hostgroup = NULL, destination_hostgroup = {}, comment = '{}' WHERE rule_id = {}", self.readyset_hostgroup, comment, rule_id)).expect("Failed to update rule"); 162 | messages::print_note( 163 | format!("Updated rule ID {} from warmup to destination", rule_id).as_str(), 164 | ); 165 | updated_rules = true; 166 | } 167 | } 168 | Ok(updated_rules) 169 | } 170 | 171 | /// This function is used to check if a given host is healthy. 172 | /// This is done by checking if the Readyset host has an active 173 | /// connection and if the snapshot is completed. 174 | pub fn health_check(&mut self) { 175 | let mut status_changes = Vec::new(); 176 | 177 | for host in self.hosts.iter_mut() { 178 | match host.check_readyset_is_ready() { 179 | Ok(ready) => match ready { 180 | ProxyStatus::Online => { 181 | status_changes.push((host, ProxyStatus::Online)); 182 | } 183 | ProxyStatus::Shunned => { 184 | status_changes.push((host, ProxyStatus::Shunned)); 185 | } 186 | ProxyStatus::OfflineSoft => { 187 | status_changes.push((host, ProxyStatus::OfflineSoft)); 188 | } 189 | ProxyStatus::OfflineHard => { 190 | status_changes.push((host, ProxyStatus::OfflineHard)); 191 | } 192 | }, 193 | Err(e) => { 194 | messages::print_error(format!("Cannot check Readyset status: {}.", e).as_str()); 195 | status_changes.push((host, ProxyStatus::Shunned)); 196 | } 197 | }; 198 | } 199 | 200 | for (host, status) in status_changes { 201 | if host.get_proxysql_status() != status { 202 | let where_clause = format!( 203 | "WHERE hostgroup_id = {} AND hostname = '{}' AND port = {}", 204 | self.readyset_hostgroup, 205 | host.get_hostname(), 206 | host.get_port() 207 | ); 208 | messages::print_note( 209 | format!( 210 | "Server HG: {}, Host: {}, Port: {} is currently {} on proxysql and {} on readyset. Changing to {}", 211 | self.readyset_hostgroup, 212 | host.get_hostname(), 213 | host.get_port(), 214 | host.get_proxysql_status(), 215 | host.get_readyset_status().to_string().to_uppercase(), 216 | status 217 | ) 218 | .as_str(), 219 | ); 220 | host.change_proxysql_status(status); 221 | if self.dry_run { 222 | messages::print_info("Dry run, skipping changes to ProxySQL"); 223 | continue; 224 | } 225 | let _ = self.conn.query_drop(format!( 226 | "UPDATE mysql_servers SET status = '{}' {}", 227 | host.get_proxysql_status(), 228 | where_clause 229 | )); 230 | let _ = self.conn.query_drop("LOAD MYSQL SERVERS TO RUNTIME"); 231 | let _ = self.conn.query_drop("SAVE MYSQL SERVERS TO DISK"); 232 | } 233 | } 234 | } 235 | 236 | /// This function is used to get the number of online hosts. 237 | /// This is done by filtering the hosts vector and counting the number of hosts with status Online. 238 | /// 239 | /// # Returns 240 | /// 241 | /// A u16 containing the number of online hosts. 242 | pub fn number_of_online_hosts(&self) -> u16 { 243 | self.hosts 244 | .iter() 245 | .filter(|host| host.is_proxysql_online()) 246 | .collect::>() 247 | .len() as u16 248 | } 249 | 250 | /// This function is used to get the first online host. 251 | /// This is done by iterating over the hosts vector and returning the first host with status Online. 252 | /// 253 | /// # Returns 254 | /// 255 | /// An Option containing a reference to the first online host. 256 | pub fn get_first_online_host(&mut self) -> Option<&mut Host> { 257 | self.hosts.iter_mut().find(|host| host.is_proxysql_online()) 258 | } 259 | 260 | /// This function is used to get all the online hosts. 261 | /// This is done by filtering the hosts vector and collecting the hosts with status Online. 262 | /// 263 | /// # Returns 264 | /// 265 | /// A vector containing references to the online hosts. 266 | pub fn get_online_hosts(&mut self) -> Vec<&mut Host> { 267 | self.hosts 268 | .iter_mut() 269 | .filter(|host| host.is_proxysql_online()) 270 | .collect() 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "1.1.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "android-tzdata" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "anstream" 37 | version = "0.6.18" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 40 | dependencies = [ 41 | "anstyle", 42 | "anstyle-parse", 43 | "anstyle-query", 44 | "anstyle-wincon", 45 | "colorchoice", 46 | "is_terminal_polyfill", 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle" 52 | version = "1.0.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 55 | 56 | [[package]] 57 | name = "anstyle-parse" 58 | version = "0.2.6" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 61 | dependencies = [ 62 | "utf8parse", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-query" 67 | version = "1.1.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 70 | dependencies = [ 71 | "windows-sys 0.59.0", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-wincon" 76 | version = "3.0.8" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" 79 | dependencies = [ 80 | "anstyle", 81 | "once_cell_polyfill", 82 | "windows-sys 0.59.0", 83 | ] 84 | 85 | [[package]] 86 | name = "autocfg" 87 | version = "1.4.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 90 | 91 | [[package]] 92 | name = "base64" 93 | version = "0.22.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 96 | 97 | [[package]] 98 | name = "bitflags" 99 | version = "2.9.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 102 | 103 | [[package]] 104 | name = "block-buffer" 105 | version = "0.10.4" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 108 | dependencies = [ 109 | "generic-array", 110 | ] 111 | 112 | [[package]] 113 | name = "btoi" 114 | version = "0.4.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" 117 | dependencies = [ 118 | "num-traits", 119 | ] 120 | 121 | [[package]] 122 | name = "bufstream" 123 | version = "0.1.4" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" 126 | 127 | [[package]] 128 | name = "bumpalo" 129 | version = "3.17.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 132 | 133 | [[package]] 134 | name = "byteorder" 135 | version = "1.5.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 138 | 139 | [[package]] 140 | name = "bytes" 141 | version = "1.10.1" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 144 | 145 | [[package]] 146 | name = "cc" 147 | version = "1.2.25" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" 150 | dependencies = [ 151 | "jobserver", 152 | "libc", 153 | "shlex", 154 | ] 155 | 156 | [[package]] 157 | name = "cfg-if" 158 | version = "1.0.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 161 | 162 | [[package]] 163 | name = "chrono" 164 | version = "0.4.41" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 167 | dependencies = [ 168 | "android-tzdata", 169 | "iana-time-zone", 170 | "js-sys", 171 | "num-traits", 172 | "wasm-bindgen", 173 | "windows-link", 174 | ] 175 | 176 | [[package]] 177 | name = "clap" 178 | version = "4.5.39" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" 181 | dependencies = [ 182 | "clap_builder", 183 | "clap_derive", 184 | ] 185 | 186 | [[package]] 187 | name = "clap_builder" 188 | version = "4.5.39" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" 191 | dependencies = [ 192 | "anstream", 193 | "anstyle", 194 | "clap_lex", 195 | "strsim", 196 | ] 197 | 198 | [[package]] 199 | name = "clap_derive" 200 | version = "4.5.32" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 203 | dependencies = [ 204 | "heck", 205 | "proc-macro2", 206 | "quote", 207 | "syn", 208 | ] 209 | 210 | [[package]] 211 | name = "clap_lex" 212 | version = "0.7.4" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 215 | 216 | [[package]] 217 | name = "cmake" 218 | version = "0.1.54" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 221 | dependencies = [ 222 | "cc", 223 | ] 224 | 225 | [[package]] 226 | name = "colorchoice" 227 | version = "1.0.3" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 230 | 231 | [[package]] 232 | name = "core-foundation-sys" 233 | version = "0.8.7" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 236 | 237 | [[package]] 238 | name = "cpufeatures" 239 | version = "0.2.17" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 242 | dependencies = [ 243 | "libc", 244 | ] 245 | 246 | [[package]] 247 | name = "crc32fast" 248 | version = "1.4.2" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 251 | dependencies = [ 252 | "cfg-if", 253 | ] 254 | 255 | [[package]] 256 | name = "crossbeam-queue" 257 | version = "0.3.12" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 260 | dependencies = [ 261 | "crossbeam-utils", 262 | ] 263 | 264 | [[package]] 265 | name = "crossbeam-utils" 266 | version = "0.8.21" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 269 | 270 | [[package]] 271 | name = "crypto-common" 272 | version = "0.1.6" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 275 | dependencies = [ 276 | "generic-array", 277 | "typenum", 278 | ] 279 | 280 | [[package]] 281 | name = "darling" 282 | version = "0.20.11" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 285 | dependencies = [ 286 | "darling_core", 287 | "darling_macro", 288 | ] 289 | 290 | [[package]] 291 | name = "darling_core" 292 | version = "0.20.11" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 295 | dependencies = [ 296 | "fnv", 297 | "ident_case", 298 | "proc-macro2", 299 | "quote", 300 | "strsim", 301 | "syn", 302 | ] 303 | 304 | [[package]] 305 | name = "darling_macro" 306 | version = "0.20.11" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 309 | dependencies = [ 310 | "darling_core", 311 | "quote", 312 | "syn", 313 | ] 314 | 315 | [[package]] 316 | name = "derive_utils" 317 | version = "0.15.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "ccfae181bab5ab6c5478b2ccb69e4c68a02f8c3ec72f6616bfec9dbc599d2ee0" 320 | dependencies = [ 321 | "proc-macro2", 322 | "quote", 323 | "syn", 324 | ] 325 | 326 | [[package]] 327 | name = "digest" 328 | version = "0.10.7" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 331 | dependencies = [ 332 | "block-buffer", 333 | "crypto-common", 334 | ] 335 | 336 | [[package]] 337 | name = "displaydoc" 338 | version = "0.2.5" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 341 | dependencies = [ 342 | "proc-macro2", 343 | "quote", 344 | "syn", 345 | ] 346 | 347 | [[package]] 348 | name = "equivalent" 349 | version = "1.0.2" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 352 | 353 | [[package]] 354 | name = "file-guard" 355 | version = "0.2.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c" 358 | dependencies = [ 359 | "libc", 360 | "winapi", 361 | ] 362 | 363 | [[package]] 364 | name = "flate2" 365 | version = "1.1.1" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" 368 | dependencies = [ 369 | "crc32fast", 370 | "libz-sys", 371 | "miniz_oxide", 372 | ] 373 | 374 | [[package]] 375 | name = "fnv" 376 | version = "1.0.7" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 379 | 380 | [[package]] 381 | name = "form_urlencoded" 382 | version = "1.2.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 385 | dependencies = [ 386 | "percent-encoding", 387 | ] 388 | 389 | [[package]] 390 | name = "generic-array" 391 | version = "0.14.7" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 394 | dependencies = [ 395 | "typenum", 396 | "version_check", 397 | ] 398 | 399 | [[package]] 400 | name = "getrandom" 401 | version = "0.2.16" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 404 | dependencies = [ 405 | "cfg-if", 406 | "libc", 407 | "wasi 0.11.0+wasi-snapshot-preview1", 408 | ] 409 | 410 | [[package]] 411 | name = "getrandom" 412 | version = "0.3.3" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 415 | dependencies = [ 416 | "cfg-if", 417 | "libc", 418 | "r-efi", 419 | "wasi 0.14.2+wasi-0.2.4", 420 | ] 421 | 422 | [[package]] 423 | name = "hashbrown" 424 | version = "0.15.3" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" 427 | 428 | [[package]] 429 | name = "heck" 430 | version = "0.5.0" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 433 | 434 | [[package]] 435 | name = "iana-time-zone" 436 | version = "0.1.63" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 439 | dependencies = [ 440 | "android_system_properties", 441 | "core-foundation-sys", 442 | "iana-time-zone-haiku", 443 | "js-sys", 444 | "log", 445 | "wasm-bindgen", 446 | "windows-core", 447 | ] 448 | 449 | [[package]] 450 | name = "iana-time-zone-haiku" 451 | version = "0.1.2" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 454 | dependencies = [ 455 | "cc", 456 | ] 457 | 458 | [[package]] 459 | name = "icu_collections" 460 | version = "2.0.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 463 | dependencies = [ 464 | "displaydoc", 465 | "potential_utf", 466 | "yoke", 467 | "zerofrom", 468 | "zerovec", 469 | ] 470 | 471 | [[package]] 472 | name = "icu_locale_core" 473 | version = "2.0.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 476 | dependencies = [ 477 | "displaydoc", 478 | "litemap", 479 | "tinystr", 480 | "writeable", 481 | "zerovec", 482 | ] 483 | 484 | [[package]] 485 | name = "icu_normalizer" 486 | version = "2.0.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 489 | dependencies = [ 490 | "displaydoc", 491 | "icu_collections", 492 | "icu_normalizer_data", 493 | "icu_properties", 494 | "icu_provider", 495 | "smallvec", 496 | "zerovec", 497 | ] 498 | 499 | [[package]] 500 | name = "icu_normalizer_data" 501 | version = "2.0.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 504 | 505 | [[package]] 506 | name = "icu_properties" 507 | version = "2.0.1" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 510 | dependencies = [ 511 | "displaydoc", 512 | "icu_collections", 513 | "icu_locale_core", 514 | "icu_properties_data", 515 | "icu_provider", 516 | "potential_utf", 517 | "zerotrie", 518 | "zerovec", 519 | ] 520 | 521 | [[package]] 522 | name = "icu_properties_data" 523 | version = "2.0.1" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 526 | 527 | [[package]] 528 | name = "icu_provider" 529 | version = "2.0.0" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 532 | dependencies = [ 533 | "displaydoc", 534 | "icu_locale_core", 535 | "stable_deref_trait", 536 | "tinystr", 537 | "writeable", 538 | "yoke", 539 | "zerofrom", 540 | "zerotrie", 541 | "zerovec", 542 | ] 543 | 544 | [[package]] 545 | name = "ident_case" 546 | version = "1.0.1" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 549 | 550 | [[package]] 551 | name = "idna" 552 | version = "1.0.3" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 555 | dependencies = [ 556 | "idna_adapter", 557 | "smallvec", 558 | "utf8_iter", 559 | ] 560 | 561 | [[package]] 562 | name = "idna_adapter" 563 | version = "1.2.1" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 566 | dependencies = [ 567 | "icu_normalizer", 568 | "icu_properties", 569 | ] 570 | 571 | [[package]] 572 | name = "indexmap" 573 | version = "2.9.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 576 | dependencies = [ 577 | "equivalent", 578 | "hashbrown", 579 | ] 580 | 581 | [[package]] 582 | name = "io-enum" 583 | version = "1.2.0" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "d197db2f7ebf90507296df3aebaf65d69f5dce8559d8dbd82776a6cadab61bbf" 586 | dependencies = [ 587 | "derive_utils", 588 | ] 589 | 590 | [[package]] 591 | name = "is_terminal_polyfill" 592 | version = "1.70.1" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 595 | 596 | [[package]] 597 | name = "itoa" 598 | version = "1.0.15" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 601 | 602 | [[package]] 603 | name = "jobserver" 604 | version = "0.1.33" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 607 | dependencies = [ 608 | "getrandom 0.3.3", 609 | "libc", 610 | ] 611 | 612 | [[package]] 613 | name = "js-sys" 614 | version = "0.3.77" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 617 | dependencies = [ 618 | "once_cell", 619 | "wasm-bindgen", 620 | ] 621 | 622 | [[package]] 623 | name = "lazy_static" 624 | version = "1.5.0" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 627 | 628 | [[package]] 629 | name = "libc" 630 | version = "0.2.172" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 633 | 634 | [[package]] 635 | name = "libz-sys" 636 | version = "1.1.22" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" 639 | dependencies = [ 640 | "cc", 641 | "pkg-config", 642 | "vcpkg", 643 | ] 644 | 645 | [[package]] 646 | name = "litemap" 647 | version = "0.8.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 650 | 651 | [[package]] 652 | name = "log" 653 | version = "0.4.27" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 656 | 657 | [[package]] 658 | name = "lru" 659 | version = "0.12.5" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 662 | 663 | [[package]] 664 | name = "memchr" 665 | version = "2.7.4" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 668 | 669 | [[package]] 670 | name = "miniz_oxide" 671 | version = "0.8.8" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 674 | dependencies = [ 675 | "adler2", 676 | ] 677 | 678 | [[package]] 679 | name = "mysql" 680 | version = "26.0.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "64453aedc258ac8c720b46c8264302fad39cef6c02483f68adbad4bcd22d6fab" 683 | dependencies = [ 684 | "bufstream", 685 | "bytes", 686 | "crossbeam-queue", 687 | "flate2", 688 | "io-enum", 689 | "libc", 690 | "lru", 691 | "mysql_common", 692 | "named_pipe", 693 | "pem", 694 | "percent-encoding", 695 | "socket2", 696 | "twox-hash", 697 | "url", 698 | ] 699 | 700 | [[package]] 701 | name = "mysql-common-derive" 702 | version = "0.32.1" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "66f62cad7623a9cb6f8f64037f0c4f69c8db8e82914334a83c9788201c2c1bfa" 705 | dependencies = [ 706 | "darling", 707 | "heck", 708 | "num-bigint", 709 | "proc-macro-crate", 710 | "proc-macro-error2", 711 | "proc-macro2", 712 | "quote", 713 | "syn", 714 | "termcolor", 715 | "thiserror 2.0.12", 716 | ] 717 | 718 | [[package]] 719 | name = "mysql_common" 720 | version = "0.34.1" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "34a9141e735d5bb02414a7ac03add09522466d4db65bdd827069f76ae0850e58" 723 | dependencies = [ 724 | "base64", 725 | "bitflags", 726 | "btoi", 727 | "byteorder", 728 | "bytes", 729 | "cc", 730 | "cmake", 731 | "crc32fast", 732 | "flate2", 733 | "lazy_static", 734 | "mysql-common-derive", 735 | "num-bigint", 736 | "num-traits", 737 | "rand", 738 | "regex", 739 | "saturating", 740 | "serde", 741 | "serde_json", 742 | "sha1", 743 | "sha2", 744 | "subprocess", 745 | "thiserror 1.0.69", 746 | "uuid", 747 | "zstd", 748 | ] 749 | 750 | [[package]] 751 | name = "named_pipe" 752 | version = "0.4.1" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "ad9c443cce91fc3e12f017290db75dde490d685cdaaf508d7159d7cf41f0eb2b" 755 | dependencies = [ 756 | "winapi", 757 | ] 758 | 759 | [[package]] 760 | name = "num-bigint" 761 | version = "0.4.6" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 764 | dependencies = [ 765 | "num-integer", 766 | "num-traits", 767 | ] 768 | 769 | [[package]] 770 | name = "num-integer" 771 | version = "0.1.46" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 774 | dependencies = [ 775 | "num-traits", 776 | ] 777 | 778 | [[package]] 779 | name = "num-traits" 780 | version = "0.2.19" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 783 | dependencies = [ 784 | "autocfg", 785 | ] 786 | 787 | [[package]] 788 | name = "once_cell" 789 | version = "1.21.3" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 792 | 793 | [[package]] 794 | name = "once_cell_polyfill" 795 | version = "1.70.1" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 798 | 799 | [[package]] 800 | name = "pem" 801 | version = "3.0.5" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" 804 | dependencies = [ 805 | "base64", 806 | "serde", 807 | ] 808 | 809 | [[package]] 810 | name = "percent-encoding" 811 | version = "2.3.1" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 814 | 815 | [[package]] 816 | name = "pkg-config" 817 | version = "0.3.32" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 820 | 821 | [[package]] 822 | name = "potential_utf" 823 | version = "0.1.2" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 826 | dependencies = [ 827 | "zerovec", 828 | ] 829 | 830 | [[package]] 831 | name = "ppv-lite86" 832 | version = "0.2.21" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 835 | dependencies = [ 836 | "zerocopy", 837 | ] 838 | 839 | [[package]] 840 | name = "proc-macro-crate" 841 | version = "3.3.0" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" 844 | dependencies = [ 845 | "toml_edit", 846 | ] 847 | 848 | [[package]] 849 | name = "proc-macro-error-attr2" 850 | version = "2.0.0" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 853 | dependencies = [ 854 | "proc-macro2", 855 | "quote", 856 | ] 857 | 858 | [[package]] 859 | name = "proc-macro-error2" 860 | version = "2.0.1" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 863 | dependencies = [ 864 | "proc-macro-error-attr2", 865 | "proc-macro2", 866 | "quote", 867 | "syn", 868 | ] 869 | 870 | [[package]] 871 | name = "proc-macro2" 872 | version = "1.0.95" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 875 | dependencies = [ 876 | "unicode-ident", 877 | ] 878 | 879 | [[package]] 880 | name = "quote" 881 | version = "1.0.40" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 884 | dependencies = [ 885 | "proc-macro2", 886 | ] 887 | 888 | [[package]] 889 | name = "r-efi" 890 | version = "5.2.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 893 | 894 | [[package]] 895 | name = "rand" 896 | version = "0.8.5" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 899 | dependencies = [ 900 | "libc", 901 | "rand_chacha", 902 | "rand_core", 903 | ] 904 | 905 | [[package]] 906 | name = "rand_chacha" 907 | version = "0.3.1" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 910 | dependencies = [ 911 | "ppv-lite86", 912 | "rand_core", 913 | ] 914 | 915 | [[package]] 916 | name = "rand_core" 917 | version = "0.6.4" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 920 | dependencies = [ 921 | "getrandom 0.2.16", 922 | ] 923 | 924 | [[package]] 925 | name = "readyset_proxysql_scheduler" 926 | version = "0.6.0" 927 | dependencies = [ 928 | "chrono", 929 | "clap", 930 | "file-guard", 931 | "mysql", 932 | "once_cell", 933 | "serde", 934 | "toml", 935 | ] 936 | 937 | [[package]] 938 | name = "regex" 939 | version = "1.11.1" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 942 | dependencies = [ 943 | "aho-corasick", 944 | "memchr", 945 | "regex-automata", 946 | "regex-syntax", 947 | ] 948 | 949 | [[package]] 950 | name = "regex-automata" 951 | version = "0.4.9" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 954 | dependencies = [ 955 | "aho-corasick", 956 | "memchr", 957 | "regex-syntax", 958 | ] 959 | 960 | [[package]] 961 | name = "regex-syntax" 962 | version = "0.8.5" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 965 | 966 | [[package]] 967 | name = "rustversion" 968 | version = "1.0.21" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 971 | 972 | [[package]] 973 | name = "ryu" 974 | version = "1.0.20" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 977 | 978 | [[package]] 979 | name = "saturating" 980 | version = "0.1.0" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" 983 | 984 | [[package]] 985 | name = "serde" 986 | version = "1.0.219" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 989 | dependencies = [ 990 | "serde_derive", 991 | ] 992 | 993 | [[package]] 994 | name = "serde_derive" 995 | version = "1.0.219" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 998 | dependencies = [ 999 | "proc-macro2", 1000 | "quote", 1001 | "syn", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "serde_json" 1006 | version = "1.0.140" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1009 | dependencies = [ 1010 | "itoa", 1011 | "memchr", 1012 | "ryu", 1013 | "serde", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "serde_spanned" 1018 | version = "0.6.8" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1021 | dependencies = [ 1022 | "serde", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "sha1" 1027 | version = "0.10.6" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1030 | dependencies = [ 1031 | "cfg-if", 1032 | "cpufeatures", 1033 | "digest", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "sha2" 1038 | version = "0.10.9" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1041 | dependencies = [ 1042 | "cfg-if", 1043 | "cpufeatures", 1044 | "digest", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "shlex" 1049 | version = "1.3.0" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1052 | 1053 | [[package]] 1054 | name = "smallvec" 1055 | version = "1.15.0" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1058 | 1059 | [[package]] 1060 | name = "socket2" 1061 | version = "0.5.10" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 1064 | dependencies = [ 1065 | "libc", 1066 | "windows-sys 0.52.0", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "stable_deref_trait" 1071 | version = "1.2.0" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1074 | 1075 | [[package]] 1076 | name = "strsim" 1077 | version = "0.11.1" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1080 | 1081 | [[package]] 1082 | name = "subprocess" 1083 | version = "0.2.9" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" 1086 | dependencies = [ 1087 | "libc", 1088 | "winapi", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "syn" 1093 | version = "2.0.101" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 1096 | dependencies = [ 1097 | "proc-macro2", 1098 | "quote", 1099 | "unicode-ident", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "synstructure" 1104 | version = "0.13.2" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1107 | dependencies = [ 1108 | "proc-macro2", 1109 | "quote", 1110 | "syn", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "termcolor" 1115 | version = "1.4.1" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1118 | dependencies = [ 1119 | "winapi-util", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "thiserror" 1124 | version = "1.0.69" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1127 | dependencies = [ 1128 | "thiserror-impl 1.0.69", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "thiserror" 1133 | version = "2.0.12" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1136 | dependencies = [ 1137 | "thiserror-impl 2.0.12", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "thiserror-impl" 1142 | version = "1.0.69" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1145 | dependencies = [ 1146 | "proc-macro2", 1147 | "quote", 1148 | "syn", 1149 | ] 1150 | 1151 | [[package]] 1152 | name = "thiserror-impl" 1153 | version = "2.0.12" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1156 | dependencies = [ 1157 | "proc-macro2", 1158 | "quote", 1159 | "syn", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "tinystr" 1164 | version = "0.8.1" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1167 | dependencies = [ 1168 | "displaydoc", 1169 | "zerovec", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "toml" 1174 | version = "0.8.22" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" 1177 | dependencies = [ 1178 | "serde", 1179 | "serde_spanned", 1180 | "toml_datetime", 1181 | "toml_edit", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "toml_datetime" 1186 | version = "0.6.9" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" 1189 | dependencies = [ 1190 | "serde", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "toml_edit" 1195 | version = "0.22.26" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" 1198 | dependencies = [ 1199 | "indexmap", 1200 | "serde", 1201 | "serde_spanned", 1202 | "toml_datetime", 1203 | "toml_write", 1204 | "winnow", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "toml_write" 1209 | version = "0.1.1" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" 1212 | 1213 | [[package]] 1214 | name = "twox-hash" 1215 | version = "2.1.0" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908" 1218 | 1219 | [[package]] 1220 | name = "typenum" 1221 | version = "1.18.0" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 1224 | 1225 | [[package]] 1226 | name = "unicode-ident" 1227 | version = "1.0.18" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1230 | 1231 | [[package]] 1232 | name = "url" 1233 | version = "2.5.4" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1236 | dependencies = [ 1237 | "form_urlencoded", 1238 | "idna", 1239 | "percent-encoding", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "utf8_iter" 1244 | version = "1.0.4" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1247 | 1248 | [[package]] 1249 | name = "utf8parse" 1250 | version = "0.2.2" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1253 | 1254 | [[package]] 1255 | name = "uuid" 1256 | version = "1.17.0" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" 1259 | dependencies = [ 1260 | "js-sys", 1261 | "wasm-bindgen", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "vcpkg" 1266 | version = "0.2.15" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1269 | 1270 | [[package]] 1271 | name = "version_check" 1272 | version = "0.9.5" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1275 | 1276 | [[package]] 1277 | name = "wasi" 1278 | version = "0.11.0+wasi-snapshot-preview1" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1281 | 1282 | [[package]] 1283 | name = "wasi" 1284 | version = "0.14.2+wasi-0.2.4" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1287 | dependencies = [ 1288 | "wit-bindgen-rt", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "wasm-bindgen" 1293 | version = "0.2.100" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1296 | dependencies = [ 1297 | "cfg-if", 1298 | "once_cell", 1299 | "rustversion", 1300 | "wasm-bindgen-macro", 1301 | ] 1302 | 1303 | [[package]] 1304 | name = "wasm-bindgen-backend" 1305 | version = "0.2.100" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1308 | dependencies = [ 1309 | "bumpalo", 1310 | "log", 1311 | "proc-macro2", 1312 | "quote", 1313 | "syn", 1314 | "wasm-bindgen-shared", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "wasm-bindgen-macro" 1319 | version = "0.2.100" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1322 | dependencies = [ 1323 | "quote", 1324 | "wasm-bindgen-macro-support", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "wasm-bindgen-macro-support" 1329 | version = "0.2.100" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1332 | dependencies = [ 1333 | "proc-macro2", 1334 | "quote", 1335 | "syn", 1336 | "wasm-bindgen-backend", 1337 | "wasm-bindgen-shared", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "wasm-bindgen-shared" 1342 | version = "0.2.100" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1345 | dependencies = [ 1346 | "unicode-ident", 1347 | ] 1348 | 1349 | [[package]] 1350 | name = "winapi" 1351 | version = "0.3.9" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1354 | dependencies = [ 1355 | "winapi-i686-pc-windows-gnu", 1356 | "winapi-x86_64-pc-windows-gnu", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "winapi-i686-pc-windows-gnu" 1361 | version = "0.4.0" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1364 | 1365 | [[package]] 1366 | name = "winapi-util" 1367 | version = "0.1.9" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1370 | dependencies = [ 1371 | "windows-sys 0.59.0", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "winapi-x86_64-pc-windows-gnu" 1376 | version = "0.4.0" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1379 | 1380 | [[package]] 1381 | name = "windows-core" 1382 | version = "0.61.2" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 1385 | dependencies = [ 1386 | "windows-implement", 1387 | "windows-interface", 1388 | "windows-link", 1389 | "windows-result", 1390 | "windows-strings", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "windows-implement" 1395 | version = "0.60.0" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 1398 | dependencies = [ 1399 | "proc-macro2", 1400 | "quote", 1401 | "syn", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "windows-interface" 1406 | version = "0.59.1" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 1409 | dependencies = [ 1410 | "proc-macro2", 1411 | "quote", 1412 | "syn", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "windows-link" 1417 | version = "0.1.1" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 1420 | 1421 | [[package]] 1422 | name = "windows-result" 1423 | version = "0.3.4" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 1426 | dependencies = [ 1427 | "windows-link", 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "windows-strings" 1432 | version = "0.4.2" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 1435 | dependencies = [ 1436 | "windows-link", 1437 | ] 1438 | 1439 | [[package]] 1440 | name = "windows-sys" 1441 | version = "0.52.0" 1442 | source = "registry+https://github.com/rust-lang/crates.io-index" 1443 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1444 | dependencies = [ 1445 | "windows-targets", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "windows-sys" 1450 | version = "0.59.0" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1453 | dependencies = [ 1454 | "windows-targets", 1455 | ] 1456 | 1457 | [[package]] 1458 | name = "windows-targets" 1459 | version = "0.52.6" 1460 | source = "registry+https://github.com/rust-lang/crates.io-index" 1461 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1462 | dependencies = [ 1463 | "windows_aarch64_gnullvm", 1464 | "windows_aarch64_msvc", 1465 | "windows_i686_gnu", 1466 | "windows_i686_gnullvm", 1467 | "windows_i686_msvc", 1468 | "windows_x86_64_gnu", 1469 | "windows_x86_64_gnullvm", 1470 | "windows_x86_64_msvc", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "windows_aarch64_gnullvm" 1475 | version = "0.52.6" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1478 | 1479 | [[package]] 1480 | name = "windows_aarch64_msvc" 1481 | version = "0.52.6" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1484 | 1485 | [[package]] 1486 | name = "windows_i686_gnu" 1487 | version = "0.52.6" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1490 | 1491 | [[package]] 1492 | name = "windows_i686_gnullvm" 1493 | version = "0.52.6" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1496 | 1497 | [[package]] 1498 | name = "windows_i686_msvc" 1499 | version = "0.52.6" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1502 | 1503 | [[package]] 1504 | name = "windows_x86_64_gnu" 1505 | version = "0.52.6" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1508 | 1509 | [[package]] 1510 | name = "windows_x86_64_gnullvm" 1511 | version = "0.52.6" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1514 | 1515 | [[package]] 1516 | name = "windows_x86_64_msvc" 1517 | version = "0.52.6" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1520 | 1521 | [[package]] 1522 | name = "winnow" 1523 | version = "0.7.10" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" 1526 | dependencies = [ 1527 | "memchr", 1528 | ] 1529 | 1530 | [[package]] 1531 | name = "wit-bindgen-rt" 1532 | version = "0.39.0" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1535 | dependencies = [ 1536 | "bitflags", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "writeable" 1541 | version = "0.6.1" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 1544 | 1545 | [[package]] 1546 | name = "yoke" 1547 | version = "0.8.0" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 1550 | dependencies = [ 1551 | "serde", 1552 | "stable_deref_trait", 1553 | "yoke-derive", 1554 | "zerofrom", 1555 | ] 1556 | 1557 | [[package]] 1558 | name = "yoke-derive" 1559 | version = "0.8.0" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 1562 | dependencies = [ 1563 | "proc-macro2", 1564 | "quote", 1565 | "syn", 1566 | "synstructure", 1567 | ] 1568 | 1569 | [[package]] 1570 | name = "zerocopy" 1571 | version = "0.8.25" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 1574 | dependencies = [ 1575 | "zerocopy-derive", 1576 | ] 1577 | 1578 | [[package]] 1579 | name = "zerocopy-derive" 1580 | version = "0.8.25" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 1583 | dependencies = [ 1584 | "proc-macro2", 1585 | "quote", 1586 | "syn", 1587 | ] 1588 | 1589 | [[package]] 1590 | name = "zerofrom" 1591 | version = "0.1.6" 1592 | source = "registry+https://github.com/rust-lang/crates.io-index" 1593 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 1594 | dependencies = [ 1595 | "zerofrom-derive", 1596 | ] 1597 | 1598 | [[package]] 1599 | name = "zerofrom-derive" 1600 | version = "0.1.6" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 1603 | dependencies = [ 1604 | "proc-macro2", 1605 | "quote", 1606 | "syn", 1607 | "synstructure", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "zerotrie" 1612 | version = "0.2.2" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 1615 | dependencies = [ 1616 | "displaydoc", 1617 | "yoke", 1618 | "zerofrom", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "zerovec" 1623 | version = "0.11.2" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 1626 | dependencies = [ 1627 | "yoke", 1628 | "zerofrom", 1629 | "zerovec-derive", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "zerovec-derive" 1634 | version = "0.11.1" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 1637 | dependencies = [ 1638 | "proc-macro2", 1639 | "quote", 1640 | "syn", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "zstd" 1645 | version = "0.13.3" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 1648 | dependencies = [ 1649 | "zstd-safe", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "zstd-safe" 1654 | version = "7.2.4" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 1657 | dependencies = [ 1658 | "zstd-sys", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "zstd-sys" 1663 | version = "2.0.15+zstd.1.5.7" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" 1666 | dependencies = [ 1667 | "cc", 1668 | "pkg-config", 1669 | ] 1670 | --------------------------------------------------------------------------------