sqlparser/ast/
value.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#[cfg(not(feature = "std"))]
19use alloc::string::String;
20
21use core::fmt;
22
23#[cfg(feature = "bigdecimal")]
24use bigdecimal::BigDecimal;
25
26#[cfg(feature = "serde")]
27use serde::{Deserialize, Serialize};
28
29use crate::{ast::Ident, tokenizer::Span};
30#[cfg(feature = "visitor")]
31use yachtsql_sqlparser_derive::{Visit, VisitMut};
32
33/// Wraps a primitive SQL [`Value`]  with its [`Span`] location
34///
35/// # Example: create a `ValueWithSpan` from a `Value`
36/// ```
37/// # use sqlparser::ast::{Value, ValueWithSpan};
38/// # use sqlparser::tokenizer::{Location, Span};
39/// let value = Value::SingleQuotedString(String::from("endpoint"));
40/// // from line 1, column 1 to line 1, column 7
41/// let span = Span::new(Location::new(1, 1), Location::new(1, 7));
42/// let value_with_span = value.with_span(span);
43/// ```
44///
45/// # Example: create a `ValueWithSpan` from a `Value` with an empty span
46///
47/// You can call [`Value::with_empty_span`] to create a `ValueWithSpan` with an empty span
48/// ```
49/// # use sqlparser::ast::{Value, ValueWithSpan};
50/// # use sqlparser::tokenizer::{Location, Span};
51/// let value = Value::SingleQuotedString(String::from("endpoint"));
52/// let value_with_span = value.with_empty_span();
53/// assert_eq!(value_with_span.span, Span::empty());
54/// ```
55///
56/// You can also use the [`From`] trait to convert  `ValueWithSpan` to/from `Value`s
57/// ```
58/// # use sqlparser::ast::{Value, ValueWithSpan};
59/// # use sqlparser::tokenizer::{Location, Span};
60/// let value = Value::SingleQuotedString(String::from("endpoint"));
61/// // converting `Value` to `ValueWithSpan` results in an empty span
62/// let value_with_span: ValueWithSpan = value.into();
63/// assert_eq!(value_with_span.span, Span::empty());
64/// // convert back to `Value`
65/// let value: Value = value_with_span.into();
66/// ```
67#[derive(Debug, Clone, Eq)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
70pub struct ValueWithSpan {
71    pub value: Value,
72    pub span: Span,
73}
74
75impl PartialEq for ValueWithSpan {
76    fn eq(&self, other: &Self) -> bool {
77        self.value == other.value
78    }
79}
80
81impl Ord for ValueWithSpan {
82    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
83        self.value.cmp(&other.value)
84    }
85}
86
87impl PartialOrd for ValueWithSpan {
88    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
89        Some(Ord::cmp(self, other))
90    }
91}
92
93impl core::hash::Hash for ValueWithSpan {
94    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
95        self.value.hash(state);
96    }
97}
98
99impl From<Value> for ValueWithSpan {
100    fn from(value: Value) -> Self {
101        value.with_empty_span()
102    }
103}
104
105impl From<ValueWithSpan> for Value {
106    fn from(value: ValueWithSpan) -> Self {
107        value.value
108    }
109}
110
111/// Primitive SQL values such as number and string
112#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
113#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
114#[cfg_attr(
115    feature = "visitor",
116    derive(Visit, VisitMut),
117    visit(with = "visit_value")
118)]
119pub enum Value {
120    /// Numeric literal
121    #[cfg(not(feature = "bigdecimal"))]
122    Number(String, bool),
123    #[cfg(feature = "bigdecimal")]
124    // HINT: use `test_utils::number` to make an instance of
125    // Value::Number This might help if you your tests pass locally
126    // but fail on CI with the `--all-features` flag enabled
127    Number(BigDecimal, bool),
128    /// 'string value'
129    SingleQuotedString(String),
130    // $<tag_name>$string value$<tag_name>$ (postgres syntax)
131    DollarQuotedString(DollarQuotedString),
132    /// Triple single quoted strings: Example '''abc'''
133    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
134    TripleSingleQuotedString(String),
135    /// Triple double quoted strings: Example """abc"""
136    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
137    TripleDoubleQuotedString(String),
138    /// e'string value' (postgres extension)
139    /// See [Postgres docs](https://www.postgresql.org/docs/8.3/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS)
140    /// for more details.
141    EscapedStringLiteral(String),
142    /// u&'string value' (postgres extension)
143    /// See [Postgres docs](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-UESCAPE)
144    /// for more details.
145    UnicodeStringLiteral(String),
146    /// B'string value'
147    SingleQuotedByteStringLiteral(String),
148    /// B"string value"
149    DoubleQuotedByteStringLiteral(String),
150    /// Triple single quoted literal with byte string prefix. Example `B'''abc'''`
151    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
152    TripleSingleQuotedByteStringLiteral(String),
153    /// Triple double quoted literal with byte string prefix. Example `B"""abc"""`
154    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
155    TripleDoubleQuotedByteStringLiteral(String),
156    /// Single quoted literal with raw string prefix. Example `R'abc'`
157    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
158    SingleQuotedRawStringLiteral(String),
159    /// Double quoted literal with raw string prefix. Example `R"abc"`
160    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
161    DoubleQuotedRawStringLiteral(String),
162    /// Triple single quoted literal with raw string prefix. Example `R'''abc'''`
163    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
164    TripleSingleQuotedRawStringLiteral(String),
165    /// Triple double quoted literal with raw string prefix. Example `R"""abc"""`
166    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
167    TripleDoubleQuotedRawStringLiteral(String),
168    /// N'string value'
169    NationalStringLiteral(String),
170    /// X'hex value'
171    HexStringLiteral(String),
172
173    DoubleQuotedString(String),
174    /// Boolean value true or false
175    Boolean(bool),
176    /// `NULL` value
177    Null,
178    /// `?` or `$` Prepared statement arg placeholder
179    Placeholder(String),
180}
181
182impl ValueWithSpan {
183    /// If the underlying literal is a string, regardless of quote style, returns the associated string value
184    pub fn into_string(self) -> Option<String> {
185        self.value.into_string()
186    }
187}
188
189impl Value {
190    /// If the underlying literal is a string, regardless of quote style, returns the associated string value
191    pub fn into_string(self) -> Option<String> {
192        match self {
193            Value::SingleQuotedString(s)
194            | Value::DoubleQuotedString(s)
195            | Value::TripleSingleQuotedString(s)
196            | Value::TripleDoubleQuotedString(s)
197            | Value::SingleQuotedByteStringLiteral(s)
198            | Value::DoubleQuotedByteStringLiteral(s)
199            | Value::TripleSingleQuotedByteStringLiteral(s)
200            | Value::TripleDoubleQuotedByteStringLiteral(s)
201            | Value::SingleQuotedRawStringLiteral(s)
202            | Value::DoubleQuotedRawStringLiteral(s)
203            | Value::TripleSingleQuotedRawStringLiteral(s)
204            | Value::TripleDoubleQuotedRawStringLiteral(s)
205            | Value::EscapedStringLiteral(s)
206            | Value::UnicodeStringLiteral(s)
207            | Value::NationalStringLiteral(s)
208            | Value::HexStringLiteral(s) => Some(s),
209            Value::DollarQuotedString(s) => Some(s.value),
210            _ => None,
211        }
212    }
213
214    pub fn with_span(self, span: Span) -> ValueWithSpan {
215        ValueWithSpan { value: self, span }
216    }
217
218    pub fn with_empty_span(self) -> ValueWithSpan {
219        self.with_span(Span::empty())
220    }
221}
222
223impl fmt::Display for ValueWithSpan {
224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225        write!(f, "{}", self.value)
226    }
227}
228
229impl fmt::Display for Value {
230    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231        match self {
232            Value::Number(v, l) => write!(f, "{}{long}", v, long = if *l { "L" } else { "" }),
233            Value::DoubleQuotedString(v) => write!(f, "\"{}\"", escape_double_quote_string(v)),
234            Value::SingleQuotedString(v) => write!(f, "'{}'", escape_single_quote_string(v)),
235            Value::TripleSingleQuotedString(v) => {
236                write!(f, "'''{v}'''")
237            }
238            Value::TripleDoubleQuotedString(v) => {
239                write!(f, r#""""{v}""""#)
240            }
241            Value::DollarQuotedString(v) => write!(f, "{v}"),
242            Value::EscapedStringLiteral(v) => write!(f, "E'{}'", escape_escaped_string(v)),
243            Value::UnicodeStringLiteral(v) => write!(f, "U&'{}'", escape_unicode_string(v)),
244            Value::NationalStringLiteral(v) => write!(f, "N'{v}'"),
245            Value::HexStringLiteral(v) => write!(f, "X'{v}'"),
246            Value::Boolean(v) => write!(f, "{v}"),
247            Value::SingleQuotedByteStringLiteral(v) => write!(f, "B'{v}'"),
248            Value::DoubleQuotedByteStringLiteral(v) => write!(f, "B\"{v}\""),
249            Value::TripleSingleQuotedByteStringLiteral(v) => write!(f, "B'''{v}'''"),
250            Value::TripleDoubleQuotedByteStringLiteral(v) => write!(f, r#"B"""{v}""""#),
251            Value::SingleQuotedRawStringLiteral(v) => write!(f, "R'{v}'"),
252            Value::DoubleQuotedRawStringLiteral(v) => write!(f, "R\"{v}\""),
253            Value::TripleSingleQuotedRawStringLiteral(v) => write!(f, "R'''{v}'''"),
254            Value::TripleDoubleQuotedRawStringLiteral(v) => write!(f, r#"R"""{v}""""#),
255            Value::Null => write!(f, "NULL"),
256            Value::Placeholder(v) => write!(f, "{v}"),
257        }
258    }
259}
260
261#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
262#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
263#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
264pub struct DollarQuotedString {
265    pub value: String,
266    pub tag: Option<String>,
267}
268
269impl fmt::Display for DollarQuotedString {
270    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271        match &self.tag {
272            Some(tag) => {
273                write!(f, "${}${}${}$", tag, self.value, tag)
274            }
275            None => {
276                write!(f, "$${}$$", self.value)
277            }
278        }
279    }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
283#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
284#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
285pub enum DateTimeField {
286    Year,
287    Years,
288    Month,
289    Months,
290    /// Week optionally followed by a WEEKDAY.
291    ///
292    /// ```sql
293    /// WEEK(MONDAY)
294    /// ```
295    ///
296    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#extract)
297    Week(Option<Ident>),
298    Weeks,
299    Day,
300    DayOfWeek,
301    DayOfYear,
302    Days,
303    Date,
304    Datetime,
305    Hour,
306    Hours,
307    Minute,
308    Minutes,
309    Second,
310    Seconds,
311    Century,
312    Decade,
313    Dow,
314    Doy,
315    Epoch,
316    Isodow,
317    IsoWeek,
318    Isoyear,
319    Julian,
320    Microsecond,
321    Microseconds,
322    Millenium,
323    Millennium,
324    Millisecond,
325    Milliseconds,
326    Nanosecond,
327    Nanoseconds,
328    Quarter,
329    Time,
330    Timezone,
331    TimezoneAbbr,
332    TimezoneHour,
333    TimezoneMinute,
334    TimezoneRegion,
335    NoDateTime,
336    /// Arbitrary abbreviation or custom date-time part.
337    ///
338    /// ```sql
339    /// EXTRACT(q FROM CURRENT_TIMESTAMP)
340    /// ```
341    /// [Snowflake](https://docs.snowflake.com/en/sql-reference/functions-date-time#supported-date-and-time-parts)
342    Custom(Ident),
343}
344
345impl fmt::Display for DateTimeField {
346    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
347        match self {
348            DateTimeField::Year => write!(f, "YEAR"),
349            DateTimeField::Years => write!(f, "YEARS"),
350            DateTimeField::Month => write!(f, "MONTH"),
351            DateTimeField::Months => write!(f, "MONTHS"),
352            DateTimeField::Week(week_day) => {
353                write!(f, "WEEK")?;
354                if let Some(week_day) = week_day {
355                    write!(f, "({week_day})")?
356                }
357                Ok(())
358            }
359            DateTimeField::Weeks => write!(f, "WEEKS"),
360            DateTimeField::Day => write!(f, "DAY"),
361            DateTimeField::DayOfWeek => write!(f, "DAYOFWEEK"),
362            DateTimeField::DayOfYear => write!(f, "DAYOFYEAR"),
363            DateTimeField::Days => write!(f, "DAYS"),
364            DateTimeField::Date => write!(f, "DATE"),
365            DateTimeField::Datetime => write!(f, "DATETIME"),
366            DateTimeField::Hour => write!(f, "HOUR"),
367            DateTimeField::Hours => write!(f, "HOURS"),
368            DateTimeField::Minute => write!(f, "MINUTE"),
369            DateTimeField::Minutes => write!(f, "MINUTES"),
370            DateTimeField::Second => write!(f, "SECOND"),
371            DateTimeField::Seconds => write!(f, "SECONDS"),
372            DateTimeField::Century => write!(f, "CENTURY"),
373            DateTimeField::Decade => write!(f, "DECADE"),
374            DateTimeField::Dow => write!(f, "DOW"),
375            DateTimeField::Doy => write!(f, "DOY"),
376            DateTimeField::Epoch => write!(f, "EPOCH"),
377            DateTimeField::Isodow => write!(f, "ISODOW"),
378            DateTimeField::Isoyear => write!(f, "ISOYEAR"),
379            DateTimeField::IsoWeek => write!(f, "ISOWEEK"),
380            DateTimeField::Julian => write!(f, "JULIAN"),
381            DateTimeField::Microsecond => write!(f, "MICROSECOND"),
382            DateTimeField::Microseconds => write!(f, "MICROSECONDS"),
383            DateTimeField::Millenium => write!(f, "MILLENIUM"),
384            DateTimeField::Millennium => write!(f, "MILLENNIUM"),
385            DateTimeField::Millisecond => write!(f, "MILLISECOND"),
386            DateTimeField::Milliseconds => write!(f, "MILLISECONDS"),
387            DateTimeField::Nanosecond => write!(f, "NANOSECOND"),
388            DateTimeField::Nanoseconds => write!(f, "NANOSECONDS"),
389            DateTimeField::Quarter => write!(f, "QUARTER"),
390            DateTimeField::Time => write!(f, "TIME"),
391            DateTimeField::Timezone => write!(f, "TIMEZONE"),
392            DateTimeField::TimezoneAbbr => write!(f, "TIMEZONE_ABBR"),
393            DateTimeField::TimezoneHour => write!(f, "TIMEZONE_HOUR"),
394            DateTimeField::TimezoneMinute => write!(f, "TIMEZONE_MINUTE"),
395            DateTimeField::TimezoneRegion => write!(f, "TIMEZONE_REGION"),
396            DateTimeField::NoDateTime => write!(f, "NODATETIME"),
397            DateTimeField::Custom(custom) => write!(f, "{custom}"),
398        }
399    }
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
403#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
404#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
405/// The Unicode Standard defines four normalization forms, which are intended to eliminate
406/// certain distinctions between visually or functionally identical characters.
407///
408/// See [Unicode Normalization Forms](https://unicode.org/reports/tr15/) for details.
409pub enum NormalizationForm {
410    /// Canonical Decomposition, followed by Canonical Composition.
411    NFC,
412    /// Canonical Decomposition.
413    NFD,
414    /// Compatibility Decomposition, followed by Canonical Composition.
415    NFKC,
416    /// Compatibility Decomposition.
417    NFKD,
418}
419
420impl fmt::Display for NormalizationForm {
421    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
422        match self {
423            NormalizationForm::NFC => write!(f, "NFC"),
424            NormalizationForm::NFD => write!(f, "NFD"),
425            NormalizationForm::NFKC => write!(f, "NFKC"),
426            NormalizationForm::NFKD => write!(f, "NFKD"),
427        }
428    }
429}
430
431pub struct EscapeQuotedString<'a> {
432    string: &'a str,
433    quote: char,
434}
435
436impl fmt::Display for EscapeQuotedString<'_> {
437    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
438        // EscapeQuotedString doesn't know which mode of escape was
439        // chosen by the user. So this code must to correctly display
440        // strings without knowing if the strings are already escaped
441        // or not.
442        //
443        // If the quote symbol in the string is repeated twice, OR, if
444        // the quote symbol is after backslash, display all the chars
445        // without any escape. However, if the quote symbol is used
446        // just between usual chars, `fmt()` should display it twice."
447        //
448        // The following table has examples
449        //
450        // | original query | mode      | AST Node                                           | serialized   |
451        // | -------------  | --------- | -------------------------------------------------- | ------------ |
452        // | `"A""B""A"`    | no-escape | `DoubleQuotedString(String::from("A\"\"B\"\"A"))`  | `"A""B""A"`  |
453        // | `"A""B""A"`    | default   | `DoubleQuotedString(String::from("A\"B\"A"))`      | `"A""B""A"`  |
454        // | `"A\"B\"A"`    | no-escape | `DoubleQuotedString(String::from("A\\\"B\\\"A"))`  | `"A\"B\"A"`  |
455        // | `"A\"B\"A"`    | default   | `DoubleQuotedString(String::from("A\"B\"A"))`      | `"A""B""A"`  |
456        let quote = self.quote;
457        let mut previous_char = char::default();
458        let mut start_idx = 0;
459        let mut peekable_chars = self.string.char_indices().peekable();
460        while let Some(&(idx, ch)) = peekable_chars.peek() {
461            match ch {
462                char if char == quote => {
463                    if previous_char == '\\' {
464                        // the quote is already escaped with a backslash, skip
465                        peekable_chars.next();
466                        continue;
467                    }
468                    peekable_chars.next();
469                    match peekable_chars.peek() {
470                        Some((_, c)) if *c == quote => {
471                            // the quote is already escaped with another quote, skip
472                            peekable_chars.next();
473                        }
474                        _ => {
475                            // The quote is not escaped.
476                            // Including idx in the range, so the quote at idx will be printed twice:
477                            // in this call to write_str() and in the next one.
478                            f.write_str(&self.string[start_idx..=idx])?;
479                            start_idx = idx;
480                        }
481                    }
482                }
483                _ => {
484                    peekable_chars.next();
485                }
486            }
487            previous_char = ch;
488        }
489        f.write_str(&self.string[start_idx..])?;
490        Ok(())
491    }
492}
493
494pub fn escape_quoted_string(string: &str, quote: char) -> EscapeQuotedString<'_> {
495    EscapeQuotedString { string, quote }
496}
497
498pub fn escape_single_quote_string(s: &str) -> EscapeQuotedString<'_> {
499    escape_quoted_string(s, '\'')
500}
501
502pub fn escape_double_quote_string(s: &str) -> EscapeQuotedString<'_> {
503    escape_quoted_string(s, '\"')
504}
505
506pub struct EscapeEscapedStringLiteral<'a>(&'a str);
507
508impl fmt::Display for EscapeEscapedStringLiteral<'_> {
509    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
510        for c in self.0.chars() {
511            match c {
512                '\'' => {
513                    write!(f, r#"\'"#)?;
514                }
515                '\\' => {
516                    write!(f, r#"\\"#)?;
517                }
518                '\n' => {
519                    write!(f, r#"\n"#)?;
520                }
521                '\t' => {
522                    write!(f, r#"\t"#)?;
523                }
524                '\r' => {
525                    write!(f, r#"\r"#)?;
526                }
527                _ => {
528                    write!(f, "{c}")?;
529                }
530            }
531        }
532        Ok(())
533    }
534}
535
536pub fn escape_escaped_string(s: &str) -> EscapeEscapedStringLiteral<'_> {
537    EscapeEscapedStringLiteral(s)
538}
539
540pub struct EscapeUnicodeStringLiteral<'a>(&'a str);
541
542impl fmt::Display for EscapeUnicodeStringLiteral<'_> {
543    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
544        for c in self.0.chars() {
545            match c {
546                '\'' => {
547                    write!(f, "''")?;
548                }
549                '\\' => {
550                    write!(f, r#"\\"#)?;
551                }
552                x if x.is_ascii() => {
553                    write!(f, "{c}")?;
554                }
555                _ => {
556                    let codepoint = c as u32;
557                    // if the character fits in 32 bits, we can use the \XXXX format
558                    // otherwise, we need to use the \+XXXXXX format
559                    if codepoint <= 0xFFFF {
560                        write!(f, "\\{codepoint:04X}")?;
561                    } else {
562                        write!(f, "\\+{codepoint:06X}")?;
563                    }
564                }
565            }
566        }
567        Ok(())
568    }
569}
570
571pub fn escape_unicode_string(s: &str) -> EscapeUnicodeStringLiteral<'_> {
572    EscapeUnicodeStringLiteral(s)
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
576#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
577#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
578pub enum TrimWhereField {
579    Both,
580    Leading,
581    Trailing,
582}
583
584impl fmt::Display for TrimWhereField {
585    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
586        use TrimWhereField::*;
587        f.write_str(match self {
588            Both => "BOTH",
589            Leading => "LEADING",
590            Trailing => "TRAILING",
591        })
592    }
593}