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