mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
Add support for PostgreSQL/Redshift geometric operators (#1723)
This commit is contained in:
parent
97f0be6991
commit
339239d0c5
11 changed files with 809 additions and 26 deletions
|
@ -386,6 +386,10 @@ pub enum DataType {
|
|||
///
|
||||
/// [bigquery]: https://cloud.google.com/bigquery/docs/user-defined-functions#templated-sql-udf-parameters
|
||||
AnyType,
|
||||
/// geometric type
|
||||
///
|
||||
/// [Postgres]: https://www.postgresql.org/docs/9.5/functions-geometry.html
|
||||
GeometricType(GeometricTypeKind),
|
||||
}
|
||||
|
||||
impl fmt::Display for DataType {
|
||||
|
@ -639,6 +643,7 @@ impl fmt::Display for DataType {
|
|||
DataType::Trigger => write!(f, "TRIGGER"),
|
||||
DataType::AnyType => write!(f, "ANY TYPE"),
|
||||
DataType::Table(fields) => write!(f, "TABLE({})", display_comma_separated(fields)),
|
||||
DataType::GeometricType(kind) => write!(f, "{}", kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -915,3 +920,34 @@ pub enum ArrayElemTypeDef {
|
|||
/// `Array(Int64)`
|
||||
Parenthesis(Box<DataType>),
|
||||
}
|
||||
|
||||
/// Represents different types of geometric shapes which are commonly used in
|
||||
/// PostgreSQL/Redshift for spatial operations and geometry-related computations.
|
||||
///
|
||||
/// [Postgres]: https://www.postgresql.org/docs/9.5/functions-geometry.html
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum GeometricTypeKind {
|
||||
Point,
|
||||
Line,
|
||||
LineSegment,
|
||||
GeometricBox,
|
||||
GeometricPath,
|
||||
Polygon,
|
||||
Circle,
|
||||
}
|
||||
|
||||
impl fmt::Display for GeometricTypeKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
GeometricTypeKind::Point => write!(f, "point"),
|
||||
GeometricTypeKind::Line => write!(f, "line"),
|
||||
GeometricTypeKind::LineSegment => write!(f, "lseg"),
|
||||
GeometricTypeKind::GeometricBox => write!(f, "box"),
|
||||
GeometricTypeKind::GeometricPath => write!(f, "path"),
|
||||
GeometricTypeKind::Polygon => write!(f, "polygon"),
|
||||
GeometricTypeKind::Circle => write!(f, "circle"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,8 @@ use crate::ast::helpers::stmt_data_loading::{
|
|||
#[cfg(feature = "visitor")]
|
||||
pub use visitor::*;
|
||||
|
||||
pub use self::data_type::GeometricTypeKind;
|
||||
|
||||
mod data_type;
|
||||
mod dcl;
|
||||
mod ddl;
|
||||
|
@ -1513,7 +1515,15 @@ impl fmt::Display for Expr {
|
|||
Expr::UnaryOp { op, expr } => {
|
||||
if op == &UnaryOperator::PGPostfixFactorial {
|
||||
write!(f, "{expr}{op}")
|
||||
} else if op == &UnaryOperator::Not {
|
||||
} else if matches!(
|
||||
op,
|
||||
UnaryOperator::Not
|
||||
| UnaryOperator::Hash
|
||||
| UnaryOperator::AtDashAt
|
||||
| UnaryOperator::DoubleAt
|
||||
| UnaryOperator::QuestionDash
|
||||
| UnaryOperator::QuestionPipe
|
||||
) {
|
||||
write!(f, "{op} {expr}")
|
||||
} else {
|
||||
write!(f, "{op}{expr}")
|
||||
|
|
|
@ -53,6 +53,21 @@ pub enum UnaryOperator {
|
|||
PGAbs,
|
||||
/// Unary logical not operator: e.g. `! false` (Hive-specific)
|
||||
BangNot,
|
||||
/// `#` Number of points in path or polygon (PostgreSQL/Redshift geometric operator)
|
||||
/// see <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
Hash,
|
||||
/// `@-@` Length or circumference (PostgreSQL/Redshift geometric operator)
|
||||
/// see <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
AtDashAt,
|
||||
/// `@@` Center (PostgreSQL/Redshift geometric operator)
|
||||
/// see <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
DoubleAt,
|
||||
/// `?-` Is horizontal? (PostgreSQL/Redshift geometric operator)
|
||||
/// see <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
QuestionDash,
|
||||
/// `?|` Is vertical? (PostgreSQL/Redshift geometric operator)
|
||||
/// see <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
QuestionPipe,
|
||||
}
|
||||
|
||||
impl fmt::Display for UnaryOperator {
|
||||
|
@ -68,6 +83,11 @@ impl fmt::Display for UnaryOperator {
|
|||
UnaryOperator::PGPrefixFactorial => "!!",
|
||||
UnaryOperator::PGAbs => "@",
|
||||
UnaryOperator::BangNot => "!",
|
||||
UnaryOperator::Hash => "#",
|
||||
UnaryOperator::AtDashAt => "@-@",
|
||||
UnaryOperator::DoubleAt => "@@",
|
||||
UnaryOperator::QuestionDash => "?-",
|
||||
UnaryOperator::QuestionPipe => "?|",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -253,6 +273,54 @@ pub enum BinaryOperator {
|
|||
/// Specifies a test for an overlap between two datetime periods:
|
||||
/// <https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#overlaps-predicate>
|
||||
Overlaps,
|
||||
/// `##` Point of closest proximity (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
DoubleHash,
|
||||
/// `<->` Distance between (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
LtDashGt,
|
||||
/// `&<` Overlaps to left? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
AndLt,
|
||||
/// `&>` Overlaps to right? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
AndGt,
|
||||
/// `<<|` Is strictly below? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
LtLtPipe,
|
||||
/// `|>>` Is strictly above? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
PipeGtGt,
|
||||
/// `&<|` Does not extend above? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
AndLtPipe,
|
||||
/// `|&>` Does not extend below? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
PipeAndGt,
|
||||
/// `<^` Is below? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
LtCaret,
|
||||
/// `>^` Is above? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
GtCaret,
|
||||
/// `?#` Intersects? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
QuestionHash,
|
||||
/// `?-` Is horizontal? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
QuestionDash,
|
||||
/// `?-|` Is perpendicular? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
QuestionDashPipe,
|
||||
/// `?||` Are Parallel? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
QuestionDoublePipe,
|
||||
/// `@` Contained or on? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
At,
|
||||
/// `~=` Same as? (PostgreSQL/Redshift geometric operator)
|
||||
/// See <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
TildeEq,
|
||||
}
|
||||
|
||||
impl fmt::Display for BinaryOperator {
|
||||
|
@ -310,6 +378,22 @@ impl fmt::Display for BinaryOperator {
|
|||
write!(f, "OPERATOR({})", display_separated(idents, "."))
|
||||
}
|
||||
BinaryOperator::Overlaps => f.write_str("OVERLAPS"),
|
||||
BinaryOperator::DoubleHash => f.write_str("##"),
|
||||
BinaryOperator::LtDashGt => f.write_str("<->"),
|
||||
BinaryOperator::AndLt => f.write_str("&<"),
|
||||
BinaryOperator::AndGt => f.write_str("&>"),
|
||||
BinaryOperator::LtLtPipe => f.write_str("<<|"),
|
||||
BinaryOperator::PipeGtGt => f.write_str("|>>"),
|
||||
BinaryOperator::AndLtPipe => f.write_str("&<|"),
|
||||
BinaryOperator::PipeAndGt => f.write_str("|&>"),
|
||||
BinaryOperator::LtCaret => f.write_str("<^"),
|
||||
BinaryOperator::GtCaret => f.write_str(">^"),
|
||||
BinaryOperator::QuestionHash => f.write_str("?#"),
|
||||
BinaryOperator::QuestionDash => f.write_str("?-"),
|
||||
BinaryOperator::QuestionDashPipe => f.write_str("?-|"),
|
||||
BinaryOperator::QuestionDoublePipe => f.write_str("?||"),
|
||||
BinaryOperator::At => f.write_str("@"),
|
||||
BinaryOperator::TildeEq => f.write_str("~="),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -599,18 +599,34 @@ pub trait Dialect: Debug + Any {
|
|||
| Token::ExclamationMarkDoubleTilde
|
||||
| Token::ExclamationMarkDoubleTildeAsterisk
|
||||
| Token::Spaceship => Ok(p!(Eq)),
|
||||
Token::Pipe => Ok(p!(Pipe)),
|
||||
Token::Pipe
|
||||
| Token::QuestionMarkDash
|
||||
| Token::DoubleSharp
|
||||
| Token::Overlap
|
||||
| Token::AmpersandLeftAngleBracket
|
||||
| Token::AmpersandRightAngleBracket
|
||||
| Token::QuestionMarkDashVerticalBar
|
||||
| Token::AmpersandLeftAngleBracketVerticalBar
|
||||
| Token::VerticalBarAmpersandRightAngleBracket
|
||||
| Token::TwoWayArrow
|
||||
| Token::LeftAngleBracketCaret
|
||||
| Token::RightAngleBracketCaret
|
||||
| Token::QuestionMarkSharp
|
||||
| Token::QuestionMarkDoubleVerticalBar
|
||||
| Token::QuestionPipe
|
||||
| Token::TildeEqual
|
||||
| Token::AtSign
|
||||
| Token::ShiftLeftVerticalBar
|
||||
| Token::VerticalBarShiftRight => Ok(p!(Pipe)),
|
||||
Token::Caret | Token::Sharp | Token::ShiftRight | Token::ShiftLeft => Ok(p!(Caret)),
|
||||
Token::Ampersand => Ok(p!(Ampersand)),
|
||||
Token::Plus | Token::Minus => Ok(p!(PlusMinus)),
|
||||
Token::Mul | Token::Div | Token::DuckIntDiv | Token::Mod | Token::StringConcat => {
|
||||
Ok(p!(MulDivModOp))
|
||||
}
|
||||
Token::DoubleColon
|
||||
| Token::ExclamationMark
|
||||
| Token::LBracket
|
||||
| Token::Overlap
|
||||
| Token::CaretAt => Ok(p!(DoubleColon)),
|
||||
Token::DoubleColon | Token::ExclamationMark | Token::LBracket | Token::CaretAt => {
|
||||
Ok(p!(DoubleColon))
|
||||
}
|
||||
Token::Arrow
|
||||
| Token::LongArrow
|
||||
| Token::HashArrow
|
||||
|
@ -622,7 +638,6 @@ pub trait Dialect: Debug + Any {
|
|||
| Token::AtAt
|
||||
| Token::Question
|
||||
| Token::QuestionAnd
|
||||
| Token::QuestionPipe
|
||||
| Token::CustomBinaryOperator(_) => Ok(p!(PgOther)),
|
||||
_ => Ok(self.prec_unknown()),
|
||||
}
|
||||
|
@ -921,6 +936,13 @@ pub trait Dialect: Debug + Any {
|
|||
fn supports_array_typedef_size(&self) -> bool {
|
||||
false
|
||||
}
|
||||
/// Returns true if the dialect supports geometric types.
|
||||
///
|
||||
/// Postgres: <https://www.postgresql.org/docs/9.5/functions-geometry.html>
|
||||
/// e.g. @@ circle '((0,0),10)'
|
||||
fn supports_geometric_types(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the operators for which precedence must be defined
|
||||
|
|
|
@ -250,4 +250,8 @@ impl Dialect for PostgreSqlDialect {
|
|||
fn supports_array_typedef_size(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_geometric_types(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,4 +113,8 @@ impl Dialect for RedshiftSqlDialect {
|
|||
fn supports_string_escape_constant(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_geometric_types(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ define_keywords!(
|
|||
BOOL,
|
||||
BOOLEAN,
|
||||
BOTH,
|
||||
BOX,
|
||||
BROWSE,
|
||||
BTREE,
|
||||
BUCKET,
|
||||
|
@ -175,6 +176,7 @@ define_keywords!(
|
|||
CHARSET,
|
||||
CHAR_LENGTH,
|
||||
CHECK,
|
||||
CIRCLE,
|
||||
CLEAR,
|
||||
CLOB,
|
||||
CLONE,
|
||||
|
@ -478,6 +480,7 @@ define_keywords!(
|
|||
LIKE,
|
||||
LIKE_REGEX,
|
||||
LIMIT,
|
||||
LINE,
|
||||
LINES,
|
||||
LIST,
|
||||
LISTEN,
|
||||
|
@ -499,6 +502,7 @@ define_keywords!(
|
|||
LOWER,
|
||||
LOW_PRIORITY,
|
||||
LS,
|
||||
LSEG,
|
||||
MACRO,
|
||||
MANAGE,
|
||||
MANAGED,
|
||||
|
@ -653,7 +657,9 @@ define_keywords!(
|
|||
PLACING,
|
||||
PLAN,
|
||||
PLANS,
|
||||
POINT,
|
||||
POLICY,
|
||||
POLYGON,
|
||||
POOL,
|
||||
PORTION,
|
||||
POSITION,
|
||||
|
|
|
@ -1220,7 +1220,17 @@ impl<'a> Parser<'a> {
|
|||
Keyword::MAP if *self.peek_token_ref() == Token::LBrace && self.dialect.support_map_literal_syntax() => {
|
||||
Ok(Some(self.parse_duckdb_map_literal()?))
|
||||
}
|
||||
_ => Ok(None)
|
||||
_ if self.dialect.supports_geometric_types() => match w.keyword {
|
||||
Keyword::CIRCLE => Ok(Some(self.parse_geometric_type(GeometricTypeKind::Circle)?)),
|
||||
Keyword::BOX => Ok(Some(self.parse_geometric_type(GeometricTypeKind::GeometricBox)?)),
|
||||
Keyword::PATH => Ok(Some(self.parse_geometric_type(GeometricTypeKind::GeometricPath)?)),
|
||||
Keyword::LINE => Ok(Some(self.parse_geometric_type(GeometricTypeKind::Line)?)),
|
||||
Keyword::LSEG => Ok(Some(self.parse_geometric_type(GeometricTypeKind::LineSegment)?)),
|
||||
Keyword::POINT => Ok(Some(self.parse_geometric_type(GeometricTypeKind::Point)?)),
|
||||
Keyword::POLYGON => Ok(Some(self.parse_geometric_type(GeometricTypeKind::Polygon)?)),
|
||||
_ => Ok(None),
|
||||
},
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1400,6 +1410,33 @@ impl<'a> Parser<'a> {
|
|||
),
|
||||
})
|
||||
}
|
||||
tok @ Token::Sharp
|
||||
| tok @ Token::AtDashAt
|
||||
| tok @ Token::AtAt
|
||||
| tok @ Token::QuestionMarkDash
|
||||
| tok @ Token::QuestionPipe
|
||||
if self.dialect.supports_geometric_types() =>
|
||||
{
|
||||
let op = match tok {
|
||||
Token::Sharp => UnaryOperator::Hash,
|
||||
Token::AtDashAt => UnaryOperator::AtDashAt,
|
||||
Token::AtAt => UnaryOperator::DoubleAt,
|
||||
Token::QuestionMarkDash => UnaryOperator::QuestionDash,
|
||||
Token::QuestionPipe => UnaryOperator::QuestionPipe,
|
||||
_ => {
|
||||
return Err(ParserError::ParserError(format!(
|
||||
"Unexpected token in unary operator parsing: {:?}",
|
||||
tok
|
||||
)))
|
||||
}
|
||||
};
|
||||
Ok(Expr::UnaryOp {
|
||||
op,
|
||||
expr: Box::new(
|
||||
self.parse_subexpr(self.dialect.prec_value(Precedence::PlusMinus))?,
|
||||
),
|
||||
})
|
||||
}
|
||||
Token::EscapedStringLiteral(_) if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) =>
|
||||
{
|
||||
self.prev_token();
|
||||
|
@ -1465,6 +1502,14 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_geometric_type(&mut self, kind: GeometricTypeKind) -> Result<Expr, ParserError> {
|
||||
let value: Value = self.parse_value()?;
|
||||
Ok(Expr::TypedString {
|
||||
data_type: DataType::GeometricType(kind),
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to parse an [Expr::CompoundFieldAccess] like `a.b.c` or `a.b[1].c`.
|
||||
/// If all the fields are `Expr::Identifier`s, return an [Expr::CompoundIdentifier] instead.
|
||||
/// If only the root exists, return the root.
|
||||
|
@ -3070,15 +3115,18 @@ impl<'a> Parser<'a> {
|
|||
Token::DuckIntDiv if dialect_is!(dialect is DuckDbDialect | GenericDialect) => {
|
||||
Some(BinaryOperator::DuckIntegerDivide)
|
||||
}
|
||||
Token::ShiftLeft if dialect_is!(dialect is PostgreSqlDialect | DuckDbDialect | GenericDialect) => {
|
||||
Token::ShiftLeft if dialect_is!(dialect is PostgreSqlDialect | DuckDbDialect | GenericDialect | RedshiftSqlDialect) => {
|
||||
Some(BinaryOperator::PGBitwiseShiftLeft)
|
||||
}
|
||||
Token::ShiftRight if dialect_is!(dialect is PostgreSqlDialect | DuckDbDialect | GenericDialect) => {
|
||||
Token::ShiftRight if dialect_is!(dialect is PostgreSqlDialect | DuckDbDialect | GenericDialect | RedshiftSqlDialect) => {
|
||||
Some(BinaryOperator::PGBitwiseShiftRight)
|
||||
}
|
||||
Token::Sharp if dialect_is!(dialect is PostgreSqlDialect) => {
|
||||
Token::Sharp if dialect_is!(dialect is PostgreSqlDialect | RedshiftSqlDialect) => {
|
||||
Some(BinaryOperator::PGBitwiseXor)
|
||||
}
|
||||
Token::Overlap if dialect_is!(dialect is PostgreSqlDialect | RedshiftSqlDialect) => {
|
||||
Some(BinaryOperator::PGOverlap)
|
||||
}
|
||||
Token::Overlap if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) => {
|
||||
Some(BinaryOperator::PGOverlap)
|
||||
}
|
||||
|
@ -3106,6 +3154,57 @@ impl<'a> Parser<'a> {
|
|||
Token::QuestionAnd => Some(BinaryOperator::QuestionAnd),
|
||||
Token::QuestionPipe => Some(BinaryOperator::QuestionPipe),
|
||||
Token::CustomBinaryOperator(s) => Some(BinaryOperator::Custom(s.clone())),
|
||||
Token::DoubleSharp if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::DoubleHash)
|
||||
}
|
||||
|
||||
Token::AmpersandLeftAngleBracket if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::AndLt)
|
||||
}
|
||||
Token::AmpersandRightAngleBracket if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::AndGt)
|
||||
}
|
||||
Token::QuestionMarkDash if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::QuestionDash)
|
||||
}
|
||||
Token::AmpersandLeftAngleBracketVerticalBar
|
||||
if self.dialect.supports_geometric_types() =>
|
||||
{
|
||||
Some(BinaryOperator::AndLtPipe)
|
||||
}
|
||||
Token::VerticalBarAmpersandRightAngleBracket
|
||||
if self.dialect.supports_geometric_types() =>
|
||||
{
|
||||
Some(BinaryOperator::PipeAndGt)
|
||||
}
|
||||
Token::TwoWayArrow if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::LtDashGt)
|
||||
}
|
||||
Token::LeftAngleBracketCaret if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::LtCaret)
|
||||
}
|
||||
Token::RightAngleBracketCaret if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::GtCaret)
|
||||
}
|
||||
Token::QuestionMarkSharp if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::QuestionHash)
|
||||
}
|
||||
Token::QuestionMarkDoubleVerticalBar if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::QuestionDoublePipe)
|
||||
}
|
||||
Token::QuestionMarkDashVerticalBar if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::QuestionDashPipe)
|
||||
}
|
||||
Token::TildeEqual if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::TildeEq)
|
||||
}
|
||||
Token::ShiftLeftVerticalBar if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::LtLtPipe)
|
||||
}
|
||||
Token::VerticalBarShiftRight if self.dialect.supports_geometric_types() => {
|
||||
Some(BinaryOperator::PipeGtGt)
|
||||
}
|
||||
Token::AtSign if self.dialect.supports_geometric_types() => Some(BinaryOperator::At),
|
||||
|
||||
Token::Word(w) => match w.keyword {
|
||||
Keyword::AND => Some(BinaryOperator::And),
|
||||
|
|
184
src/tokenizer.rs
184
src/tokenizer.rs
|
@ -170,8 +170,10 @@ pub enum Token {
|
|||
RBrace,
|
||||
/// Right Arrow `=>`
|
||||
RArrow,
|
||||
/// Sharp `#` used for PostgreSQL Bitwise XOR operator
|
||||
/// Sharp `#` used for PostgreSQL Bitwise XOR operator, also PostgreSQL/Redshift geometrical unary/binary operator (Number of points in path or polygon/Intersection)
|
||||
Sharp,
|
||||
/// `##` PostgreSQL/Redshift geometrical binary operator (Point of closest proximity)
|
||||
DoubleSharp,
|
||||
/// Tilde `~` used for PostgreSQL Bitwise NOT operator or case sensitive match regular expression operator
|
||||
Tilde,
|
||||
/// `~*` , a case insensitive match regular expression operator in PostgreSQL
|
||||
|
@ -198,7 +200,7 @@ pub enum Token {
|
|||
ExclamationMark,
|
||||
/// Double Exclamation Mark `!!` used for PostgreSQL prefix factorial operator
|
||||
DoubleExclamationMark,
|
||||
/// AtSign `@` used for PostgreSQL abs operator
|
||||
/// AtSign `@` used for PostgreSQL abs operator, also PostgreSQL/Redshift geometrical unary/binary operator (Center, Contained or on)
|
||||
AtSign,
|
||||
/// `^@`, a "starts with" string operator in PostgreSQL
|
||||
CaretAt,
|
||||
|
@ -214,6 +216,36 @@ pub enum Token {
|
|||
LongArrow,
|
||||
/// `#>`, extracts JSON sub-object at the specified path
|
||||
HashArrow,
|
||||
/// `@-@` PostgreSQL/Redshift geometrical unary operator (Length or circumference)
|
||||
AtDashAt,
|
||||
/// `?-` PostgreSQL/Redshift geometrical unary/binary operator (Is horizontal?/Are horizontally aligned?)
|
||||
QuestionMarkDash,
|
||||
/// `&<` PostgreSQL/Redshift geometrical binary operator (Overlaps to left?)
|
||||
AmpersandLeftAngleBracket,
|
||||
/// `&>` PostgreSQL/Redshift geometrical binary operator (Overlaps to right?)`
|
||||
AmpersandRightAngleBracket,
|
||||
/// `&<|` PostgreSQL/Redshift geometrical binary operator (Does not extend above?)`
|
||||
AmpersandLeftAngleBracketVerticalBar,
|
||||
/// `|&>` PostgreSQL/Redshift geometrical binary operator (Does not extend below?)`
|
||||
VerticalBarAmpersandRightAngleBracket,
|
||||
/// `<->` PostgreSQL/Redshift geometrical binary operator (Distance between)
|
||||
TwoWayArrow,
|
||||
/// `<^` PostgreSQL/Redshift geometrical binary operator (Is below?)
|
||||
LeftAngleBracketCaret,
|
||||
/// `>^` PostgreSQL/Redshift geometrical binary operator (Is above?)
|
||||
RightAngleBracketCaret,
|
||||
/// `?#` PostgreSQL/Redshift geometrical binary operator (Intersects or overlaps)
|
||||
QuestionMarkSharp,
|
||||
/// `?-|` PostgreSQL/Redshift geometrical binary operator (Is perpendicular?)
|
||||
QuestionMarkDashVerticalBar,
|
||||
/// `?||` PostgreSQL/Redshift geometrical binary operator (Are parallel?)
|
||||
QuestionMarkDoubleVerticalBar,
|
||||
/// `~=` PostgreSQL/Redshift geometrical binary operator (Same as)
|
||||
TildeEqual,
|
||||
/// `<<| PostgreSQL/Redshift geometrical binary operator (Is strictly below?)
|
||||
ShiftLeftVerticalBar,
|
||||
/// `|>> PostgreSQL/Redshift geometrical binary operator (Is strictly above?)
|
||||
VerticalBarShiftRight,
|
||||
/// `#>>`, extracts JSON sub-object at the specified path as text
|
||||
HashLongArrow,
|
||||
/// jsonb @> jsonb -> boolean: Test whether left json contains the right json
|
||||
|
@ -303,6 +335,7 @@ impl fmt::Display for Token {
|
|||
Token::RBrace => f.write_str("}"),
|
||||
Token::RArrow => f.write_str("=>"),
|
||||
Token::Sharp => f.write_str("#"),
|
||||
Token::DoubleSharp => f.write_str("##"),
|
||||
Token::ExclamationMark => f.write_str("!"),
|
||||
Token::DoubleExclamationMark => f.write_str("!!"),
|
||||
Token::Tilde => f.write_str("~"),
|
||||
|
@ -320,6 +353,21 @@ impl fmt::Display for Token {
|
|||
Token::Overlap => f.write_str("&&"),
|
||||
Token::PGSquareRoot => f.write_str("|/"),
|
||||
Token::PGCubeRoot => f.write_str("||/"),
|
||||
Token::AtDashAt => f.write_str("@-@"),
|
||||
Token::QuestionMarkDash => f.write_str("?-"),
|
||||
Token::AmpersandLeftAngleBracket => f.write_str("&<"),
|
||||
Token::AmpersandRightAngleBracket => f.write_str("&>"),
|
||||
Token::AmpersandLeftAngleBracketVerticalBar => f.write_str("&<|"),
|
||||
Token::VerticalBarAmpersandRightAngleBracket => f.write_str("|&>"),
|
||||
Token::TwoWayArrow => f.write_str("<->"),
|
||||
Token::LeftAngleBracketCaret => f.write_str("<^"),
|
||||
Token::RightAngleBracketCaret => f.write_str(">^"),
|
||||
Token::QuestionMarkSharp => f.write_str("?#"),
|
||||
Token::QuestionMarkDashVerticalBar => f.write_str("?-|"),
|
||||
Token::QuestionMarkDoubleVerticalBar => f.write_str("?||"),
|
||||
Token::TildeEqual => f.write_str("~="),
|
||||
Token::ShiftLeftVerticalBar => f.write_str("<<|"),
|
||||
Token::VerticalBarShiftRight => f.write_str("|>>"),
|
||||
Token::Placeholder(ref s) => write!(f, "{s}"),
|
||||
Token::Arrow => write!(f, "->"),
|
||||
Token::LongArrow => write!(f, "->>"),
|
||||
|
@ -1308,6 +1356,28 @@ impl<'a> Tokenizer<'a> {
|
|||
_ => self.start_binop(chars, "||", Token::StringConcat),
|
||||
}
|
||||
}
|
||||
Some('&') if self.dialect.supports_geometric_types() => {
|
||||
chars.next(); // consume
|
||||
match chars.peek() {
|
||||
Some('>') => self.consume_for_binop(
|
||||
chars,
|
||||
"|&>",
|
||||
Token::VerticalBarAmpersandRightAngleBracket,
|
||||
),
|
||||
_ => self.start_binop_opt(chars, "|&", None),
|
||||
}
|
||||
}
|
||||
Some('>') if self.dialect.supports_geometric_types() => {
|
||||
chars.next(); // consume
|
||||
match chars.peek() {
|
||||
Some('>') => self.consume_for_binop(
|
||||
chars,
|
||||
"|>>",
|
||||
Token::VerticalBarShiftRight,
|
||||
),
|
||||
_ => self.start_binop_opt(chars, "|>", None),
|
||||
}
|
||||
}
|
||||
// Bitshift '|' operator
|
||||
_ => self.start_binop(chars, "|", Token::Pipe),
|
||||
}
|
||||
|
@ -1356,8 +1426,34 @@ impl<'a> Tokenizer<'a> {
|
|||
_ => self.start_binop(chars, "<=", Token::LtEq),
|
||||
}
|
||||
}
|
||||
Some('|') if self.dialect.supports_geometric_types() => {
|
||||
self.consume_for_binop(chars, "<<|", Token::ShiftLeftVerticalBar)
|
||||
}
|
||||
Some('>') => self.consume_for_binop(chars, "<>", Token::Neq),
|
||||
Some('<') if self.dialect.supports_geometric_types() => {
|
||||
chars.next(); // consume
|
||||
match chars.peek() {
|
||||
Some('|') => self.consume_for_binop(
|
||||
chars,
|
||||
"<<|",
|
||||
Token::ShiftLeftVerticalBar,
|
||||
),
|
||||
_ => self.start_binop(chars, "<<", Token::ShiftLeft),
|
||||
}
|
||||
}
|
||||
Some('<') => self.consume_for_binop(chars, "<<", Token::ShiftLeft),
|
||||
Some('-') if self.dialect.supports_geometric_types() => {
|
||||
chars.next(); // consume
|
||||
match chars.peek() {
|
||||
Some('>') => {
|
||||
self.consume_for_binop(chars, "<->", Token::TwoWayArrow)
|
||||
}
|
||||
_ => self.start_binop_opt(chars, "<-", None),
|
||||
}
|
||||
}
|
||||
Some('^') if self.dialect.supports_geometric_types() => {
|
||||
self.consume_for_binop(chars, "<^", Token::LeftAngleBracketCaret)
|
||||
}
|
||||
Some('@') => self.consume_for_binop(chars, "<@", Token::ArrowAt),
|
||||
_ => self.start_binop(chars, "<", Token::Lt),
|
||||
}
|
||||
|
@ -1367,6 +1463,9 @@ impl<'a> Tokenizer<'a> {
|
|||
match chars.peek() {
|
||||
Some('=') => self.consume_for_binop(chars, ">=", Token::GtEq),
|
||||
Some('>') => self.consume_for_binop(chars, ">>", Token::ShiftRight),
|
||||
Some('^') if self.dialect.supports_geometric_types() => {
|
||||
self.consume_for_binop(chars, ">^", Token::RightAngleBracketCaret)
|
||||
}
|
||||
_ => self.start_binop(chars, ">", Token::Gt),
|
||||
}
|
||||
}
|
||||
|
@ -1385,6 +1484,22 @@ impl<'a> Tokenizer<'a> {
|
|||
'&' => {
|
||||
chars.next(); // consume the '&'
|
||||
match chars.peek() {
|
||||
Some('>') if self.dialect.supports_geometric_types() => {
|
||||
chars.next();
|
||||
self.consume_and_return(chars, Token::AmpersandRightAngleBracket)
|
||||
}
|
||||
Some('<') if self.dialect.supports_geometric_types() => {
|
||||
chars.next(); // consume
|
||||
match chars.peek() {
|
||||
Some('|') => self.consume_and_return(
|
||||
chars,
|
||||
Token::AmpersandLeftAngleBracketVerticalBar,
|
||||
),
|
||||
_ => {
|
||||
self.start_binop(chars, "&<", Token::AmpersandLeftAngleBracket)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some('&') => {
|
||||
chars.next(); // consume the second '&'
|
||||
self.start_binop(chars, "&&", Token::Overlap)
|
||||
|
@ -1415,6 +1530,9 @@ impl<'a> Tokenizer<'a> {
|
|||
chars.next(); // consume
|
||||
match chars.peek() {
|
||||
Some('*') => self.consume_for_binop(chars, "~*", Token::TildeAsterisk),
|
||||
Some('=') if self.dialect.supports_geometric_types() => {
|
||||
self.consume_for_binop(chars, "~=", Token::TildeEqual)
|
||||
}
|
||||
Some('~') => {
|
||||
chars.next();
|
||||
match chars.peek() {
|
||||
|
@ -1441,6 +1559,9 @@ impl<'a> Tokenizer<'a> {
|
|||
}
|
||||
}
|
||||
Some(' ') => Ok(Some(Token::Sharp)),
|
||||
Some('#') if self.dialect.supports_geometric_types() => {
|
||||
self.consume_for_binop(chars, "##", Token::DoubleSharp)
|
||||
}
|
||||
Some(sch) if self.dialect.is_identifier_start('#') => {
|
||||
self.tokenize_identifier_or_keyword([ch, *sch], chars)
|
||||
}
|
||||
|
@ -1450,6 +1571,16 @@ impl<'a> Tokenizer<'a> {
|
|||
'@' => {
|
||||
chars.next();
|
||||
match chars.peek() {
|
||||
Some('@') if self.dialect.supports_geometric_types() => {
|
||||
self.consume_and_return(chars, Token::AtAt)
|
||||
}
|
||||
Some('-') if self.dialect.supports_geometric_types() => {
|
||||
chars.next();
|
||||
match chars.peek() {
|
||||
Some('@') => self.consume_and_return(chars, Token::AtDashAt),
|
||||
_ => self.start_binop_opt(chars, "@-", None),
|
||||
}
|
||||
}
|
||||
Some('>') => self.consume_and_return(chars, Token::AtArrow),
|
||||
Some('?') => self.consume_and_return(chars, Token::AtQuestion),
|
||||
Some('@') => {
|
||||
|
@ -1482,11 +1613,30 @@ impl<'a> Tokenizer<'a> {
|
|||
}
|
||||
}
|
||||
// Postgres uses ? for jsonb operators, not prepared statements
|
||||
'?' if dialect_of!(self is PostgreSqlDialect) => {
|
||||
chars.next();
|
||||
'?' if self.dialect.supports_geometric_types() => {
|
||||
chars.next(); // consume
|
||||
match chars.peek() {
|
||||
Some('|') => self.consume_and_return(chars, Token::QuestionPipe),
|
||||
Some('|') => {
|
||||
chars.next();
|
||||
match chars.peek() {
|
||||
Some('|') => self.consume_and_return(
|
||||
chars,
|
||||
Token::QuestionMarkDoubleVerticalBar,
|
||||
),
|
||||
_ => Ok(Some(Token::QuestionPipe)),
|
||||
}
|
||||
}
|
||||
|
||||
Some('&') => self.consume_and_return(chars, Token::QuestionAnd),
|
||||
Some('-') => {
|
||||
chars.next(); // consume
|
||||
match chars.peek() {
|
||||
Some('|') => self
|
||||
.consume_and_return(chars, Token::QuestionMarkDashVerticalBar),
|
||||
_ => Ok(Some(Token::QuestionMarkDash)),
|
||||
}
|
||||
}
|
||||
Some('#') => self.consume_and_return(chars, Token::QuestionMarkSharp),
|
||||
_ => self.consume_and_return(chars, Token::Question),
|
||||
}
|
||||
}
|
||||
|
@ -1520,7 +1670,7 @@ impl<'a> Tokenizer<'a> {
|
|||
default: Token,
|
||||
) -> Result<Option<Token>, TokenizerError> {
|
||||
chars.next(); // consume the first char
|
||||
self.start_binop(chars, prefix, default)
|
||||
self.start_binop_opt(chars, prefix, Some(default))
|
||||
}
|
||||
|
||||
/// parse a custom binary operator
|
||||
|
@ -1529,6 +1679,16 @@ impl<'a> Tokenizer<'a> {
|
|||
chars: &mut State,
|
||||
prefix: &str,
|
||||
default: Token,
|
||||
) -> Result<Option<Token>, TokenizerError> {
|
||||
self.start_binop_opt(chars, prefix, Some(default))
|
||||
}
|
||||
|
||||
/// parse a custom binary operator
|
||||
fn start_binop_opt(
|
||||
&self,
|
||||
chars: &mut State,
|
||||
prefix: &str,
|
||||
default: Option<Token>,
|
||||
) -> Result<Option<Token>, TokenizerError> {
|
||||
let mut custom = None;
|
||||
while let Some(&ch) = chars.peek() {
|
||||
|
@ -1539,10 +1699,14 @@ impl<'a> Tokenizer<'a> {
|
|||
custom.get_or_insert_with(|| prefix.to_string()).push(ch);
|
||||
chars.next();
|
||||
}
|
||||
|
||||
Ok(Some(
|
||||
custom.map(Token::CustomBinaryOperator).unwrap_or(default),
|
||||
))
|
||||
match (custom, default) {
|
||||
(Some(custom), _) => Ok(Token::CustomBinaryOperator(custom).into()),
|
||||
(None, Some(tok)) => Ok(Some(tok)),
|
||||
(None, None) => self.tokenizer_error(
|
||||
chars.location(),
|
||||
format!("Expected a valid binary operator after '{}'", prefix),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokenize dollar preceded value (i.e: a string/placeholder)
|
||||
|
|
|
@ -13860,3 +13860,361 @@ fn test_select_from_first() {
|
|||
assert_eq!(ast.to_string(), q);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_geometric_unary_operators() {
|
||||
// Number of points in path or polygon
|
||||
let sql = "# path '((1,0),(0,1),(-1,0))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::UnaryOp {
|
||||
op: UnaryOperator::Hash,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Length or circumference
|
||||
let sql = "@-@ path '((0,0),(1,0))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::UnaryOp {
|
||||
op: UnaryOperator::AtDashAt,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Center
|
||||
let sql = "@@ circle '((0,0),10)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::UnaryOp {
|
||||
op: UnaryOperator::DoubleAt,
|
||||
..
|
||||
}
|
||||
));
|
||||
// Is horizontal?
|
||||
let sql = "?- lseg '((-1,0),(1,0))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::UnaryOp {
|
||||
op: UnaryOperator::QuestionDash,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is vertical?
|
||||
let sql = "?| lseg '((-1,0),(1,0))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::UnaryOp {
|
||||
op: UnaryOperator::QuestionPipe,
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_geomtery_type() {
|
||||
let sql = "point '1,2'";
|
||||
assert_eq!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::Point),
|
||||
value: Value::SingleQuotedString("1,2".to_string()),
|
||||
}
|
||||
);
|
||||
|
||||
let sql = "line '1,2,3,4'";
|
||||
assert_eq!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::Line),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
}
|
||||
);
|
||||
|
||||
let sql = "path '1,2,3,4'";
|
||||
assert_eq!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::GeometricPath),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
}
|
||||
);
|
||||
let sql = "box '1,2,3,4'";
|
||||
assert_eq!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::GeometricBox),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
}
|
||||
);
|
||||
|
||||
let sql = "circle '1,2,3'";
|
||||
assert_eq!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::Circle),
|
||||
value: Value::SingleQuotedString("1,2,3".to_string()),
|
||||
}
|
||||
);
|
||||
|
||||
let sql = "polygon '1,2,3,4'";
|
||||
assert_eq!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::Polygon),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
}
|
||||
);
|
||||
let sql = "lseg '1,2,3,4'";
|
||||
assert_eq!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::TypedString {
|
||||
data_type: DataType::GeometricType(GeometricTypeKind::LineSegment),
|
||||
value: Value::SingleQuotedString("1,2,3,4".to_string()),
|
||||
}
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_geometric_binary_operators() {
|
||||
// Translation plus
|
||||
let sql = "box '((0,0),(1,1))' + point '(2.0,0)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::Plus,
|
||||
..
|
||||
}
|
||||
));
|
||||
// Translation minus
|
||||
let sql = "box '((0,0),(1,1))' - point '(2.0,0)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::Minus,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Scaling multiply
|
||||
let sql = "box '((0,0),(1,1))' * point '(2.0,0)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::Multiply,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Scaling divide
|
||||
let sql = "box '((0,0),(1,1))' / point '(2.0,0)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::Divide,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Intersection
|
||||
let sql = "'((1,-1),(-1,1))' # '((1,1),(-1,-1))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::PGBitwiseXor,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
//Point of closest proximity
|
||||
let sql = "point '(0,0)' ## lseg '((2,0),(0,2))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::DoubleHash,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Point of closest proximity
|
||||
let sql = "box '((0,0),(1,1))' && box '((0,0),(2,2))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::PGOverlap,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Overlaps to left?
|
||||
let sql = "box '((0,0),(1,1))' &< box '((0,0),(2,2))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::AndLt,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Overlaps to right?
|
||||
let sql = "box '((0,0),(3,3))' &> box '((0,0),(2,2))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::AndGt,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Distance between
|
||||
let sql = "circle '((0,0),1)' <-> circle '((5,0),1)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::LtDashGt,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is left of?
|
||||
let sql = "circle '((0,0),1)' << circle '((5,0),1)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::PGBitwiseShiftLeft,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is right of?
|
||||
let sql = "circle '((5,0),1)' >> circle '((0,0),1)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::PGBitwiseShiftRight,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is below?
|
||||
let sql = "circle '((0,0),1)' <^ circle '((0,5),1)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::LtCaret,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Intersects or overlaps
|
||||
let sql = "lseg '((-1,0),(1,0))' ?# box '((-2,-2),(2,2))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::QuestionHash,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is horizontal?
|
||||
let sql = "point '(1,0)' ?- point '(0,0)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::QuestionDash,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is perpendicular?
|
||||
let sql = "lseg '((0,0),(0,1))' ?-| lseg '((0,0),(1,0))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::QuestionDashPipe,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is vertical?
|
||||
let sql = "point '(0,1)' ?| point '(0,0)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::QuestionPipe,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Are parallel?
|
||||
let sql = "lseg '((-1,0),(1,0))' ?|| lseg '((-1,2),(1,2))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::QuestionDoublePipe,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Contained or on?
|
||||
let sql = "point '(1,1)' @ circle '((0,0),2)'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::At,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
//
|
||||
// Same as?
|
||||
let sql = "polygon '((0,0),(1,1))' ~= polygon '((1,1),(0,0))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::TildeEq,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is strictly below?
|
||||
let sql = "box '((0,0),(3,3))' <<| box '((3,4),(5,5))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::LtLtPipe,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Is strictly above?
|
||||
let sql = "box '((3,4),(5,5))' |>> box '((0,0),(3,3))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::PipeGtGt,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Does not extend above?
|
||||
let sql = "box '((0,0),(1,1))' &<| box '((0,0),(2,2))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::AndLtPipe,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
// Does not extend below?
|
||||
let sql = "box '((0,0),(3,3))' |&> box '((0,0),(2,2))'";
|
||||
assert!(matches!(
|
||||
all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::PipeAndGt,
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
|
|
|
@ -2066,12 +2066,8 @@ fn parse_pg_custom_binary_ops() {
|
|||
let operators = [
|
||||
// PostGIS
|
||||
"&&&", // n-D bounding boxes intersect
|
||||
"&<", // (is strictly to the left of)
|
||||
"&>", // (is strictly to the right of)
|
||||
"|=|", // distance between A and B trajectories at their closest point of approach
|
||||
"<<#>>", // n-D distance between A and B bounding boxes
|
||||
"|>>", // A's bounding box is strictly above B's.
|
||||
"~=", // bounding box is the same
|
||||
// PGroonga
|
||||
"&@", // Full text search by a keyword
|
||||
"&@~", // Full text search by easy to use query language
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue