Skip to main content

pgevolve_core/parse/
error.rs

1//! Errors raised by the source parser.
2
3use std::path::PathBuf;
4
5use thiserror::Error;
6
7/// Position within a source file.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct SourceLocation {
10    /// Path to the file (relative to the source root, when available).
11    pub file: PathBuf,
12    /// 1-based line number.
13    pub line: usize,
14    /// 1-based column.
15    pub column: usize,
16}
17
18impl SourceLocation {
19    /// Construct.
20    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/// Errors raised by the source parser.
32#[derive(Debug, Error)]
33pub enum ParseError {
34    /// I/O error while reading a source file.
35    #[error("I/O error reading {path}: {source}")]
36    Io {
37        /// Path that failed.
38        path: PathBuf,
39        /// Underlying I/O error.
40        #[source]
41        source: std::io::Error,
42    },
43
44    /// `pg_query` rejected the SQL.
45    #[error("pg_query parse error at {location}: {message}")]
46    PgQuery {
47        /// Source location.
48        location: SourceLocation,
49        /// Message from `pg_query`.
50        message: String,
51    },
52
53    /// CREATE was for an object kind not supported in v0.1.
54    #[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        /// Source location.
60        location: SourceLocation,
61        /// Object kind name (e.g., "CREATE VIEW").
62        kind: &'static str,
63    },
64
65    /// CREATE was missing required schema qualification and no `-- @pgevolve schema=...`
66    /// directive applied.
67    #[error(
68        "{location}: object name must be schema-qualified, or the file must declare \
69         `-- @pgevolve schema=<name>`"
70    )]
71    UnqualifiedName {
72        /// Source location.
73        location: SourceLocation,
74    },
75
76    /// Generic structural error during AST → IR conversion.
77    #[error("{location}: {message}")]
78    Structural {
79        /// Source location.
80        location: SourceLocation,
81        /// Diagnostic message.
82        message: String,
83    },
84
85    /// IR construction failed (e.g., invalid identifier in source).
86    #[error("{location}: {source}")]
87    Ir {
88        /// Source location.
89        location: SourceLocation,
90        /// Underlying error.
91        #[source]
92        source: crate::ir::IrError,
93    },
94
95    /// A directive was malformed.
96    #[error("{location}: invalid pgevolve directive: {message}")]
97    InvalidDirective {
98        /// Source location.
99        location: SourceLocation,
100        /// Diagnostic.
101        message: String,
102    },
103
104    /// Two definitions of the same object qname were found.
105    #[error("duplicate object {qname} defined at {first} and {second}")]
106    DuplicateObject {
107        /// Object qname (rendered).
108        qname: String,
109        /// First definition location.
110        first: SourceLocation,
111        /// Second definition location.
112        second: SourceLocation,
113    },
114
115    /// One or more structural references could not be resolved in source IR.
116    #[error("AST resolution failed:\n{}", format_resolution_errors(.0))]
117    AstResolution(Vec<crate::parse::ast_resolution::AstResolutionError>),
118
119    /// AST canonicalization pass failed (view body normalization or reference
120    /// resolution error).
121    #[error("{0}")]
122    AstCanon(crate::parse::ast_canon::AstCanonError),
123
124    /// A string could not be parsed as a valid identifier.
125    #[error("invalid identifier {0:?}: {1}")]
126    InvalidIdentifier(String, String),
127
128    // ── Check option parse errors ────────────────────────────────────────────
129    /// `ViewStmt.with_check_option` held an unrecognized integer.
130    #[error("unknown ViewStmt.with_check_option integer value: {0}")]
131    UnknownCheckOptionVariant(i32),
132
133    /// A `check_option` WITH-option had an unrecognized string value.
134    #[error("unknown check_option value: {0:?} (expected 'local' or 'cascaded')")]
135    UnknownCheckOptionValue(String),
136
137    // ── Publication parse errors ─────────────────────────────────────────────
138    /// A publication with this name was declared more than once.
139    #[error("{1}: publication {0:?} declared more than once")]
140    DuplicatePublication(crate::identifier::Identifier, SourceLocation),
141
142    /// `FOR ALL TABLES` was combined with explicit object specs.
143    #[error(
144        "{1}: publication {0:?}: FOR ALL TABLES cannot be combined with \
145         FOR TABLE or FOR TABLES IN SCHEMA"
146    )]
147    PublicationAllTablesWithObjects(crate::identifier::Identifier, SourceLocation),
148
149    /// A publication object spec node had an unexpected shape.
150    #[error("{1}: publication {0:?}: malformed publication object spec node")]
151    PublicationObjectMalformed(crate::identifier::Identifier, SourceLocation),
152
153    /// `FOR TABLES IN CURRENT SCHEMA` is not declarative — pgevolve rejects it.
154    #[error(
155        "{1}: publication {0:?}: FOR TABLES IN CURRENT SCHEMA is not supported \
156         (not declarative; use explicit schema names)"
157    )]
158    PublicationCurrentSchemaForm(crate::identifier::Identifier, SourceLocation),
159
160    /// An unrecognized `PublicationObjSpecType` integer was encountered.
161    #[error(
162        "{2}: publication {1:?}: unknown publication object type {0} \
163         (expected 1=TABLE, 2=TABLES IN SCHEMA, 3=CUR_SCHEMA)"
164    )]
165    UnknownPublicationObjectType(i32, crate::identifier::Identifier, SourceLocation),
166
167    /// A table in a publication was not schema-qualified.
168    #[error("{1}: publication {0:?}: table must be schema-qualified")]
169    UnqualifiedPublicationTable(crate::identifier::Identifier, SourceLocation),
170
171    /// Failed to parse the row filter expression for a published table.
172    #[error("{3}: publication {0:?} table {1}: row filter parse error: {2}")]
173    PublicationFilterParse(
174        crate::identifier::Identifier,
175        crate::identifier::QualifiedName,
176        String,
177        SourceLocation,
178    ),
179
180    /// A `WITH (...)` option node for a publication had an unexpected shape.
181    #[error("{1}: publication {0:?}: malformed publication option node")]
182    PublicationOptionMalformed(crate::identifier::Identifier, SourceLocation),
183
184    /// An unrecognized key appeared in a publication `WITH (...)` clause.
185    #[error("{2}: publication {1:?}: unknown publication option {0:?}")]
186    UnknownPublicationOption(String, crate::identifier::Identifier, SourceLocation),
187
188    /// An unrecognized value appeared in a `publish = '...'` clause.
189    #[error(
190        "{2}: publication {1:?}: unknown publish kind {0:?} \
191         (valid: insert, update, delete, truncate)"
192    )]
193    UnknownPublishKind(String, crate::identifier::Identifier, SourceLocation),
194
195    /// A `publish = '...'` clause was empty (no DML kinds enabled).
196    #[error("{1}: publication {0:?}: empty publish list — at least one DML kind required")]
197    EmptyPublishBitset(crate::identifier::Identifier, SourceLocation),
198
199    /// A `CREATE PUBLICATION p` had no scope clause (empty selective).
200    #[error(
201        "{1}: publication {0:?}: no scope clause — add FOR ALL TABLES, \
202         FOR TABLE ..., or FOR TABLES IN SCHEMA ..."
203    )]
204    EmptyPublicationScope(crate::identifier::Identifier, SourceLocation),
205
206    /// `ALTER PUBLICATION p RENAME TO q` is not supported.
207    #[error("{1}: publication {0:?}: RENAME is not supported in pgevolve")]
208    PublicationRenameNotSupported(crate::identifier::Identifier, SourceLocation),
209
210    /// `ALTER PUBLICATION p ...` appeared before the matching `CREATE PUBLICATION p`.
211    #[error("{1}: publication {0:?}: ALTER PUBLICATION before CREATE PUBLICATION")]
212    AlterPublicationBeforeCreate(crate::identifier::Identifier, SourceLocation),
213
214    // ── Subscription parse errors ────────────────────────────────────────────
215    /// A subscription with this name was declared more than once.
216    #[error("{1}: subscription {0:?} declared more than once")]
217    DuplicateSubscription(crate::identifier::Identifier, SourceLocation),
218
219    /// `CREATE SUBSCRIPTION` had an empty CONNECTION string.
220    #[error("{1}: subscription {0:?}: CONNECTION string must not be empty")]
221    SubscriptionEmptyConnection(crate::identifier::Identifier, SourceLocation),
222
223    /// `CREATE SUBSCRIPTION` had an empty PUBLICATION list.
224    #[error("{1}: subscription {0:?}: PUBLICATION list must not be empty")]
225    SubscriptionEmptyPublications(crate::identifier::Identifier, SourceLocation),
226
227    /// A `WITH (...)` option node for a subscription had an unexpected shape.
228    #[error("{1}: subscription {0:?}: malformed subscription option node")]
229    SubscriptionOptionMalformed(crate::identifier::Identifier, SourceLocation),
230
231    /// An unrecognized key appeared in a subscription `WITH (...)` clause.
232    #[error("{2}: subscription {1:?}: unknown subscription option {0:?}")]
233    UnknownSubscriptionOption(String, crate::identifier::Identifier, SourceLocation),
234
235    /// An unrecognized value appeared in a `streaming = ...` clause.
236    #[error(
237        "{2}: subscription {1:?}: unknown streaming mode {0:?} \
238         (valid: off, on, parallel)"
239    )]
240    UnknownStreamingMode(String, crate::identifier::Identifier, SourceLocation),
241
242    /// An unrecognized value appeared in an `origin = ...` clause.
243    #[error(
244        "{2}: subscription {1:?}: unknown origin mode {0:?} \
245         (valid: any, none)"
246    )]
247    UnknownOriginMode(String, crate::identifier::Identifier, SourceLocation),
248
249    /// `ALTER SUBSCRIPTION … REFRESH PUBLICATION` is not supported.
250    #[error("{1}: subscription {0:?}: REFRESH PUBLICATION is not supported in pgevolve")]
251    SubscriptionRefreshNotSupported(crate::identifier::Identifier, SourceLocation),
252
253    /// `ALTER SUBSCRIPTION … SKIP (…)` is not supported.
254    #[error("{1}: subscription {0:?}: SKIP is not supported in pgevolve")]
255    SubscriptionSkipNotSupported(crate::identifier::Identifier, SourceLocation),
256
257    /// `ALTER SUBSCRIPTION … ENABLE/DISABLE` (standalone, without SET) is not
258    /// supported. Declare `WITH (enabled = true/false)` in source instead.
259    #[error(
260        "{1}: subscription {0:?}: standalone ENABLE/DISABLE is not supported — \
261         use WITH (enabled = true/false) in source"
262    )]
263    SubscriptionStandaloneEnableDisableNotSupported(crate::identifier::Identifier, SourceLocation),
264
265    /// `ALTER SUBSCRIPTION … RENAME TO …` is not supported.
266    #[error("{1}: subscription {0:?}: RENAME is not supported in pgevolve")]
267    SubscriptionRenameNotSupported(crate::identifier::Identifier, SourceLocation),
268
269    /// `ALTER SUBSCRIPTION p …` appeared before the matching `CREATE SUBSCRIPTION p`.
270    #[error("{1}: subscription {0:?}: ALTER SUBSCRIPTION before CREATE SUBSCRIPTION")]
271    AlterSubscriptionBeforeCreate(crate::identifier::Identifier, SourceLocation),
272
273    // ── Statistic parse errors ───────────────────────────────────────────────
274    /// A statistic with this qname was declared more than once.
275    #[error("{1}: statistic {0} declared more than once")]
276    DuplicateStatistic(crate::identifier::QualifiedName, SourceLocation),
277
278    /// `CREATE STATISTICS` had no name (anonymous form is not supported).
279    #[error("{0}: anonymous CREATE STATISTICS is not supported — provide an explicit name")]
280    StatisticAnonymous(SourceLocation),
281
282    /// `CREATE STATISTICS` had an empty kinds clause (at least one of ndistinct, dependencies, mcv is required).
283    #[error(
284        "{1}: statistic {0}: empty kinds clause (must list at least one of ndistinct, dependencies, mcv)"
285    )]
286    StatisticEmptyKinds(crate::identifier::QualifiedName, SourceLocation),
287
288    /// `CREATE STATISTICS` had no columns in the ON clause.
289    #[error("{1}: statistic {0}: empty column/expression list")]
290    StatisticEmptyColumns(crate::identifier::QualifiedName, SourceLocation),
291
292    /// An unrecognized kind appeared in a `CREATE STATISTICS (…)` clause.
293    #[error(
294        "{2}: statistic {1}: unknown statistic kind {0:?} \
295         (valid: ndistinct, dependencies, mcv)"
296    )]
297    UnknownStatisticKind(String, crate::identifier::QualifiedName, SourceLocation),
298
299    /// `CREATE STATISTICS … INCLUDE (…)` is not supported (PG 18+, deferred to v0.4.x).
300    #[error("{1}: statistic {0}: INCLUDE clause is not supported (deferred to v0.4.x)")]
301    StatisticIncludeNotSupported(crate::identifier::QualifiedName, SourceLocation),
302
303    /// `ALTER STATISTICS … RENAME TO …` is not supported.
304    #[error("{1}: statistic {0}: RENAME is not supported in pgevolve")]
305    StatisticRenameNotSupported(crate::identifier::QualifiedName, SourceLocation),
306
307    /// `ALTER STATISTICS s …` appeared before the matching `CREATE STATISTICS s`.
308    #[error("{1}: statistic {0}: ALTER STATISTICS before CREATE STATISTICS")]
309    AlterStatisticBeforeCreate(crate::identifier::QualifiedName, SourceLocation),
310
311    /// `COMMENT ON STATISTICS s …` appeared before the matching `CREATE STATISTICS s`.
312    #[error("{1}: statistic {0}: COMMENT ON STATISTICS before CREATE STATISTICS")]
313    CommentOnStatisticBeforeCreate(crate::identifier::QualifiedName, SourceLocation),
314
315    // ── Event-trigger parse errors ───────────────────────────────────────────
316    /// An event trigger with this name was declared more than once.
317    #[error("{1}: event trigger {0:?} declared more than once")]
318    DuplicateEventTrigger(crate::identifier::Identifier, SourceLocation),
319
320    /// `CREATE EVENT TRIGGER … ON <event>` named an event pgevolve does not
321    /// recognize (valid: `ddl_command_start`, `ddl_command_end`, `sql_drop`,
322    /// `table_rewrite`).
323    #[error(
324        "{2}: event trigger {1:?}: unknown event {0:?} \
325         (valid: ddl_command_start, ddl_command_end, sql_drop, table_rewrite)"
326    )]
327    UnknownEventTriggerEvent(String, crate::identifier::Identifier, SourceLocation),
328
329    /// A `WHEN TAG IN (...)` filter node had an unexpected shape.
330    #[error("{1}: event trigger {0:?}: malformed WHEN clause")]
331    EventTriggerWhenClauseMalformed(crate::identifier::Identifier, SourceLocation),
332
333    /// `ALTER EVENT TRIGGER e …` set an `evtenabled` code pgevolve does not
334    /// recognize (valid: `O`, `D`, `R`, `A`).
335    #[error("{2}: event trigger {1:?}: unknown enable state {0:?} (valid: O, D, R, A)")]
336    UnknownEventTriggerEnabled(String, crate::identifier::Identifier, SourceLocation),
337
338    /// `ALTER EVENT TRIGGER e …` appeared before the matching
339    /// `CREATE EVENT TRIGGER e`.
340    #[error("{1}: event trigger {0:?}: ALTER EVENT TRIGGER before CREATE EVENT TRIGGER")]
341    AlterEventTriggerBeforeCreate(crate::identifier::Identifier, SourceLocation),
342
343    /// `COMMENT ON EVENT TRIGGER e …` appeared before the matching
344    /// `CREATE EVENT TRIGGER e`.
345    #[error("{1}: event trigger {0:?}: COMMENT ON EVENT TRIGGER before CREATE EVENT TRIGGER")]
346    CommentOnEventTriggerBeforeCreate(crate::identifier::Identifier, SourceLocation),
347
348    /// `ALTER EVENT TRIGGER e OWNER TO role` appeared before the matching
349    /// `CREATE EVENT TRIGGER e`.
350    #[error("{1}: event trigger {0:?}: OWNER TO before CREATE EVENT TRIGGER")]
351    EventTriggerOwnerBeforeCreate(crate::identifier::Identifier, SourceLocation),
352
353    /// `ALTER EVENT TRIGGER e RENAME TO f` is not supported.
354    #[error("{1}: event trigger {0:?}: RENAME is not supported in pgevolve")]
355    EventTriggerRenameNotSupported(crate::identifier::Identifier, SourceLocation),
356}
357
358fn format_resolution_errors(errs: &[crate::parse::ast_resolution::AstResolutionError]) -> String {
359    let mut s = String::new();
360    for (i, e) in errs.iter().enumerate() {
361        if i > 0 {
362            s.push('\n');
363        }
364        s.push_str("  - ");
365        s.push_str(&e.to_string());
366    }
367    s
368}