Joshua Nelson 20d65a8248 Remove hashbrown dependency
Hashbrown is now the hashmap used in the standard library, so no need to
pull in an external dependency. This switches to using `AHashMap`
directly.
2020-10-17 02:44:09 -07:00

241 lines
7.3 KiB
Rust

use crate::error::Error;
use crate::query_as::query_as;
use crate::sqlite::type_info::DataType;
use crate::sqlite::{SqliteConnection, SqliteTypeInfo};
use crate::HashMap;
use std::str::from_utf8;
// affinity
const SQLITE_AFF_NONE: u8 = 0x40; /* '@' */
const SQLITE_AFF_BLOB: u8 = 0x41; /* 'A' */
const SQLITE_AFF_TEXT: u8 = 0x42; /* 'B' */
const SQLITE_AFF_NUMERIC: u8 = 0x43; /* 'C' */
const SQLITE_AFF_INTEGER: u8 = 0x44; /* 'D' */
const SQLITE_AFF_REAL: u8 = 0x45; /* 'E' */
// opcodes
const OP_INIT: &str = "Init";
const OP_GOTO: &str = "Goto";
const OP_COLUMN: &str = "Column";
const OP_AGG_STEP: &str = "AggStep";
const OP_FUNCTION: &str = "Function";
const OP_MOVE: &str = "Move";
const OP_COPY: &str = "Copy";
const OP_SCOPY: &str = "SCopy";
const OP_INT_COPY: &str = "IntCopy";
const OP_CAST: &str = "Cast";
const OP_STRING8: &str = "String8";
const OP_INT64: &str = "Int64";
const OP_INTEGER: &str = "Integer";
const OP_REAL: &str = "Real";
const OP_NOT: &str = "Not";
const OP_BLOB: &str = "Blob";
const OP_VARIABLE: &str = "Variable";
const OP_COUNT: &str = "Count";
const OP_ROWID: &str = "Rowid";
const OP_OR: &str = "Or";
const OP_AND: &str = "And";
const OP_BIT_AND: &str = "BitAnd";
const OP_BIT_OR: &str = "BitOr";
const OP_SHIFT_LEFT: &str = "ShiftLeft";
const OP_SHIFT_RIGHT: &str = "ShiftRight";
const OP_ADD: &str = "Add";
const OP_SUBTRACT: &str = "Subtract";
const OP_MULTIPLY: &str = "Multiply";
const OP_DIVIDE: &str = "Divide";
const OP_REMAINDER: &str = "Remainder";
const OP_CONCAT: &str = "Concat";
const OP_RESULT_ROW: &str = "ResultRow";
fn affinity_to_type(affinity: u8) -> DataType {
match affinity {
SQLITE_AFF_BLOB => DataType::Blob,
SQLITE_AFF_INTEGER => DataType::Int64,
SQLITE_AFF_NUMERIC => DataType::Numeric,
SQLITE_AFF_REAL => DataType::Float,
SQLITE_AFF_TEXT => DataType::Text,
SQLITE_AFF_NONE | _ => DataType::Null,
}
}
fn opcode_to_type(op: &str) -> DataType {
match op {
OP_REAL => DataType::Float,
OP_BLOB => DataType::Blob,
OP_AND | OP_OR => DataType::Bool,
OP_ROWID | OP_COUNT | OP_INT64 | OP_INTEGER => DataType::Int64,
OP_STRING8 => DataType::Text,
OP_COLUMN | _ => DataType::Null,
}
}
pub(super) async fn explain(
conn: &mut SqliteConnection,
query: &str,
) -> Result<(Vec<SqliteTypeInfo>, Vec<Option<bool>>), Error> {
let mut r = HashMap::<i64, DataType>::with_capacity(6);
let mut n = HashMap::<i64, bool>::with_capacity(6);
let program =
query_as::<_, (i64, String, i64, i64, i64, Vec<u8>)>(&*format!("EXPLAIN {}", query))
.fetch_all(&mut *conn)
.await?;
let mut program_i = 0;
let program_size = program.len();
while program_i < program_size {
let (_, ref opcode, p1, p2, p3, ref p4) = program[program_i];
match &**opcode {
OP_INIT => {
// start at <p2>
program_i = p2 as usize;
continue;
}
OP_GOTO => {
// goto <p2>
program_i = p2 as usize;
continue;
}
OP_COLUMN => {
// r[p3] = <value of column>
r.insert(p3, DataType::Null);
n.insert(p3, true);
}
OP_VARIABLE => {
// r[p2] = <value of variable>
r.insert(p2, DataType::Null);
n.insert(p3, true);
}
OP_FUNCTION => {
// r[p1] = func( _ )
match from_utf8(p4).map_err(Error::protocol)? {
"last_insert_rowid(0)" => {
// last_insert_rowid() -> INTEGER
r.insert(p3, DataType::Int64);
n.insert(p3, false);
}
_ => {}
}
}
OP_AGG_STEP => {
let p4 = from_utf8(p4).map_err(Error::protocol)?;
if p4.starts_with("count(") {
// count(_) -> INTEGER
r.insert(p3, DataType::Int64);
n.insert(p3, false);
} else if let Some(v) = r.get(&p2).copied() {
// r[p3] = AGG ( r[p2] )
r.insert(p3, v);
let val = n.get(&p2).copied().unwrap_or(true);
n.insert(p3, val);
}
}
OP_CAST => {
// affinity(r[p1])
if let Some(v) = r.get_mut(&p1) {
*v = affinity_to_type(p2 as u8);
}
}
OP_COPY | OP_MOVE | OP_SCOPY | OP_INT_COPY => {
// r[p2] = r[p1]
if let Some(v) = r.get(&p1).copied() {
r.insert(p2, v);
let val = n.get(&p1).copied().unwrap_or(true);
n.insert(p2, val);
}
}
OP_OR | OP_AND | OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_INTEGER | OP_ROWID => {
// r[p2] = <value of constant>
r.insert(p2, opcode_to_type(&opcode));
n.insert(p2, false);
}
OP_NOT => {
// r[p2] = NOT r[p1]
if let Some(a) = r.get(&p1).copied() {
r.insert(p2, a);
let val = n.get(&p1).copied().unwrap_or(true);
n.insert(p2, val);
}
}
OP_BIT_AND | OP_BIT_OR | OP_SHIFT_LEFT | OP_SHIFT_RIGHT | OP_ADD | OP_SUBTRACT
| OP_MULTIPLY | OP_DIVIDE | OP_REMAINDER | OP_CONCAT => {
// r[p3] = r[p1] + r[p2]
match (r.get(&p1).copied(), r.get(&p2).copied()) {
(Some(a), Some(b)) => {
r.insert(p3, if matches!(a, DataType::Null) { b } else { a });
}
(Some(v), None) => {
r.insert(p3, v);
}
(None, Some(v)) => {
r.insert(p3, v);
}
_ => {}
}
match (n.get(&p1).copied(), n.get(&p2).copied()) {
(Some(a), Some(b)) => {
n.insert(p3, a || b);
}
(None, Some(b)) => {
n.insert(p3, b);
}
(Some(a), None) => {
n.insert(p3, a);
}
_ => {}
}
}
OP_RESULT_ROW => {
// output = r[p1 .. p1 + p2]
let mut output = Vec::with_capacity(p2 as usize);
let mut nullable = Vec::with_capacity(p2 as usize);
for i in p1..p1 + p2 {
output.push(SqliteTypeInfo(r.remove(&i).unwrap_or(DataType::Null)));
nullable.push(if n.remove(&i).unwrap_or(true) {
None
} else {
Some(false)
});
}
return Ok((output, nullable));
}
_ => {
// ignore unsupported operations
// if we fail to find an r later, we just give up
}
}
program_i += 1;
}
// no rows
Ok((vec![], vec![]))
}