mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-20 00:54:18 +00:00
Add and improve sqlite describe performance benchmarks (#2467)
* add basic describe benchmarks * separate memory from query state * move branch tracking & deduplication logic into a dedicated BranchList class * Convert to using IntMap * move intmap to a separate module, drop dead code, clean up function names * Update Cargo.lock * skip branches to check foreign keys, as they generally shouldn't impact query result type
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::connection::intmap::IntMap;
|
||||
use crate::connection::{execute, ConnectionState};
|
||||
use crate::error::Error;
|
||||
use crate::from_row::FromRow;
|
||||
@@ -136,7 +137,7 @@ enum ColumnType {
|
||||
datatype: DataType,
|
||||
nullable: Option<bool>,
|
||||
},
|
||||
Record(Vec<ColumnType>),
|
||||
Record(IntMap<ColumnType>),
|
||||
}
|
||||
|
||||
impl Default for ColumnType {
|
||||
@@ -199,60 +200,37 @@ impl RegDataType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
enum CursorDataType {
|
||||
Normal {
|
||||
cols: HashMap<i64, ColumnType>,
|
||||
cols: IntMap<ColumnType>,
|
||||
|
||||
is_empty: Option<bool>,
|
||||
},
|
||||
Pseudo(i64),
|
||||
}
|
||||
|
||||
impl CursorDataType {
|
||||
fn from_sparse_record(record: &HashMap<i64, ColumnType>, is_empty: Option<bool>) -> Self {
|
||||
fn from_intmap(record: &IntMap<ColumnType>, is_empty: Option<bool>) -> Self {
|
||||
Self::Normal {
|
||||
cols: record
|
||||
.iter()
|
||||
.map(|(colnum, datatype)| (*colnum, datatype.clone()))
|
||||
.collect(),
|
||||
cols: record.clone(),
|
||||
is_empty,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_dense_record(record: &Vec<ColumnType>, is_empty: Option<bool>) -> Self {
|
||||
Self::Normal {
|
||||
cols: (0..).zip(record.iter().cloned()).collect(),
|
||||
cols: IntMap::from_dense_record(record),
|
||||
is_empty,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_to_dense_record(&self, registers: &HashMap<i64, RegDataType>) -> Vec<ColumnType> {
|
||||
match self {
|
||||
Self::Normal { cols, .. } => {
|
||||
let mut rowdata = vec![ColumnType::default(); cols.len()];
|
||||
for (idx, col) in cols.iter() {
|
||||
rowdata[*idx as usize] = col.clone();
|
||||
}
|
||||
rowdata
|
||||
}
|
||||
Self::Pseudo(i) => match registers.get(i) {
|
||||
Some(RegDataType::Single(ColumnType::Record(r))) => r.clone(),
|
||||
_ => Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn map_to_sparse_record(
|
||||
&self,
|
||||
registers: &HashMap<i64, RegDataType>,
|
||||
) -> HashMap<i64, ColumnType> {
|
||||
fn map_to_intmap(&self, registers: &IntMap<RegDataType>) -> IntMap<ColumnType> {
|
||||
match self {
|
||||
Self::Normal { cols, .. } => cols.clone(),
|
||||
Self::Pseudo(i) => match registers.get(i) {
|
||||
Some(RegDataType::Single(ColumnType::Record(r))) => {
|
||||
(0..).zip(r.iter().cloned()).collect()
|
||||
}
|
||||
_ => HashMap::new(),
|
||||
Some(RegDataType::Single(ColumnType::Record(r))) => r.clone(),
|
||||
_ => IntMap::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -292,7 +270,7 @@ fn opcode_to_type(op: &str) -> DataType {
|
||||
|
||||
fn root_block_columns(
|
||||
conn: &mut ConnectionState,
|
||||
) -> Result<HashMap<(i64, i64), HashMap<i64, ColumnType>>, Error> {
|
||||
) -> Result<HashMap<(i64, i64), IntMap<ColumnType>>, Error> {
|
||||
let table_block_columns: Vec<(i64, i64, i64, String, bool)> = execute::iter(
|
||||
conn,
|
||||
"SELECT s.dbnum, s.rootpage, col.cid as colnum, col.type, col.\"notnull\"
|
||||
@@ -319,7 +297,7 @@ fn root_block_columns(
|
||||
.map(|row| FromRow::from_row(&row?))
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
let mut row_info: HashMap<(i64, i64), HashMap<i64, ColumnType>> = HashMap::new();
|
||||
let mut row_info: HashMap<(i64, i64), IntMap<ColumnType>> = HashMap::new();
|
||||
for (dbnum, block, colnum, datatype, notnull) in table_block_columns {
|
||||
let row_info = row_info.entry((dbnum, block)).or_default();
|
||||
row_info.insert(
|
||||
@@ -340,66 +318,43 @@ struct QueryState {
|
||||
pub visited: Vec<u8>,
|
||||
// A log of the order of execution of each instruction
|
||||
pub history: Vec<usize>,
|
||||
// Registers
|
||||
pub r: HashMap<i64, RegDataType>,
|
||||
// Rows that pointers point to
|
||||
pub p: HashMap<i64, CursorDataType>,
|
||||
// Next instruction to execute
|
||||
pub program_i: usize,
|
||||
// State of the virtual machine
|
||||
pub mem: MemoryState,
|
||||
// Results published by the execution
|
||||
pub result: Option<Vec<(Option<SqliteTypeInfo>, Option<bool>)>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
struct BranchStateHash {
|
||||
instruction: usize,
|
||||
//register index, data type
|
||||
registers: Vec<(i64, RegDataType)>,
|
||||
//cursor index, is_empty, pseudo register index
|
||||
cursor_metadata: Vec<(i64, Option<bool>, Option<i64>)>,
|
||||
//cursor index, column index, data type
|
||||
cursors: Vec<(i64, i64, Option<ColumnType>)>,
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct MemoryState {
|
||||
// Next instruction to execute
|
||||
pub program_i: usize,
|
||||
// Registers
|
||||
pub r: IntMap<RegDataType>,
|
||||
// Rows that pointers point to
|
||||
pub p: IntMap<CursorDataType>,
|
||||
}
|
||||
|
||||
impl BranchStateHash {
|
||||
pub fn from_query_state(st: &QueryState) -> Self {
|
||||
let mut reg = vec![];
|
||||
for (k, v) in &st.r {
|
||||
reg.push((*k, v.clone()));
|
||||
}
|
||||
reg.sort_by_key(|v| v.0);
|
||||
struct BranchList {
|
||||
states: Vec<QueryState>,
|
||||
visited_branch_state: HashSet<MemoryState>,
|
||||
}
|
||||
|
||||
let mut cur = vec![];
|
||||
let mut cur_meta = vec![];
|
||||
for (k, v) in &st.p {
|
||||
match v {
|
||||
CursorDataType::Normal { cols, is_empty } => {
|
||||
cur_meta.push((*k, *is_empty, None));
|
||||
for (i, col) in cols {
|
||||
cur.push((*k, *i, Some(col.clone())));
|
||||
}
|
||||
}
|
||||
CursorDataType::Pseudo(i) => {
|
||||
cur_meta.push((*k, None, Some(*i)));
|
||||
//don't bother copying columns, they are in register i
|
||||
}
|
||||
}
|
||||
}
|
||||
cur_meta.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
cur.sort_by(|a, b| {
|
||||
if a.0 == b.0 {
|
||||
a.1.cmp(&b.1)
|
||||
} else {
|
||||
a.0.cmp(&b.0)
|
||||
}
|
||||
});
|
||||
impl BranchList {
|
||||
pub fn new(state: QueryState) -> Self {
|
||||
Self {
|
||||
instruction: st.program_i,
|
||||
registers: reg,
|
||||
cursor_metadata: cur_meta,
|
||||
cursors: cur,
|
||||
states: vec![state],
|
||||
visited_branch_state: HashSet::new(),
|
||||
}
|
||||
}
|
||||
pub fn push(&mut self, state: QueryState) {
|
||||
if !self.visited_branch_state.contains(&state.mem) {
|
||||
self.visited_branch_state.insert(state.mem.clone());
|
||||
self.states.push(state);
|
||||
}
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<QueryState> {
|
||||
self.states.pop()
|
||||
}
|
||||
}
|
||||
|
||||
// Opcode Reference: https://sqlite.org/opcode.html
|
||||
@@ -418,24 +373,24 @@ pub(super) fn explain(
|
||||
let mut logger =
|
||||
crate::logger::QueryPlanLogger::new(query, &program, conn.log_settings.clone());
|
||||
|
||||
let mut states = vec![QueryState {
|
||||
let mut states = BranchList::new(QueryState {
|
||||
visited: vec![0; program_size],
|
||||
history: Vec::new(),
|
||||
r: HashMap::with_capacity(6),
|
||||
p: HashMap::with_capacity(6),
|
||||
program_i: 0,
|
||||
result: None,
|
||||
}];
|
||||
|
||||
let mut visited_branch_state: HashSet<BranchStateHash> = HashSet::new();
|
||||
mem: MemoryState {
|
||||
program_i: 0,
|
||||
r: IntMap::new(),
|
||||
p: IntMap::new(),
|
||||
},
|
||||
});
|
||||
|
||||
let mut gas = MAX_TOTAL_INSTRUCTION_COUNT;
|
||||
let mut result_states = Vec::new();
|
||||
|
||||
while let Some(mut state) = states.pop() {
|
||||
while state.program_i < program_size {
|
||||
let (_, ref opcode, p1, p2, p3, ref p4) = program[state.program_i];
|
||||
state.history.push(state.program_i);
|
||||
while state.mem.program_i < program_size {
|
||||
let (_, ref opcode, p1, p2, p3, ref p4) = program[state.mem.program_i];
|
||||
state.history.push(state.mem.program_i);
|
||||
|
||||
//limit the number of 'instructions' that can be evaluated
|
||||
if gas > 0 {
|
||||
@@ -444,7 +399,7 @@ pub(super) fn explain(
|
||||
break;
|
||||
}
|
||||
|
||||
if state.visited[state.program_i] > MAX_LOOP_COUNT {
|
||||
if state.visited[state.mem.program_i] > MAX_LOOP_COUNT {
|
||||
if logger.log_enabled() {
|
||||
let program_history: Vec<&(i64, String, i64, i64, i64, Vec<u8>)> =
|
||||
state.history.iter().map(|i| &program[*i]).collect();
|
||||
@@ -455,32 +410,42 @@ pub(super) fn explain(
|
||||
break;
|
||||
}
|
||||
|
||||
state.visited[state.program_i] += 1;
|
||||
state.visited[state.mem.program_i] += 1;
|
||||
|
||||
match &**opcode {
|
||||
OP_INIT => {
|
||||
// start at <p2>
|
||||
state.program_i = p2 as usize;
|
||||
state.mem.program_i = p2 as usize;
|
||||
continue;
|
||||
}
|
||||
|
||||
OP_GOTO => {
|
||||
// goto <p2>
|
||||
|
||||
state.program_i = p2 as usize;
|
||||
state.mem.program_i = p2 as usize;
|
||||
continue;
|
||||
}
|
||||
|
||||
OP_GO_SUB => {
|
||||
// store current instruction in r[p1], goto <p2>
|
||||
state.r.insert(p1, RegDataType::Int(state.program_i as i64));
|
||||
state.program_i = p2 as usize;
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(p1, RegDataType::Int(state.mem.program_i as i64));
|
||||
state.mem.program_i = p2 as usize;
|
||||
continue;
|
||||
}
|
||||
|
||||
OP_DECR_JUMP_ZERO | OP_ELSE_EQ | OP_EQ | OP_FILTER | OP_FK_IF_ZERO | OP_FOUND
|
||||
| OP_GE | OP_GT | OP_IDX_GE | OP_IDX_GT | OP_IDX_LE | OP_IDX_LT | OP_IF_NO_HOPE
|
||||
| OP_IF_NOT | OP_IF_NOT_OPEN | OP_IF_NOT_ZERO | OP_IF_NULL_ROW | OP_IF_SMALLER
|
||||
OP_FK_IF_ZERO => {
|
||||
// goto <p2> if no constraints are unsatisfied (assumed to be true)
|
||||
|
||||
state.mem.program_i = p2 as usize;
|
||||
continue;
|
||||
}
|
||||
|
||||
OP_DECR_JUMP_ZERO | OP_ELSE_EQ | OP_EQ | OP_FILTER | OP_FOUND | OP_GE | OP_GT
|
||||
| OP_IDX_GE | OP_IDX_GT | OP_IDX_LE | OP_IDX_LT | OP_IF_NO_HOPE | OP_IF_NOT
|
||||
| OP_IF_NOT_OPEN | OP_IF_NOT_ZERO | OP_IF_NULL_ROW | OP_IF_SMALLER
|
||||
| OP_INCR_VACUUM | OP_IS_NULL | OP_IS_NULL_OR_TYPE | OP_LE | OP_LT | OP_NE
|
||||
| OP_NEXT | OP_NO_CONFLICT | OP_NOT_EXISTS | OP_ONCE | OP_PREV | OP_PROGRAM
|
||||
| OP_ROW_SET_READ | OP_ROW_SET_TEST | OP_SEEK_GE | OP_SEEK_GT | OP_SEEK_LE
|
||||
@@ -489,50 +454,42 @@ pub(super) fn explain(
|
||||
// goto <p2> or next instruction (depending on actual values)
|
||||
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p2 as usize;
|
||||
branch_state.mem.program_i = p2 as usize;
|
||||
states.push(branch_state);
|
||||
|
||||
let bs_hash = BranchStateHash::from_query_state(&branch_state);
|
||||
if !visited_branch_state.contains(&bs_hash) {
|
||||
visited_branch_state.insert(bs_hash);
|
||||
states.push(branch_state);
|
||||
}
|
||||
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
OP_NOT_NULL => {
|
||||
// goto <p2> or next instruction (depending on actual values)
|
||||
|
||||
let might_branch = match state.r.get(&p1) {
|
||||
let might_branch = match state.mem.r.get(&p1) {
|
||||
Some(r_p1) => !matches!(r_p1.map_to_datatype(), DataType::Null),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let might_not_branch = match state.r.get(&p1) {
|
||||
let might_not_branch = match state.mem.r.get(&p1) {
|
||||
Some(r_p1) => !matches!(r_p1.map_to_nullable(), Some(false)),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if might_branch {
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p2 as usize;
|
||||
branch_state.mem.program_i = p2 as usize;
|
||||
if let Some(RegDataType::Single(ColumnType::Single { nullable, .. })) =
|
||||
branch_state.r.get_mut(&p1)
|
||||
branch_state.mem.r.get_mut(&p1)
|
||||
{
|
||||
*nullable = Some(false);
|
||||
}
|
||||
|
||||
let bs_hash = BranchStateHash::from_query_state(&branch_state);
|
||||
if !visited_branch_state.contains(&bs_hash) {
|
||||
visited_branch_state.insert(bs_hash);
|
||||
states.push(branch_state);
|
||||
}
|
||||
states.push(branch_state);
|
||||
}
|
||||
|
||||
if might_not_branch {
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(p1, RegDataType::Single(ColumnType::default()));
|
||||
continue;
|
||||
@@ -548,50 +505,41 @@ pub(super) fn explain(
|
||||
//don't bother checking actual types, just don't branch to instruction 0
|
||||
if p2 != 0 {
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p2 as usize;
|
||||
|
||||
let bs_hash = BranchStateHash::from_query_state(&branch_state);
|
||||
if !visited_branch_state.contains(&bs_hash) {
|
||||
visited_branch_state.insert(bs_hash);
|
||||
states.push(branch_state);
|
||||
}
|
||||
branch_state.mem.program_i = p2 as usize;
|
||||
states.push(branch_state);
|
||||
}
|
||||
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
OP_IF => {
|
||||
// goto <p2> if r[p1] is true (1) or r[p1] is null and p3 is nonzero
|
||||
|
||||
let might_branch = match state.r.get(&p1) {
|
||||
let might_branch = match state.mem.r.get(&p1) {
|
||||
Some(RegDataType::Int(r_p1)) => *r_p1 != 0,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
let might_not_branch = match state.r.get(&p1) {
|
||||
let might_not_branch = match state.mem.r.get(&p1) {
|
||||
Some(RegDataType::Int(r_p1)) => *r_p1 == 0,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if might_branch {
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p2 as usize;
|
||||
branch_state.mem.program_i = p2 as usize;
|
||||
if p3 == 0 {
|
||||
branch_state.r.insert(p1, RegDataType::Int(1));
|
||||
branch_state.mem.r.insert(p1, RegDataType::Int(1));
|
||||
}
|
||||
|
||||
let bs_hash = BranchStateHash::from_query_state(&branch_state);
|
||||
if !visited_branch_state.contains(&bs_hash) {
|
||||
visited_branch_state.insert(bs_hash);
|
||||
states.push(branch_state);
|
||||
}
|
||||
states.push(branch_state);
|
||||
}
|
||||
|
||||
if might_not_branch {
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
if p3 == 0 {
|
||||
state.r.insert(p1, RegDataType::Int(0));
|
||||
state.mem.r.insert(p1, RegDataType::Int(0));
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
@@ -604,34 +552,34 @@ pub(super) fn explain(
|
||||
|
||||
// as a workaround for large offset clauses, both branches will be attempted after 1 loop
|
||||
|
||||
let might_branch = match state.r.get(&p1) {
|
||||
let might_branch = match state.mem.r.get(&p1) {
|
||||
Some(RegDataType::Int(r_p1)) => *r_p1 >= 1,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
let might_not_branch = match state.r.get(&p1) {
|
||||
let might_not_branch = match state.mem.r.get(&p1) {
|
||||
Some(RegDataType::Int(r_p1)) => *r_p1 < 1,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
let loop_detected = state.visited[state.program_i] > 1;
|
||||
let loop_detected = state.visited[state.mem.program_i] > 1;
|
||||
if might_branch || loop_detected {
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p2 as usize;
|
||||
if let Some(RegDataType::Int(r_p1)) = branch_state.r.get_mut(&p1) {
|
||||
branch_state.mem.program_i = p2 as usize;
|
||||
if let Some(RegDataType::Int(r_p1)) = branch_state.mem.r.get_mut(&p1) {
|
||||
*r_p1 -= 1;
|
||||
}
|
||||
states.push(branch_state);
|
||||
}
|
||||
|
||||
if might_not_branch {
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
continue;
|
||||
} else if loop_detected {
|
||||
state.program_i += 1;
|
||||
if matches!(state.r.get_mut(&p1), Some(RegDataType::Int(..))) {
|
||||
state.mem.program_i += 1;
|
||||
if matches!(state.mem.r.get_mut(&p1), Some(RegDataType::Int(..))) {
|
||||
//forget the exact value, in case some later cares
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p1,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Int64,
|
||||
@@ -649,19 +597,19 @@ pub(super) fn explain(
|
||||
// goto <p2> if cursor p1 is empty and p2 != 0, else next instruction
|
||||
|
||||
if p2 == 0 {
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(cursor) = state.p.get(&p1) {
|
||||
if let Some(cursor) = state.mem.p.get(&p1) {
|
||||
if matches!(cursor.is_empty(), None | Some(true)) {
|
||||
//only take this branch if the cursor is empty
|
||||
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p2 as usize;
|
||||
branch_state.mem.program_i = p2 as usize;
|
||||
|
||||
if let Some(CursorDataType::Normal { is_empty, .. }) =
|
||||
branch_state.p.get_mut(&p1)
|
||||
branch_state.mem.p.get_mut(&p1)
|
||||
{
|
||||
*is_empty = Some(true);
|
||||
}
|
||||
@@ -670,7 +618,7 @@ pub(super) fn explain(
|
||||
|
||||
if matches!(cursor.is_empty(), None | Some(false)) {
|
||||
//only take this branch if the cursor is non-empty
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
@@ -689,12 +637,12 @@ pub(super) fn explain(
|
||||
OP_INIT_COROUTINE => {
|
||||
// goto <p2> or next instruction (depending on actual values)
|
||||
|
||||
state.r.insert(p1, RegDataType::Int(p3));
|
||||
state.mem.r.insert(p1, RegDataType::Int(p3));
|
||||
|
||||
if p2 != 0 {
|
||||
state.program_i = p2 as usize;
|
||||
state.mem.program_i = p2 as usize;
|
||||
} else {
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -702,13 +650,13 @@ pub(super) fn explain(
|
||||
OP_END_COROUTINE => {
|
||||
// jump to p2 of the yield instruction pointed at by register p1
|
||||
|
||||
if let Some(RegDataType::Int(yield_i)) = state.r.get(&p1) {
|
||||
if let Some(RegDataType::Int(yield_i)) = state.mem.r.get(&p1) {
|
||||
if let Some((_, yield_op, _, yield_p2, _, _)) =
|
||||
program.get(*yield_i as usize)
|
||||
{
|
||||
if OP_YIELD == yield_op.as_str() {
|
||||
state.program_i = (*yield_p2) as usize;
|
||||
state.r.remove(&p1);
|
||||
state.mem.program_i = (*yield_p2) as usize;
|
||||
state.mem.r.remove(&p1);
|
||||
continue;
|
||||
} else {
|
||||
if logger.log_enabled() {
|
||||
@@ -746,9 +694,9 @@ pub(super) fn explain(
|
||||
OP_RETURN => {
|
||||
// jump to the instruction after the instruction pointed at by register p1
|
||||
|
||||
if let Some(RegDataType::Int(return_i)) = state.r.get(&p1) {
|
||||
state.program_i = (*return_i + 1) as usize;
|
||||
state.r.remove(&p1);
|
||||
if let Some(RegDataType::Int(return_i)) = state.mem.r.get(&p1) {
|
||||
state.mem.program_i = (*return_i + 1) as usize;
|
||||
state.mem.r.remove(&p1);
|
||||
continue;
|
||||
} else {
|
||||
if logger.log_enabled() {
|
||||
@@ -763,8 +711,8 @@ pub(super) fn explain(
|
||||
OP_YIELD => {
|
||||
// jump to p2 of the yield instruction pointed at by register p1, store prior instruction in p1
|
||||
|
||||
if let Some(RegDataType::Int(yield_i)) = state.r.get_mut(&p1) {
|
||||
let program_i: usize = state.program_i;
|
||||
if let Some(RegDataType::Int(yield_i)) = state.mem.r.get_mut(&p1) {
|
||||
let program_i: usize = state.mem.program_i;
|
||||
|
||||
//if yielding to a yield operation, go to the NEXT instruction after that instruction
|
||||
if program
|
||||
@@ -772,11 +720,11 @@ pub(super) fn explain(
|
||||
.map(|(_, yield_op, _, _, _, _)| yield_op.as_str())
|
||||
== Some(OP_YIELD)
|
||||
{
|
||||
state.program_i = (*yield_i + 1) as usize;
|
||||
state.mem.program_i = (*yield_i + 1) as usize;
|
||||
*yield_i = program_i as i64;
|
||||
continue;
|
||||
} else {
|
||||
state.program_i = *yield_i as usize;
|
||||
state.mem.program_i = *yield_i as usize;
|
||||
*yield_i = program_i as i64;
|
||||
continue;
|
||||
}
|
||||
@@ -794,44 +742,35 @@ pub(super) fn explain(
|
||||
// goto one of <p1>, <p2>, or <p3> based on the result of a prior compare
|
||||
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p1 as usize;
|
||||
let bs_hash = BranchStateHash::from_query_state(&branch_state);
|
||||
if !visited_branch_state.contains(&bs_hash) {
|
||||
visited_branch_state.insert(bs_hash);
|
||||
states.push(branch_state);
|
||||
}
|
||||
branch_state.mem.program_i = p1 as usize;
|
||||
states.push(branch_state);
|
||||
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p2 as usize;
|
||||
let bs_hash = BranchStateHash::from_query_state(&branch_state);
|
||||
if !visited_branch_state.contains(&bs_hash) {
|
||||
visited_branch_state.insert(bs_hash);
|
||||
states.push(branch_state);
|
||||
}
|
||||
branch_state.mem.program_i = p2 as usize;
|
||||
states.push(branch_state);
|
||||
|
||||
let mut branch_state = state.clone();
|
||||
branch_state.program_i = p3 as usize;
|
||||
let bs_hash = BranchStateHash::from_query_state(&branch_state);
|
||||
if !visited_branch_state.contains(&bs_hash) {
|
||||
visited_branch_state.insert(bs_hash);
|
||||
states.push(branch_state);
|
||||
}
|
||||
branch_state.mem.program_i = p3 as usize;
|
||||
states.push(branch_state);
|
||||
}
|
||||
|
||||
OP_COLUMN => {
|
||||
//Get the row stored at p1, or NULL; get the column stored at p2, or NULL
|
||||
if let Some(record) = state.p.get(&p1).map(|c| c.map_to_sparse_record(&state.r))
|
||||
if let Some(record) =
|
||||
state.mem.p.get(&p1).map(|c| c.map_to_intmap(&state.mem.r))
|
||||
{
|
||||
if let Some(col) = record.get(&p2) {
|
||||
// insert into p3 the datatype of the col
|
||||
state.r.insert(p3, RegDataType::Single(col.clone()));
|
||||
state.mem.r.insert(p3, RegDataType::Single(col.clone()));
|
||||
} else {
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(p3, RegDataType::Single(ColumnType::default()));
|
||||
}
|
||||
} else {
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(p3, RegDataType::Single(ColumnType::default()));
|
||||
}
|
||||
@@ -841,7 +780,7 @@ pub(super) fn explain(
|
||||
//Copy sequence number from cursor p1 to register p2, increment cursor p1 sequence number
|
||||
|
||||
//Cursor emulation doesn't sequence value, but it is an int
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p2,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Int64,
|
||||
@@ -852,15 +791,17 @@ pub(super) fn explain(
|
||||
|
||||
OP_ROW_DATA | OP_SORTER_DATA => {
|
||||
//Get entire row from cursor p1, store it into register p2
|
||||
if let Some(record) = state.p.get(&p1) {
|
||||
let rowdata = record.map_to_dense_record(&state.r);
|
||||
if let Some(record) = state.mem.p.get(&p1) {
|
||||
let rowdata = record.map_to_intmap(&state.mem.r);
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(p2, RegDataType::Single(ColumnType::Record(rowdata)));
|
||||
} else {
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(p2, RegDataType::Single(ColumnType::Record(Vec::new())));
|
||||
.insert(p2, RegDataType::Single(ColumnType::Record(IntMap::new())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,25 +811,28 @@ pub(super) fn explain(
|
||||
for reg in p1..p1 + p2 {
|
||||
record.push(
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.get(®)
|
||||
.map(|d| d.clone().map_to_columntype())
|
||||
.unwrap_or(ColumnType::default()),
|
||||
);
|
||||
}
|
||||
state
|
||||
.r
|
||||
.insert(p3, RegDataType::Single(ColumnType::Record(record)));
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Record(IntMap::from_dense_record(&record))),
|
||||
);
|
||||
}
|
||||
|
||||
OP_INSERT | OP_IDX_INSERT | OP_SORTER_INSERT => {
|
||||
if let Some(RegDataType::Single(ColumnType::Record(record))) = state.r.get(&p2)
|
||||
if let Some(RegDataType::Single(ColumnType::Record(record))) =
|
||||
state.mem.r.get(&p2)
|
||||
{
|
||||
if let Some(CursorDataType::Normal { cols, is_empty }) =
|
||||
state.p.get_mut(&p1)
|
||||
state.mem.p.get_mut(&p1)
|
||||
{
|
||||
// Insert the record into wherever pointer p1 is
|
||||
*cols = (0..).zip(record.iter().cloned()).collect();
|
||||
*cols = record.clone();
|
||||
*is_empty = Some(false);
|
||||
}
|
||||
}
|
||||
@@ -897,7 +841,8 @@ pub(super) fn explain(
|
||||
|
||||
OP_DELETE => {
|
||||
// delete a record from cursor p1
|
||||
if let Some(CursorDataType::Normal { is_empty, .. }) = state.p.get_mut(&p1) {
|
||||
if let Some(CursorDataType::Normal { is_empty, .. }) = state.mem.p.get_mut(&p1)
|
||||
{
|
||||
if *is_empty == Some(false) {
|
||||
*is_empty = None; //the cursor might be empty now
|
||||
}
|
||||
@@ -906,7 +851,7 @@ pub(super) fn explain(
|
||||
|
||||
OP_OPEN_PSEUDO => {
|
||||
// Create a cursor p1 aliasing the record from register p2
|
||||
state.p.insert(p1, CursorDataType::Pseudo(p2));
|
||||
state.mem.p.insert(p1, CursorDataType::Pseudo(p2));
|
||||
}
|
||||
|
||||
OP_OPEN_READ | OP_OPEN_WRITE => {
|
||||
@@ -914,22 +859,23 @@ pub(super) fn explain(
|
||||
if p3 == 0 || p3 == 1 {
|
||||
if let Some(columns) = root_block_cols.get(&(p3, p2)) {
|
||||
state
|
||||
.mem
|
||||
.p
|
||||
.insert(p1, CursorDataType::from_sparse_record(columns, None));
|
||||
.insert(p1, CursorDataType::from_intmap(columns, None));
|
||||
} else {
|
||||
state.p.insert(
|
||||
state.mem.p.insert(
|
||||
p1,
|
||||
CursorDataType::Normal {
|
||||
cols: HashMap::with_capacity(6),
|
||||
cols: IntMap::new(),
|
||||
is_empty: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
state.p.insert(
|
||||
state.mem.p.insert(
|
||||
p1,
|
||||
CursorDataType::Normal {
|
||||
cols: HashMap::with_capacity(6),
|
||||
cols: IntMap::new(),
|
||||
is_empty: None,
|
||||
},
|
||||
);
|
||||
@@ -938,7 +884,7 @@ pub(super) fn explain(
|
||||
|
||||
OP_OPEN_EPHEMERAL | OP_OPEN_AUTOINDEX | OP_SORTER_OPEN => {
|
||||
//Create a new pointer which is referenced by p1
|
||||
state.p.insert(
|
||||
state.mem.p.insert(
|
||||
p1,
|
||||
CursorDataType::from_dense_record(
|
||||
&vec![ColumnType::null(); p2 as usize],
|
||||
@@ -949,14 +895,17 @@ pub(super) fn explain(
|
||||
|
||||
OP_VARIABLE => {
|
||||
// r[p2] = <value of variable>
|
||||
state.r.insert(p2, RegDataType::Single(ColumnType::null()));
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(p2, RegDataType::Single(ColumnType::null()));
|
||||
}
|
||||
|
||||
// if there is a value in p3, and the query passes, then
|
||||
// we know that it is not nullable
|
||||
OP_HALT_IF_NULL => {
|
||||
if let Some(RegDataType::Single(ColumnType::Single { nullable, .. })) =
|
||||
state.r.get_mut(&p3)
|
||||
state.mem.r.get_mut(&p3)
|
||||
{
|
||||
*nullable = Some(false);
|
||||
}
|
||||
@@ -967,7 +916,7 @@ pub(super) fn explain(
|
||||
match from_utf8(p4).map_err(Error::protocol)? {
|
||||
"last_insert_rowid(0)" => {
|
||||
// last_insert_rowid() -> INTEGER
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Int64,
|
||||
@@ -977,7 +926,7 @@ pub(super) fn explain(
|
||||
}
|
||||
"date(-1)" | "time(-1)" | "datetime(-1)" | "strftime(-1)" => {
|
||||
// date|time|datetime|strftime(...) -> TEXT
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Text,
|
||||
@@ -987,7 +936,7 @@ pub(super) fn explain(
|
||||
}
|
||||
"julianday(-1)" => {
|
||||
// julianday(...) -> REAL
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Float,
|
||||
@@ -997,7 +946,7 @@ pub(super) fn explain(
|
||||
}
|
||||
"unixepoch(-1)" => {
|
||||
// unixepoch(p2...) -> INTEGER
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Int64,
|
||||
@@ -1006,13 +955,14 @@ pub(super) fn explain(
|
||||
);
|
||||
}
|
||||
|
||||
_ => logger.add_unknown_operation(&program[state.program_i]),
|
||||
_ => logger.add_unknown_operation(&program[state.mem.program_i]),
|
||||
}
|
||||
}
|
||||
|
||||
OP_NULL_ROW => {
|
||||
// all columns in cursor X are potentially nullable
|
||||
if let Some(CursorDataType::Normal { ref mut cols, .. }) = state.p.get_mut(&p1)
|
||||
if let Some(CursorDataType::Normal { ref mut cols, .. }) =
|
||||
state.mem.p.get_mut(&p1)
|
||||
{
|
||||
for col in cols.values_mut() {
|
||||
if let ColumnType::Single {
|
||||
@@ -1037,7 +987,7 @@ pub(super) fn explain(
|
||||
|| p4.starts_with("ntile(")
|
||||
{
|
||||
// count(_) -> INTEGER
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Int64,
|
||||
@@ -1045,7 +995,7 @@ pub(super) fn explain(
|
||||
}),
|
||||
);
|
||||
} else if p4.starts_with("sum(") {
|
||||
if let Some(r_p2) = state.r.get(&p2) {
|
||||
if let Some(r_p2) = state.mem.r.get(&p2) {
|
||||
let datatype = match r_p2.map_to_datatype() {
|
||||
DataType::Int64 => DataType::Int64,
|
||||
DataType::Int => DataType::Int,
|
||||
@@ -1053,14 +1003,14 @@ pub(super) fn explain(
|
||||
_ => DataType::Float,
|
||||
};
|
||||
let nullable = r_p2.map_to_nullable();
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single { datatype, nullable }),
|
||||
);
|
||||
}
|
||||
} else if let Some(v) = state.r.get(&p2).cloned() {
|
||||
} else if let Some(v) = state.mem.r.get(&p2).cloned() {
|
||||
// r[p3] = AGG ( r[p2] )
|
||||
state.r.insert(p3, v);
|
||||
state.mem.r.insert(p3, v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1074,7 +1024,7 @@ pub(super) fn explain(
|
||||
|| p4.starts_with("ntile(")
|
||||
{
|
||||
// count(_) -> INTEGER
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p1,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Int64,
|
||||
@@ -1086,7 +1036,7 @@ pub(super) fn explain(
|
||||
|
||||
OP_CAST => {
|
||||
// affinity(r[p1])
|
||||
if let Some(v) = state.r.get_mut(&p1) {
|
||||
if let Some(v) = state.mem.r.get_mut(&p1) {
|
||||
*v = RegDataType::Single(ColumnType::Single {
|
||||
datatype: affinity_to_type(p2 as u8),
|
||||
nullable: v.map_to_nullable(),
|
||||
@@ -1096,8 +1046,8 @@ pub(super) fn explain(
|
||||
|
||||
OP_SCOPY | OP_INT_COPY => {
|
||||
// r[p2] = r[p1]
|
||||
if let Some(v) = state.r.get(&p1).cloned() {
|
||||
state.r.insert(p2, v);
|
||||
if let Some(v) = state.mem.r.get(&p1).cloned() {
|
||||
state.mem.r.insert(p2, v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,8 +1057,8 @@ pub(super) fn explain(
|
||||
for i in 0..=p3 {
|
||||
let src = p1 + i;
|
||||
let dst = p2 + i;
|
||||
if let Some(v) = state.r.get(&src).cloned() {
|
||||
state.r.insert(dst, v);
|
||||
if let Some(v) = state.mem.r.get(&src).cloned() {
|
||||
state.mem.r.insert(dst, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1120,9 +1070,12 @@ pub(super) fn explain(
|
||||
for i in 0..p3 {
|
||||
let src = p1 + i;
|
||||
let dst = p2 + i;
|
||||
if let Some(v) = state.r.get(&src).cloned() {
|
||||
state.r.insert(dst, v);
|
||||
state.r.insert(src, RegDataType::Single(ColumnType::null()));
|
||||
if let Some(v) = state.mem.r.get(&src).cloned() {
|
||||
state.mem.r.insert(dst, v);
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(src, RegDataType::Single(ColumnType::null()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1130,12 +1083,12 @@ pub(super) fn explain(
|
||||
|
||||
OP_INTEGER => {
|
||||
// r[p2] = p1
|
||||
state.r.insert(p2, RegDataType::Int(p1));
|
||||
state.mem.r.insert(p2, RegDataType::Int(p1));
|
||||
}
|
||||
|
||||
OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_ROWID | OP_NEWROWID => {
|
||||
// r[p2] = <value of constant>
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p2,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: opcode_to_type(&opcode),
|
||||
@@ -1146,8 +1099,8 @@ pub(super) fn explain(
|
||||
|
||||
OP_NOT => {
|
||||
// r[p2] = NOT r[p1]
|
||||
if let Some(a) = state.r.get(&p1).cloned() {
|
||||
state.r.insert(p2, a);
|
||||
if let Some(a) = state.mem.r.get(&p1).cloned() {
|
||||
state.mem.r.insert(p2, a);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,16 +1109,19 @@ pub(super) fn explain(
|
||||
let idx_range = if p2 < p3 { p2..=p3 } else { p2..=p2 };
|
||||
|
||||
for idx in idx_range {
|
||||
state.r.insert(idx, RegDataType::Single(ColumnType::null()));
|
||||
state
|
||||
.mem
|
||||
.r
|
||||
.insert(idx, RegDataType::Single(ColumnType::null()));
|
||||
}
|
||||
}
|
||||
|
||||
OP_OR | OP_AND | 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 (state.r.get(&p1).cloned(), state.r.get(&p2).cloned()) {
|
||||
match (state.mem.r.get(&p1).cloned(), state.mem.r.get(&p2).cloned()) {
|
||||
(Some(a), Some(b)) => {
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: if matches!(a.map_to_datatype(), DataType::Null) {
|
||||
@@ -1184,7 +1140,7 @@ pub(super) fn explain(
|
||||
}
|
||||
|
||||
(Some(v), None) => {
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: v.map_to_datatype(),
|
||||
@@ -1194,7 +1150,7 @@ pub(super) fn explain(
|
||||
}
|
||||
|
||||
(None, Some(v)) => {
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p3,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: v.map_to_datatype(),
|
||||
@@ -1209,7 +1165,7 @@ pub(super) fn explain(
|
||||
|
||||
OP_OFFSET_LIMIT => {
|
||||
// r[p2] = if r[p2] < 0 { r[p1] } else if r[p1]<0 { -1 } else { r[p1] + r[p3] }
|
||||
state.r.insert(
|
||||
state.mem.r.insert(
|
||||
p2,
|
||||
RegDataType::Single(ColumnType::Single {
|
||||
datatype: DataType::Int64,
|
||||
@@ -1224,7 +1180,7 @@ pub(super) fn explain(
|
||||
state.result = Some(
|
||||
(p1..p1 + p2)
|
||||
.map(|i| {
|
||||
let coltype = state.r.get(&i);
|
||||
let coltype = state.mem.r.get(&i);
|
||||
|
||||
let sqltype =
|
||||
coltype.map(|d| d.map_to_datatype()).map(SqliteTypeInfo);
|
||||
@@ -1257,11 +1213,11 @@ pub(super) fn explain(
|
||||
_ => {
|
||||
// ignore unsupported operations
|
||||
// if we fail to find an r later, we just give up
|
||||
logger.add_unknown_operation(&program[state.program_i]);
|
||||
logger.add_unknown_operation(&program[state.mem.program_i]);
|
||||
}
|
||||
}
|
||||
|
||||
state.program_i += 1;
|
||||
state.mem.program_i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
115
sqlx-sqlite/src/connection/intmap.rs
Normal file
115
sqlx-sqlite/src/connection/intmap.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
/// Simplistic map implementation built on a Vec of Options (index = key)
|
||||
#[derive(Debug, Clone, Eq, Default)]
|
||||
pub(crate) struct IntMap<V: std::fmt::Debug + Clone + Eq + PartialEq + std::hash::Hash>(
|
||||
Vec<Option<V>>,
|
||||
);
|
||||
|
||||
impl<V: std::fmt::Debug + Clone + Eq + PartialEq + std::hash::Hash> IntMap<V> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub(crate) fn expand(&mut self, size: i64) -> usize {
|
||||
let idx = size.try_into().expect("negative column index unsupported");
|
||||
while self.0.len() <= idx {
|
||||
self.0.push(None);
|
||||
}
|
||||
idx
|
||||
}
|
||||
|
||||
pub(crate) fn from_dense_record(record: &Vec<V>) -> Self {
|
||||
Self(record.iter().cloned().map(Some).collect())
|
||||
}
|
||||
|
||||
pub(crate) fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.0.iter_mut().filter_map(Option::as_mut)
|
||||
}
|
||||
|
||||
pub(crate) fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.0.iter().filter_map(Option::as_ref)
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, idx: &i64) -> Option<&V> {
|
||||
let idx: usize = (*idx)
|
||||
.try_into()
|
||||
.expect("negative column index unsupported");
|
||||
|
||||
match self.0.get(idx) {
|
||||
Some(Some(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut(&mut self, idx: &i64) -> Option<&mut V> {
|
||||
let idx: usize = (*idx)
|
||||
.try_into()
|
||||
.expect("negative column index unsupported");
|
||||
match self.0.get_mut(idx) {
|
||||
Some(Some(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, idx: i64, value: V) -> Option<V> {
|
||||
let idx: usize = self.expand(idx);
|
||||
|
||||
std::mem::replace(&mut self.0[idx], Some(value))
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, idx: &i64) -> Option<V> {
|
||||
let idx: usize = (*idx)
|
||||
.try_into()
|
||||
.expect("negative column index unsupported");
|
||||
|
||||
let item = self.0.get_mut(idx);
|
||||
match item {
|
||||
Some(content) => std::mem::replace(content, None),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: std::fmt::Debug + Clone + Eq + PartialEq + std::hash::Hash> std::hash::Hash for IntMap<V> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
for value in self.values() {
|
||||
value.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: std::fmt::Debug + Clone + Eq + PartialEq + std::hash::Hash> PartialEq for IntMap<V> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if !self
|
||||
.0
|
||||
.iter()
|
||||
.zip(other.0.iter())
|
||||
.all(|(l, r)| PartialEq::eq(l, r))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.0.len() > other.0.len() {
|
||||
self.0[other.0.len()..].iter().all(Option::is_none)
|
||||
} else if self.0.len() < other.0.len() {
|
||||
other.0[self.0.len()..].iter().all(Option::is_none)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: std::fmt::Debug + Clone + Eq + PartialEq + std::hash::Hash + Default> FromIterator<(i64, V)>
|
||||
for IntMap<V>
|
||||
{
|
||||
fn from_iter<I>(iter: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (i64, V)>,
|
||||
{
|
||||
let mut result = Self(Vec::new());
|
||||
for (idx, val) in iter {
|
||||
let idx = result.expand(idx);
|
||||
result.0[idx] = Some(val);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ pub(crate) mod execute;
|
||||
mod executor;
|
||||
mod explain;
|
||||
mod handle;
|
||||
mod intmap;
|
||||
|
||||
mod worker;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user