Skip to content

Commit 637c9b3

Browse files
committed
feat(native): extract raw and expanded SQL tests with one native extractor
1 parent 2dfc4b7 commit 637c9b3

48 files changed

Lines changed: 2845 additions & 1795 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//! Whether macro expansion would expand a call in one unexpanded SQL test body.
2+
3+
use sqlbuild_core::text::main::is_python_space::is_python_space;
4+
use sqlbuild_sqltext::sql_scan::main::matching_paren::matching_paren;
5+
use sqlbuild_sqltext::sql_scan::main::non_code_end::non_code_end;
6+
use sqlbuild_sqltext::sql_scan::models::QuotePolicy;
7+
8+
/// Declaration calls that expansion substitutes rather than running as macros.
9+
const DECLARATION_CALLS: [&str; 2] = ["enum", "const"];
10+
11+
/// Whether expansion's call scanner finds a macro call; text it cannot scan leaves expansion to fail.
12+
pub(crate) fn calls_macro(sql: &str) -> bool {
13+
if !sql.contains('@') {
14+
return false;
15+
}
16+
let bytes = sql.as_bytes();
17+
let mut index = 0;
18+
while index < bytes.len() {
19+
match non_code_end(bytes, index, QuotePolicy::COMPILER) {
20+
Ok(Some(end)) => {
21+
index = end;
22+
continue;
23+
}
24+
Ok(None) => {}
25+
Err(_) => return false,
26+
}
27+
if bytes[index] == b'@'
28+
&& let Some(open) = call_open(sql, index)
29+
{
30+
if !DECLARATION_CALLS.contains(&name_at(sql, index + 1)) {
31+
return true;
32+
}
33+
let Ok(close) = matching_paren(bytes, open, QuotePolicy::COMPILER) else {
34+
return false;
35+
};
36+
index = close + 1;
37+
continue;
38+
}
39+
index += char_at(sql, index).map_or(1, char::len_utf8);
40+
}
41+
false
42+
}
43+
44+
/// Python `_is_macro_call_start`, which also skips whitespace between name characters.
45+
fn call_open(sql: &str, at: usize) -> Option<usize> {
46+
let first = char_at(sql, at + 1)?;
47+
if !(first == '_' || (first.is_alphabetic() && !first.is_numeric())) {
48+
return None;
49+
}
50+
let mut cursor = at + 1 + first.len_utf8();
51+
while let Some(character) = char_at(sql, cursor) {
52+
if !is_identifier_continue(character) {
53+
break;
54+
}
55+
cursor = space_end(sql, cursor + character.len_utf8());
56+
}
57+
(sql.as_bytes().get(cursor) == Some(&b'(')).then_some(cursor)
58+
}
59+
60+
fn name_at(sql: &str, start: usize) -> &str {
61+
let mut end = start;
62+
while let Some(character) = char_at(sql, end) {
63+
if !is_identifier_continue(character) {
64+
break;
65+
}
66+
end += character.len_utf8();
67+
}
68+
&sql[start..end]
69+
}
70+
71+
fn is_identifier_continue(character: char) -> bool {
72+
character == '_' || character.is_alphanumeric()
73+
}
74+
75+
fn space_end(sql: &str, start: usize) -> usize {
76+
let mut index = start;
77+
while let Some(character) = char_at(sql, index) {
78+
if !is_python_space(character) {
79+
break;
80+
}
81+
index += character.len_utf8();
82+
}
83+
index
84+
}
85+
86+
fn char_at(sql: &str, index: usize) -> Option<char> {
87+
sql.get(index..).and_then(|rest| rest.chars().next())
88+
}

0 commit comments

Comments
 (0)