Skip to content

Commit 360b0fe

Browse files
committed
ExceptionStatement raises custom SQL errors
1 parent 2f400ae commit 360b0fe

File tree

13 files changed

+149
-1
lines changed

13 files changed

+149
-1
lines changed

src/backend/mysql/query.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,17 @@ impl QueryBuilder for MysqlQueryBuilder {
197197

198198
fn prepare_returning(&self, _returning: &Option<ReturningClause>, _sql: &mut dyn SqlWriter) {}
199199

200+
fn prepare_exception_statement(&self, exception: &ExceptionStatement, sql: &mut dyn SqlWriter) {
201+
let mut quoted_exception_message = String::new();
202+
self.write_string_quoted(&exception.message, &mut quoted_exception_message);
203+
write!(
204+
sql,
205+
"SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = {}",
206+
quoted_exception_message
207+
)
208+
.unwrap();
209+
}
210+
200211
fn random_function(&self) -> &str {
201212
"RAND"
202213
}

src/backend/postgres/query.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ impl QueryBuilder for PostgresQueryBuilder {
178178
sql.push_param(value.clone(), self as _);
179179
}
180180

181+
fn prepare_exception_statement(&self, exception: &ExceptionStatement, sql: &mut dyn SqlWriter) {
182+
let mut quoted_exception_message = String::new();
183+
self.write_string_quoted(&exception.message, &mut quoted_exception_message);
184+
write!(sql, "RAISE EXCEPTION {}", quoted_exception_message).unwrap();
185+
}
186+
181187
fn write_string_quoted(&self, string: &str, buffer: &mut String) {
182188
let escaped = self.escape_string(string);
183189
let string = if escaped.find('\\').is_some() {

src/backend/query_builder.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,9 @@ pub trait QueryBuilder:
453453
SimpleExpr::Constant(val) => {
454454
self.prepare_constant(val, sql);
455455
}
456+
SimpleExpr::Exception(val) => {
457+
self.prepare_exception_statement(val, sql);
458+
}
456459
}
457460
}
458461

@@ -1051,6 +1054,15 @@ pub trait QueryBuilder:
10511054
}
10521055
}
10531056

