1#![doc = include_str!("../ARCHITECTURE.md")]
38use std::sync::OnceLock;
99use std::{collections::HashMap, path::PathBuf, str::FromStr};
100
101use anstream::adapter::strip_str;
102use semver::Version;
103use serde::{Deserialize, Serialize};
104use strum::VariantNames;
105
106pub use error_message::{ErrorMessage, ErrorMessages, SourceLocation};
107pub use prqlc_parser::error::{Error, ErrorSource, Errors, MessageKind, Reason, WithErrorInfo};
108pub use prqlc_parser::lexer::lr;
109pub use prqlc_parser::parser::pr;
110pub use prqlc_parser::span::Span;
111
112mod codegen;
113pub mod debug;
114mod error_message;
115pub mod ir;
116pub mod parser;
117pub mod semantic;
118pub mod sql;
119#[cfg(feature = "cli")]
120pub mod utils;
121#[cfg(not(feature = "cli"))]
122pub(crate) mod utils;
123
124pub type Result<T, E = Error> = core::result::Result<T, E>;
125
126pub fn compiler_version() -> Version {
138 if let Ok(prql_version_override) = std::env::var("PRQL_VERSION_OVERRIDE") {
139 return Version::parse(&prql_version_override).unwrap_or_else(|e| {
140 panic!("Could not parse PRQL version {prql_version_override}\n{e}")
141 });
142 };
143
144 static COMPILER_VERSION: OnceLock<Version> = OnceLock::new();
145 COMPILER_VERSION
146 .get_or_init(|| {
147 if let Ok(prql_version_override) = std::env::var("PRQL_VERSION_OVERRIDE") {
148 return Version::parse(&prql_version_override).unwrap_or_else(|e| {
149 panic!("Could not parse PRQL version {prql_version_override}\n{e}")
150 });
151 }
152 let git_version = env!("VERGEN_GIT_DESCRIBE");
153 let cargo_version = env!("CARGO_PKG_VERSION");
154 Version::parse(git_version)
155 .or_else(|e| {
156 log::info!("Could not parse git version number {git_version}\n{e}");
157 Version::parse(cargo_version)
158 })
159 .unwrap_or_else(|e| {
160 panic!("Could not parse prqlc version number {cargo_version}\n{e}")
161 })
162 })
163 .clone()
164}
165
166pub fn compile(prql: &str, options: &Options) -> Result<String, ErrorMessages> {
190 let sources = SourceTree::from(prql);
191
192 Ok(&sources)
193 .and_then(parser::parse)
194 .and_then(|ast| {
195 semantic::resolve_and_lower(ast, &[], None)
196 .map_err(|e| e.with_source(ErrorSource::NameResolver).into())
197 })
198 .and_then(|rq| {
199 sql::compile(rq, options).map_err(|e| e.with_source(ErrorSource::SQL).into())
200 })
201 .map_err(|e| {
202 let error_messages = ErrorMessages::from(e).composed(&sources);
203 match options.display {
204 DisplayOptions::AnsiColor => error_messages,
205 DisplayOptions::Plain => ErrorMessages {
206 inner: error_messages
207 .inner
208 .into_iter()
209 .map(|e| ErrorMessage {
210 display: e.display.map(|s| strip_str(&s).to_string()),
211 ..e
212 })
213 .collect(),
214 },
215 }
216 })
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub enum Target {
221 Sql(Option<sql::Dialect>),
223}
224
225impl Default for Target {
226 fn default() -> Self {
227 Self::Sql(None)
228 }
229}
230
231impl Target {
232 pub fn names() -> Vec<String> {
233 let mut names = vec!["sql.any".to_string()];
234
235 let dialects = sql::Dialect::VARIANTS;
236 names.extend(dialects.iter().map(|d| format!("sql.{d}")));
237
238 names
239 }
240}
241
242impl FromStr for Target {
243 type Err = Error;
244
245 fn from_str(s: &str) -> Result<Target, Self::Err> {
246 if let Some(dialect) = s.strip_prefix("sql.") {
247 if dialect == "any" {
248 return Ok(Target::Sql(None));
249 }
250
251 if let Ok(dialect) = sql::Dialect::from_str(dialect) {
252 return Ok(Target::Sql(Some(dialect)));
253 }
254 }
255
256 Err(Error::new(Reason::NotFound {
257 name: format!("{s:?}"),
258 namespace: "target".to_string(),
259 }))
260 }
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct Options {
266 pub format: bool,
271
272 pub target: Target,
274
275 pub signature_comment: bool,
279
280 pub color: bool,
282
283 pub display: DisplayOptions,
295}
296
297impl Default for Options {
298 fn default() -> Self {
299 Self {
300 format: true,
301 target: Target::Sql(None),
302 signature_comment: true,
303 color: true,
304 display: DisplayOptions::AnsiColor,
305 }
306 }
307}
308
309impl Options {
310 pub fn with_format(mut self, format: bool) -> Self {
311 self.format = format;
312 self
313 }
314
315 pub fn no_format(self) -> Self {
316 self.with_format(false)
317 }
318
319 pub fn with_signature_comment(mut self, signature_comment: bool) -> Self {
320 self.signature_comment = signature_comment;
321 self
322 }
323
324 pub fn no_signature(self) -> Self {
325 self.with_signature_comment(false)
326 }
327
328 pub fn with_target(mut self, target: Target) -> Self {
329 self.target = target;
330 self
331 }
332
333 #[deprecated(note = "`color` is replaced by `display`; see `Options` docs for more details")]
334 pub fn with_color(mut self, color: bool) -> Self {
335 self.color = color;
336 self
337 }
338
339 pub fn with_display(mut self, display: DisplayOptions) -> Self {
340 self.display = display;
341 self
342 }
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize, strum::EnumString)]
346#[strum(serialize_all = "snake_case")]
347#[non_exhaustive]
348pub enum DisplayOptions {
349 Plain,
351 AnsiColor,
353}
354
355#[doc = include_str!("../README.md")]
356#[cfg(doctest)]
357pub struct ReadmeDoctests;
358
359pub fn prql_to_tokens(prql: &str) -> Result<lr::Tokens, ErrorMessages> {
361 prqlc_parser::lexer::lex_source(prql).map_err(|e| {
362 e.into_iter()
363 .map(|e| e.into())
364 .collect::<Vec<ErrorMessage>>()
365 .into()
366 })
367}
368
369pub fn prql_to_pl(prql: &str) -> Result<pr::ModuleDef, ErrorMessages> {
372 let source_tree = SourceTree::from(prql);
373 prql_to_pl_tree(&source_tree)
374}
375
376pub fn prql_to_pl_tree(prql: &SourceTree) -> Result<pr::ModuleDef, ErrorMessages> {
378 parser::parse(prql).map_err(|e| ErrorMessages::from(e).composed(prql))
379}
380
381pub fn pl_to_rq(pl: pr::ModuleDef) -> Result<ir::rq::RelationalQuery, ErrorMessages> {
384 semantic::resolve_and_lower(pl, &[], None)
385 .map_err(|e| e.with_source(ErrorSource::NameResolver).into())
386}
387
388pub fn pl_to_rq_tree(
390 pl: pr::ModuleDef,
391 main_path: &[String],
392 database_module_path: &[String],
393) -> Result<ir::rq::RelationalQuery, ErrorMessages> {
394 semantic::resolve_and_lower(pl, main_path, Some(database_module_path))
395 .map_err(|e| e.with_source(ErrorSource::NameResolver).into())
396}
397
398pub fn rq_to_sql(rq: ir::rq::RelationalQuery, options: &Options) -> Result<String, ErrorMessages> {
400 sql::compile(rq, options).map_err(|e| e.with_source(ErrorSource::SQL).into())
401}
402
403pub fn pl_to_prql(pl: &pr::ModuleDef) -> Result<String, ErrorMessages> {
405 Ok(codegen::WriteSource::write(&pl.stmts, codegen::WriteOpt::default()).unwrap())
406}
407
408pub mod json {
410 use super::*;
411
412 pub fn from_pl(pl: &pr::ModuleDef) -> Result<String, ErrorMessages> {
414 serde_json::to_string(pl).map_err(convert_json_err)
415 }
416
417 pub fn to_pl(json: &str) -> Result<pr::ModuleDef, ErrorMessages> {
419 serde_json::from_str(json).map_err(convert_json_err)
420 }
421
422 pub fn from_rq(rq: &ir::rq::RelationalQuery) -> Result<String, ErrorMessages> {
424 serde_json::to_string(rq).map_err(convert_json_err)
425 }
426
427 pub fn to_rq(json: &str) -> Result<ir::rq::RelationalQuery, ErrorMessages> {
429 serde_json::from_str(json).map_err(convert_json_err)
430 }
431
432 fn convert_json_err(err: serde_json::Error) -> ErrorMessages {
433 ErrorMessages::from(Error::new_simple(err.to_string()))
434 }
435}
436
437#[derive(Debug, Clone, Default, Serialize)]
444pub struct SourceTree {
445 pub root: Option<PathBuf>,
447
448 pub sources: HashMap<PathBuf, String>,
451
452 source_ids: HashMap<u16, PathBuf>,
454}
455
456impl SourceTree {
457 pub fn single(path: PathBuf, content: String) -> Self {
458 SourceTree {
459 sources: [(path.clone(), content)].into(),
460 source_ids: [(1, path)].into(),
461 root: None,
462 }
463 }
464
465 pub fn new<I>(iter: I, root: Option<PathBuf>) -> Self
466 where
467 I: IntoIterator<Item = (PathBuf, String)>,
468 {
469 let mut res = SourceTree {
470 sources: HashMap::new(),
471 source_ids: HashMap::new(),
472 root,
473 };
474
475 for (index, (path, content)) in iter.into_iter().enumerate() {
476 res.sources.insert(path.clone(), content);
477 res.source_ids.insert((index + 1) as u16, path);
478 }
479 res
480 }
481
482 pub fn insert(&mut self, path: PathBuf, content: String) {
483 let last_id = self.source_ids.keys().max().cloned().unwrap_or(0);
484 self.sources.insert(path.clone(), content);
485 self.source_ids.insert(last_id + 1, path);
486 }
487
488 pub fn get_path(&self, source_id: u16) -> Option<&PathBuf> {
489 self.source_ids.get(&source_id)
490 }
491}
492
493impl<S: ToString> From<S> for SourceTree {
494 fn from(source: S) -> Self {
495 SourceTree::single(PathBuf::from(""), source.to_string())
496 }
497}
498
499pub mod internal {
501 use super::*;
502
503 pub fn pl_to_lineage(
505 pl: pr::ModuleDef,
506 ) -> Result<semantic::reporting::FrameCollector, ErrorMessages> {
507 let ast = Some(pl.clone());
508
509 let root_module = semantic::resolve(pl).map_err(ErrorMessages::from)?;
510
511 let (main, _) = root_module.find_main_rel(&[]).unwrap();
512 let mut fc =
513 semantic::reporting::collect_frames(*main.clone().into_relation_var().unwrap());
514 fc.ast = ast;
515
516 Ok(fc)
517 }
518
519 pub mod json {
520 use super::*;
521
522 pub fn from_lineage(
524 fc: &semantic::reporting::FrameCollector,
525 ) -> Result<String, ErrorMessages> {
526 serde_json::to_string(fc).map_err(convert_json_err)
527 }
528
529 fn convert_json_err(err: serde_json::Error) -> ErrorMessages {
530 ErrorMessages::from(Error::new_simple(err.to_string()))
531 }
532 }
533}
534
535#[cfg(test)]
536mod tests {
537 use std::str::FromStr;
538
539 use insta::assert_debug_snapshot;
540
541 use crate::pr::Ident;
542 use crate::Target;
543
544 pub fn compile(prql: &str) -> Result<String, super::ErrorMessages> {
545 anstream::ColorChoice::Never.write_global();
546 super::compile(prql, &super::Options::default().no_signature())
547 }
548
549 #[test]
550 fn test_starts_with() {
551 let a = Ident::from_path(vec!["a", "b", "c"]);
553 let b = Ident::from_path(vec!["a", "b"]);
554 let c = Ident::from_path(vec!["a", "b", "c", "d"]);
555 let d = Ident::from_path(vec!["a", "b", "d"]);
556 let e = Ident::from_path(vec!["a", "c"]);
557 let f = Ident::from_path(vec!["b", "c"]);
558 assert!(a.starts_with(&b));
559 assert!(a.starts_with(&a));
560 assert!(!a.starts_with(&c));
561 assert!(!a.starts_with(&d));
562 assert!(!a.starts_with(&e));
563 assert!(!a.starts_with(&f));
564 }
565
566 #[test]
567 fn test_target_from_str() {
568 assert_debug_snapshot!(Target::from_str("sql.postgres"), @r"
569 Ok(
570 Sql(
571 Some(
572 Postgres,
573 ),
574 ),
575 )
576 ");
577
578 assert_debug_snapshot!(Target::from_str("sql.poostgres"), @r#"
579 Err(
580 Error {
581 kind: Error,
582 span: None,
583 reason: NotFound {
584 name: "\"sql.poostgres\"",
585 namespace: "target",
586 },
587 hints: [],
588 code: None,
589 },
590 )
591 "#);
592
593 assert_debug_snapshot!(Target::from_str("postgres"), @r#"
594 Err(
595 Error {
596 kind: Error,
597 span: None,
598 reason: NotFound {
599 name: "\"postgres\"",
600 namespace: "target",
601 },
602 hints: [],
603 code: None,
604 },
605 )
606 "#);
607 }
608
609 #[test]
611 fn test_target_names() {
612 let _: Vec<_> = Target::names()
613 .into_iter()
614 .map(|name| Target::from_str(&name))
615 .collect();
616 }
617}