1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7use use_pg_identifier::{PgIdentifier, PgIdentifierError};
8use use_pg_table::PgTableRef;
9
10#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
12pub struct PgSequenceName(PgIdentifier);
13
14impl PgSequenceName {
15 pub fn new(input: impl AsRef<str>) -> Result<Self, PgSequenceError> {
21 PgIdentifier::new(input)
22 .map(Self)
23 .map_err(PgSequenceError::Identifier)
24 }
25
26 #[must_use]
28 pub fn as_str(&self) -> &str {
29 self.0.as_str()
30 }
31}
32
33impl fmt::Display for PgSequenceName {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 self.0.fmt(formatter)
36 }
37}
38
39impl FromStr for PgSequenceName {
40 type Err = PgSequenceError;
41
42 fn from_str(input: &str) -> Result<Self, Self::Err> {
43 Self::new(input)
44 }
45}
46
47#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
49pub enum PgSequenceOwner {
50 Unowned,
52 OwnedBy {
54 table: PgTableRef,
56 column: PgIdentifier,
58 },
59}
60
61impl PgSequenceOwner {
62 #[must_use]
64 pub const fn owned_by(table: PgTableRef, column: PgIdentifier) -> Self {
65 Self::OwnedBy { table, column }
66 }
67
68 #[must_use]
70 pub const fn is_owned(&self) -> bool {
71 matches!(self, Self::OwnedBy { .. })
72 }
73}
74
75impl fmt::Display for PgSequenceOwner {
76 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self {
78 Self::Unowned => formatter.write_str("unowned"),
79 Self::OwnedBy { table, column } => write!(formatter, "owned by {table}.{column}"),
80 }
81 }
82}
83
84#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
86pub struct PgSequenceOptions {
87 increment: i64,
88 min_value: Option<i64>,
89 max_value: Option<i64>,
90 start: i64,
91 cache: u64,
92 cycle: bool,
93}
94
95impl Default for PgSequenceOptions {
96 fn default() -> Self {
97 Self {
98 increment: 1,
99 min_value: None,
100 max_value: None,
101 start: 1,
102 cache: 1,
103 cycle: false,
104 }
105 }
106}
107
108impl PgSequenceOptions {
109 pub const fn with_increment(mut self, increment: i64) -> Result<Self, PgSequenceError> {
115 if increment == 0 {
116 return Err(PgSequenceError::InvalidIncrement);
117 }
118 self.increment = increment;
119 Ok(self)
120 }
121
122 #[must_use]
124 pub const fn with_bounds(mut self, min_value: Option<i64>, max_value: Option<i64>) -> Self {
125 self.min_value = min_value;
126 self.max_value = max_value;
127 self
128 }
129
130 #[must_use]
132 pub const fn with_start(mut self, start: i64) -> Self {
133 self.start = start;
134 self
135 }
136
137 pub const fn with_cache(mut self, cache: u64) -> Result<Self, PgSequenceError> {
143 if cache == 0 {
144 return Err(PgSequenceError::InvalidCache);
145 }
146 self.cache = cache;
147 Ok(self)
148 }
149
150 #[must_use]
152 pub const fn with_cycle(mut self, cycle: bool) -> Self {
153 self.cycle = cycle;
154 self
155 }
156
157 #[must_use]
159 pub const fn increment(self) -> i64 {
160 self.increment
161 }
162
163 #[must_use]
165 pub const fn min_value(self) -> Option<i64> {
166 self.min_value
167 }
168
169 #[must_use]
171 pub const fn max_value(self) -> Option<i64> {
172 self.max_value
173 }
174
175 #[must_use]
177 pub const fn start(self) -> i64 {
178 self.start
179 }
180
181 #[must_use]
183 pub const fn cache(self) -> u64 {
184 self.cache
185 }
186
187 #[must_use]
189 pub const fn cycle(self) -> bool {
190 self.cycle
191 }
192}
193
194#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
196pub struct PgSequence {
197 name: PgSequenceName,
198 options: PgSequenceOptions,
199 owner: PgSequenceOwner,
200}
201
202impl PgSequence {
203 #[must_use]
205 pub fn new(name: PgSequenceName) -> Self {
206 Self {
207 name,
208 options: PgSequenceOptions::default(),
209 owner: PgSequenceOwner::Unowned,
210 }
211 }
212
213 #[must_use]
215 pub const fn with_options(mut self, options: PgSequenceOptions) -> Self {
216 self.options = options;
217 self
218 }
219
220 #[must_use]
222 pub fn with_owner(mut self, owner: PgSequenceOwner) -> Self {
223 self.owner = owner;
224 self
225 }
226
227 #[must_use]
229 pub const fn name(&self) -> &PgSequenceName {
230 &self.name
231 }
232
233 #[must_use]
235 pub const fn options(&self) -> PgSequenceOptions {
236 self.options
237 }
238
239 #[must_use]
241 pub const fn owner(&self) -> &PgSequenceOwner {
242 &self.owner
243 }
244}
245
246#[derive(Clone, Debug, Eq, PartialEq)]
248pub enum PgSequenceError {
249 InvalidIncrement,
250 InvalidCache,
251 Identifier(PgIdentifierError),
252}
253
254impl fmt::Display for PgSequenceError {
255 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
256 match self {
257 Self::InvalidIncrement => {
258 formatter.write_str("PostgreSQL sequence increment cannot be zero")
259 }
260 Self::InvalidCache => {
261 formatter.write_str("PostgreSQL sequence cache must be greater than zero")
262 }
263 Self::Identifier(error) => {
264 write!(formatter, "invalid PostgreSQL sequence identifier: {error}")
265 }
266 }
267 }
268}
269
270impl Error for PgSequenceError {}
271
272#[cfg(test)]
273mod tests {
274 use super::{PgSequence, PgSequenceError, PgSequenceName, PgSequenceOptions, PgSequenceOwner};
275 use use_pg_identifier::PgIdentifier;
276 use use_pg_schema::PgSchemaName;
277 use use_pg_table::{PgTableName, PgTableRef};
278
279 #[test]
280 fn uses_simple_default_options() -> Result<(), PgSequenceError> {
281 let sequence = PgSequence::new(PgSequenceName::new("users_id_seq")?);
282 assert_eq!(sequence.options().increment(), 1);
283 assert_eq!(sequence.options().cache(), 1);
284 assert!(!sequence.options().cycle());
285 Ok(())
286 }
287
288 #[test]
289 fn validates_custom_options() -> Result<(), PgSequenceError> {
290 let options = PgSequenceOptions::default()
291 .with_increment(10)?
292 .with_bounds(Some(1), Some(1000))
293 .with_start(10)
294 .with_cache(20)?
295 .with_cycle(true);
296 assert_eq!(options.increment(), 10);
297 assert_eq!(options.min_value(), Some(1));
298 assert_eq!(options.max_value(), Some(1000));
299 assert!(options.cycle());
300 assert_eq!(
301 PgSequenceOptions::default().with_increment(0),
302 Err(PgSequenceError::InvalidIncrement)
303 );
304 Ok(())
305 }
306
307 #[test]
308 fn tracks_sequence_ownership() -> Result<(), Box<dyn std::error::Error>> {
309 let table = PgTableRef::qualified(PgSchemaName::public(), PgTableName::new("users")?);
310 let owner = PgSequenceOwner::owned_by(table, PgIdentifier::new("id")?);
311 assert!(owner.is_owned());
312 assert_eq!(owner.to_string(), "owned by public.users.id");
313 Ok(())
314 }
315}