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!(
76 length of provided string literal, $val,
77 is greater than specified max of $max_len
78 ));
79 }
80 }
81
82 #[allow(dead_code)]
83 const fn check_value(val: &str) {
84 if equal(val, ".") || equal(val, "..") {
85 panic!(stringify!(provided string literal, $val, is an invalid identifier));
86 }
87 }
88
89 // `str` does not implement `const PartialEq`, which is why we need to
90 // hand roll this equals function.
91 const fn equal(lhs: &str, rhs: &str) -> bool {
92 let lhs = lhs.as_bytes();
93 let rhs = rhs.as_bytes();
94 if lhs.len() != rhs.len() {
95 return false;
96 }
97 let mut i = 0;
98 while i < lhs.len() {
99 if lhs[i] != rhs[i] {
100 return false;
101 }
102 i += 1;
103 }
104 true
105 }
106
107 const _X_VAL: &str = $val;
108 const _X_LEN: usize = _X_VAL.len();
109 const _X_CHECK_LEN: () = check_len::<$max_len, _X_LEN>();
110 const _X_CHECK_VALUE: () = check_value(_X_VAL);
111 }}
112}