use rupg_common::SqlState;
use crate::binary::Recv;
use crate::error::TypeError;
use crate::tsvector::{TSVECTOR_MAX_DATA, TSVECTOR_MAX_WORD, is_space, ts_compare_string};
pub const TSQUERY_NOT: u8 = 1;
pub const TSQUERY_AND: u8 = 2;
pub const TSQUERY_OR: u8 = 3;
pub const TSQUERY_PHRASE: u8 = 4;
pub const TSQUERY_MAX_DISTANCE: u16 = 1 << 14;
const STACK_DEPTH: usize = 32;
const MAX_DEPTH: usize = 1000;
const MAX_ITEMS: u32 = 0x3fff_ffff / 12;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum QueryItem {
Operand { word: String, weight: u8, prefix: bool },
Operator { op: u8, distance: u16, left: u32 },
}
fn priority(op: u8) -> i32 {
match op {
TSQUERY_NOT => 4,
TSQUERY_PHRASE => 3,
TSQUERY_AND => 2,
_ => 1,
}
}
fn is_operator(c: char) -> bool {
matches!(c, '!' | '&' | '|' | '(' | ')' | '<')
}
pub(crate) fn too_deep() -> TypeError {
let mut error =
TypeError::new(SqlState::STATEMENT_TOO_COMPLEX, "stack depth limit exceeded".to_string());
error.hint = Some("Increase the configuration parameter \"max_stack_depth\" (currently 2048kB), after ensuring the platform's stack depth limit is adequate.".to_string());
error
}
fn internal(message: String) -> TypeError {
TypeError::new(SqlState::INTERNAL_ERROR, message)
}
pub fn tsquery_bad_distance() -> TypeError {
TypeError::new(
SqlState::INVALID_PARAMETER_VALUE,
format!(
"distance in phrase operator must be an integer value between zero and {TSQUERY_MAX_DISTANCE} inclusive"
),
)
}
fn crc_table(index: u32) -> u32 {
let mut c = index;
for _ in 0..8 {
c = if c & 1 == 0 { c >> 1 } else { (c >> 1) ^ 0xedb8_8320 };
}
c
}
pub fn legacy_crc32(bytes: &[u8]) -> i32 {
let mut crc = u32::MAX;
for &b in bytes {
crc = crc_table(((crc >> 24) ^ u32::from(b)) & 0xff) ^ (crc << 8);
}
(!crc).cast_signed()
}
fn link(items: &mut [QueryItem]) -> Result<(), TypeError> {
let mut open: Vec<(usize, u8)> = Vec::new();
let mut lefts = Vec::new();
for (i, item) in items.iter().enumerate() {
if i > 0 && open.is_empty() {
return Err(internal("malformed tsquery: extra nodes".to_string()));
}
if open.len() >= MAX_DEPTH {
return Err(too_deep());
}
match item {
QueryItem::Operator { op, .. } => {
open.push((i, if *op == TSQUERY_NOT { 1 } else { 2 }))
}
QueryItem::Operand { .. } => {
while let Some((at, wait)) = open.last_mut() {
*wait -= 1;
if *wait == 1 {
lefts.push((*at, i + 1 - *at));
break;
}
open.pop();
}
}
}
}
if items.is_empty() || !open.is_empty() {
return Err(internal("malformed tsquery: operand not found".to_string()));
}
for (at, offset) in lefts {
if let QueryItem::Operator { left, .. } = &mut items[at] {
*left = u32::try_from(offset).unwrap_or(u32::MAX);
}
}
Ok(())
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Wait {
FirstOperand,
Operand,
Operator,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Word {
Start,
End,
Next,
EndComplex,
CharComplex,
}
enum Token {
End,
Bad,
Operand(String, u8, bool),
Operator(u8, u16),
Open,
Close,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TsQueryMode {
Standard,
Plain,
Web,
}
#[derive(Debug)]
enum Pol {
Item(QueryItem),
Stop,
}
#[derive(Debug)]
pub struct TsQueryPush<'a> {
text: &'a str,
items: Vec<Pol>,
data: usize,
}
impl TsQueryPush<'_> {
pub fn value(&mut self, word: String, weight: u8, prefix: bool) -> Result<(), TypeError> {
if word.len() > TSVECTOR_MAX_WORD {
return Err(TypeError::new(
SqlState::PROGRAM_LIMIT_EXCEEDED,
format!("word is too long in tsquery: \"{}\"", self.text),
));
}
if self.data > TSVECTOR_MAX_DATA {
return Err(TypeError::new(
SqlState::PROGRAM_LIMIT_EXCEEDED,
format!("value is too big in tsquery: \"{}\"", self.text),
));
}
self.data += word.len() + 1;
self.items.push(Pol::Item(QueryItem::Operand { word, weight, prefix }));
Ok(())
}
pub fn operator(&mut self, op: u8, distance: u16) {
let distance = if op == TSQUERY_PHRASE { distance } else { 0 };
self.items.push(Pol::Item(QueryItem::Operator { op, distance, left: 0 }));
}
pub fn stop(&mut self) {
self.items.push(Pol::Stop);
}
}
pub type TsQueryPushVal<'f> =
dyn FnMut(&mut TsQueryPush<'_>, &str, u8, bool) -> Result<(), TypeError> + 'f;
struct Parser<'a> {
text: &'a str,
at: usize,
count: i32,
wait: Wait,
mode: TsQueryMode,
push: TsQueryPush<'a>,
}
impl Parser<'_> {
fn syntax(&self) -> TypeError {
TypeError::new(
SqlState::SYNTAX_ERROR,
format!("syntax error in tsquery: \"{}\"", self.text),
)
}
fn peek(&self) -> Option<char> {
self.text[self.at..].chars().next()
}
fn word(&mut self) -> Result<Option<String>, TypeError> {
let web = self.mode == TsQueryMode::Web;
let mut word = String::new();
let mut state = Word::Start;
let mut old = Word::End;
loop {
let c = self.peek();
match state {
Word::Start => match c {
None => return Ok(None),
Some('\'') if !web => state = Word::EndComplex,
Some('\\') if !web => {
state = Word::Next;
old = Word::End;
}
Some(c) if is_operator(c) || (web && c == '"') => return Err(self.syntax()),
Some(c) if is_space(c) => {}
Some(c) => {
word.push(c);
state = Word::End;
}
},
Word::Next => match c {
None => {
return Err(TypeError::new(
SqlState::SYNTAX_ERROR,
format!("there is no escaped character: \"{}\"", self.text),
));
}
Some(c) => {
word.push(c);
state = old;
}
},
Word::End => match c {
Some('\\') if !web => {
state = Word::Next;
old = Word::End;
}
None => break,
Some(c) if is_space(c) || is_operator(c) || c == ':' || (web && c == '"') => {
break;
}
Some(c) => word.push(c),
},
Word::EndComplex => match c {
Some('\'') => state = Word::CharComplex,
Some('\\') => {
state = Word::Next;
old = Word::EndComplex;
}
None => return Err(self.syntax()),
Some(c) => word.push(c),
},
Word::CharComplex => {
if c != Some('\'') {
if word.is_empty() {
return Err(self.syntax());
}
break;
}
word.push('\'');
state = Word::EndComplex;
}
}
self.at += c.map_or(0, char::len_utf8);
}
Ok(Some(word))
}
fn modifiers(&mut self) -> (u8, bool) {
let bytes = self.text.as_bytes();
let (mut weight, mut prefix) = (0, false);
if bytes.get(self.at) != Some(&b':') {
return (weight, prefix);
}
self.at += 1;
while let Some(&b) = bytes.get(self.at) {
match b {
b'a' | b'A' => weight |= 8,
b'b' | b'B' => weight |= 4,
b'c' | b'C' => weight |= 2,
b'd' | b'D' => weight |= 1,
b'*' => prefix = true,
_ => break,
}
self.at += 1;
}
(weight, prefix)
}
fn phrase(&mut self) -> Result<Option<u16>, TypeError> {
let bytes = self.text.as_bytes();
let mut p = self.at + 1;
let distance = match bytes.get(p) {
Some(b'-') => {
p += 1;
1
}
Some(d) if d.is_ascii_digit() => {
let digits = bytes[p..].iter().take_while(|b| b.is_ascii_digit()).count();
let value = bytes[p..p + digits].iter().try_fold(0u64, |v, d| {
v.checked_mul(10).and_then(|v| v.checked_add(u64::from(d - b'0')))
});
p += digits;
match value.and_then(|v| u16::try_from(v).ok()) {
Some(v) if v <= TSQUERY_MAX_DISTANCE => v,
_ => return Err(tsquery_bad_distance()),
}
}
_ => return Ok(None),
};
if bytes.get(p) != Some(&b'>') || p + 1 >= bytes.len() {
return Ok(None);
}
self.at = p + 1;
Ok(Some(distance))
}
fn token(&mut self) -> Result<Token, TypeError> {
match self.mode {
TsQueryMode::Standard => self.token_standard(),
TsQueryMode::Plain => Ok(self.token_plain()),
TsQueryMode::Web => self.token_web(),
}
}
fn token_plain(&mut self) -> Token {
if self.at >= self.text.len() {
return Token::End;
}
let word = self.text[self.at..].to_string();
self.at = self.text.len();
self.count += 1;
Token::Operand(word, 0, false)
}
fn or_operator(&mut self) -> bool {
let rest = &self.text[self.at..];
if !rest.get(..2).is_some_and(|or| or.eq_ignore_ascii_case("or")) {
return false;
}
let mut after = rest[2..].chars();
match after.next() {
None => return false,
Some(c) if c == '-' || c == '_' || c.is_ascii_alphanumeric() => return false,
Some(_) => {}
}
if !after.any(|c| !is_space(c)) {
return false;
}
self.at += 2;
true
}
fn token_web(&mut self) -> Result<Token, TypeError> {
loop {
let c = self.peek();
match self.wait {
Wait::FirstOperand | Wait::Operand => match c {
Some('-') => {
self.at += 1;
self.wait = Wait::Operand;
return Ok(Token::Operator(TSQUERY_NOT, 0));
}
Some('"') => {
let start = self.at + 1;
let len = self.text[start..].find('"').unwrap_or(self.text.len() - start);
let word = self.text[start..start + len].to_string();
self.at = (start + len + 1).min(self.text.len());
self.wait = Wait::Operator;
self.count += 1;
return Ok(Token::Operand(word, 0, false));
}
Some(c) if is_operator(c) => {
self.at += 1;
self.wait = Wait::Operand;
continue;
}
Some(c) if is_space(c) => {}
_ => {
return Ok(match self.word()? {
Some(word) => {
self.wait = Wait::Operator;
Token::Operand(word, 0, false)
}
None if self.wait == Wait::FirstOperand => Token::End,
None => {
self.push.stop();
Token::End
}
});
}
},
Wait::Operator => match c {
None => return Ok(Token::End),
Some(_) if self.or_operator() => {
self.wait = Wait::Operand;
return Ok(Token::Operator(TSQUERY_OR, 0));
}
Some(c) if is_operator(c) => {
self.at += 1;
continue;
}
Some(c) if !is_space(c) => {
self.wait = Wait::Operand;
return Ok(Token::Operator(TSQUERY_AND, 0));
}
Some(_) => {}
},
}
self.at += c.map_or(0, char::len_utf8);
}
}
fn token_standard(&mut self) -> Result<Token, TypeError> {
loop {
let c = self.peek();
match self.wait {
Wait::FirstOperand | Wait::Operand => match c {
Some('!') => {
self.at += 1;
self.wait = Wait::Operand;
return Ok(Token::Operator(TSQUERY_NOT, 0));
}
Some('(') => {
self.at += 1;
self.wait = Wait::Operand;
self.count += 1;
return Ok(Token::Open);
}
Some(':') => return Ok(Token::Bad),
Some(c) if is_space(c) => {}
_ => {
return match self.word()? {
Some(word) => {
let (weight, prefix) = self.modifiers();
self.wait = Wait::Operator;
Ok(Token::Operand(word, weight, prefix))
}
None if self.wait == Wait::FirstOperand => Ok(Token::End),
None => Err(TypeError::new(
SqlState::SYNTAX_ERROR,
format!("no operand in tsquery: \"{}\"", self.text),
)),
};
}
},
Wait::Operator => match c {
Some('&') => {
self.at += 1;
self.wait = Wait::Operand;
return Ok(Token::Operator(TSQUERY_AND, 0));
}
Some('|') => {
self.at += 1;
self.wait = Wait::Operand;
return Ok(Token::Operator(TSQUERY_OR, 0));
}
Some('<') => {
let Some(distance) = self.phrase()? else { return Ok(Token::Bad) };
self.wait = Wait::Operand;
return Ok(Token::Operator(TSQUERY_PHRASE, distance));
}
Some(')') => {
self.at += 1;
self.count -= 1;
return Ok(if self.count < 0 { Token::Bad } else { Token::Close });
}
None => return Ok(if self.count == 0 { Token::End } else { Token::Bad }),
Some(c) if is_space(c) => {}
Some(_) => return Ok(Token::Bad),
},
}
self.at += c.map_or(0, char::len_utf8);
}
}
fn clean(&mut self, stack: &mut Vec<(u8, u16)>, op: u8) {
let p = priority(op);
while let Some(&(top, distance)) = stack.last() {
let q = priority(top);
if (op != TSQUERY_NOT && p > q) || (op == TSQUERY_NOT && p >= q) {
break;
}
stack.pop();
self.push.operator(top, distance);
}
}
fn makepol(&mut self, depth: usize, pushval: &mut TsQueryPushVal<'_>) -> Result<(), TypeError> {
if depth >= MAX_DEPTH {
return Err(too_deep());
}
let mut stack: Vec<(u8, u16)> = Vec::new();
loop {
match self.token()? {
Token::End => break,
Token::Operand(word, weight, prefix) => {
pushval(&mut self.push, &word, weight, prefix)?
}
Token::Operator(op, distance) => {
self.clean(&mut stack, op);
if stack.len() == STACK_DEPTH {
return Err(internal("tsquery stack too small".to_string()));
}
stack.push((op, distance));
}
Token::Open => self.makepol(depth + 1, pushval)?,
Token::Close => break,
Token::Bad => return Err(self.syntax()),
}
}
self.clean(&mut stack, TSQUERY_OR);
Ok(())
}
}
pub fn tsquery_parse(text: &str) -> Result<Vec<QueryItem>, TypeError> {
let mut asis = |push: &mut TsQueryPush<'_>, word: &str, weight: u8, prefix: bool| {
push.value(word.to_string(), weight, prefix)
};
Ok(tsquery_parse_with(text, TsQueryMode::Standard, &mut asis)?.0)
}
pub fn tsquery_parse_with(
text: &str,
mode: TsQueryMode,
pushval: &mut TsQueryPushVal<'_>,
) -> Result<(Vec<QueryItem>, bool), TypeError> {
let push = TsQueryPush { text, items: Vec::new(), data: 0 };
let mut parser = Parser { text, at: 0, count: 0, wait: Wait::FirstOperand, mode, push };
parser.makepol(0, pushval)?;
let mut items = parser.push.items;
if items.is_empty() {
return Ok((Vec::new(), false));
}
items.reverse();
if items.iter().any(|item| matches!(item, Pol::Stop)) {
let mut rest = items.into_iter();
let tree = tree(&mut rest, 0)?;
if rest.next().is_some() {
return Err(internal("malformed tsquery: extra nodes".to_string()));
}
return Ok(match clean_stops(tree).0 {
Some(tree) => (plain(tree), false),
None => (Vec::new(), true),
});
}
let mut items: Vec<QueryItem> = items
.into_iter()
.filter_map(|item| match item {
Pol::Item(item) => Some(item),
Pol::Stop => None,
})
.collect();
link(&mut items)?;
Ok((items, false))
}
enum Node {
Operand(QueryItem),
Stop,
Operator { op: u8, distance: u16, right: Box<Node>, left: Option<Box<Node>> },
}
fn tree(items: &mut std::vec::IntoIter<Pol>, depth: usize) -> Result<Node, TypeError> {
if depth >= MAX_DEPTH {
return Err(too_deep());
}
match items.next() {
None => Err(internal("malformed tsquery: operand not found".to_string())),
Some(Pol::Stop) => Ok(Node::Stop),
Some(Pol::Item(QueryItem::Operator { op, distance, .. })) => {
let right = Box::new(tree(items, depth + 1)?);
let left =
if op == TSQUERY_NOT { None } else { Some(Box::new(tree(items, depth + 1)?)) };
Ok(Node::Operator { op, distance, right, left })
}
Some(Pol::Item(item)) => Ok(Node::Operand(item)),
}
}
fn clean_stops(node: Node) -> (Option<Node>, i32, i32) {
let Node::Operator { op, distance, right, left } = node else {
return match node {
Node::Stop => (None, 0, 0),
node => (Some(node), 0, 0),
};
};
let (right, rladd, rradd) = clean_stops(*right);
let Some(left) = left else {
let node = right.map(|right| Node::Operator { op, distance, right: Box::new(right), left });
return (node, rladd, rradd);
};
let (left, lladd, lradd) = clean_stops(*left);
let phrase = op == TSQUERY_PHRASE;
let own = if phrase { i32::from(distance) } else { 0 };
match (left, right) {
(None, None) => {
let add = if phrase { lladd + own + rladd } else { lladd.max(rladd) };
(None, add, add)
}
(None, Some(right)) if phrase => (Some(right), lladd + own + rladd, rradd),
(None, Some(right)) => (Some(right), rladd, rradd),
(Some(left), None) if phrase => (Some(left), lladd, lradd + own + rradd),
(Some(left), None) => (Some(left), lladd, lradd),
(Some(left), Some(right)) => {
let (right, left) = (Box::new(right), Some(Box::new(left)));
if phrase {
let distance = u16::try_from(own + lradd + rladd).unwrap_or(u16::MAX);
(Some(Node::Operator { op, distance, right, left }), lladd, rradd)
} else {
(Some(Node::Operator { op, distance, right, left }), 0, 0)
}
}
}
}
fn plain(node: Node) -> Vec<QueryItem> {
match node {
Node::Operand(item) => vec![item],
Node::Stop => Vec::new(),
Node::Operator { op, distance, right, left: None } => {
let mut items = vec![QueryItem::Operator { op, distance, left: 1 }];
items.extend(plain(*right));
items
}
Node::Operator { op, distance, right, left: Some(left) } => {
join(op, distance, plain(*right), plain(*left))
}
}
}
fn infix(items: &[QueryItem], at: usize, parent: i32, right_phrase: bool, out: &mut Vec<u8>) {
match &items[at] {
QueryItem::Operand { word, weight, prefix } => {
out.push(b'\'');
for &b in word.as_bytes() {
if b == b'\'' || b == b'\\' {
out.push(b);
}
out.push(b);
}
out.push(b'\'');
if *weight != 0 || *prefix {
out.push(b':');
if *prefix {
out.push(b'*');
}
for (bit, letter) in [(8, b'A'), (4, b'B'), (2, b'C'), (1, b'D')] {
if weight & bit != 0 {
out.push(letter);
}
}
}
}
QueryItem::Operator { op, distance, left } => {
let p = priority(*op);
let paren = p < parent || (*op == TSQUERY_PHRASE && right_phrase);
if paren {
out.extend_from_slice(b"( ");
}
if *op == TSQUERY_NOT {
out.push(b'!');
} else {
infix(items, at + *left as usize, p, false, out);
match *op {
TSQUERY_OR => out.extend_from_slice(b" | "),
TSQUERY_AND => out.extend_from_slice(b" & "),
_ if *distance == 1 => out.extend_from_slice(b" <-> "),
_ => out.extend_from_slice(format!(" <{distance}> ").as_bytes()),
}
}
infix(items, at + 1, p, *op == TSQUERY_PHRASE, out);
if paren {
out.extend_from_slice(b" )");
}
}
}
}
pub fn tsquery_deparse(items: &[QueryItem], out: &mut Vec<u8>) {
if !items.is_empty() {
infix(items, 0, -1, false, out);
}
}
pub fn tsquery_send(items: &[QueryItem], out: &mut Vec<u8>) {
let count = i32::try_from(items.len()).unwrap_or(i32::MAX);
out.extend_from_slice(&count.to_be_bytes());
for item in items {
match item {
QueryItem::Operand { word, weight, prefix } => {
out.extend_from_slice(&[1, *weight, u8::from(*prefix)]);
out.extend_from_slice(word.as_bytes());
out.push(0);
}
QueryItem::Operator { op, distance, .. } => {
out.extend_from_slice(&[2, *op]);
if *op == TSQUERY_PHRASE {
out.extend_from_slice(&distance.to_be_bytes());
}
}
}
}
}
pub fn tsquery_recv(recv: &mut Recv<'_>) -> Result<Vec<QueryItem>, TypeError> {
let size = recv.u32()?;
if size > MAX_ITEMS {
return Err(internal("invalid size of tsquery".to_string()));
}
let mut items = Vec::with_capacity(recv.remaining().min(size as usize));
let mut data = 0;
for i in 0..size {
let kind = recv.bytes(1)?[0].cast_signed();
match kind {
1 => {
let weight = recv.bytes(1)?[0];
let prefix = recv.bytes(1)?[0] != 0;
let word = recv.cstring()?;
let error = if weight > 0xf {
"invalid tsquery: invalid weight bitmap"
} else if word.is_empty() {
"invalid tsquery: empty operand"
} else if word.len() > TSVECTOR_MAX_WORD {
"invalid tsquery: operand too long"
} else if data > TSVECTOR_MAX_DATA {
"invalid tsquery: total operand length exceeded"
} else {
""
};
if !error.is_empty() {
return Err(internal(error.to_string()));
}
data += word.len() + 1;
items.push(QueryItem::Operand { word: word.to_string(), weight, prefix });
}
2 => {
let op = recv.bytes(1)?[0].cast_signed();
let Ok(op @ TSQUERY_NOT..=TSQUERY_PHRASE) = u8::try_from(op) else {
return Err(internal(format!(
"invalid tsquery: unrecognized operator type {op}"
)));
};
if i == size - 1 {
return Err(internal("invalid pointer to right operand".to_string()));
}
let mut distance = 0;
if op == TSQUERY_PHRASE {
distance = recv.i16()?.cast_unsigned();
if distance > TSQUERY_MAX_DISTANCE {
return Err(internal(format!(
"invalid tsquery: invalid phrase distance {distance}"
)));
}
}
items.push(QueryItem::Operator { op, distance, left: 0 });
}
_ => return Err(internal(format!("unrecognized tsquery node type: {kind}"))),
}
}
link(&mut items)?;
Ok(items)
}
fn data_len(items: &[QueryItem]) -> usize {
let words = items.iter().filter_map(|item| match item {
QueryItem::Operand { word, .. } => Some(word.len() + 1),
QueryItem::Operator { .. } => None,
});
words.sum()
}
fn node_cmp(a: &[QueryItem], i: usize, b: &[QueryItem], j: usize) -> i32 {
match (&a[i], &b[j]) {
(QueryItem::Operator { .. }, QueryItem::Operand { .. }) => -1,
(QueryItem::Operand { .. }, QueryItem::Operator { .. }) => 1,
(
QueryItem::Operator { op: x, distance: dx, left: lx },
QueryItem::Operator { op: y, distance: dy, left: ly },
) => {
if x != y {
return if x > y { -1 } else { 1 };
}
let right = node_cmp(a, i + 1, b, j + 1);
if right != 0 {
return right;
}
if *x != TSQUERY_NOT {
let left = node_cmp(a, i + *lx as usize, b, j + *ly as usize);
if left != 0 {
return left;
}
}
if *x == TSQUERY_PHRASE && dx != dy {
return if dx > dy { -1 } else { 1 };
}
0
}
(QueryItem::Operand { word: x, .. }, QueryItem::Operand { word: y, .. }) => {
let (cx, cy) = (legacy_crc32(x.as_bytes()), legacy_crc32(y.as_bytes()));
if cx != cy {
return if cx > cy { -1 } else { 1 };
}
ts_compare_string(x.as_bytes(), y.as_bytes())
}
}
}
pub fn tsquery_cmp(a: &[QueryItem], b: &[QueryItem]) -> i32 {
if a.len() != b.len() {
return if a.len() < b.len() { -1 } else { 1 };
}
let (x, y) = (data_len(a), data_len(b));
if x != y {
return if x < y { -1 } else { 1 };
}
if a.is_empty() { 0 } else { node_cmp(a, 0, b, 0) }
}
fn join(op: u8, distance: u16, right: Vec<QueryItem>, left: Vec<QueryItem>) -> Vec<QueryItem> {
let offset = u32::try_from(right.len() + 1).unwrap_or(u32::MAX);
let mut items = Vec::with_capacity(1 + right.len() + left.len());
items.push(QueryItem::Operator { op, distance, left: offset });
items.extend(right);
items.extend(left);
items
}
pub fn tsquery_join(
op: u8,
distance: u16,
left: &[QueryItem],
right: &[QueryItem],
) -> Result<Vec<QueryItem>, TypeError> {
let mut items = if op == TSQUERY_NOT {
let mut items = vec![QueryItem::Operator { op, distance: 0, left: 1 }];
items.extend_from_slice(right);
items
} else {
join(op, distance, right.to_vec(), left.to_vec())
};
let mut data = 0;
for item in &items {
if let QueryItem::Operand { word, .. } = item {
if data > TSVECTOR_MAX_DATA {
return Err(TypeError::new(
SqlState::PROGRAM_LIMIT_EXCEEDED,
"tsquery is too large".to_string(),
));
}
data += word.len() + 1;
}
}
link(&mut items)?;
Ok(items)
}
fn clean_not(items: &[QueryItem], at: usize) -> Option<Vec<QueryItem>> {
match &items[at] {
QueryItem::Operand { .. } => Some(vec![items[at].clone()]),
QueryItem::Operator { op: TSQUERY_NOT, .. } => None,
QueryItem::Operator { op: TSQUERY_OR, left, .. } => {
let left = clean_not(items, at + *left as usize)?;
let right = clean_not(items, at + 1)?;
Some(join(TSQUERY_OR, 0, right, left))
}
QueryItem::Operator { op, distance, left } => {
let left = clean_not(items, at + *left as usize);
let right = clean_not(items, at + 1);
match (left, right) {
(Some(left), Some(right)) => Some(join(*op, *distance, right, left)),
(left, right) => left.or(right),
}
}
}
}
pub fn tsquery_tree(items: &[QueryItem]) -> String {
if items.is_empty() {
return String::new();
}
let Some(tree) = clean_not(items, 0) else { return "T".to_string() };
let mut out = Vec::new();
tsquery_deparse(&tree, &mut out);
String::from_utf8(out).unwrap_or_default()
}
fn sorted_words(items: &[QueryItem]) -> Vec<&[u8]> {
let mut words: Vec<&[u8]> = items
.iter()
.filter_map(|item| match item {
QueryItem::Operand { word, .. } => Some(word.as_bytes()),
QueryItem::Operator { .. } => None,
})
.collect();
words.sort_unstable();
words.dedup();
words
}
pub fn tsquery_contains(query: &[QueryItem], ex: &[QueryItem]) -> bool {
let (query, ex) = (sorted_words(query), sorted_words(ex));
ex.iter().all(|word| query.binary_search(word).is_ok())
}
#[cfg(test)]
mod tests {
use super::*;
fn text(input: &str) -> String {
let mut out = Vec::new();
tsquery_deparse(&tsquery_parse(input).unwrap(), &mut out);
String::from_utf8(out).unwrap()
}
fn text_in(mode: TsQueryMode, input: &str) -> (String, bool) {
let mut pushval = |push: &mut TsQueryPush<'_>, word: &str, weight: u8, prefix: bool| {
if word == "stop" {
push.stop();
return Ok(());
}
push.value(word.to_string(), weight, prefix)
};
let (items, stops) = tsquery_parse_with(input, mode, &mut pushval).unwrap();
let mut out = Vec::new();
tsquery_deparse(&items, &mut out);
(String::from_utf8(out).unwrap(), stops)
}
#[test]
fn modes() {
let web = |input| text_in(TsQueryMode::Web, input).0;
assert_eq!(web("a or -b c"), "'a' | !'b' & 'c'");
assert_eq!(web("\"a b\" or_c"), "'a b' & 'or_c'");
assert_eq!(web("a & | ( ) < b"), "'a' & 'b'");
assert_eq!(web("it's a\\b"), "'it''s' & 'a\\\\b'");
assert_eq!(web("a or"), "'a' & 'or'");
assert_eq!(text_in(TsQueryMode::Plain, "a & b").0, "'a & b'");
assert_eq!(text_in(TsQueryMode::Standard, "a <-> stop <-> b").0, "'a' <2> 'b'");
assert_eq!(text_in(TsQueryMode::Standard, "(a <-> stop) <-> b").0, "'a' <2> 'b'");
assert_eq!(text_in(TsQueryMode::Standard, "stop & !stop"), (String::new(), true));
assert_eq!(text_in(TsQueryMode::Standard, "stop | a").0, "'a'");
}
fn error(input: &str) -> (String, String) {
let error = tsquery_parse(input).unwrap_err();
(error.sqlstate.as_str().to_string(), error.message)
}
#[test]
fn parse_and_deparse() {
assert_eq!(text("a & b | c & d"), "'a' & 'b' | 'c' & 'd'");
assert_eq!(text("(a | b) & (c | d)"), "( 'a' | 'b' ) & ( 'c' | 'd' )");
assert_eq!(text("a & !(b | c)"), "'a' & !( 'b' | 'c' )");
assert_eq!(text("a <-> (b <-> c)"), "'a' <-> ( 'b' <-> 'c' )");
assert_eq!(text("(a <-> b) <-> c"), "'a' <-> 'b' <-> 'c'");
assert_eq!(text("a | b & !c <-> d"), "'a' | 'b' & !'c' <-> 'd'");
assert_eq!(text("a <0> b & c <1> d"), "'a' <0> 'b' & 'c' <-> 'd'");
assert_eq!(text("a:dcba* & b:"), "'a':*ABCD & 'b'");
assert_eq!(text(r"'it''s' & c\'d & e\\f"), r"'it''s' & 'c''d' & 'e\\f'");
assert_eq!(text("aé&ß"), "'aé' & 'ß'");
assert_eq!(tsquery_parse(" ").unwrap(), Vec::new());
}
#[test]
fn parse_errors() {
let syntax =
|input: &str| ("42601".to_string(), format!("syntax error in tsquery: \"{input}\""));
for input in [
"& a", "a b", "a & (b", "a & b)", "()", "a <-", "a <->", "a <x> b", "a:x", ":a", "'a",
"''", "a<b",
] {
assert_eq!(error(input), syntax(input), "{input}");
}
assert_eq!(
error("a &"),
("42601".to_string(), "no operand in tsquery: \"a &\"".to_string())
);
assert_eq!(error("!").1, "no operand in tsquery: \"!\"");
assert_eq!(error(r"a\").1, r#"there is no escaped character: "a\""#);
assert_eq!(error("a <16385> b").0, "22023");
assert_eq!(error("a <99999999999999999999> b").0, "22023");
assert_eq!(text("a <00016384> b"), "'a' <16384> 'b'");
assert_eq!(error(&"x".repeat(2048)).0, "54000");
assert_eq!(
error(&format!("{}a", "!".repeat(33))),
("XX000".to_string(), "tsquery stack too small".to_string())
);
assert!(tsquery_parse(&format!("{}a", "!".repeat(32))).is_ok());
assert_eq!(error(&format!("{}a", "(".repeat(1000))).0, "54001");
assert!(tsquery_parse(&format!("{}a{}", "(".repeat(999), ")".repeat(999))).is_ok());
}
#[test]
fn order() {
let cmp =
|a: &str, b: &str| tsquery_cmp(&tsquery_parse(a).unwrap(), &tsquery_parse(b).unwrap());
assert_eq!(legacy_crc32(b"a"), 397_917_884);
assert_eq!(legacy_crc32(b"b"), -1_900_089_594);
assert_eq!(
(cmp("a", "b"), cmp("b", "a"), cmp("a", "a & b"), cmp("ab", "a")),
(-1, 1, -1, 1)
);
assert_eq!((cmp("a & b", "a | b"), cmp("a <1> b", "a <2> b"), cmp("!a", "b")), (1, 1, 1));
assert_eq!((cmp("a:A", "a"), cmp("a:*", "a"), cmp("a & b", "b & a")), (0, 0, 1));
}
#[test]
fn functions() {
let q = |input: &str| tsquery_parse(input).unwrap();
let show = |items: &[QueryItem]| {
let mut out = Vec::new();
tsquery_deparse(items, &mut out);
String::from_utf8(out).unwrap()
};
let tree = |input: &str| tsquery_tree(&q(input));
assert_eq!(
[tree("a & !b"), tree("!a"), tree("!a | b"), tree("a <-> !b"), tree("(a | !b) & c")],
["'a'", "T", "T", "'a'", "'c'"]
);
assert_eq!(
show(&tsquery_join(TSQUERY_AND, 0, &q("a | b"), &q("c")).unwrap()),
"( 'a' | 'b' ) & 'c'"
);
assert_eq!(
show(&tsquery_join(TSQUERY_PHRASE, 1, &q("a"), &q("b <-> c")).unwrap()),
"'a' <-> ( 'b' <-> 'c' )"
);
assert_eq!(
show(&tsquery_join(TSQUERY_NOT, 0, &[], &q("a & b")).unwrap()),
"!( 'a' & 'b' )"
);
assert!(tsquery_contains(&q("a & b"), &q("a")));
assert!(!tsquery_contains(&q("a"), &q("a & b")));
assert!(tsquery_contains(&q("a & a"), &q("a & a & a")));
}
#[test]
fn binary() {
let items = tsquery_parse("x <3> !y:*").unwrap();
let mut out = Vec::new();
tsquery_send(&items, &mut out);
assert_eq!(out, b"\0\0\0\x04\x02\x04\x00\x03\x02\x01\x01\x00\x01y\x00\x01\x00\x00x\x00");
assert_eq!(tsquery_recv(&mut Recv::new(&out)).unwrap(), items);
let message = |bytes: &[u8]| tsquery_recv(&mut Recv::new(bytes)).unwrap_err().message;
assert_eq!(message(b"\0\0\0\0"), "malformed tsquery: operand not found");
assert_eq!(message(b"\0\0\0\x02\x01\0\0a\0\x01\0\0b\0"), "malformed tsquery: extra nodes");
assert_eq!(message(b"\0\0\0\x01\x02\x02"), "invalid pointer to right operand");
assert_eq!(message(b"\0\0\0\x02\x02\x09"), "invalid tsquery: unrecognized operator type 9");
assert_eq!(message(b"\0\0\0\x01\x01\x10\0a\0"), "invalid tsquery: invalid weight bitmap");
assert_eq!(message(b"\0\0\0\x01\x01\0\0\0"), "invalid tsquery: empty operand");
assert_eq!(message(b"\0\0\0\x01\xff"), "unrecognized tsquery node type: -1");
assert_eq!(message(b"\x7f\0\0\0"), "invalid size of tsquery");
assert_eq!(
message(b"\0\0\0\x03\x02\x04\x40\x01"),
"invalid tsquery: invalid phrase distance 16385"
);
}
}