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}