mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-22 23:14:07 +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
|
/// [bigquery]: https://cloud.google.com/bigquery/docs/user-defined-functions#templated-sql-udf-parameters
|
||||||
AnyType,
|
AnyType,
|
||||||
|
/// geometric type
|
||||||
|
///
|
||||||
|
/// [Postgres]: https://www.postgresql.org/docs/9.5/functions-geometry.html
|
||||||
|
GeometricType(GeometricTypeKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DataType {
|
impl fmt::Display for DataType {
|
||||||
|
@ -639,6 +643,7 @@ impl fmt::Display for DataType {
|
||||||
DataType::Trigger => write!(f, "TRIGGER"),
|
DataType::Trigger => write!(f, "TRIGGER"),
|
||||||
DataType::AnyType => write!(f, "ANY TYPE"),
|
DataType::AnyType => write!(f, "ANY TYPE"),
|
||||||
DataType::Table(fields) => write!(f, "TABLE({})", display_comma_separated(fields)),
|
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)`
|
/// `Array(Int64)`
|
||||||
Parenthesis(Box<DataType>),
|
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")]
|
#[cfg(feature = "visitor")]
|
||||||
pub use visitor::*;
|
pub use visitor::*;
|
||||||
|
|
||||||
|
pub use self::data_type::GeometricTypeKind;
|
||||||
|
|
||||||
mod data_type;
|
mod data_type;
|
||||||
mod dcl;
|
mod dcl;
|
||||||
mod ddl;
|
mod ddl;
|
||||||
|
@ -1513,7 +1515,15 @@ impl fmt::Display for Expr {
|
||||||
Expr::UnaryOp { op, expr } => {
|
Expr::UnaryOp { op, expr } => {
|
||||||
if op == &UnaryOperator::PGPostfixFactorial {
|
if op == &UnaryOperator::PGPostfixFactorial {
|
||||||
write!(f, "{expr}{op}")
|
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}")
|
write!(f, "{op} {expr}")
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{op}{expr}")
|
write!(f, "{op}{expr}")
|
||||||
|
|
|
@ -53,6 +53,21 @@ pub enum UnaryOperator {
|
||||||
PGAbs,
|
PGAbs,
|
||||||
/// Unary logical not operator: e.g. `! false` (Hive-specific)
|
/// Unary logical not operator: e.g. `! false` (Hive-specific)
|
||||||
BangNot,
|
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 {
|
impl fmt::Display for UnaryOperator {
|
||||||
|
@ -68,6 +83,11 @@ impl fmt::Display for UnaryOperator {
|
||||||
UnaryOperator::PGPrefixFactorial => "!!",
|
UnaryOperator::PGPrefixFactorial => "!!",
|
||||||
UnaryOperator::PGAbs => "@",
|
UnaryOperator::PGAbs => "@",
|
||||||
UnaryOperator::BangNot => "!",
|
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:
|
/// Specifies a test for an overlap between two datetime periods:
|
||||||
/// <https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#overlaps-predicate>
|
/// <https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#overlaps-predicate>
|
||||||
Overlaps,
|
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 {
|
impl fmt::Display for BinaryOperator {
|
||||||
|
@ -310,6 +378,22 @@ impl fmt::Display for BinaryOperator {
|
||||||
write!(f, "OPERATOR({})", display_separated(idents, "."))
|
write!(f, "OPERATOR({})", display_separated(idents, "."))
|
||||||
}
|
}
|
||||||
BinaryOperator::Overlaps => f.write_str("OVERLAPS"),
|
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::ExclamationMarkDoubleTilde
|
||||||
| Token::ExclamationMarkDoubleTildeAsterisk
|
| Token::ExclamationMarkDoubleTildeAsterisk
|
||||||
| Token::Spaceship => Ok(p!(Eq)),
|
| 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::Caret | Token::Sharp | Token::ShiftRight | Token::ShiftLeft => Ok(p!(Caret)),
|
||||||
Token::Ampersand => Ok(p!(Ampersand)),
|
Token::Ampersand => Ok(p!(Ampersand)),
|
||||||
Token::Plus | Token::Minus => Ok(p!(PlusMinus)),
|
Token::Plus | Token::Minus => Ok(p!(PlusMinus)),
|
||||||
Token::Mul | Token::Div | Token::DuckIntDiv | Token::Mod | Token::StringConcat => {
|
Token::Mul | Token::Div | Token::DuckIntDiv | Token::Mod | Token::StringConcat => {
|
||||||
Ok(p!(MulDivModOp))
|
Ok(p!(MulDivModOp))
|
||||||
}
|
}
|
||||||
Token::DoubleColon
|
Token::DoubleColon | Token::ExclamationMark | Token::LBracket | Token::CaretAt => {
|
||||||
| Token::ExclamationMark
|
Ok(p!(DoubleColon))
|
||||||
| Token::LBracket
|
}
|
||||||
| Token::Overlap
|
|
||||||
| Token::CaretAt => Ok(p!(DoubleColon)),
|
|
||||||
Token::Arrow
|
Token::Arrow
|
||||||
| Token::LongArrow
|
| Token::LongArrow
|
||||||
| Token::HashArrow
|
| Token::HashArrow
|
||||||
|
@ -622,7 +638,6 @@ pub trait Dialect: Debug + Any {
|
||||||
| Token::AtAt
|
| Token::AtAt
|
||||||
| Token::Question
|
| Token::Question
|
||||||
| Token::QuestionAnd
|
| Token::QuestionAnd
|
||||||
| Token::QuestionPipe
|
|
||||||
| Token::CustomBinaryOperator(_) => Ok(p!(PgOther)),
|
| Token::CustomBinaryOperator(_) => Ok(p!(PgOther)),
|
||||||
_ => Ok(self.prec_unknown()),
|
_ => Ok(self.prec_unknown()),
|
||||||
}
|
}
|
||||||
|
@ -921,6 +936,13 @@ pub trait Dialect: Debug + Any {
|
||||||
fn supports_array_typedef_size(&self) -> bool {
|
fn supports_array_typedef_size(&self) -> bool {
|
||||||
false
|
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
|
/// 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 {
|
fn supports_array_typedef_size(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_geometric_types(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,4 +113,8 @@ impl Dialect for RedshiftSqlDialect {
|
||||||
fn supports_string_escape_constant(&self) -> bool {
|
fn supports_string_escape_constant(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_geometric_types(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,7 @@ define_keywords!(
|
||||||
BOOL,
|
BOOL,
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
BOTH,
|
BOTH,
|
||||||
|
BOX,
|
||||||
BROWSE,
|
BROWSE,
|
||||||
BTREE,
|
BTREE,
|
||||||
BUCKET,
|
BUCKET,
|
||||||
|
@ -175,6 +176,7 @@ define_keywords!(
|
||||||
CHARSET,
|
CHARSET,
|
||||||
CHAR_LENGTH,
|
CHAR_LENGTH,
|
||||||
CHECK,
|
CHECK,
|
||||||
|
CIRCLE,
|
||||||
CLEAR,
|
CLEAR,
|
||||||
CLOB,
|
CLOB,
|
||||||
CLONE,
|
CLONE,
|
||||||
|
@ -478,6 +480,7 @@ define_keywords!(
|
||||||
LIKE,
|
LIKE,
|
||||||
LIKE_REGEX,
|
LIKE_REGEX,
|
||||||
LIMIT,
|
LIMIT,
|
||||||
|
LINE,
|
||||||
LINES,
|
LINES,
|
||||||
LIST,
|
LIST,
|
||||||
LISTEN,
|
LISTEN,
|
||||||
|
@ -499,6 +502,7 @@ define_keywords!(
|
||||||
LOWER,
|
LOWER,
|
||||||
LOW_PRIORITY,
|
LOW_PRIORITY,
|
||||||
LS,
|
LS,
|
||||||
|
LSEG,
|
||||||
MACRO,
|
MACRO,
|
||||||
MANAGE,
|
MANAGE,
|
||||||
MANAGED,
|
MANAGED,
|
||||||
|
@ -653,7 +657,9 @@ define_keywords!(
|
||||||
PLACING,
|
PLACING,
|
||||||
PLAN,
|
PLAN,
|
||||||
PLANS,
|
PLANS,
|
||||||
|
POINT,
|
||||||
POLICY,
|
POLICY,
|
||||||
|
POLYGON,
|
||||||
POOL,
|
POOL,
|
||||||
PORTION,
|
PORTION,
|
||||||
POSITION,
|
POSITION,
|
||||||
|
|
|
@ -1220,7 +1220,17 @@ impl<'a> Parser<'a> {
|
||||||
Keyword::MAP if *self.peek_token_ref() == Token::LBrace && self.dialect.support_map_literal_syntax() => {
|
Keyword::MAP if *self.peek_token_ref() == Token::LBrace && self.dialect.support_map_literal_syntax() => {
|
||||||
Ok(Some(self.parse_duckdb_map_literal()?))
|
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) =>
|
Token::EscapedStringLiteral(_) if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) =>
|
||||||
{
|
{
|
||||||
self.prev_token();
|
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`.
|
/// 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 all the fields are `Expr::Identifier`s, return an [Expr::CompoundIdentifier] instead.
|
||||||
/// If only the root exists, return the root.
|
/// 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) => {
|
Token::DuckIntDiv if dialect_is!(dialect is DuckDbDialect | GenericDialect) => {
|
||||||
Some(BinaryOperator::DuckIntegerDivide)
|
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)
|
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)
|
Some(BinaryOperator::PGBitwiseShiftRight)
|
||||||
}
|
}
|
||||||
Token::Sharp if dialect_is!(dialect is PostgreSqlDialect) => {
|
Token::Sharp if dialect_is!(dialect is PostgreSqlDialect | RedshiftSqlDialect) => {
|
||||||
Some(BinaryOperator::PGBitwiseXor)
|
Some(BinaryOperator::PGBitwiseXor)
|
||||||
}
|
}
|
||||||
|
Token::Overlap if dialect_is!(dialect is PostgreSqlDialect | RedshiftSqlDialect) => {
|
||||||
|
Some(BinaryOperator::PGOverlap)
|
||||||
|
}
|
||||||
Token::Overlap if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) => {
|
Token::Overlap if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) => {
|
||||||
Some(BinaryOperator::PGOverlap)
|
Some(BinaryOperator::PGOverlap)
|
||||||
}
|
}
|
||||||
|
@ -3106,6 +3154,57 @@ impl<'a> Parser<'a> {
|
||||||
Token::QuestionAnd => Some(BinaryOperator::QuestionAnd),
|
Token::QuestionAnd => Some(BinaryOperator::QuestionAnd),
|
||||||
Token::QuestionPipe => Some(BinaryOperator::QuestionPipe),
|
Token::QuestionPipe => Some(BinaryOperator::QuestionPipe),
|
||||||
Token::CustomBinaryOperator(s) => Some(BinaryOperator::Custom(s.clone())),
|
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 {
|
Token::Word(w) => match w.keyword {
|
||||||
Keyword::AND => Some(BinaryOperator::And),
|
Keyword::AND => Some(BinaryOperator::And),
|
||||||
|
|
184
src/tokenizer.rs
184
src/tokenizer.rs
|
@ -170,8 +170,10 @@ pub enum Token {
|
||||||
RBrace,
|
RBrace,
|
||||||
/// Right Arrow `=>`
|
/// Right Arrow `=>`
|
||||||
RArrow,
|
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,
|
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 `~` used for PostgreSQL Bitwise NOT operator or case sensitive match regular expression operator
|
||||||
Tilde,
|
Tilde,
|
||||||
/// `~*` , a case insensitive match regular expression operator in PostgreSQL
|
/// `~*` , a case insensitive match regular expression operator in PostgreSQL
|
||||||
|
@ -198,7 +200,7 @@ pub enum Token {
|
||||||
ExclamationMark,
|
ExclamationMark,
|
||||||
/// Double Exclamation Mark `!!` used for PostgreSQL prefix factorial operator
|
/// Double Exclamation Mark `!!` used for PostgreSQL prefix factorial operator
|
||||||
DoubleExclamationMark,
|
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,
|
AtSign,
|
||||||
/// `^@`, a "starts with" string operator in PostgreSQL
|
/// `^@`, a "starts with" string operator in PostgreSQL
|
||||||
CaretAt,
|
CaretAt,
|
||||||
|
@ -214,6 +216,36 @@ pub enum Token {
|
||||||
LongArrow,
|
LongArrow,
|
||||||
/// `#>`, extracts JSON sub-object at the specified path
|
/// `#>`, extracts JSON sub-object at the specified path
|
||||||
HashArrow,
|
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
|
/// `#>>`, extracts JSON sub-object at the specified path as text
|
||||||
HashLongArrow,
|
HashLongArrow,
|
||||||
/// jsonb @> jsonb -> boolean: Test whether left json contains the right json
|
/// 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::RBrace => f.write_str("}"),
|
||||||
Token::RArrow => f.write_str("=>"),
|
Token::RArrow => f.write_str("=>"),
|
||||||
Token::Sharp => f.write_str("#"),
|
Token::Sharp => f.write_str("#"),
|
||||||
|
Token::DoubleSharp => f.write_str("##"),
|
||||||
Token::ExclamationMark => f.write_str("!"),
|
Token::ExclamationMark => f.write_str("!"),
|
||||||
Token::DoubleExclamationMark => f.write_str("!!"),
|
Token::DoubleExclamationMark => f.write_str("!!"),
|
||||||
Token::Tilde => f.write_str("~"),
|
Token::Tilde => f.write_str("~"),
|
||||||
|
@ -320,6 +353,21 @@ impl fmt::Display for Token {
|
||||||
Token::Overlap => f.write_str("&&"),
|
Token::Overlap => f.write_str("&&"),
|
||||||
Token::PGSquareRoot => f.write_str("|/"),
|
Token::PGSquareRoot => f.write_str("|/"),
|
||||||
Token::PGCubeRoot => 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::Placeholder(ref s) => write!(f, "{s}"),
|
||||||
Token::Arrow => write!(f, "->"),
|
Token::Arrow => write!(f, "->"),
|
||||||
Token::LongArrow => write!(f, "->>"),
|
Token::LongArrow => write!(f, "->>"),
|
||||||
|
@ -1308,6 +1356,28 @@ impl<'a> Tokenizer<'a> {
|
||||||
_ => self.start_binop(chars, "||", Token::StringConcat),
|
_ => 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
|
// Bitshift '|' operator
|
||||||
_ => self.start_binop(chars, "|", Token::Pipe),
|
_ => self.start_binop(chars, "|", Token::Pipe),
|
||||||
}
|
}
|
||||||
|
@ -1356,8 +1426,34 @@ impl<'a> Tokenizer<'a> {
|
||||||
_ => self.start_binop(chars, "<=", Token::LtEq),
|
_ => 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('>') => 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('<') => 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),
|
Some('@') => self.consume_for_binop(chars, "<@", Token::ArrowAt),
|
||||||
_ => self.start_binop(chars, "<", Token::Lt),
|
_ => self.start_binop(chars, "<", Token::Lt),
|
||||||
}
|
}
|
||||||
|
@ -1367,6 +1463,9 @@ impl<'a> Tokenizer<'a> {
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some('=') => self.consume_for_binop(chars, ">=", Token::GtEq),
|
Some('=') => self.consume_for_binop(chars, ">=", Token::GtEq),
|
||||||
Some('>') => self.consume_for_binop(chars, ">>", Token::ShiftRight),
|
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),
|
_ => self.start_binop(chars, ">", Token::Gt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1385,6 +1484,22 @@ impl<'a> Tokenizer<'a> {
|
||||||
'&' => {
|
'&' => {
|
||||||
chars.next(); // consume the '&'
|
chars.next(); // consume the '&'
|
||||||
match chars.peek() {
|
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('&') => {
|
Some('&') => {
|
||||||
chars.next(); // consume the second '&'
|
chars.next(); // consume the second '&'
|
||||||
self.start_binop(chars, "&&", Token::Overlap)
|
self.start_binop(chars, "&&", Token::Overlap)
|
||||||
|
@ -1415,6 +1530,9 @@ impl<'a> Tokenizer<'a> {
|
||||||
chars.next(); // consume
|
chars.next(); // consume
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some('*') => self.consume_for_binop(chars, "~*", Token::TildeAsterisk),
|
Some('*') => self.consume_for_binop(chars, "~*", Token::TildeAsterisk),
|
||||||
|
Some('=') if self.dialect.supports_geometric_types() => {
|
||||||
|
self.consume_for_binop(chars, "~=", Token::TildeEqual)
|
||||||
|
}
|
||||||
Some('~') => {
|
Some('~') => {
|
||||||
chars.next();
|
chars.next();
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
|
@ -1441,6 +1559,9 @@ impl<'a> Tokenizer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(' ') => Ok(Some(Token::Sharp)),
|
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('#') => {
|
Some(sch) if self.dialect.is_identifier_start('#') => {
|
||||||
self.tokenize_identifier_or_keyword([ch, *sch], chars)
|
self.tokenize_identifier_or_keyword([ch, *sch], chars)
|
||||||
}
|
}
|
||||||
|
@ -1450,6 +1571,16 @@ impl<'a> Tokenizer<'a> {
|
||||||
'@' => {
|
'@' => {
|
||||||
chars.next();
|
chars.next();
|
||||||
match chars.peek() {
|
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::AtArrow),
|
||||||
Some('?') => self.consume_and_return(chars, Token::AtQuestion),
|
Some('?') => self.consume_and_return(chars, Token::AtQuestion),
|
||||||
Some('@') => {
|
Some('@') => {
|
||||||
|
@ -1482,11 +1613,30 @@ impl<'a> Tokenizer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Postgres uses ? for jsonb operators, not prepared statements
|
// Postgres uses ? for jsonb operators, not prepared statements
|
||||||
'?' if dialect_of!(self is PostgreSqlDialect) => {
|
'?' if self.dialect.supports_geometric_types() => {
|
||||||
chars.next();
|
chars.next(); // consume
|
||||||
match chars.peek() {
|
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('&') => 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),
|
_ => self.consume_and_return(chars, Token::Question),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1520,7 +1670,7 @@ impl<'a> Tokenizer<'a> {
|
||||||
default: Token,
|
default: Token,
|
||||||
) -> Result<Option<Token>, TokenizerError> {
|
) -> Result<Option<Token>, TokenizerError> {
|
||||||
chars.next(); // consume the first char
|
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
|
/// parse a custom binary operator
|
||||||
|
@ -1529,6 +1679,16 @@ impl<'a> Tokenizer<'a> {
|
||||||
chars: &mut State,
|
chars: &mut State,
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
default: Token,
|
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> {
|
) -> Result<Option<Token>, TokenizerError> {
|
||||||
let mut custom = None;
|
let mut custom = None;
|
||||||
while let Some(&ch) = chars.peek() {
|
while let Some(&ch) = chars.peek() {
|
||||||
|
@ -1539,10 +1699,14 @@ impl<'a> Tokenizer<'a> {
|
||||||
custom.get_or_insert_with(|| prefix.to_string()).push(ch);
|
custom.get_or_insert_with(|| prefix.to_string()).push(ch);
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
|
match (custom, default) {
|
||||||
Ok(Some(
|
(Some(custom), _) => Ok(Token::CustomBinaryOperator(custom).into()),
|
||||||
custom.map(Token::CustomBinaryOperator).unwrap_or(default),
|
(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)
|
/// Tokenize dollar preceded value (i.e: a string/placeholder)
|
||||||
|
|
|
@ -13860,3 +13860,361 @@ fn test_select_from_first() {
|
||||||
assert_eq!(ast.to_string(), q);
|
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 = [
|
let operators = [
|
||||||
// PostGIS
|
// PostGIS
|
||||||
"&&&", // n-D bounding boxes intersect
|
"&&&", // 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
|
"|=|", // distance between A and B trajectories at their closest point of approach
|
||||||
"<<#>>", // n-D distance between A and B bounding boxes
|
"<<#>>", // n-D distance between A and B bounding boxes
|
||||||
"|>>", // A's bounding box is strictly above B's.
|
|
||||||
"~=", // bounding box is the same
|
|
||||||
// PGroonga
|
// PGroonga
|
||||||
"&@", // Full text search by a keyword
|
"&@", // Full text search by a keyword
|
||||||
"&@~", // Full text search by easy to use query language
|
"&@~", // Full text search by easy to use query language
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue