#![allow(clippy::result_large_err)]
pub mod ast;
mod error;
mod parser;
pub mod semantic;
pub mod sql;
#[cfg(test)]
mod tests;
mod utils;
pub use error::{downcast, Error, ErrorMessage, ErrorMessages, Reason, SourceLocation, Span};
use once_cell::sync::Lazy;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub static PRQL_VERSION: Lazy<Version> =
Lazy::new(|| Version::parse(env!("CARGO_PKG_VERSION")).expect("Invalid PRQL version number"));
pub fn compile(prql: &str, options: &Options) -> Result<String, ErrorMessages> {
parser::parse(prql)
.and_then(semantic::resolve)
.and_then(|rq| sql::compile(rq, options))
.map_err(error::downcast)
.map_err(|e| e.composed("", prql, options.color))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Target {
Sql(Option<sql::Dialect>),
}
impl Default for Target {
fn default() -> Self {
Self::Sql(None)
}
}
impl Target {
pub fn names() -> Vec<String> {
let mut names = vec!["sql.any".to_string()];
let dialects = sql::Dialect::names();
names.extend(dialects.into_iter().map(|d| format!("sql.{d}")));
names
}
}
impl FromStr for Target {
type Err = Error;
fn from_str(s: &str) -> Result<Target, Self::Err> {
if let Some(dialect) = s.strip_prefix("sql.") {
if dialect == "any" {
return Ok(Target::Sql(None));
}
if let Ok(dialect) = sql::Dialect::from_str(dialect) {
return Ok(Target::Sql(Some(dialect)));
}
}
Err(Error::new(Reason::NotFound {
name: format!("{s:?}"),
namespace: "target".to_string(),
}))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Options {
pub format: bool,
pub target: Target,
pub signature_comment: bool,
pub color: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
format: true,
target: Target::Sql(None),
signature_comment: true,
color: false,
}
}
}
impl Options {
pub fn no_format(mut self) -> Self {
self.format = false;
self
}
pub fn with_signature_comment(mut self, signature_comment: bool) -> Self {
self.signature_comment = signature_comment;
self
}
pub fn no_signature(mut self) -> Self {
self.signature_comment = false;
self
}
pub fn with_target(mut self, target: Target) -> Self {
self.target = target;
self
}
pub fn some(self) -> Option<Self> {
Some(self)
}
pub fn with_color(mut self, color: bool) -> Self {
self.color = color;
self
}
}
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;
pub fn prql_to_pl(prql: &str) -> Result<Vec<ast::pl::Stmt>, ErrorMessages> {
parser::parse(prql)
.map_err(error::downcast)
.map_err(|e| e.composed("", prql, false))
}
pub fn pl_to_rq(pl: Vec<ast::pl::Stmt>) -> Result<ast::rq::Query, ErrorMessages> {
semantic::resolve(pl).map_err(|e| e.into())
}
pub fn rq_to_sql(rq: ast::rq::Query, options: &Options) -> Result<String, ErrorMessages> {
sql::compile(rq, options).map_err(|e| e.into())
}
pub fn pl_to_prql(pl: Vec<ast::pl::Stmt>) -> Result<String, ErrorMessages> {
Ok(format!("{}", ast::pl::Statements(pl)))
}
pub mod json {
use super::*;
pub fn from_pl(pl: Vec<ast::pl::Stmt>) -> Result<String, ErrorMessages> {
serde_json::to_string(&pl).map_err(|e| anyhow::anyhow!(e).into())
}
pub fn to_pl(json: &str) -> Result<Vec<ast::pl::Stmt>, ErrorMessages> {
serde_json::from_str(json).map_err(|e| anyhow::anyhow!(e).into())
}
pub fn from_rq(rq: ast::rq::Query) -> Result<String, ErrorMessages> {
serde_json::to_string(&rq).map_err(|e| anyhow::anyhow!(e).into())
}
pub fn to_rq(json: &str) -> Result<ast::rq::Query, ErrorMessages> {
serde_json::from_str(json).map_err(|e| anyhow::anyhow!(e).into())
}
}
#[cfg(test)]
mod tests_lib {
use crate::Target;
use insta::assert_debug_snapshot;
use std::str::FromStr;
#[test]
fn test_target_from_str() {
assert_debug_snapshot!(Target::from_str("sql.postgres"), @r###"
Ok(
Sql(
Some(
PostgreSql,
),
),
)
"###);
assert_debug_snapshot!(Target::from_str("sql.poostgres"), @r###"
Err(
Error {
kind: Error,
span: None,
reason: NotFound {
name: "\"sql.poostgres\"",
namespace: "target",
},
help: None,
code: None,
},
)
"###);
assert_debug_snapshot!(Target::from_str("postgres"), @r###"
Err(
Error {
kind: Error,
span: None,
reason: NotFound {
name: "\"postgres\"",
namespace: "target",
},
help: None,
code: None,
},
)
"###);
}
}