| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| #[cfg(not(feature = "std"))] | |
| use alloc::string::String; | |
| use core::{ | |
| fmt, | |
| ops::{Deref, DerefMut}, | |
| }; | |
| #[cfg(feature = "bigdecimal")] | |
| use bigdecimal::BigDecimal; | |
| #[cfg(feature = "serde")] | |
| use serde::{Deserialize, Serialize}; | |
| use crate::{ast::Ident, tokenizer::Span}; | |
| #[cfg(feature = "visitor")] | |
| use sqlparser_derive::{Visit, VisitMut}; | |
| /// Wraps a primitive SQL [`Value`] with its [`Span`] location | |
| /// | |
| /// # Example: create a `ValueWithSpan` from a `Value` | |
| /// ``` | |
| /// # use sqlparser::ast::{Value, ValueWithSpan}; | |
| /// # use sqlparser::tokenizer::{Location, Span}; | |
| /// let value = Value::SingleQuotedString(String::from("endpoint")); | |
| /// // from line 1, column 1 to line 1, column 7 | |
| /// let span = Span::new(Location::new(1, 1), Location::new(1, 7)); | |
| /// let value_with_span = value.with_span(span); | |
| /// ``` | |
| /// | |
| /// # Example: create a `ValueWithSpan` from a `Value` with an empty span | |
| /// | |
| /// You can call [`Value::with_empty_span`] to create a `ValueWithSpan` with an empty span | |
| /// ``` | |
| /// # use sqlparser::ast::{Value, ValueWithSpan}; | |
| /// # use sqlparser::tokenizer::{Location, Span}; | |
| /// let value = Value::SingleQuotedString(String::from("endpoint")); | |
| /// let value_with_span = value.with_empty_span(); | |
| /// assert_eq!(value_with_span.span, Span::empty()); | |
| /// ``` | |
| /// | |
| /// You can also use the [`From`] trait to convert `ValueWithSpan` to/from `Value`s | |
| /// ``` | |
| /// # use sqlparser::ast::{Value, ValueWithSpan}; | |
| /// # use sqlparser::tokenizer::{Location, Span}; | |
| /// let value = Value::SingleQuotedString(String::from("endpoint")); | |
| /// // converting `Value` to `ValueWithSpan` results in an empty span | |
| /// let value_with_span: ValueWithSpan = value.into(); | |
| /// assert_eq!(value_with_span.span, Span::empty()); | |
| /// // convert back to `Value` | |
| /// let value: Value = value_with_span.into(); | |
| /// ``` | |
| /// A `Value` paired with its source `Span` location. | |
| #[derive(Debug, Clone, Eq)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr( | |
| feature = "visitor", | |
| derive(Visit, VisitMut), | |
| visit(with = "visit_value") | |
| )] | |
| pub struct ValueWithSpan { | |
| /// The wrapped `Value`. | |
| pub value: Value, | |
| /// The source `Span` covering the token(s) that produced the value. | |
| pub span: Span, | |
| } | |
| impl PartialEq for ValueWithSpan { | |
| fn eq(&self, other: &Self) -> bool { | |
| self.value == other.value | |
| } | |
| } | |
| impl Ord for ValueWithSpan { | |
| fn cmp(&self, other: &Self) -> core::cmp::Ordering { | |
| self.value.cmp(&other.value) | |
| } | |
| } | |
| impl PartialOrd for ValueWithSpan { | |
| fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { | |
| Some(Ord::cmp(self, other)) | |
| } | |
| } | |
| impl core::hash::Hash for ValueWithSpan { | |
| fn hash<H: core::hash::Hasher>(&self, state: &mut H) { | |
| self.value.hash(state); | |
| } | |
| } | |
| impl From<Value> for ValueWithSpan { | |
| fn from(value: Value) -> Self { | |
| value.with_empty_span() | |
| } | |
| } | |
| impl From<ValueWithSpan> for Value { | |
| fn from(value: ValueWithSpan) -> Self { | |
| value.value | |
| } | |
| } | |
| impl Deref for ValueWithSpan { | |
| type Target = Value; | |
| fn deref(&self) -> &Self::Target { | |
| &self.value | |
| } | |
| } | |
| impl DerefMut for ValueWithSpan { | |
| fn deref_mut(&mut self) -> &mut Self::Target { | |
| &mut self.value | |
| } | |
| } | |
| /// Primitive SQL values such as number and string | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum Value { | |
| /// Numeric literal | |
| #[cfg(not(feature = "bigdecimal"))] | |
| Number(String, bool), | |
| #[cfg(feature = "bigdecimal")] | |
| /// HINT: use `test_utils::number` to make an instance of | |
| /// Value::Number This might help if you your tests pass locally | |
| /// but fail on CI with the `--all-features` flag enabled | |
| /// Numeric literal (uses `BigDecimal` when the `bigdecimal` feature is enabled). | |
| Number(BigDecimal, bool), | |
| /// 'string value' | |
| SingleQuotedString(String), | |
| /// Dollar-quoted string literal, e.g. `$$...$$` or `$tag$...$tag$` (Postgres syntax). | |
| DollarQuotedString(DollarQuotedString), | |
| /// Triple single quoted strings: Example '''abc''' | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) | |
| TripleSingleQuotedString(String), | |
| /// Triple double quoted strings: Example """abc""" | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) | |
| TripleDoubleQuotedString(String), | |
| /// e'string value' (postgres extension) | |
| /// See [Postgres docs](https://www.postgresql.org/docs/8.3/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS) | |
| /// for more details. | |
| EscapedStringLiteral(String), | |
| /// u&'string value' (postgres extension) | |
| /// See [Postgres docs](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-UESCAPE) | |
| /// for more details. | |
| UnicodeStringLiteral(String), | |
| /// B'string value' | |
| SingleQuotedByteStringLiteral(String), | |
| /// B"string value" | |
| DoubleQuotedByteStringLiteral(String), | |
| /// Triple single quoted literal with byte string prefix. Example `B'''abc'''` | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) | |
| TripleSingleQuotedByteStringLiteral(String), | |
| /// Triple double quoted literal with byte string prefix. Example `B"""abc"""` | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) | |
| TripleDoubleQuotedByteStringLiteral(String), | |
| /// Single quoted literal with raw string prefix. Example `R'abc'` | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) | |
| SingleQuotedRawStringLiteral(String), | |
| /// Double quoted literal with raw string prefix. Example `R"abc"` | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) | |
| DoubleQuotedRawStringLiteral(String), | |
| /// Triple single quoted literal with raw string prefix. Example `R'''abc'''` | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) | |
| TripleSingleQuotedRawStringLiteral(String), | |
| /// Triple double quoted literal with raw string prefix. Example `R"""abc"""` | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) | |
| TripleDoubleQuotedRawStringLiteral(String), | |
| /// N'string value' | |
| NationalStringLiteral(String), | |
| /// Quote delimited literal. Examples `Q'{ab'c}'`, `Q'|ab'c|'`, `Q'|ab|c|'` | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA) | |
| QuoteDelimitedStringLiteral(QuoteDelimitedString), | |
| /// "National" quote delimited literal. Examples `Q'{ab'c}'`, `Q'|ab'c|'`, `Q'|ab|c|'` | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA) | |
| NationalQuoteDelimitedStringLiteral(QuoteDelimitedString), | |
| /// X'hex value' | |
| HexStringLiteral(String), | |
| /// Double quoted string literal, e.g. `"abc"`. | |
| DoubleQuotedString(String), | |
| /// Boolean value true or false | |
| Boolean(bool), | |
| /// `NULL` value | |
| Null, | |
| /// `?` or `$` Prepared statement arg placeholder | |
| Placeholder(String), | |
| } | |
| impl ValueWithSpan { | |
| /// If the underlying literal is a string, regardless of quote style, returns the associated string value | |
| pub fn into_string(self) -> Option<String> { | |
| self.value.into_string() | |
| } | |
| } | |
| impl Value { | |
| /// If the underlying literal is a string, regardless of quote style, returns the associated string value | |
| pub fn into_string(self) -> Option<String> { | |
| match self { | |
| Value::SingleQuotedString(s) | |
| | Value::DoubleQuotedString(s) | |
| | Value::TripleSingleQuotedString(s) | |
| | Value::TripleDoubleQuotedString(s) | |
| | Value::SingleQuotedByteStringLiteral(s) | |
| | Value::DoubleQuotedByteStringLiteral(s) | |
| | Value::TripleSingleQuotedByteStringLiteral(s) | |
| | Value::TripleDoubleQuotedByteStringLiteral(s) | |
| | Value::SingleQuotedRawStringLiteral(s) | |
| | Value::DoubleQuotedRawStringLiteral(s) | |
| | Value::TripleSingleQuotedRawStringLiteral(s) | |
| | Value::TripleDoubleQuotedRawStringLiteral(s) | |
| | Value::EscapedStringLiteral(s) | |
| | Value::UnicodeStringLiteral(s) | |
| | Value::NationalStringLiteral(s) | |
| | Value::HexStringLiteral(s) => Some(s), | |
| Value::DollarQuotedString(s) => Some(s.value), | |
| Value::QuoteDelimitedStringLiteral(s) => Some(s.value), | |
| Value::NationalQuoteDelimitedStringLiteral(s) => Some(s.value), | |
| _ => None, | |
| } | |
| } | |
| /// Attach the provided `span` to this `Value` and return `ValueWithSpan`. | |
| pub fn with_span(self, span: Span) -> ValueWithSpan { | |
| ValueWithSpan { value: self, span } | |
| } | |
| /// Convenience for attaching an empty span to this `Value`. | |
| pub fn with_empty_span(self) -> ValueWithSpan { | |
| self.with_span(Span::empty()) | |
| } | |
| } | |
| impl fmt::Display for ValueWithSpan { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| write!(f, "{}", self.value) | |
| } | |
| } | |
| impl fmt::Display for Value { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| Value::Number(v, l) => write!(f, "{}{long}", v, long = if *l { "L" } else { "" }), | |
| Value::DoubleQuotedString(v) => write!(f, "\"{}\"", escape_double_quote_string(v)), | |
| Value::SingleQuotedString(v) => write!(f, "'{}'", escape_single_quote_string(v)), | |
| Value::TripleSingleQuotedString(v) => { | |
| write!(f, "'''{v}'''") | |
| } | |
| Value::TripleDoubleQuotedString(v) => { | |
| write!(f, r#""""{v}""""#) | |
| } | |
| Value::DollarQuotedString(v) => write!(f, "{v}"), | |
| Value::EscapedStringLiteral(v) => write!(f, "E'{}'", escape_escaped_string(v)), | |
| Value::UnicodeStringLiteral(v) => write!(f, "U&'{}'", escape_unicode_string(v)), | |
| Value::NationalStringLiteral(v) => write!(f, "N'{}'", escape_single_quote_string(v)), | |
| Value::QuoteDelimitedStringLiteral(v) => v.fmt(f), | |
| Value::NationalQuoteDelimitedStringLiteral(v) => write!(f, "N{v}"), | |
| Value::HexStringLiteral(v) => write!(f, "X'{}'", escape_single_quote_string(v)), | |
| Value::Boolean(v) => write!(f, "{v}"), | |
| Value::SingleQuotedByteStringLiteral(v) => { | |
| write!(f, "B'{}'", escape_single_quote_string(v)) | |
| } | |
| Value::DoubleQuotedByteStringLiteral(v) => { | |
| write!(f, "B\"{}\"", escape_double_quote_string(v)) | |
| } | |
| Value::TripleSingleQuotedByteStringLiteral(v) => write!(f, "B'''{v}'''"), | |
| Value::TripleDoubleQuotedByteStringLiteral(v) => write!(f, r#"B"""{v}""""#), | |
| Value::SingleQuotedRawStringLiteral(v) => { | |
| write!(f, "R'{}'", escape_single_quote_string(v)) | |
| } | |
| Value::DoubleQuotedRawStringLiteral(v) => { | |
| write!(f, "R\"{}\"", escape_double_quote_string(v)) | |
| } | |
| Value::TripleSingleQuotedRawStringLiteral(v) => write!(f, "R'''{v}'''"), | |
| Value::TripleDoubleQuotedRawStringLiteral(v) => write!(f, r#"R"""{v}""""#), | |
| Value::Null => write!(f, "NULL"), | |
| Value::Placeholder(v) => write!(f, "{v}"), | |
| } | |
| } | |
| } | |
| /// A dollar-quoted string literal, e.g. `$$...$$` or `$tag$...$tag$`. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct DollarQuotedString { | |
| /// Inner string contents. | |
| pub value: String, | |
| /// Optional tag used in the opening/closing delimiter. | |
| pub tag: Option<String>, | |
| } | |
| impl fmt::Display for DollarQuotedString { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match &self.tag { | |
| Some(tag) => { | |
| write!(f, "${}${}${}$", tag, self.value, tag) | |
| } | |
| None => { | |
| write!(f, "$${}$$", self.value) | |
| } | |
| } | |
| } | |
| } | |
| /// A quote delimited string literal, e.g. `Q'_abc_'`. | |
| /// | |
| /// See [Value::QuoteDelimitedStringLiteral] and/or | |
| /// [Value::NationalQuoteDelimitedStringLiteral]. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct QuoteDelimitedString { | |
| /// the quote start character; i.e. the character _after_ the opening `Q'` | |
| pub start_quote: char, | |
| /// the string literal value itself | |
| pub value: String, | |
| /// the quote end character; i.e. the character _before_ the closing `'` | |
| pub end_quote: char, | |
| } | |
| impl fmt::Display for QuoteDelimitedString { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "Q'{}{}{}'", self.start_quote, self.value, self.end_quote) | |
| } | |
| } | |
| /// Represents the date/time fields used by functions like `EXTRACT`. | |
| /// | |
| /// Each variant corresponds to a supported date/time part (for example | |
| /// `YEAR`, `MONTH`, `DAY`, etc.). The `Custom` variant allows arbitrary | |
| /// identifiers (e.g. dialect-specific abbreviations). | |
| #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum DateTimeField { | |
| /// `YEAR` | |
| Year, | |
| /// `YEARS` (plural form) | |
| Years, | |
| /// `MONTH` | |
| Month, | |
| /// `MONTHS` (plural form) | |
| Months, | |
| /// `WEEK`, optionally followed by a weekday, e.g. `WEEK(MONDAY)`. | |
| /// | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#extract) | |
| Week(Option<Ident>), | |
| /// `WEEKS` (plural form) | |
| Weeks, | |
| /// `DAY` | |
| Day, | |
| /// `DAYOFWEEK` | |
| DayOfWeek, | |
| /// `DAYOFYEAR` | |
| DayOfYear, | |
| /// `DAYS` (plural form) | |
| Days, | |
| /// `DATE` | |
| Date, | |
| /// `DATETIME` | |
| Datetime, | |
| /// `HOUR` | |
| Hour, | |
| /// `HOURS` (plural form) | |
| Hours, | |
| /// `MINUTE` | |
| Minute, | |
| /// `MINUTES` (plural form) | |
| Minutes, | |
| /// `SECOND` | |
| Second, | |
| /// `SECONDS` (plural form) | |
| Seconds, | |
| /// `CENTURY` | |
| Century, | |
| /// `DECADE` | |
| Decade, | |
| /// `DOW` (day of week short form) | |
| Dow, | |
| /// `DOY` (day of year short form) | |
| Doy, | |
| /// `EPOCH` | |
| Epoch, | |
| /// `ISODOW` | |
| Isodow, | |
| /// `ISOYEAR` | |
| Isoyear, | |
| /// `ISOWEEK` | |
| IsoWeek, | |
| /// `JULIAN` | |
| Julian, | |
| /// `MICROSECOND` | |
| Microsecond, | |
| /// `MICROSECONDS` (plural form) | |
| Microseconds, | |
| /// `MILLENIUM` (alternate spelling) | |
| Millenium, | |
| /// `MILLENNIUM` (alternate spelling) | |
| Millennium, | |
| /// `MILLISECOND` | |
| Millisecond, | |
| /// `MILLISECONDS` (plural form) | |
| Milliseconds, | |
| /// `NANOSECOND` | |
| Nanosecond, | |
| /// `NANOSECONDS` (plural form) | |
| Nanoseconds, | |
| /// `QUARTER` | |
| Quarter, | |
| /// `TIME` | |
| Time, | |
| /// `TIMEZONE` | |
| Timezone, | |
| /// `TIMEZONE_ABBR` | |
| TimezoneAbbr, | |
| /// `TIMEZONE_HOUR` | |
| TimezoneHour, | |
| /// `TIMEZONE_MINUTE` | |
| TimezoneMinute, | |
| /// `TIMEZONE_REGION` | |
| TimezoneRegion, | |
| /// `NODATETIME` indicates no date/time part | |
| NoDateTime, | |
| /// Arbitrary abbreviation or custom date-time part. | |
| /// | |
| /// ```sql | |
| /// EXTRACT(q FROM CURRENT_TIMESTAMP) | |
| /// ``` | |
| /// [Snowflake](https://docs.snowflake.com/en/sql-reference/functions-date-time#supported-date-and-time-parts) | |
| Custom(Ident), | |
| } | |
| impl fmt::Display for DateTimeField { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| DateTimeField::Year => write!(f, "YEAR"), | |
| DateTimeField::Years => write!(f, "YEARS"), | |
| DateTimeField::Month => write!(f, "MONTH"), | |
| DateTimeField::Months => write!(f, "MONTHS"), | |
| DateTimeField::Week(week_day) => { | |
| write!(f, "WEEK")?; | |
| if let Some(week_day) = week_day { | |
| write!(f, "({week_day})")? | |
| } | |
| Ok(()) | |
| } | |
| DateTimeField::Weeks => write!(f, "WEEKS"), | |
| DateTimeField::Day => write!(f, "DAY"), | |
| DateTimeField::DayOfWeek => write!(f, "DAYOFWEEK"), | |
| DateTimeField::DayOfYear => write!(f, "DAYOFYEAR"), | |
| DateTimeField::Days => write!(f, "DAYS"), | |
| DateTimeField::Date => write!(f, "DATE"), | |
| DateTimeField::Datetime => write!(f, "DATETIME"), | |
| DateTimeField::Hour => write!(f, "HOUR"), | |
| DateTimeField::Hours => write!(f, "HOURS"), | |
| DateTimeField::Minute => write!(f, "MINUTE"), | |
| DateTimeField::Minutes => write!(f, "MINUTES"), | |
| DateTimeField::Second => write!(f, "SECOND"), | |
| DateTimeField::Seconds => write!(f, "SECONDS"), | |
| DateTimeField::Century => write!(f, "CENTURY"), | |
| DateTimeField::Decade => write!(f, "DECADE"), | |
| DateTimeField::Dow => write!(f, "DOW"), | |
| DateTimeField::Doy => write!(f, "DOY"), | |
| DateTimeField::Epoch => write!(f, "EPOCH"), | |
| DateTimeField::Isodow => write!(f, "ISODOW"), | |
| DateTimeField::Isoyear => write!(f, "ISOYEAR"), | |
| DateTimeField::IsoWeek => write!(f, "ISOWEEK"), | |
| DateTimeField::Julian => write!(f, "JULIAN"), | |
| DateTimeField::Microsecond => write!(f, "MICROSECOND"), | |
| DateTimeField::Microseconds => write!(f, "MICROSECONDS"), | |
| DateTimeField::Millenium => write!(f, "MILLENIUM"), | |
| DateTimeField::Millennium => write!(f, "MILLENNIUM"), | |
| DateTimeField::Millisecond => write!(f, "MILLISECOND"), | |
| DateTimeField::Milliseconds => write!(f, "MILLISECONDS"), | |
| DateTimeField::Nanosecond => write!(f, "NANOSECOND"), | |
| DateTimeField::Nanoseconds => write!(f, "NANOSECONDS"), | |
| DateTimeField::Quarter => write!(f, "QUARTER"), | |
| DateTimeField::Time => write!(f, "TIME"), | |
| DateTimeField::Timezone => write!(f, "TIMEZONE"), | |
| DateTimeField::TimezoneAbbr => write!(f, "TIMEZONE_ABBR"), | |
| DateTimeField::TimezoneHour => write!(f, "TIMEZONE_HOUR"), | |
| DateTimeField::TimezoneMinute => write!(f, "TIMEZONE_MINUTE"), | |
| DateTimeField::TimezoneRegion => write!(f, "TIMEZONE_REGION"), | |
| DateTimeField::NoDateTime => write!(f, "NODATETIME"), | |
| DateTimeField::Custom(custom) => write!(f, "{custom}"), | |
| } | |
| } | |
| } | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| /// The Unicode Standard defines four normalization forms, which are intended to eliminate | |
| /// certain distinctions between visually or functionally identical characters. | |
| /// | |
| /// See [Unicode Normalization Forms](https://unicode.org/reports/tr15/) for details. | |
| pub enum NormalizationForm { | |
| /// Canonical Decomposition, followed by Canonical Composition. | |
| NFC, | |
| /// Canonical Decomposition. | |
| NFD, | |
| /// Compatibility Decomposition, followed by Canonical Composition. | |
| NFKC, | |
| /// Compatibility Decomposition. | |
| NFKD, | |
| } | |
| impl fmt::Display for NormalizationForm { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| NormalizationForm::NFC => write!(f, "NFC"), | |
| NormalizationForm::NFD => write!(f, "NFD"), | |
| NormalizationForm::NFKC => write!(f, "NFKC"), | |
| NormalizationForm::NFKD => write!(f, "NFKD"), | |
| } | |
| } | |
| } | |
| pub struct EscapeQuotedString<'a> { | |
| string: &'a str, | |
| quote: char, | |
| } | |
| impl fmt::Display for EscapeQuotedString<'_> { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| // EscapeQuotedString doesn't know which mode of escape was | |
| // chosen by the user. So this code must to correctly display | |
| // strings without knowing if the strings are already escaped | |
| // or not. | |
| // | |
| // If the quote symbol in the string is repeated twice, OR, if | |
| // the quote symbol is after backslash, display all the chars | |
| // without any escape. However, if the quote symbol is used | |
| // just between usual chars, `fmt()` should display it twice." | |
| // | |
| // The following table has examples | |
| // | |
| // | original query | mode | AST Node | serialized | | |
| // | ------------- | --------- | -------------------------------------------------- | ------------ | | |
| // | `"A""B""A"` | no-escape | `DoubleQuotedString(String::from("A\"\"B\"\"A"))` | `"A""B""A"` | | |
| // | `"A""B""A"` | default | `DoubleQuotedString(String::from("A\"B\"A"))` | `"A""B""A"` | | |
| // | `"A\"B\"A"` | no-escape | `DoubleQuotedString(String::from("A\\\"B\\\"A"))` | `"A\"B\"A"` | | |
| // | `"A\"B\"A"` | default | `DoubleQuotedString(String::from("A\"B\"A"))` | `"A""B""A"` | | |
| let quote = self.quote; | |
| let mut previous_char = char::default(); | |
| let mut start_idx = 0; | |
| let mut peekable_chars = self.string.char_indices().peekable(); | |
| while let Some(&(idx, ch)) = peekable_chars.peek() { | |
| match ch { | |
| char if char == quote => { | |
| if previous_char == '\\' { | |
| // the quote is already escaped with a backslash, skip | |
| peekable_chars.next(); | |
| continue; | |
| } | |
| peekable_chars.next(); | |
| match peekable_chars.peek() { | |
| Some((_, c)) if *c == quote => { | |
| // the quote is already escaped with another quote, skip | |
| peekable_chars.next(); | |
| } | |
| _ => { | |
| // The quote is not escaped. | |
| // Including idx in the range, so the quote at idx will be printed twice: | |
| // in this call to write_str() and in the next one. | |
| let end_idx = idx + ch.len_utf8(); | |
| f.write_str(&self.string[start_idx..end_idx])?; | |
| start_idx = idx; | |
| } | |
| } | |
| } | |
| _ => { | |
| peekable_chars.next(); | |
| } | |
| } | |
| previous_char = ch; | |
| } | |
| f.write_str(&self.string[start_idx..])?; | |
| Ok(()) | |
| } | |
| } | |
| /// Return a helper which formats `string` for inclusion inside a quoted | |
| /// literal that uses `quote` as the delimiter. | |
| pub fn escape_quoted_string(string: &str, quote: char) -> EscapeQuotedString<'_> { | |
| EscapeQuotedString { string, quote } | |
| } | |
| /// Convenience wrapper for escaping strings for single-quoted literals (`'`). | |
| pub fn escape_single_quote_string(s: &str) -> EscapeQuotedString<'_> { | |
| escape_quoted_string(s, '\'') | |
| } | |
| /// Convenience wrapper for escaping strings for double-quoted literals (`").` | |
| pub fn escape_double_quote_string(s: &str) -> EscapeQuotedString<'_> { | |
| escape_quoted_string(s, '\"') | |
| } | |
| /// A character written as a single-quoted SQL string literal. | |
| pub(crate) struct SingleQuotedChar(pub(crate) char); | |
| impl fmt::Display for SingleQuotedChar { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| if self.0 == '\'' { | |
| f.write_str("''''") | |
| } else { | |
| write!(f, "'{}'", self.0) | |
| } | |
| } | |
| } | |
| pub struct EscapeEscapedStringLiteral<'a>(&'a str); | |
| impl fmt::Display for EscapeEscapedStringLiteral<'_> { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| for c in self.0.chars() { | |
| match c { | |
| '\'' => { | |
| write!(f, r#"\'"#)?; | |
| } | |
| '\\' => { | |
| write!(f, r#"\\"#)?; | |
| } | |
| '\n' => { | |
| write!(f, r#"\n"#)?; | |
| } | |
| '\t' => { | |
| write!(f, r#"\t"#)?; | |
| } | |
| '\r' => { | |
| write!(f, r#"\r"#)?; | |
| } | |
| _ => { | |
| write!(f, "{c}")?; | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// Return a helper which escapes characters for string literals that use | |
| /// PostgreSQL-style escaped string literals (e.g. `E'...')`. | |
| pub fn escape_escaped_string(s: &str) -> EscapeEscapedStringLiteral<'_> { | |
| EscapeEscapedStringLiteral(s) | |
| } | |
| pub struct EscapeUnicodeStringLiteral<'a>(&'a str); | |
| impl fmt::Display for EscapeUnicodeStringLiteral<'_> { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| for c in self.0.chars() { | |
| match c { | |
| '\'' => { | |
| write!(f, "''")?; | |
| } | |
| '\\' => { | |
| write!(f, r#"\\"#)?; | |
| } | |
| x if x.is_ascii() => { | |
| write!(f, "{c}")?; | |
| } | |
| _ => { | |
| let codepoint = c as u32; | |
| // if the character fits in 32 bits, we can use the \XXXX format | |
| // otherwise, we need to use the \+XXXXXX format | |
| if codepoint <= 0xFFFF { | |
| write!(f, "\\{codepoint:04X}")?; | |
| } else { | |
| write!(f, "\\+{codepoint:06X}")?; | |
| } | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// Return a helper which escapes non-ASCII characters using `\XXXX` or | |
| /// `\+XXXXXX` Unicode escape formats (used for `U&'...'` style literals). | |
| pub fn escape_unicode_string(s: &str) -> EscapeUnicodeStringLiteral<'_> { | |
| EscapeUnicodeStringLiteral(s) | |
| } | |
| /// The side on which `TRIM` should be applied. | |
| /// | |
| /// Corresponds to `TRIM(BOTH|LEADING|TRAILING)` SQL syntax. | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum TrimWhereField { | |
| /// `BOTH` (trim from both ends) | |
| Both, | |
| /// `LEADING` (trim from start) | |
| Leading, | |
| /// `TRAILING` (trim from end) | |
| Trailing, | |
| } | |
| impl fmt::Display for TrimWhereField { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| use TrimWhereField::*; | |
| f.write_str(match self { | |
| Both => "BOTH", | |
| Leading => "LEADING", | |
| Trailing => "TRAILING", | |
| }) | |
| } | |
| } | |
| #[cfg(test)] | |
| #[test] | |
| fn test_escape_quoted_string_with_multibyte_quote_char() { | |
| assert_eq!( | |
| format!("{}", escape_quoted_string("a🦀b🦀c", '🦀')), | |
| "a🦀🦀b🦀🦀c" | |
| ); | |
| } | |
| #[cfg(test)] | |
| #[test] | |
| fn test_hex_string_literal_display_escaping() { | |
| assert_eq!( | |
| Value::HexStringLiteral("'".to_string()).to_string(), | |
| "X''''" | |
| ); | |
| assert_eq!( | |
| Value::HexStringLiteral("it's".to_string()).to_string(), | |
| "X'it''s'" | |
| ); | |
| } | |
| #[cfg(test)] | |
| #[test] | |
| fn test_byte_and_raw_string_literal_display_escaping() { | |
| assert_eq!( | |
| Value::SingleQuotedByteStringLiteral("'".to_string()).to_string(), | |
| "B''''" | |
| ); | |
| assert_eq!( | |
| Value::SingleQuotedByteStringLiteral("it's".to_string()).to_string(), | |
| "B'it''s'" | |
| ); | |
| assert_eq!( | |
| Value::DoubleQuotedByteStringLiteral("\"".to_string()).to_string(), | |
| "B\"\"\"\"" | |
| ); | |
| assert_eq!( | |
| Value::DoubleQuotedByteStringLiteral("it\"s".to_string()).to_string(), | |
| "B\"it\"\"s\"" | |
| ); | |
| assert_eq!( | |
| Value::SingleQuotedRawStringLiteral("'".to_string()).to_string(), | |
| "R''''" | |
| ); | |
| assert_eq!( | |
| Value::SingleQuotedRawStringLiteral("it's".to_string()).to_string(), | |
| "R'it''s'" | |
| ); | |
| assert_eq!( | |
| Value::DoubleQuotedRawStringLiteral("\"".to_string()).to_string(), | |
| "R\"\"\"\"" | |
| ); | |
| assert_eq!( | |
| Value::DoubleQuotedRawStringLiteral("it\"s".to_string()).to_string(), | |
| "R\"it\"\"s\"" | |
| ); | |
| } |