use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceLocation {
pub file: PathBuf,
pub line: usize,
pub column: usize,
}
impl SourceLocation {
pub const fn new(file: PathBuf, line: usize, column: usize) -> Self {
Self { file, line, column }
}
}
impl std::fmt::Display for SourceLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}:{}", self.file.display(), self.line, self.column)
}
}
#[derive(Debug, Error)]
pub enum ParseError {
#[error("I/O error reading {path}: {source}")]
Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("pg_query parse error at {location}: {message}")]
PgQuery {
location: SourceLocation,
message: String,
},
#[error(
"{location}: {kind} is not supported in pgevolve v0.1 — \
see docs §2 for the v0.1 object-kind list; expected to land in a later phase"
)]
UnsupportedObjectKind {
location: SourceLocation,
kind: &'static str,
},
#[error(
"{location}: object name must be schema-qualified, or the file must declare \
`-- @pgevolve schema=<name>`"
)]
UnqualifiedName {
location: SourceLocation,
},
#[error("{location}: {message}")]
Structural {
location: SourceLocation,
message: String,
},
#[error("{location}: {source}")]
Ir {
location: SourceLocation,
#[source]
source: crate::ir::IrError,
},
#[error("{location}: invalid pgevolve directive: {message}")]
InvalidDirective {
location: SourceLocation,
message: String,
},
#[error("duplicate object {qname} defined at {first} and {second}")]
DuplicateObject {
qname: String,
first: SourceLocation,
second: SourceLocation,
},
#[error("AST resolution failed:\n{}", format_resolution_errors(.0))]
AstResolution(Vec<crate::parse::ast_resolution::AstResolutionError>),
#[error("{0}")]
AstCanon(crate::parse::ast_canon::AstCanonError),
#[error("invalid identifier {0:?}: {1}")]
InvalidIdentifier(String, String),
#[error("{1}: publication {0:?} declared more than once")]
DuplicatePublication(crate::identifier::Identifier, SourceLocation),
#[error(
"{1}: publication {0:?}: FOR ALL TABLES cannot be combined with \
FOR TABLE or FOR TABLES IN SCHEMA"
)]
PublicationAllTablesWithObjects(crate::identifier::Identifier, SourceLocation),
#[error("{1}: publication {0:?}: malformed publication object spec node")]
PublicationObjectMalformed(crate::identifier::Identifier, SourceLocation),
#[error(
"{1}: publication {0:?}: FOR TABLES IN CURRENT SCHEMA is not supported \
(not declarative; use explicit schema names)"
)]
PublicationCurrentSchemaForm(crate::identifier::Identifier, SourceLocation),
#[error(
"{2}: publication {1:?}: unknown publication object type {0} \
(expected 1=TABLE, 2=TABLES IN SCHEMA, 3=CUR_SCHEMA)"
)]
UnknownPublicationObjectType(i32, crate::identifier::Identifier, SourceLocation),
#[error("{1}: publication {0:?}: table must be schema-qualified")]
UnqualifiedPublicationTable(crate::identifier::Identifier, SourceLocation),
#[error("{3}: publication {0:?} table {1}: row filter parse error: {2}")]
PublicationFilterParse(
crate::identifier::Identifier,
crate::identifier::QualifiedName,
String,
SourceLocation,
),
#[error("{1}: publication {0:?}: malformed publication option node")]
PublicationOptionMalformed(crate::identifier::Identifier, SourceLocation),
#[error("{2}: publication {1:?}: unknown publication option {0:?}")]
UnknownPublicationOption(String, crate::identifier::Identifier, SourceLocation),
#[error(
"{2}: publication {1:?}: unknown publish kind {0:?} \
(valid: insert, update, delete, truncate)"
)]
UnknownPublishKind(String, crate::identifier::Identifier, SourceLocation),
#[error("{1}: publication {0:?}: empty publish list — at least one DML kind required")]
EmptyPublishBitset(crate::identifier::Identifier, SourceLocation),
#[error(
"{1}: publication {0:?}: no scope clause — add FOR ALL TABLES, \
FOR TABLE ..., or FOR TABLES IN SCHEMA ..."
)]
EmptyPublicationScope(crate::identifier::Identifier, SourceLocation),
#[error("{1}: publication {0:?}: RENAME is not supported in pgevolve")]
PublicationRenameNotSupported(crate::identifier::Identifier, SourceLocation),
#[error("{1}: publication {0:?}: ALTER PUBLICATION before CREATE PUBLICATION")]
AlterPublicationBeforeCreate(crate::identifier::Identifier, SourceLocation),
#[error("{1}: subscription {0:?} declared more than once")]
DuplicateSubscription(crate::identifier::Identifier, SourceLocation),
#[error("{1}: subscription {0:?}: CONNECTION string must not be empty")]
SubscriptionEmptyConnection(crate::identifier::Identifier, SourceLocation),
#[error("{1}: subscription {0:?}: PUBLICATION list must not be empty")]
SubscriptionEmptyPublications(crate::identifier::Identifier, SourceLocation),
#[error("{1}: subscription {0:?}: malformed subscription option node")]
SubscriptionOptionMalformed(crate::identifier::Identifier, SourceLocation),
#[error("{2}: subscription {1:?}: unknown subscription option {0:?}")]
UnknownSubscriptionOption(String, crate::identifier::Identifier, SourceLocation),
#[error(
"{2}: subscription {1:?}: unknown streaming mode {0:?} \
(valid: off, on, parallel)"
)]
UnknownStreamingMode(String, crate::identifier::Identifier, SourceLocation),
#[error(
"{2}: subscription {1:?}: unknown origin mode {0:?} \
(valid: any, none)"
)]
UnknownOriginMode(String, crate::identifier::Identifier, SourceLocation),
#[error("{1}: subscription {0:?}: REFRESH PUBLICATION is not supported in pgevolve")]
SubscriptionRefreshNotSupported(crate::identifier::Identifier, SourceLocation),
#[error("{1}: subscription {0:?}: SKIP is not supported in pgevolve")]
SubscriptionSkipNotSupported(crate::identifier::Identifier, SourceLocation),
#[error(
"{1}: subscription {0:?}: standalone ENABLE/DISABLE is not supported — \
use WITH (enabled = true/false) in source"
)]
SubscriptionStandaloneEnableDisableNotSupported(crate::identifier::Identifier, SourceLocation),
#[error("{1}: subscription {0:?}: RENAME is not supported in pgevolve")]
SubscriptionRenameNotSupported(crate::identifier::Identifier, SourceLocation),
#[error("{1}: subscription {0:?}: ALTER SUBSCRIPTION before CREATE SUBSCRIPTION")]
AlterSubscriptionBeforeCreate(crate::identifier::Identifier, SourceLocation),
}
fn format_resolution_errors(errs: &[crate::parse::ast_resolution::AstResolutionError]) -> String {
let mut s = String::new();
for (i, e) in errs.iter().enumerate() {
if i > 0 {
s.push('\n');
}
s.push_str(" - ");
s.push_str(&e.to_string());
}
s
}