1use crate::prelude::{String, Vec};
2use drizzle_core::{SQLIndexInfo, SQLSchemaType, SQLViewInfo, TableRef};
3use drizzle_types::mysql::ddl::{ViewAlgorithm, ViewCheckOption, ViewSqlSecurity};
4
5pub const SAVE_FOREIGN_KEY_CHECKS: &str =
13 "SET @drizzle_foreign_key_checks = @@SESSION.foreign_key_checks";
14
15pub const DISABLE_FOREIGN_KEY_CHECKS: &str = "SET FOREIGN_KEY_CHECKS = 0";
18
19pub const RESTORE_FOREIGN_KEY_CHECKS: &str = "SET FOREIGN_KEY_CHECKS = \
23 COALESCE(@drizzle_foreign_key_checks, @@SESSION.foreign_key_checks), \
24 @drizzle_foreign_key_checks = NULL";
25
26pub trait MySQLViewInfo: SQLViewInfo + core::fmt::Debug {
28 fn algorithm(&self) -> Option<ViewAlgorithm>;
30 fn sql_security(&self) -> Option<ViewSqlSecurity>;
32 fn check_option(&self) -> Option<ViewCheckOption>;
34}
35
36#[must_use]
43pub fn create_view_sql(view: &dyn MySQLViewInfo) -> String {
44 if view.is_existing() {
45 return String::new();
46 }
47
48 let mut sql = String::from("CREATE ");
49 if let Some(algorithm) = view.algorithm() {
50 sql.push_str(match algorithm {
51 ViewAlgorithm::Undefined => "ALGORITHM=UNDEFINED ",
52 ViewAlgorithm::Merge => "ALGORITHM=MERGE ",
53 ViewAlgorithm::Temptable => "ALGORITHM=TEMPTABLE ",
54 });
55 }
56 if let Some(security) = view.sql_security() {
57 sql.push_str(match security {
58 ViewSqlSecurity::Definer => "SQL SECURITY DEFINER ",
59 ViewSqlSecurity::Invoker => "SQL SECURITY INVOKER ",
60 });
61 }
62 sql.push_str("VIEW ");
63 if let Some(database) = drizzle_core::SQLTableInfo::schema(view) {
64 push_identifier(&mut sql, database);
65 sql.push('.');
66 }
67 push_identifier(&mut sql, drizzle_core::SQLTableInfo::name(view));
68 sql.push_str(" AS ");
69 sql.push_str(&view.definition_sql());
70 if let Some(check_option) = view.check_option() {
71 sql.push_str(match check_option {
72 ViewCheckOption::Cascaded => " WITH CASCADED CHECK OPTION",
73 ViewCheckOption::Local => " WITH LOCAL CHECK OPTION",
74 });
75 }
76 sql.push(';');
77 sql
78}
79
80fn flush_identifier(tokens: &mut Vec<String>, token: &mut String) {
81 if !token.is_empty() {
82 tokens.push(core::mem::take(token));
83 }
84}
85
86fn identifier_tokens(sql: &str) -> Vec<String> {
87 #[derive(Clone, Copy)]
88 enum State {
89 Sql,
90 QuotedIdentifier,
91 SingleQuotedString,
92 DoubleQuotedString,
93 LineComment,
94 BlockComment,
95 }
96
97 let mut tokens = Vec::new();
98 let mut token = String::new();
99 let characters: Vec<_> = sql.chars().collect();
100 let mut state = State::Sql;
101 let mut index = 0;
102
103 while index < characters.len() {
104 let character = characters[index];
105 match state {
106 State::Sql => match character {
107 '`' => {
108 flush_identifier(&mut tokens, &mut token);
109 state = State::QuotedIdentifier;
110 }
111 '\'' => {
112 flush_identifier(&mut tokens, &mut token);
113 state = State::SingleQuotedString;
114 }
115 '"' => {
116 flush_identifier(&mut tokens, &mut token);
117 state = State::DoubleQuotedString;
118 }
119 '#' => {
120 flush_identifier(&mut tokens, &mut token);
121 state = State::LineComment;
122 }
123 '-' if characters.get(index + 1) == Some(&'-')
124 && characters
125 .get(index + 2)
126 .is_none_or(|next| next.is_whitespace() || next.is_control()) =>
127 {
128 flush_identifier(&mut tokens, &mut token);
129 state = State::LineComment;
130 index += 1;
131 }
132 '/' if characters.get(index + 1) == Some(&'*') => {
133 flush_identifier(&mut tokens, &mut token);
134 state = State::BlockComment;
135 index += 1;
136 }
137 character if character.is_alphanumeric() || matches!(character, '_' | '$') => {
138 token.push(character);
139 }
140 _ => flush_identifier(&mut tokens, &mut token),
141 },
142 State::QuotedIdentifier => {
143 if character == '`' {
144 if characters.get(index + 1) == Some(&'`') {
145 token.push('`');
146 index += 1;
147 } else {
148 flush_identifier(&mut tokens, &mut token);
149 state = State::Sql;
150 }
151 } else {
152 token.push(character);
153 }
154 }
155 State::SingleQuotedString | State::DoubleQuotedString => {
156 let quote = match state {
157 State::SingleQuotedString => '\'',
158 State::DoubleQuotedString => '"',
159 _ => unreachable!(),
160 };
161 if character == '\\' {
162 index += usize::from(index + 1 < characters.len());
163 } else if character == quote {
164 if characters.get(index + 1) == Some("e) {
165 index += 1;
166 } else {
167 state = State::Sql;
168 }
169 }
170 }
171 State::LineComment => {
172 if matches!(character, '\n' | '\r') {
173 state = State::Sql;
174 }
175 }
176 State::BlockComment => {
177 if character == '*' && characters.get(index + 1) == Some(&'/') {
178 state = State::Sql;
179 index += 1;
180 }
181 }
182 }
183 index += 1;
184 }
185 flush_identifier(&mut tokens, &mut token);
186 tokens
187}
188
189#[doc(hidden)]
201pub fn order_schema_views(
202 views: &[&'static dyn MySQLViewInfo],
203) -> Result<Vec<String>, drizzle_core::error::DrizzleError> {
204 let mut pending: Vec<_> = views
205 .iter()
206 .copied()
207 .filter(|view| !view.is_existing())
208 .collect();
209 pending.sort_by(|left, right| left.qualified_name().cmp(&right.qualified_name()));
210
211 let mut statements = Vec::with_capacity(pending.len());
212 while !pending.is_empty() {
213 let ready = pending.iter().position(|view| {
214 let tokens = identifier_tokens(&view.definition_sql());
215 !pending.iter().any(|candidate| {
216 !core::ptr::eq(*view, *candidate)
217 && tokens.iter().any(|token| token == candidate.name())
218 })
219 });
220 let Some(ready) = ready else {
221 let names = pending
222 .iter()
223 .map(|view| view.qualified_name().into_owned())
224 .collect::<Vec<_>>()
225 .join(", ");
226 let mut message = String::from("Cyclic view dependency detected in MySQLSchema: ");
227 message.push_str(&names);
228 return Err(drizzle_core::error::DrizzleError::Statement(message.into()));
229 };
230 statements.push(create_view_sql(pending.remove(ready)));
231 }
232 Ok(statements)
233}
234
235fn push_identifier(sql: &mut String, identifier: &str) {
236 sql.push('`');
237 for character in identifier.chars() {
238 if character == '`' {
239 sql.push('`');
240 }
241 sql.push(character);
242 }
243 sql.push('`');
244}
245
246#[derive(Debug, Clone)]
252pub enum MySQLSchemaType {
253 Table(&'static TableRef),
255 Index(&'static dyn SQLIndexInfo),
257 View(&'static dyn MySQLViewInfo),
259}
260
261impl SQLSchemaType for MySQLSchemaType {}