1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
use super::SourceAlias;
/// Each projection field is defined by
/// 1. a definition
/// 1. an alias
/// 1. an output type
///
/// by example `SELECT a.title as title`
/// * `a.title` is the definition
/// * `title` is the field name
/// * the type here is the same as the input type (most likely text)
///
/// Other example: `count(c.*) as comment_count`
/// * `count(c.*)` is the definition
/// * `comment_count` is the field name
/// * type is int as the SQL `count` aggregate function returns an integer.
pub struct ProjectionField {
/// Field name alias, this is the output name of the field.
pub name: String,
/// Field definition. Some field definitions can be fairly complex like `CASE … WHEN …` or using functions.
pub definition: String,
/// This indicates the SQL type of the output data.
pub output_type: String,
}
impl ProjectionField {
/// [ProjectionField] constructor
pub fn new(name: &str, definition: &str, output_type: &str) -> Self {
Self {
name: name.to_string(),
definition: definition.to_string(),
output_type: output_type.to_string(),
}
}
}
/// Projection is a definition of field mapping during a query.
/// Fields come from one or several source structures (can be tables, views or
/// sub queries) and are mapped to a Query as output.
pub struct Projection {
fields: Vec<ProjectionField>,
}
impl Projection {
/// Instantiate a new Projection
pub fn new(fields: Vec<ProjectionField>) -> Self {
Self { fields }
}
/// Create a Projection from a list of tuples `&[(name, definition, sql_type)]`.
pub fn from(fields: &[(&str, &str, &str)]) -> Self {
let field_defs: Vec<ProjectionField> = fields
.iter()
.map(|(name, definition, sql_type)| ProjectionField::new(name, definition, sql_type))
.collect();
Self::new(field_defs)
}
/// Add a new field to the definition. This is one of the projection
/// building tool to create a projection out of an existing structure.
/// This is a blanket implementation.
pub fn add_field(&mut self, field_name: &str, definition: &str, output_type: &str) {
self.fields.push(ProjectionField {
name: field_name.to_string(),
definition: definition.to_string(),
output_type: output_type.to_string(),
})
}
/// Returns the list of the ProjectionFields of this Projection.
pub fn get_fields(&self) -> &Vec<ProjectionField> {
&self.fields
}
/// Turn the Projection into a string suitable for use in SQL queries.
pub fn expand(&self, aliases: SourceAlias) -> String {
let mut fields: String = self
.get_fields()
.iter()
.map(|field| format!("{} as {}", field.definition, field.name))
.collect::<Vec<String>>()
.join(", ");
for (alias, source) in aliases.get_iterator() {
fields = fields.replace(alias, source);
}
fields
}
}
impl Default for Projection {
fn default() -> Self {
Self::new(Vec::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_projection() {
let mut projection = Projection::default();
projection.add_field("test_id", "{:test:}.test_id", "integer");
projection.add_field("name", "{:test:}.name", "text");
projection.add_field("created_at", "{:test:}.created_at", "timestamp");
projection.add_field("thing_count", "count({:thing:}.*)", "integer");
let aliases = SourceAlias::new(&[("{:test:}", "pika"), ("{:thing:}", "thing_alias")]);
assert_eq!(
"pika.test_id as test_id, pika.name as name, pika.created_at as created_at, count(thing_alias.*) as thing_count".to_string(),
projection.expand(aliases)
)
}
#[test]
fn list_constructor() {
let projection = Projection::from(&[
("something_id", "{:test:}.something_id", "integer"),
("name", "{:test:}.name", "text"),
("created_at", "{:test:}.created_at", "timestamp"),
]);
let aliases = SourceAlias::new(&[("{:test:}", "test")]);
assert_eq!(
"test.something_id as something_id, test.name as name, test.created_at as created_at",
projection.expand(aliases)
);
}
}