characters/
characters.rs

1//! cargo build --example series
2//! sqlite3 :memory: '.read examples/test.sql'
3
4use sqlite_loadable::prelude::*;
5use sqlite_loadable::{
6    api, define_table_function,
7    table::{BestIndexError, ConstraintOperator, IndexInfo, VTab, VTabArguments, VTabCursor},
8    Result,
9};
10
11use std::mem;
12
13static CREATE_SQL: &str = "CREATE TABLE x(value, input hidden)";
14enum Columns {
15    Value,
16    Input,
17}
18fn column(index: i32) -> Option<Columns> {
19    match index {
20        0 => Some(Columns::Value),
21        1 => Some(Columns::Input),
22        _ => None,
23    }
24}
25
26#[repr(C)]
27pub struct CharactersTable {
28    /// must be first
29    base: sqlite3_vtab,
30}
31
32impl<'vtab> VTab<'vtab> for CharactersTable {
33    type Aux = ();
34    type Cursor = CharactersCursor;
35
36    fn connect(
37        _db: *mut sqlite3,
38        _aux: Option<&Self::Aux>,
39        _args: VTabArguments,
40    ) -> Result<(String, CharactersTable)> {
41        let vtab = CharactersTable { base: unsafe { mem::zeroed() } };
42        // TODO db.config(VTabConfig::Innocuous)?;
43        Ok((CREATE_SQL.to_owned(), vtab))
44    }
45    fn destroy(&self) -> Result<()> {
46        Ok(())
47    }
48
49    fn best_index(&self, mut info: IndexInfo) -> core::result::Result<(), BestIndexError> {
50        let mut has_input = false;
51        for mut constraint in info.constraints() {
52            match column(constraint.column_idx()) {
53                Some(Columns::Input) => {
54                    if constraint.usable() && constraint.op() == Some(ConstraintOperator::EQ) {
55                        constraint.set_omit(true);
56                        constraint.set_argv_index(1);
57                        has_input = true;
58                    } else {
59                        return Err(BestIndexError::Constraint);
60                    }
61                }
62                _ => todo!(),
63            }
64        }
65        if !has_input {
66            return Err(BestIndexError::Error);
67        }
68        info.set_estimated_cost(100000.0);
69        info.set_estimated_rows(100000);
70        info.set_idxnum(1);
71
72        Ok(())
73    }
74
75    fn open(&mut self) -> Result<CharactersCursor> {
76        Ok(CharactersCursor::new())
77    }
78}
79
80#[repr(C)]
81pub struct CharactersCursor {
82    /// Base class. Must be first
83    base: sqlite3_vtab_cursor,
84    input: Option<String>,
85    characters: Option<Vec<char>>,
86    idx: usize,
87}
88impl CharactersCursor {
89    fn new() -> CharactersCursor {
90        CharactersCursor {
91            base: unsafe { mem::zeroed() },
92            input: None,
93            characters: None,
94            idx: 0,
95        }
96    }
97}
98
99impl VTabCursor for CharactersCursor {
100    fn filter(
101        &mut self,
102        _idx_num: i32,
103        _idx_str: Option<&str>,
104        values: &[*mut sqlite3_value],
105    ) -> Result<()> {
106        let input =
107            api::value_text(values.get(0).expect("1st input constraint is required"))?.to_owned();
108        self.characters = Some(input.chars().collect());
109        self.input = Some(input);
110        self.idx = 0;
111        Ok(())
112    }
113
114    fn next(&mut self) -> Result<()> {
115        self.idx += 1;
116        Ok(())
117    }
118
119    fn eof(&self) -> bool {
120        match &self.characters {
121            Some(chars) => chars.get(self.idx).is_none(),
122            None => true,
123        }
124    }
125
126    fn column(&self, context: *mut sqlite3_context, i: i32) -> Result<()> {
127        match column(i) {
128            Some(Columns::Value) => {
129                api::result_text(
130                    context,
131                    self.characters
132                        .as_ref()
133                        .unwrap()
134                        .get(self.idx)
135                        .unwrap()
136                        .to_string()
137                        .as_str(),
138                )?;
139            }
140            Some(Columns::Input) => {
141                api::result_text(context, self.input.as_ref().unwrap())?;
142            }
143            _ => (),
144        }
145        Ok(())
146    }
147
148    fn rowid(&self) -> Result<i64> {
149        Ok(self.idx as i64)
150    }
151}
152
153#[sqlite_entrypoint]
154pub fn sqlite3_characters_init(db: *mut sqlite3) -> Result<()> {
155    define_table_function::<CharactersTable>(db, "characters", None)?;
156    Ok(())
157}