Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support unqualified references #64

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions src/analyzer/error.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
use std::error::Error;
use std::fmt;
// use crate::Position;
use crate::Position;

#[derive(Clone, Debug, PartialEq)]
pub enum AnalyzeErrorKind {
AmbiguousRecord { record: String },
ColumnNotFound { column: String },
DuplicateColumn { scope: String, column: String },
DuplicateRecord { scope: String, record: String },
DuplicateColumn { column: String },
DuplicateRecord { record: String },
RecordNotFound { record: String },
}

impl fmt::Display for AnalyzeErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use AnalyzeErrorKind::*;

match self {
AnalyzeErrorKind::ColumnNotFound { column } => {
AmbiguousRecord { record } => {
write!(f, "ambiguous record name `{}`", record)
}
ColumnNotFound { column } => {
write!(f, "referenced column `{}` not found", column)
}
AnalyzeErrorKind::DuplicateColumn { scope, column } => {
// TODO: Need position
write!(f, "duplicate column `{}` in scope `{}`", column, scope)
DuplicateColumn { column } => {
write!(f, "duplicate column name `{}`", column)
}
AnalyzeErrorKind::DuplicateRecord { scope, record } => {
write!(f, "duplicate record `{}` in scope `{}`", record, scope)
DuplicateRecord { record } => {
write!(f, "duplicate record name `{}`", record)
}
AnalyzeErrorKind::RecordNotFound { record } => {
RecordNotFound { record } => {
write!(f, "record `{}` not found", record)
}
}
Expand All @@ -33,12 +38,12 @@ impl fmt::Display for AnalyzeErrorKind {
#[derive(Debug, PartialEq)]
pub struct AnalyzeError {
pub kind: AnalyzeErrorKind,
// pub position: Position,
pub position: Position,
}

impl fmt::Display for AnalyzeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.kind)
write!(f, "{} ({})", self.kind, self.position)
}
}

Expand Down
115 changes: 91 additions & 24 deletions src/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub mod error;

use crate::parser::nodes::*;
use error::*;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};

pub type AnalyzeResult = Result<ValidatedParseTree, AnalyzeError>;

Expand All @@ -37,7 +37,31 @@ impl ValidatedParseTree {
}
}

type RefSet = HashSet<String>;
// Map of possibly duplicate record names to the scopes they are defined in
#[derive(Debug, Default)]
struct RefSet(HashMap<String, HashSet<String>>);

impl RefSet {
/// Attempts to add the record with the given scope to the set.
/// Returns true if there was not already a record with the same name & scope
/// or returns false if there was.
fn insert_record_scope(&mut self, scope: &str, record_name: &str) -> bool {
self.0
.entry(record_name.to_owned())
.or_insert_with(HashSet::new)
.insert(scope.to_owned())
}

fn get_record_scopes(&self, record_name: &str) -> Option<&HashSet<String>> {
self.0.get(record_name)
}

fn has_record_scope(&self, record_name: &str, scope: &str) -> bool {
self.get_record_scopes(record_name)
.map(|scopes| scopes.contains(scope))
.unwrap_or(false)
}
}

