1mod argument;
19mod cast;
20mod operator;
21mod returning;
22
23pub use argument::PgExternArgumentEntity;
24pub use cast::PgCastEntity;
25pub use operator::PgOperatorEntity;
26pub use returning::{PgExternReturnEntity, PgExternReturnEntityIteratedItem};
27
28use crate::fmt;
29use crate::metadata::{Returns, SqlMapping};
30use crate::pgrx_sql::PgrxSql;
31use crate::to_sql::ToSql;
32use crate::to_sql::entity::ToSqlConfigEntity;
33use crate::{ExternArgs, SqlGraphEntity, SqlGraphIdentifier, TypeMatch};
34
35use eyre::{WrapErr, eyre};
36
37#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
39pub struct PgExternEntity {
40 pub name: &'static str,
41 pub unaliased_name: &'static str,
42 pub module_path: &'static str,
43 pub full_path: &'static str,
44 pub metadata: crate::metadata::FunctionMetadataEntity,
45 pub fn_args: Vec<PgExternArgumentEntity>,
46 pub fn_return: PgExternReturnEntity,
47 pub schema: Option<&'static str>,
48 pub file: &'static str,
49 pub line: u32,
50 pub extern_attrs: Vec<ExternArgs>,
51 pub search_path: Option<Vec<&'static str>>,
52 pub operator: Option<PgOperatorEntity>,
53 pub cast: Option<PgCastEntity>,
54 pub to_sql_config: ToSqlConfigEntity,
55}
56
57impl From<PgExternEntity> for SqlGraphEntity {
58 fn from(val: PgExternEntity) -> Self {
59 SqlGraphEntity::Function(val)
60 }
61}
62
63impl SqlGraphIdentifier for PgExternEntity {
64 fn dot_identifier(&self) -> String {
65 format!("fn {}", self.name)
66 }
67 fn rust_identifier(&self) -> String {
68 self.full_path.to_string()
69 }
70
71 fn file(&self) -> Option<&'static str> {
72 Some(self.file)
73 }
74
75 fn line(&self) -> Option<u32> {
76 Some(self.line)
77 }
78}
79
80impl PgExternEntity {
81 fn sql_name(&self, context: &PgrxSql) -> String {
82 let self_index = context.externs[self];
83 let schema = self
84 .schema
85 .map(|schema| format!("{schema}."))
86 .unwrap_or_else(|| context.schema_prefix_for(&self_index));
87
88 format!("{schema}\"{}\"", self.name)
89 }
90}
91
92impl ToSql for PgExternEntity {
93 fn to_sql(&self, context: &PgrxSql) -> eyre::Result<String> {
94 let self_index = context.externs[self];
95 let mut extern_attrs = self.extern_attrs.clone();
96 let mut strict_upgrade = !extern_attrs.iter().any(|i| i == &ExternArgs::Strict);
99 if strict_upgrade {
100 for arg in &self.metadata.arguments {
103 if arg.optional {
104 strict_upgrade = false;
105 }
106 }
107 }
108
109 if strict_upgrade {
110 extern_attrs.push(ExternArgs::Strict);
111 }
112 extern_attrs.sort();
113 extern_attrs.dedup();
114
115 let module_pathname = &context.get_module_pathname();
116 let schema = self
117 .schema
118 .map(|schema| format!("{schema}."))
119 .unwrap_or_else(|| context.schema_prefix_for(&self_index));
120 let arguments = if !self.fn_args.is_empty() {
121 let mut args = Vec::new();
122 let metadata_without_arg_skips = &self
123 .metadata
124 .arguments
125 .iter()
126 .filter(|v| v.argument_sql != Ok(SqlMapping::Skip))
127 .collect::<Vec<_>>();
128 for (idx, arg) in self.fn_args.iter().enumerate() {
129 let graph_index = context
130 .graph
131 .neighbors_undirected(self_index)
132 .find(|neighbor| context.graph[*neighbor].type_matches(&arg.used_ty))
133 .ok_or_else(|| eyre!("Could not find arg type in graph. Got: {:?}", arg))?;
134 let needs_comma = idx < (metadata_without_arg_skips.len().saturating_sub(1));
135 let metadata_argument = &self.metadata.arguments[idx];
136 match metadata_argument.argument_sql {
137 Ok(SqlMapping::As(ref argument_sql)) => {
138 let buf = format!(
139 "\
140 \t\"{pattern}\" {variadic}{schema_prefix}{sql_type}{default}{maybe_comma}/* {type_name} */\
141 ",
142 pattern = arg.pattern,
143 schema_prefix = context.schema_prefix_for(&graph_index),
144 sql_type = argument_sql,
146 default = if let Some(def) = arg.used_ty.default {
147 format!(" DEFAULT {def}")
148 } else {
149 String::from("")
150 },
151 variadic = if metadata_argument.variadic { "VARIADIC " } else { "" },
152 maybe_comma = if needs_comma { ", " } else { " " },
153 type_name = metadata_argument.type_name,
154 );
155 args.push(buf);
156 }
157 Ok(SqlMapping::Composite { array_brackets }) => {
158 let sql = self.fn_args[idx]
159 .used_ty
160 .composite_type
161 .map(|v| fmt::with_array_brackets(v.into(), array_brackets))
162 .ok_or_else(|| {
163 eyre!(
164 "Macro expansion time suggested a composite_type!() in return"
165 )
166 })?;
167 let buf = format!(
168 "\
169 \t\"{pattern}\" {variadic}{schema_prefix}{sql_type}{default}{maybe_comma}/* {type_name} */\
170 ",
171 pattern = arg.pattern,
172 schema_prefix = context.schema_prefix_for(&graph_index),
173 sql_type = sql,
175 default = if let Some(def) = arg.used_ty.default {
176 format!(" DEFAULT {def}")
177 } else {
178 String::from("")
179 },
180 variadic = if metadata_argument.variadic { "VARIADIC " } else { "" },
181 maybe_comma = if needs_comma { ", " } else { " " },
182 type_name = metadata_argument.type_name,
183 );
184 args.push(buf);
185 }
186 Ok(SqlMapping::Skip) => (),
187 Err(err) => return Err(err).wrap_err("While mapping argument"),
188 }
189 }
190 String::from("\n") + &args.join("\n") + "\n"
191 } else {
192 Default::default()
193 };
194
195 let returns = match &self.fn_return {
196 PgExternReturnEntity::None => String::from("RETURNS void"),
197 PgExternReturnEntity::Type { ty } => {
198 let graph_index = context
199 .graph
200 .neighbors_undirected(self_index)
201 .find(|neighbor| context.graph[*neighbor].type_matches(ty))
202 .ok_or_else(|| eyre!("Could not find return type in graph."))?;
203 let metadata_retval = self.metadata.retval.clone();
204 let sql_type = match metadata_retval.return_sql {
205 Ok(Returns::One(SqlMapping::As(ref sql))) => sql.clone(),
206 Ok(Returns::One(SqlMapping::Composite { array_brackets })) => {
207 fmt::with_array_brackets(ty.composite_type.unwrap().into(), array_brackets)
208 }
209 Ok(other) => {
210 return Err(eyre!(
211 "Got non-plain mapped/composite return variant SQL in what macro-expansion thought was a type, got: {other:?}"
212 ));
213 }
214 Err(err) => return Err(err).wrap_err("Error mapping return SQL"),
215 };
216 format!(
217 "RETURNS {schema_prefix}{sql_type} /* {full_path} */",
218 schema_prefix = context.schema_prefix_for(&graph_index),
219 full_path = ty.full_path
220 )
221 }
222 PgExternReturnEntity::SetOf { ty, .. } => {
223 let graph_index = context
224 .graph
225 .neighbors_undirected(self_index)
226 .find(|neighbor| context.graph[*neighbor].type_matches(ty))
227 .ok_or_else(|| eyre!("Could not find return type in graph."))?;
228 let metadata_retval = self.metadata.retval.clone();
229 let sql_type = match metadata_retval.return_sql {
230 Ok(Returns::SetOf(SqlMapping::As(ref sql))) => sql.clone(),
231 Ok(Returns::SetOf(SqlMapping::Composite { array_brackets })) => {
232 fmt::with_array_brackets(ty.composite_type.unwrap().into(), array_brackets)
233 }
234 Ok(_other) => {
235 return Err(eyre!(
236 "Got non-setof mapped/composite return variant SQL in what macro-expansion thought was a setof"
237 ));
238 }
239 Err(err) => return Err(err).wrap_err("Error mapping return SQL"),
240 };
241 format!(
242 "RETURNS SETOF {schema_prefix}{sql_type} /* {full_path} */",
243 schema_prefix = context.schema_prefix_for(&graph_index),
244 full_path = ty.full_path
245 )
246 }
247 PgExternReturnEntity::Iterated { tys: table_items, .. } => {
248 let mut items = String::new();
249 let metadata_retval = self.metadata.retval.clone();
250 let metadata_retval_sqls: Vec<String> = match metadata_retval.return_sql {
251 Ok(Returns::Table(variants)) => variants
252 .iter()
253 .enumerate()
254 .map(|(idx, variant)| match variant {
255 SqlMapping::As(sql) => sql.clone(),
256 SqlMapping::Composite { array_brackets } => {
257 let composite = table_items[idx].ty.composite_type.unwrap();
258 fmt::with_array_brackets(composite.into(), *array_brackets)
259 }
260 SqlMapping::Skip => todo!(),
261 })
262 .collect(),
263 Ok(_other) => {
264 return Err(eyre!(
265 "Got non-table return variant SQL in what macro-expansion thought was a table"
266 ));
267 }
268 Err(err) => return Err(err).wrap_err("Error mapping return SQL"),
269 };
270
271 for (idx, returning::PgExternReturnEntityIteratedItem { ty, name: col_name }) in
272 table_items.iter().enumerate()
273 {
274 let graph_index =
275 context.graph.neighbors_undirected(self_index).find(|neighbor| {
276 context.graph[*neighbor].id_or_name_matches(&ty.ty_id, ty.ty_source)
277 });
278
279 let needs_comma = idx < (table_items.len() - 1);
280 let item = format!(
281 "\n\t{col_name} {schema_prefix}{ty_resolved}{needs_comma} /* {ty_name} */",
282 col_name = col_name.expect(
283 "An iterator of tuples should have `named!()` macro declarations."
284 ),
285 schema_prefix = if let Some(graph_index) = graph_index {
286 context.schema_prefix_for(&graph_index)
287 } else {
288 "".into()
289 },
290 ty_resolved = metadata_retval_sqls[idx],
291 needs_comma = if needs_comma { ", " } else { " " },
292 ty_name = ty.full_path
293 );
294 items.push_str(&item);
295 }
296 format!("RETURNS TABLE ({items}\n)")
297 }
298 PgExternReturnEntity::Trigger => String::from("RETURNS trigger"),
299 };
300 let PgExternEntity { name, module_path, file, line, .. } = self;
301
302 let fn_sql = format!(
303 "\
304 CREATE {or_replace} FUNCTION {schema}\"{name}\"({arguments}) {returns}\n\
305 {extern_attrs}\
306 {search_path}\
307 LANGUAGE c /* Rust */\n\
308 AS '{module_pathname}', '{unaliased_name}_wrapper';\
309 ",
310 or_replace =
311 if extern_attrs.contains(&ExternArgs::CreateOrReplace) { "OR REPLACE" } else { "" },
312 search_path = if let Some(search_path) = &self.search_path {
313 let retval = format!("SET search_path TO {}", search_path.join(", "));
314 retval + "\n"
315 } else {
316 Default::default()
317 },
318 extern_attrs = if extern_attrs.is_empty() {
319 String::default()
320 } else {
321 let mut retval = extern_attrs
322 .iter()
323 .filter(|attr| **attr != ExternArgs::CreateOrReplace)
324 .map(|attr| {
325 if matches!(attr, ExternArgs::Support(..)) {
326 let support_fn_name = attr.to_string();
327
328 let support_fn_name =
329 if let Some(entity) = context.find_matching_fn(&support_fn_name) {
330 entity.sql_name(context)
331 } else {
332 panic!("cannot locate SUPPORT function `{support_fn_name}` attached to function `{}`", self.full_path)
333 };
334
335 format!("SUPPORT {support_fn_name}")
336 } else {
337 attr.to_string().to_uppercase()
338 }
339 })
340 .collect::<Vec<_>>()
341 .join(" ");
342 retval.push('\n');
343 retval
344 },
345 unaliased_name = self.unaliased_name,
346 );
347
348 let requires = {
349 let requires_attrs = self
350 .extern_attrs
351 .iter()
352 .filter_map(|x| match x {
353 ExternArgs::Requires(requirements) => Some(requirements.clone()),
354 ExternArgs::Support(support_fn) => Some(vec![support_fn.clone()]),
355 _ => None,
356 })
357 .flatten()
358 .collect::<Vec<_>>();
359
360 if !requires_attrs.is_empty() {
361 format!(
362 "-- requires:\n{}\n",
363 requires_attrs
364 .iter()
365 .map(|i| format!("-- {i}"))
366 .collect::<Vec<_>>()
367 .join("\n")
368 )
369 } else {
370 "".to_string()
371 }
372 };
373
374 let mut ext_sql = format!(
375 "\n\
376 -- {file}:{line}\n\
377 -- {module_path}::{name}\n\
378 {requires}\
379 {fn_sql}"
380 );
381
382 if let Some(op) = &self.operator {
383 let mut optionals = vec![];
384 if let Some(it) = op.commutator {
385 optionals.push(format!("\tCOMMUTATOR = {it}"));
386 };
387 if let Some(it) = op.negator {
388 optionals.push(format!("\tNEGATOR = {it}"));
389 };
390 if let Some(it) = op.restrict {
391 optionals.push(format!("\tRESTRICT = {it}"));
392 };
393 if let Some(it) = op.join {
394 optionals.push(format!("\tJOIN = {it}"));
395 };
396 if op.hashes {
397 optionals.push(String::from("\tHASHES"));
398 };
399 if op.merges {
400 optionals.push(String::from("\tMERGES"));
401 };
402
403 let left_arg =
404 self.metadata.arguments.first().ok_or_else(|| {
405 eyre!("Did not find `left_arg` for operator `{}`.", self.name)
406 })?;
407 let left_fn_arg = self
408 .fn_args
409 .first()
410 .ok_or_else(|| eyre!("Did not find `left_arg` for operator `{}`.", self.name))?;
411 let left_arg_graph_index = context
412 .graph
413 .neighbors_undirected(self_index)
414 .find(|neighbor| {
415 context.graph[*neighbor]
416 .id_or_name_matches(&left_fn_arg.used_ty.ty_id, left_arg.type_name)
417 })
418 .ok_or_else(|| {
419 eyre!("Could not find left arg type in graph. Got: {:?}", left_arg)
420 })?;
421 let left_arg_sql = match left_arg.argument_sql {
422 Ok(SqlMapping::As(ref sql)) => sql.clone(),
423 Ok(SqlMapping::Composite { array_brackets }) => {
424 if array_brackets {
425 let composite_type = self.fn_args[0].used_ty.composite_type
426 .ok_or(eyre!("Found a composite type but macro expansion time did not reveal a name, use `pgrx::composite_type!()`"))?;
427 format!("{composite_type}[]")
428 } else {
429 self.fn_args[0].used_ty.composite_type
430 .ok_or(eyre!("Found a composite type but macro expansion time did not reveal a name, use `pgrx::composite_type!()`"))?.to_string()
431 }
432 }
433 Ok(SqlMapping::Skip) => {
434 return Err(eyre!(
435 "Found an skipped SQL type in an operator, this is not valid"
436 ));
437 }
438 Err(err) => return Err(err.into()),
439 };
440
441 let right_arg =
442 self.metadata.arguments.get(1).ok_or_else(|| {
443 eyre!("Did not find `left_arg` for operator `{}`.", self.name)
444 })?;
445 let right_fn_arg = self
446 .fn_args
447 .get(1)
448 .ok_or_else(|| eyre!("Did not find `left_arg` for operator `{}`.", self.name))?;
449 let right_arg_graph_index = context
450 .graph
451 .neighbors_undirected(self_index)
452 .find(|neighbor| {
453 context.graph[*neighbor]
454 .id_or_name_matches(&right_fn_arg.used_ty.ty_id, right_arg.type_name)
455 })
456 .ok_or_else(|| {
457 eyre!("Could not find right arg type in graph. Got: {:?}", right_arg)
458 })?;
459 let right_arg_sql = match right_arg.argument_sql {
460 Ok(SqlMapping::As(ref sql)) => sql.clone(),
461 Ok(SqlMapping::Composite { array_brackets }) => {
462 if array_brackets {
463 let composite_type = self.fn_args[1].used_ty.composite_type
464 .ok_or(eyre!("Found a composite type but macro expansion time did not reveal a name, use `pgrx::composite_type!()`"))?;
465 format!("{composite_type}[]")
466 } else {
467 self.fn_args[0].used_ty.composite_type
468 .ok_or(eyre!("Found a composite type but macro expansion time did not reveal a name, use `pgrx::composite_type!()`"))?.to_string()
469 }
470 }
471 Ok(SqlMapping::Skip) => {
472 return Err(eyre!(
473 "Found an skipped SQL type in an operator, this is not valid"
474 ));
475 }
476 Err(err) => return Err(err.into()),
477 };
478
479 let schema = self
480 .schema
481 .map(|schema| format!("{schema}."))
482 .unwrap_or_else(|| context.schema_prefix_for(&self_index));
483
484 let operator_sql = format!(
485 "\n\n\
486 -- {file}:{line}\n\
487 -- {module_path}::{name}\n\
488 CREATE OPERATOR {schema}{opname} (\n\
489 \tPROCEDURE={schema}\"{name}\",\n\
490 \tLEFTARG={schema_prefix_left}{left_arg_sql}, /* {left_name} */\n\
491 \tRIGHTARG={schema_prefix_right}{right_arg_sql}{maybe_comma} /* {right_name} */\n\
492 {optionals}\
493 );\
494 ",
495 opname = op.opname.unwrap(),
496 left_name = left_arg.type_name,
497 right_name = right_arg.type_name,
498 schema_prefix_left = context.schema_prefix_for(&left_arg_graph_index),
499 schema_prefix_right = context.schema_prefix_for(&right_arg_graph_index),
500 maybe_comma = if !optionals.is_empty() { "," } else { "" },
501 optionals = if !optionals.is_empty() {
502 optionals.join(",\n") + "\n"
503 } else {
504 "".to_string()
505 },
506 );
507 ext_sql += &operator_sql
508 };
509 if let Some(cast) = &self.cast {
510 let target_arg = &self.metadata.retval;
511 let target_fn_arg = &self.fn_return;
512 let target_arg_graph_index = context
513 .graph
514 .neighbors_undirected(self_index)
515 .find(|neighbor| match (&context.graph[*neighbor], target_fn_arg) {
516 (SqlGraphEntity::Type(ty), PgExternReturnEntity::Type { ty: rty }) => {
517 ty.id_matches(&rty.ty_id)
518 }
519 (SqlGraphEntity::Enum(en), PgExternReturnEntity::Type { ty: rty }) => {
520 en.id_matches(&rty.ty_id)
521 }
522 (SqlGraphEntity::BuiltinType(defined), _) => defined == target_arg.type_name,
523 _ => false,
524 })
525 .ok_or_else(|| {
526 eyre!("Could not find source type in graph. Got: {:?}", target_arg)
527 })?;
528 let target_arg_sql = match target_arg.argument_sql {
529 Ok(SqlMapping::As(ref sql)) => sql.clone(),
530 Ok(SqlMapping::Composite { array_brackets }) => {
531 if array_brackets {
532 let composite_type = self.fn_args[0].used_ty.composite_type
533 .ok_or(eyre!("Found a composite type but macro expansion time did not reveal a name, use `pgrx::composite_type!()`"))?;
534 format!("{composite_type}[]")
535 } else {
536 self.fn_args[0].used_ty.composite_type
537 .ok_or(eyre!("Found a composite type but macro expansion time did not reveal a name, use `pgrx::composite_type!()`"))?.to_string()
538 }
539 }
540 Ok(SqlMapping::Skip) => {
541 return Err(eyre!("Found an skipped SQL type in a cast, this is not valid"));
542 }
543 Err(err) => return Err(err.into()),
544 };
545 let source_arg = self
546 .metadata
547 .arguments
548 .first()
549 .ok_or_else(|| eyre!("Did not find source type for cast `{}`.", self.name))?;
550 let source_fn_arg = self
551 .fn_args
552 .first()
553 .ok_or_else(|| eyre!("Did not find source type for cast `{}`.", self.name))?;
554 let source_arg_graph_index = context
555 .graph
556 .neighbors_undirected(self_index)
557 .find(|neighbor| {
558 context.graph[*neighbor]
559 .id_or_name_matches(&source_fn_arg.used_ty.ty_id, source_arg.type_name)
560 })
561 .ok_or_else(|| {
562 eyre!("Could not find source type in graph. Got: {:?}", source_arg)
563 })?;
564 let source_arg_sql = match source_arg.argument_sql {
565 Ok(SqlMapping::As(ref sql)) => sql.clone(),
566 Ok(SqlMapping::Composite { array_brackets }) => {
567 if array_brackets {
568 let composite_type = self.fn_args[0].used_ty.composite_type
569 .ok_or(eyre!("Found a composite type but macro expansion time did not reveal a name, use `pgrx::composite_type!()`"))?;
570 format!("{composite_type}[]")
571 } else {
572 self.fn_args[0].used_ty.composite_type
573 .ok_or(eyre!("Found a composite type but macro expansion time did not reveal a name, use `pgrx::composite_type!()`"))?.to_string()
574 }
575 }
576 Ok(SqlMapping::Skip) => {
577 return Err(eyre!("Found an skipped SQL type in a cast, this is not valid"));
578 }
579 Err(err) => return Err(err.into()),
580 };
581 let optional = match cast {
582 PgCastEntity::Default => String::from(""),
583 PgCastEntity::Assignment => String::from(" AS ASSIGNMENT"),
584 PgCastEntity::Implicit => String::from(" AS IMPLICIT"),
585 };
586
587 let cast_sql = format!(
588 "\n\n\
589 -- {file}:{line}\n\
590 -- {module_path}::{name}\n\
591 CREATE CAST (\n\
592 \t{schema_prefix_source}{source_arg_sql} /* {source_name} */\n\
593 \tAS\n\
594 \t{schema_prefix_target}{target_arg_sql} /* {target_name} */\n\
595 )\n\
596 WITH FUNCTION {function_name}{optional};\
597 ",
598 file = self.file,
599 line = self.line,
600 name = self.name,
601 module_path = self.module_path,
602 schema_prefix_source = context.schema_prefix_for(&source_arg_graph_index),
603 source_name = source_arg.type_name,
604 schema_prefix_target = context.schema_prefix_for(&target_arg_graph_index),
605 target_name = target_arg.type_name,
606 function_name = self.name,
607 );
608 ext_sql += &cast_sql
609 };
610 Ok(ext_sql)
611 }
612}