mz_sql_parser/
ident.rs

1// Copyright 2018 sqlparser-rs contributors. All rights reserved.
2// Copyright Materialize, Inc. and contributors. All rights reserved.
3//
4// This file is derived from the sqlparser-rs project, available at
5// https://github.com/andygrove/sqlparser-rs. It was incorporated
6// directly into Materialize on December 21, 2019.
7//
8// Licensed under the Apache License, Version 2.0 (the "License");
9// you may not use this file except in compliance with the License.
10// You may obtain a copy of the License in the LICENSE file at the
11// root of this repository, or online at
12//
13//     http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20
21/// A macro that creates an [`Ident`] from a string literal, validating all of our invariants at
22/// compile time.
23///
24/// # Examples
25///
26/// ```
27/// use mz_sql_parser::ident;
28///
29/// // Checks invariants at compile time, returning an `Ident`.
30/// let id = ident!("my_postgres_source");
31///
32/// assert_eq!(id.as_str(), "my_postgres_source");
33/// ```
34///
35/// ```compile_fail
36/// use mz_sql_parser::ident;
37///
38/// const TOO_LONG: &str = "I am a very long identifier that is more than 255 characters long which is the max length for idents.\
39/// I am a very long identifier that is more than 255 characters long which is the max length for idents.\
40/// Use that sentance twice since 255 characters is actually a lot.";
41///
42/// // This fails to build since the string is too long.
43/// let _id = ident!(TOO_LONG);
44/// ```
45///
46/// ```compile_fail
47/// use mz_sql_parser::ident;
48/// const FORBIDDEN: &str = ".";
49/// ident!(FORBIDDEN);
50/// ```
51///
52/// ```ignore
53/// use mz_sql_parser::ident;
54/// const FORBIDDEN: &str = "..";
55/// ident!(FORBIDDEN);
56/// ```
57///
58/// [`Ident`]: crate::ast::Ident
59///
60#[macro_export]
61macro_rules! ident {
62    ($val:expr) => {{
63        let _x: &'static str = $val;
64        $crate::ident!(@internal_check 255, $val);
65
66        $crate::ast::Ident::new_unchecked($val)
67    }};
68
69    // Internal helper macro to assert the length of the provided string literal is less than our
70    // maximum.
71    (@internal_check $max_len:literal, $val:expr) => {{
72        #[allow(dead_code)]
73        const fn check_len<const MAX: usize, const LEN: usize>() {
74            if LEN > MAX {
75                panic!(stringify!(length of provided string literal, $val, is greater than specified max of $max_len));
76            }
77        }
78
79        #[allow(dead_code)]
80        const fn check_value(val: &str) {
81            if equal(val, ".") || equal(val, "..") {
82                panic!(stringify!(provided string literal, $val, is an invalid identifier));
83            }
84        }
85
86        // `str` does not implement `const PartialEq`, which is why we need to
87        // hand roll this equals function.
88        const fn equal(lhs: &str, rhs: &str) -> bool {
89            let lhs = lhs.as_bytes();
90            let rhs = rhs.as_bytes();
91            if lhs.len() != rhs.len() {
92                return false;
93            }
94            let mut i = 0;
95            while i < lhs.len() {
96                if lhs[i] != rhs[i] {
97                    return false;
98                }
99                i += 1;
100            }
101            true
102        }
103
104        const _X_VAL: &str = $val;
105        const _X_LEN: usize = _X_VAL.len();
106        const _X_CHECK_LEN: () = check_len::<$max_len, _X_LEN>();
107        const _X_CHECK_VALUE: () = check_value(_X_VAL);
108    }}
109}