1mod expression;
19mod source;
20
21use std::collections::{BTreeMap, BTreeSet};
22use std::fmt;
23
24use hawdb_sql_syntax::{
25 CreatePropertyGraph, ElementExposure, ElementLabel, Identifier, PropertyExposure,
26 QualifiedName, Span,
27};
28
29use super::{PgqDataType, PropertyGraphElementSchema, PropertyGraphSchema};
30use crate::SqlDataType;
31use expression::BoundExpression;
32
33pub trait PgqSourceCatalog {
36 fn source_table(&self, name: &[String]) -> Option<&PgqSourceTableSchema>;
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct PgqSourceTableSchema {
41 pub name: Vec<String>,
43 pub columns: Vec<PgqSourceColumnSchema>,
45 pub primary_key: Vec<String>,
46 pub foreign_keys: Vec<PgqSourceForeignKeySchema>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct PgqSourceColumnSchema {
51 pub name: String,
52 pub data_type: SqlDataType,
53 pub nullable: bool,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct PgqSourceForeignKeySchema {
58 pub columns: Vec<String>,
59 pub referenced_table: Vec<String>,
60 pub referenced_columns: Vec<String>,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64#[non_exhaustive]
65pub enum PgqCreateBindErrorCode {
66 InvalidSyntaxTree,
67 InvalidSourceSchema,
68 UnknownTable,
69 UnknownColumn,
70 DuplicateAlias,
71 MissingKey,
72 InvalidKey,
73 UnknownVertex,
74 MissingForeignKey,
75 AmbiguousForeignKey,
76 DuplicateLabel,
77 MissingPropertyName,
78 DuplicateProperty,
79 LabelMismatch,
80 PropertyTypeMismatch,
81 PropertyExpressionMismatch,
82 UnsupportedExpression,
83 InvalidLiteral,
84 TypeMismatch,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct PgqCreateBindError {
89 pub code: PgqCreateBindErrorCode,
90 pub span: Span,
91 pub message: String,
92}
93
94impl fmt::Display for PgqCreateBindError {
95 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(
97 formatter,
98 "SQL/PGQ creation binding error {:?} at bytes {}..{}: {}",
99 self.code, self.span.start, self.span.end, self.message
100 )
101 }
102}
103
104impl std::error::Error for PgqCreateBindError {}
105
106type Result<T> = std::result::Result<T, PgqCreateBindError>;
107use PgqCreateBindErrorCode as Code;
108
109pub fn bind_postgres_create_property_graph(
114 input: &str,
115 create: &CreatePropertyGraph,
116 catalog: &dyn PgqSourceCatalog,
117) -> Result<PropertyGraphSchema> {
118 let binder = Binder {
119 input,
120 enclosing: create.span,
121 };
122 binder.text(create.span)?;
123 let mut graph = PropertyGraphSchema::new(binder.name(&create.name)?);
124 let mut aliases = BTreeSet::new();
125 let mut vertices = BTreeMap::new();
126 let mut elements = Vec::new();
127 for vertex in &create.vertex_tables {
128 binder.text(vertex.span)?;
129 let source = binder.source(catalog, &vertex.table)?;
130 let alias = binder.alias(source, vertex.alias.as_ref(), vertex.span)?;
131 binder.unique_alias(&mut aliases, &alias, vertex.span)?;
132 binder.key(source, &vertex.key, vertex.span)?;
133 vertices.insert(alias.clone(), source);
134 elements.push((true, source, alias, vertex.exposure.as_ref(), vertex.span));
135 }
136 for edge in &create.edge_tables {
137 binder.text(edge.span)?;
138 let source = binder.source(catalog, &edge.table)?;
139 let alias = binder.alias(source, edge.alias.as_ref(), edge.span)?;
140 binder.unique_alias(&mut aliases, &alias, edge.span)?;
141 binder.key(source, &edge.key, edge.span)?;
142 binder.endpoint(source, &edge.source, &vertices)?;
143 binder.endpoint(source, &edge.destination, &vertices)?;
144 elements.push((false, source, alias, edge.exposure.as_ref(), edge.span));
145 }
146 let mut labels = BTreeMap::new();
147 let mut property_types = BTreeMap::new();
148 for (vertex, source, alias, exposure, span) in elements {
149 let bound = binder.exposure(source, &alias, exposure, span)?;
150 for (label, properties) in bound {
151 let mut schema = PropertyGraphElementSchema::default();
152 for (name, expression) in properties {
153 if property_types
154 .get(&name)
155 .is_some_and(|previous| *previous != expression.data_type)
156 {
157 return Err(binder.error(
158 Code::PropertyTypeMismatch,
159 span,
160 format!("property {name} has inconsistent scalar types"),
161 ));
162 }
163 property_types.insert(name.clone(), expression.data_type);
164 schema.properties.insert(name, expression.data_type);
165 }
166 if labels
167 .get(&label)
168 .is_some_and(|previous| previous != &schema)
169 {
170 return Err(binder.error(
171 Code::LabelMismatch,
172 span,
173 format!("label {label} has inconsistent property sets"),
174 ));
175 }
176 labels.insert(label.clone(), schema.clone());
177 if vertex {
178 graph.add_vertex_label(label, schema);
179 } else {
180 graph.add_edge_label(label, schema);
181 }
182 }
183 }
184 Ok(graph)
185}
186
187struct Binder<'a> {
188 input: &'a str,
189 enclosing: Span,
190}
191
192impl Binder<'_> {
193 fn error(&self, code: Code, span: Span, message: impl Into<String>) -> PgqCreateBindError {
194 let span = if self.input.get(span.start..span.end).is_some() {
195 span
196 } else if self
197 .input
198 .get(self.enclosing.start..self.enclosing.end)
199 .is_some()
200 {
201 self.enclosing
202 } else {
203 Span::default()
204 };
205 PgqCreateBindError {
206 code,
207 span,
208 message: message.into(),
209 }
210 }
211
212 fn text(&self, span: Span) -> Result<&str> {
213 self.input
214 .get(span.start..span.end)
215 .ok_or_else(|| self.error(Code::InvalidSyntaxTree, span, "invalid source span"))
216 }
217
218 fn identifier(&self, identifier: &Identifier) -> Result<String> {
219 let text = self.text(identifier.span)?;
220 let decoded = if identifier.quoted {
221 text.strip_prefix('"')
222 .and_then(|text| text.strip_suffix('"'))
223 .filter(|text| !text.replace("\"\"", "").contains('"'))
224 .map(|text| text.replace("\"\"", "\""))
225 } else {
226 let mut chars = text.chars();
227 chars
228 .next()
229 .filter(|c| *c == '_' || c.is_alphabetic())
230 .filter(|_| chars.all(|c| c == '_' || c == '$' || c.is_alphanumeric()))
231 .map(|_| text.to_ascii_lowercase())
232 };
233 decoded
234 .filter(|name| !name.is_empty() && !name.contains('\0'))
235 .ok_or_else(|| {
236 self.error(
237 Code::InvalidSyntaxTree,
238 identifier.span,
239 "invalid identifier",
240 )
241 })
242 }
243
244 fn name(&self, name: &QualifiedName) -> Result<Vec<String>> {
245 self.text(name.span)?;
246 if name.parts.is_empty() {
247 return Err(self.error(Code::InvalidSyntaxTree, name.span, "empty qualified name"));
248 }
249 name.parts
250 .iter()
251 .map(|part| self.identifier(part))
252 .collect()
253 }
254
255 fn unique_alias(&self, aliases: &mut BTreeSet<String>, alias: &str, span: Span) -> Result<()> {
256 if !aliases.insert(alias.to_owned()) {
257 return Err(self.error(
258 Code::DuplicateAlias,
259 span,
260 format!("duplicate element alias {alias}"),
261 ));
262 }
263 Ok(())
264 }
265
266 fn exposure(
267 &self,
268 source: &PgqSourceTableSchema,
269 alias: &str,
270 exposure: Option<&ElementExposure>,
271 span: Span,
272 ) -> Result<BTreeMap<String, BTreeMap<String, BoundExpression>>> {
273 let Some(exposure) = exposure else {
274 return Ok(BTreeMap::from([(
275 alias.to_owned(),
276 self.properties(source, &PropertyExposure::AllColumns, span)?,
277 )]));
278 };
279 self.text(exposure.span)?;
280 if exposure.labels.is_empty() {
281 return Err(self.error(
282 Code::InvalidSyntaxTree,
283 exposure.span,
284 "empty label exposure",
285 ));
286 }
287 let mut labels = BTreeMap::new();
288 let mut expressions = BTreeMap::new();
289 for label in &exposure.labels {
290 self.text(label.span)?;
291 let name = match &label.label {
292 ElementLabel::Implicit | ElementLabel::Default => alias.to_owned(),
293 ElementLabel::Named(name) => self.identifier(name)?,
294 };
295 if labels.contains_key(&name) {
296 return Err(self.error(
297 Code::DuplicateLabel,
298 label.span,
299 format!("duplicate label {name}"),
300 ));
301 }
302 let properties = self.properties(source, &label.properties, label.span)?;
303 for (property, expression) in &properties {
304 if let Some(previous) = expressions.get(property)
305 && previous != expression
306 {
307 return Err(self.error(
308 Code::PropertyExpressionMismatch,
309 label.span,
310 format!("property {property} uses different expressions on one element"),
311 ));
312 }
313 expressions.insert(property.clone(), expression.clone());
314 }
315 labels.insert(name, properties);
316 }
317 Ok(labels)
318 }
319
320 fn properties(
321 &self,
322 source: &PgqSourceTableSchema,
323 exposure: &PropertyExposure,
324 span: Span,
325 ) -> Result<BTreeMap<String, BoundExpression>> {
326 let mut properties = BTreeMap::new();
327 match exposure {
328 PropertyExposure::AllColumns => {
329 for (index, column) in source.columns.iter().enumerate() {
330 properties.insert(
331 column.name.clone(),
332 BoundExpression::column(
333 index,
334 scalar_type(column.data_type).ok_or_else(|| {
335 self.error(
336 Code::UnsupportedExpression,
337 span,
338 "VECTOR graph properties are not supported",
339 )
340 })?,
341 ),
342 );
343 }
344 }
345 PropertyExposure::NoProperties => {}
346 PropertyExposure::Expressions(expressions) => {
347 if expressions.is_empty() {
348 return Err(self.error(
349 Code::InvalidSyntaxTree,
350 span,
351 "empty property expression list",
352 ));
353 }
354 for property in expressions {
355 self.text(property.span)?;
356 let mut expression = self.expression(source, &property.expression, 0)?;
357 let name = match &property.alias {
358 Some(alias) => self.identifier(alias)?,
359 None => expression
360 .implicit_column_name(source, &property.expression)
361 .ok_or_else(|| {
362 self.error(
363 Code::MissingPropertyName,
364 property.span,
365 "non-column property requires AS",
366 )
367 })?,
368 };
369 self.resolve_unknown(&mut expression, PgqDataType::String, property.span)?;
370 if properties.insert(name.clone(), expression).is_some() {
371 return Err(self.error(
372 Code::DuplicateProperty,
373 property.span,
374 format!("duplicate property {name}"),
375 ));
376 }
377 }
378 }
379 }
380 Ok(properties)
381 }
382}
383
384fn scalar_type(data_type: SqlDataType) -> Option<PgqDataType> {
385 Some(match data_type {
386 SqlDataType::Boolean => PgqDataType::Boolean,
387 SqlDataType::BigInt => PgqDataType::Int64,
388 SqlDataType::DoublePrecision => PgqDataType::Float64,
389 SqlDataType::Text => PgqDataType::String,
390 SqlDataType::Bytea => PgqDataType::Binary,
391 SqlDataType::Uuid => PgqDataType::Uuid,
392 SqlDataType::Vector(_) => return None,
393 })
394}
395
396#[cfg(test)]
397mod tests;