1use std::path::PathBuf;
4
5use thiserror::Error;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct SourceLocation {
10 pub file: PathBuf,
12 pub line: usize,
14 pub column: usize,
16}
17
18impl SourceLocation {
19 pub const fn new(file: PathBuf, line: usize, column: usize) -> Self {
21 Self { file, line, column }
22 }
23}
24
25impl std::fmt::Display for SourceLocation {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(f, "{}:{}:{}", self.file.display(), self.line, self.column)
28 }
29}
30
31#[derive(Debug, Error)]
33pub enum ParseError {
34 #[error("I/O error reading {path}: {source}")]
36 Io {
37 path: PathBuf,
39 #[source]
41 source: std::io::Error,
42 },
43
44 #[error("pg_query parse error at {location}: {message}")]
46 PgQuery {
47 location: SourceLocation,
49 message: String,
51 },
52
53 #[error(
55 "{location}: {kind} is not supported in pgevolve v0.1 — \
56 see docs §2 for the v0.1 object-kind list; expected to land in a later phase"
57 )]
58 UnsupportedObjectKind {
59 location: SourceLocation,
61 kind: &'static str,
63 },
64
65 #[error(
68 "{location}: object name must be schema-qualified, or the file must declare \
69 `-- @pgevolve schema=<name>`"
70 )]
71 UnqualifiedName {
72 location: SourceLocation,
74 },
75
76 #[error("{location}: {message}")]
78 Structural {
79 location: SourceLocation,
81 message: String,
83 },
84
85 #[error("{location}: {source}")]
87 Ir {
88 location: SourceLocation,
90 #[source]
92 source: crate::ir::IrError,
93 },
94
95 #[error("{location}: invalid pgevolve directive: {message}")]
97 InvalidDirective {
98 location: SourceLocation,
100 message: String,
102 },
103
104 #[error("duplicate object {qname} defined at {first} and {second}")]
106 DuplicateObject {
107 qname: String,
109 first: SourceLocation,
111 second: SourceLocation,
113 },
114
115 #[error("AST resolution failed:\n{}", format_resolution_errors(.0))]
117 AstResolution(Vec<crate::parse::ast_resolution::AstResolutionError>),
118
119 #[error("{0}")]
122 AstCanon(crate::parse::ast_canon::AstCanonError),
123
124 #[error("invalid identifier {0:?}: {1}")]
126 InvalidIdentifier(String, String),
127
128 #[error("{1}: publication {0:?} declared more than once")]
131 DuplicatePublication(crate::identifier::Identifier, SourceLocation),
132
133 #[error(
135 "{1}: publication {0:?}: FOR ALL TABLES cannot be combined with \
136 FOR TABLE or FOR TABLES IN SCHEMA"
137 )]
138 PublicationAllTablesWithObjects(crate::identifier::Identifier, SourceLocation),
139
140 #[error("{1}: publication {0:?}: malformed publication object spec node")]
142 PublicationObjectMalformed(crate::identifier::Identifier, SourceLocation),
143
144 #[error(
146 "{1}: publication {0:?}: FOR TABLES IN CURRENT SCHEMA is not supported \
147 (not declarative; use explicit schema names)"
148 )]
149 PublicationCurrentSchemaForm(crate::identifier::Identifier, SourceLocation),
150
151 #[error(
153 "{2}: publication {1:?}: unknown publication object type {0} \
154 (expected 1=TABLE, 2=TABLES IN SCHEMA, 3=CUR_SCHEMA)"
155 )]
156 UnknownPublicationObjectType(i32, crate::identifier::Identifier, SourceLocation),
157
158 #[error("{1}: publication {0:?}: table must be schema-qualified")]
160 UnqualifiedPublicationTable(crate::identifier::Identifier, SourceLocation),
161
162 #[error("{3}: publication {0:?} table {1}: row filter parse error: {2}")]
164 PublicationFilterParse(
165 crate::identifier::Identifier,
166 crate::identifier::QualifiedName,
167 String,
168 SourceLocation,
169 ),
170
171 #[error("{1}: publication {0:?}: malformed publication option node")]
173 PublicationOptionMalformed(crate::identifier::Identifier, SourceLocation),
174
175 #[error("{2}: publication {1:?}: unknown publication option {0:?}")]
177 UnknownPublicationOption(String, crate::identifier::Identifier, SourceLocation),
178
179 #[error(
181 "{2}: publication {1:?}: unknown publish kind {0:?} \
182 (valid: insert, update, delete, truncate)"
183 )]
184 UnknownPublishKind(String, crate::identifier::Identifier, SourceLocation),
185
186 #[error("{1}: publication {0:?}: empty publish list — at least one DML kind required")]
188 EmptyPublishBitset(crate::identifier::Identifier, SourceLocation),
189
190 #[error(
192 "{1}: publication {0:?}: no scope clause — add FOR ALL TABLES, \
193 FOR TABLE ..., or FOR TABLES IN SCHEMA ..."
194 )]
195 EmptyPublicationScope(crate::identifier::Identifier, SourceLocation),
196
197 #[error("{1}: publication {0:?}: RENAME is not supported in pgevolve")]
199 PublicationRenameNotSupported(crate::identifier::Identifier, SourceLocation),
200
201 #[error("{1}: publication {0:?}: ALTER PUBLICATION before CREATE PUBLICATION")]
203 AlterPublicationBeforeCreate(crate::identifier::Identifier, SourceLocation),
204
205 #[error("{1}: subscription {0:?} declared more than once")]
208 DuplicateSubscription(crate::identifier::Identifier, SourceLocation),
209
210 #[error("{1}: subscription {0:?}: CONNECTION string must not be empty")]
212 SubscriptionEmptyConnection(crate::identifier::Identifier, SourceLocation),
213
214 #[error("{1}: subscription {0:?}: PUBLICATION list must not be empty")]
216 SubscriptionEmptyPublications(crate::identifier::Identifier, SourceLocation),
217
218 #[error("{1}: subscription {0:?}: malformed subscription option node")]
220 SubscriptionOptionMalformed(crate::identifier::Identifier, SourceLocation),
221
222 #[error("{2}: subscription {1:?}: unknown subscription option {0:?}")]
224 UnknownSubscriptionOption(String, crate::identifier::Identifier, SourceLocation),
225
226 #[error(
228 "{2}: subscription {1:?}: unknown streaming mode {0:?} \
229 (valid: off, on, parallel)"
230 )]
231 UnknownStreamingMode(String, crate::identifier::Identifier, SourceLocation),
232
233 #[error(
235 "{2}: subscription {1:?}: unknown origin mode {0:?} \
236 (valid: any, none)"
237 )]
238 UnknownOriginMode(String, crate::identifier::Identifier, SourceLocation),
239
240 #[error("{1}: subscription {0:?}: REFRESH PUBLICATION is not supported in pgevolve")]
242 SubscriptionRefreshNotSupported(crate::identifier::Identifier, SourceLocation),
243
244 #[error("{1}: subscription {0:?}: SKIP is not supported in pgevolve")]
246 SubscriptionSkipNotSupported(crate::identifier::Identifier, SourceLocation),
247
248 #[error(
251 "{1}: subscription {0:?}: standalone ENABLE/DISABLE is not supported — \
252 use WITH (enabled = true/false) in source"
253 )]
254 SubscriptionStandaloneEnableDisableNotSupported(crate::identifier::Identifier, SourceLocation),
255
256 #[error("{1}: subscription {0:?}: RENAME is not supported in pgevolve")]
258 SubscriptionRenameNotSupported(crate::identifier::Identifier, SourceLocation),
259
260 #[error("{1}: subscription {0:?}: ALTER SUBSCRIPTION before CREATE SUBSCRIPTION")]
262 AlterSubscriptionBeforeCreate(crate::identifier::Identifier, SourceLocation),
263}
264
265fn format_resolution_errors(errs: &[crate::parse::ast_resolution::AstResolutionError]) -> String {
266 let mut s = String::new();
267 for (i, e) in errs.iter().enumerate() {
268 if i > 0 {
269 s.push('\n');
270 }
271 s.push_str(" - ");
272 s.push_str(&e.to_string());
273 }
274 s
275}