pub fn analyze(parse_tree: ParseTree) -> AnalyzeResult {
let mut refset = RefSet::default();
Expand All @@ -59,8 +83,8 @@ pub fn analyze(parse_tree: ParseTree) -> AnalyzeResult {
}

fn analyze_table(
schema: Option<&Schema>,
table: &Table,
schema: Option<&SchemaNode>,
table: &TableNode,
refset: &mut RefSet,
) -> Result<(), AnalyzeError> {
// TODO: This is mostly copy-pasta
Expand All @@ -70,6 +94,7 @@ fn analyze_table(
.alias
.as_ref()
.unwrap_or(&table.identity.name);

match schema {
Some(schema) => format!(
"{}.{}",
Expand All @@ -87,14 +112,12 @@ fn analyze_table(
analyze_record(record, refset, &table_scope)?;

if let Some(name) = &record.name {
let key = format!("{}.{}", table_scope, name);

if !refset.insert(key) {
if !refset.insert_record_scope(&table_scope, name) {
return Err(AnalyzeError {
kind: AnalyzeErrorKind::DuplicateRecord {
scope: table_scope,
record: name.clone(),
},
position: record.position,
});
}
}
Expand All @@ -104,7 +127,7 @@ fn analyze_table(
}

fn analyze_record(
record: &Record,
record: &RecordNode,
refset: &RefSet,
parent_scope: &str,
) -> Result<(), AnalyzeError> {
Expand All @@ -114,40 +137,84 @@ fn analyze_record(
if !attrnames.insert(&attr.name) {
return Err(AnalyzeError {
kind: AnalyzeErrorKind::DuplicateColumn {
scope: parent_scope.to_owned(),
column: attr.name.clone(),
},
position: attr.position,
});
}

if let Value::Reference(refval) = &attr.value {
// Column-level references only need validation that the column being referenced
// is explicitly declared in the record already, since they cannot come from the
// database.
// is explicitly declared in the record already
if let Reference::ColumnLevel(c) = refval {
if !attrnames.contains(&c.column) {
return Err(AnalyzeError {
kind: AnalyzeErrorKind::ColumnNotFound {
column: c.column.clone(),
},
position: attr.position,
});
}
continue;
}

let expected_key = match refval {
Reference::SchemaLevel(s) => format!("{}.{}.{}", s.schema, s.table, s.record),
Reference::TableLevel(t) => format!("{}.{}", t.table, t.record),
Reference::RecordLevel(r) => format!("{}.{}", parent_scope, r.record),
match refval {
Reference::SchemaLevel(s) => {
let scope = format!("{}.{}", s.schema, s.table);

if !refset.has_record_scope(&s.record, &scope) {
return Err(AnalyzeError {
kind: AnalyzeErrorKind::RecordNotFound {
record: format!("{}.{}", scope, s.record),
},
position: attr.position,
});
}
}
Reference::TableLevel(t) => {
if !refset.has_record_scope(&t.record, &t.table) {
return Err(AnalyzeError {
kind: AnalyzeErrorKind::RecordNotFound {
record: format!("{}.{}", t.table, t.record),
},
position: attr.position,
});
}
}
Reference::RecordLevel(r) => {
match refset.get_record_scopes(&r.record) {
None => {
return Err(AnalyzeError {
kind: AnalyzeErrorKind::RecordNotFound {
record: r.record.clone(),
},
position: attr.position,
});
}
Some(scopes) => {
// An empty scopes set at this point indicates a bug in the
// analyzer code rather than a result of user input, valid
// or otherwise
assert!(scopes.len() > 0, "record scopes should not be empty");

// If there is only a single record from any scope with a matching
// name, then the reference is valid.
//
// If there are multiple scopes with a record of the same name,
// then an unqualified reference is ambiguous unless the current
// table scope has a record of the same name.
if scopes.len() > 1 && !scopes.contains(parent_scope) {
return Err(AnalyzeError {
kind: AnalyzeErrorKind::AmbiguousRecord {
record: r.record.clone(),
},
position: attr.position,
});
}
}
}
}
Reference::ColumnLevel(_) => unreachable!(),
};

if !refset.contains(&expected_key) {
return Err(AnalyzeError {
kind: AnalyzeErrorKind::RecordNotFound {
record: expected_key,
},
});
}
}
}
Expand Down
18 changes: 9 additions & 9 deletions src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ pub mod error;

use crate::analyzer::ValidatedParseTree;
use crate::parser::nodes::{
Attribute,
AttributeNode,
Reference,
ReferencedColumn,
StructuralIdentity,
StructuralNodeIdentity,
StructuralNode,
Table,
TableNode,
Value,
};
use error::{ClientError, LoadError};
Expand Down Expand Up @@ -46,7 +46,7 @@ impl<'a, 'b> Loader<'a, 'b> {
}
}

fn load_table(&mut self, schema: Option<&StructuralIdentity>, table: &Table) -> LoadResult<()> {
fn load_table(&mut self, schema: Option<&StructuralNodeIdentity>, table: &TableNode) -> LoadResult<()> {
// TODO: A lot of this is copy-pasta from analyzer
//
// *something something* visitor pattern
Expand Down Expand Up @@ -89,7 +89,7 @@ impl<'a, 'b> Loader<'a, 'b> {
&mut self,
qualified_table_name: &str,
table_scope: &str,
attributes: &[Attribute],
attributes: &[AttributeNode],
) -> Result<SimpleQueryRow, LoadError> {
let statement = InsertStatement::build(self.transaction)
.attributes(attributes)
Expand Down Expand Up @@ -176,7 +176,7 @@ struct InsertStatementBuilder<
where
'fragment2: 'fragment1
{
attributes: &'attribute [Attribute],
attributes: &'attribute [AttributeNode],
attribute_indexes: HashMap<&'attribute str, usize>,
current_scope: &'current_scope str,
fragment_runner: FragmentRunner<'fragment1, 'fragment2>,
Expand All @@ -185,7 +185,7 @@ where
}

impl<'a, 'c, 'f1, 'f2, 'q, 'r> InsertStatementBuilder<'a, 'c, 'f1, 'f2, 'q, 'r> {
fn attributes(mut self, attributes: &'a [Attribute]) -> Self {
fn attributes(mut self, attributes: &'a [AttributeNode]) -> Self {
self.attributes = attributes;
self.attribute_indexes = HashMap::new();
self
Expand Down Expand Up @@ -239,7 +239,7 @@ impl<'a, 'c, 'f1, 'f2, 'q, 'r> InsertStatementBuilder<'a, 'c, 'f1, 'f2, 'q, 'r>
Ok(InsertStatement(statement))
}

fn write_value(&mut self, attribute: &Attribute, out: &mut String) -> Result<(), LoadError> {
fn write_value(&mut self, attribute: &AttributeNode, out: &mut String) -> Result<(), LoadError> {
match &attribute.value {
Value::Bool(b) => out.push_str(&b.to_string()),
Value::Number(n) => out.push_str(n),
Expand Down Expand Up @@ -270,7 +270,7 @@ impl<'a, 'c, 'f1, 'f2, 'q, 'r> InsertStatementBuilder<'a, 'c, 'f1, 'f2, 'q, 'r>
Ok(())
}

fn follow_ref(&self, attribute: &Attribute, refval: &Reference) -> Result<String, LoadError> {
fn follow_ref(&self, attribute: &AttributeNode, refval: &Reference) -> Result<String, LoadError> {
use ReferencedColumn::*;

let mut col = &attribute.name;
Expand Down
Loading