1057+
// Translate [`Exception`] into SQL statement.
1058+
fn prepare_exception_statement(
1059+
&self,
1060+
_exception: &ExceptionStatement,
1061+
_sql: &mut dyn SqlWriter,
1062+
) {
1063+
panic!("Exception handling not implemented for this backend");
1064+
}
1065+
10541066
/// Convert a SQL value into syntax-specific string
10551067
fn value_to_string(&self, v: &Value) -> String {
10561068
self.value_to_string_common(v)

src/backend/sqlite/query.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ impl QueryBuilder for SqliteQueryBuilder {
8484
"MIN"
8585
}
8686

87+
fn prepare_exception_statement(&self, exception: &ExceptionStatement, sql: &mut dyn SqlWriter) {
88+
let mut quoted_exception_message = String::new();
89+
self.write_string_quoted(&exception.message, &mut quoted_exception_message);
90+
write!(sql, "SELECT RAISE(ABORT, {})", quoted_exception_message).unwrap();
91+
}
92+
8793
fn char_length_function(&self) -> &str {
8894
"LENGTH"
8995
}

src/exception.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//! Custom SQL exceptions and errors
2+
use inherent::inherent;
3+
4+
use crate::backend::SchemaBuilder;
5+
6+
/// SQL Exceptions
7+
#[derive(Debug, Clone, PartialEq)]
8+
pub struct ExceptionStatement {
9+
pub(crate) message: String,
10+
}
11+
12+
impl ExceptionStatement {
13+
pub fn new(message: String) -> Self {
14+
Self { message }
15+
}
16+
}
17+
18+
pub trait ExceptionStatementBuilder {
19+
/// Build corresponding SQL statement for certain database backend and return SQL string
20+
fn build<T: SchemaBuilder>(&self, schema_builder: T) -> String;
21+
22+
/// Build corresponding SQL statement for certain database backend and return SQL string
23+
fn build_any(&self, schema_builder: &dyn SchemaBuilder) -> String;
24+
25+
/// Build corresponding SQL statement for certain database backend and return SQL string
26+
fn to_string<T: SchemaBuilder>(&self, schema_builder: T) -> String {
27+
self.build(schema_builder)
28+
}
29+
}
30+
31+
#[inherent]
32+
impl ExceptionStatementBuilder for ExceptionStatement {
33+
pub fn build<T: SchemaBuilder>(&self, schema_builder: T) -> String {
34+
let mut sql = String::with_capacity(256);
35+
schema_builder.prepare_exception_statement(self, &mut sql);
36+
sql
37+
}
38+
39+
pub fn build_any(&self, schema_builder: &dyn SchemaBuilder) -> String {
40+
let mut sql = String::with_capacity(256);
41+
schema_builder.prepare_exception_statement(self, &mut sql);
42+
sql
43+
}
44+
45+
pub fn to_string<T: SchemaBuilder>(&self, schema_builder: T) -> String;
46+
}

src/expr.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//!
55
//! [`SimpleExpr`] is the expression common among select fields, where clauses and many other places.
66
7-
use crate::{func::*, query::*, types::*, value::*};
7+
use crate::{exception::ExceptionStatement, func::*, query::*, types::*, value::*};
88

99
/// Helper to build a [`SimpleExpr`].
1010
#[derive(Debug, Clone)]
@@ -35,6 +35,7 @@ pub enum SimpleExpr {
3535
AsEnum(DynIden, Box<SimpleExpr>),
3636
Case(Box<CaseStatement>),
3737
Constant(Value),
38+
Exception(ExceptionStatement),
3839
}
3940

4041
/// "Operator" methods for building expressions.

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,7 @@
809809

810810
pub mod backend;
811811
pub mod error;
812+
pub mod exception;
812813
pub mod expr;
813814
pub mod extension;
814815
pub mod foreign_key;
@@ -827,6 +828,7 @@ pub mod value;
827828
pub mod tests_cfg;
828829

829830
pub use backend::*;
831+
pub use exception::*;
830832
pub use expr::*;
831833
pub use foreign_key::*;
832834
pub use func::*;

tests/mysql/exception.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use super::*;
2+
use pretty_assertions::assert_eq;
3+
4+
#[test]
5+
fn signal_sqlstate() {
6+
let message = "Some error occurred";
7+
assert_eq!(
8+
ExceptionStatement::new(message.to_string()).to_string(MysqlQueryBuilder),
9+
format!("SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '{message}'")
10+
);
11+
}
12+
13+
#[test]
14+
fn escapes_message() {
15+
let unescaped_message = "Does this 'break'?";
16+
assert_eq!(
17+
ExceptionStatement::new(unescaped_message.to_string()).to_string(MysqlQueryBuilder),
18+
format!("SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Does this \\'break\\'?'")
19+
);
20+
}

tests/mysql/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use sea_query::{extension::mysql::*, tests_cfg::*, *};
22

3+
mod exception;
34
mod foreign_key;
45
mod index;
56
mod query;

tests/postgres/exception.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use super::*;
2+
use pretty_assertions::assert_eq;
3+
4+
#[test]
5+
fn raise_exception() {
6+
let message = "Some error occurred";
7+
assert_eq!(
8+
ExceptionStatement::new(message.to_string()).to_string(PostgresQueryBuilder),
9+
format!("RAISE EXCEPTION '{message}'")
10+
);
11+
}
12+
13+
#[test]
14+
fn escapes_message() {
15+
let unescaped_message = "Does this 'break'?";
16+
assert_eq!(
17+
ExceptionStatement::new(unescaped_message.to_string()).to_string(PostgresQueryBuilder),
18+
format!("RAISE EXCEPTION E'Does this \\'break\\'?'")
19+
);
20+
}

tests/postgres/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use sea_query::{tests_cfg::*, *};
22

3+
mod exception;
34
mod foreign_key;
45
mod index;
56
mod query;

tests/sqlite/exception.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use super::*;
2+
use pretty_assertions::assert_eq;
3+
4+
#[test]
5+
fn select_raise_abort() {
6+
let message = "Some error occurred here";
7+
assert_eq!(
8+
ExceptionStatement::new(message.to_string()).to_string(SqliteQueryBuilder),
9+
format!("SELECT RAISE(ABORT, '{}')", message)
10+
);
11+
}
12+
13+
#[test]
14+
fn escapes_message() {
15+
let unescaped_message = "Does this 'break'?";
16+
let escaped_message = "Does this ''break''?";
17+
assert_eq!(
18+
ExceptionStatement::new(unescaped_message.to_string()).to_string(SqliteQueryBuilder),
19+
format!("SELECT RAISE(ABORT, '{}')", escaped_message)
20+
);
21+
}

tests/sqlite/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use sea_query::{tests_cfg::*, *};
22

3+
mod exception;
34
mod foreign_key;
45
mod index;
56
mod query;

0 commit comments

Comments
 (0)