diff --git a/CHANGELOG.md b/CHANGELOG.md index 73719c7e5..7d90bd35f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ pub struct TableName(pub Option, pub DynIden); * Enable `clippy::nursery` https://github.com/SeaQL/sea-query/pull/938 * Removed unnecessary `'static` bounds from type signatures https://github.com/SeaQL/sea-query/pull/921 +* `cast_as_quoted` now allows you to [qualify the type + name](https://github.com/SeaQL/sea-query/issues/827). * Most `Value` variants are now unboxed (except `BigDecimal` and `Array`). Previously the size is 24 bytes. https://github.com/SeaQL/sea-query/pull/925 ```rust assert_eq!(std::mem::size_of::(), 32); @@ -96,6 +98,11 @@ assert_eq!( ### Breaking Changes +* Changed `Expr::TypeName(DynIden)` to `Expr::TypeName(TypeName)`, which can be + [qualified](https://github.com/SeaQL/sea-query/issues/827). + + If you manually construct this variant and it no longer compiles, just add + `.into()`. * Removed inherent `SimpleExpr` methods that duplicate `ExprTrait`. If you encounter the following error, please add `use sea_query::ExprTrait` in scope https://github.com/SeaQL/sea-query/pull/890 ```rust error[E0599]: no method named `like` found for enum `sea_query::Expr` in the current scope diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index 496e29c37..7299316c1 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -454,8 +454,8 @@ pub trait QueryBuilder: Expr::Constant(val) => { self.prepare_constant(val, sql); } - Expr::TypeName(iden) => { - self.prepare_iden(iden, sql); + Expr::TypeName(type_name) => { + self.prepare_type_name(type_name, sql); } } } @@ -910,6 +910,16 @@ pub trait QueryBuilder: self.prepare_function_name_common(function, sql) } + /// Translate [`TypeName`] into an SQL statement. + fn prepare_type_name(&self, type_name: &TypeName, sql: &mut dyn SqlWriter) { + let TypeName(schema_name, r#type) = type_name; + if let Some(schema_name) = schema_name { + self.prepare_schema_name(schema_name, sql); + write!(sql, ".").unwrap(); + } + self.prepare_iden(r#type, sql); + } + /// Translate [`JoinType`] into SQL statement. fn prepare_join_type(&self, join_type: &JoinType, sql: &mut dyn SqlWriter) { self.prepare_join_type_common(join_type, sql) diff --git a/src/backend/table_ref_builder.rs b/src/backend/table_ref_builder.rs index 57f64e681..a6c8af228 100644 --- a/src/backend/table_ref_builder.rs +++ b/src/backend/table_ref_builder.rs @@ -19,14 +19,20 @@ pub trait TableRefBuilder: QuotedBuilder { /// Translate [`TableName`] into an SQL statement. fn prepare_table_name(&self, table_name: &TableName, sql: &mut dyn SqlWriter) { let TableName(schema_name, table) = table_name; - if let Some(SchemaName(database_name, schema)) = schema_name { - if let Some(DatabaseName(database)) = database_name { - self.prepare_iden(database, sql); - write!(sql, ".").unwrap(); - } - self.prepare_iden(schema, sql); + if let Some(schema_name) = schema_name { + self.prepare_schema_name(schema_name, sql); write!(sql, ".").unwrap(); } self.prepare_iden(table, sql); } + + /// Translate [`SchemaName`] into an SQL statement. + fn prepare_schema_name(&self, schema_name: &SchemaName, sql: &mut dyn SqlWriter) { + let SchemaName(database_name, schema) = schema_name; + if let Some(DatabaseName(database)) = database_name { + self.prepare_iden(database, sql); + write!(sql, ".").unwrap(); + } + self.prepare_iden(schema, sql); + } } diff --git a/src/expr.rs b/src/expr.rs index a5ac64174..9582b01fa 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -43,7 +43,7 @@ pub enum Expr { AsEnum(DynIden, Box), Case(Box), Constant(Value), - TypeName(DynIden), + TypeName(TypeName), } /// "Operator" methods for building expressions. diff --git a/src/func.rs b/src/func.rs index 7c8b8977b..38ed889d0 100644 --- a/src/func.rs +++ b/src/func.rs @@ -476,6 +476,8 @@ impl Func { /// Call `CAST` function with a case-sensitive custom type. /// + /// Type can be qualified with a schema name. + /// /// # Examples /// /// ``` @@ -497,15 +499,33 @@ impl Func { /// query.to_string(SqliteQueryBuilder), /// r#"SELECT CAST('hello' AS "MyType")"# /// ); + /// + /// // Also works with a schema-qualified type name: + /// + /// let query = Query::select() + /// .expr(Func::cast_as_quoted("hello", ("MySchema", "MyType"))) + /// .to_owned(); + /// + /// assert_eq!( + /// query.to_string(MysqlQueryBuilder), + /// r#"SELECT CAST('hello' AS `MySchema`.`MyType`)"# + /// ); + /// assert_eq!( + /// query.to_string(PostgresQueryBuilder), + /// r#"SELECT CAST('hello' AS "MySchema"."MyType")"# + /// ); + /// assert_eq!( + /// query.to_string(SqliteQueryBuilder), + /// r#"SELECT CAST('hello' AS "MySchema"."MyType")"# + /// ); /// ``` - pub fn cast_as_quoted(expr: V, iden: I) -> FunctionCall + pub fn cast_as_quoted(expr: V, r#type: I) -> FunctionCall where V: Into, - I: IntoIden, + I: Into, { let expr: Expr = expr.into(); - FunctionCall::new(Func::Cast) - .arg(expr.binary(BinOper::As, Expr::TypeName(iden.into_iden()))) + FunctionCall::new(Func::Cast).arg(expr.binary(BinOper::As, Expr::TypeName(r#type.into()))) } /// Call `COALESCE` function. diff --git a/src/types/mod.rs b/src/types/mod.rs index 0ba1274b0..5982a958e 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -146,6 +146,10 @@ pub struct DatabaseName(pub DynIden); #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SchemaName(pub Option, pub DynIden); +/// An SQL type name, potentially qualified as `(database.)(schema.)type`. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TypeName(pub Option, pub DynIden); + /// A table name, potentially qualified as `(database.)(schema.)table`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TableName(pub Option, pub DynIden); diff --git a/src/types/qualification.rs b/src/types/qualification.rs index 1430b1988..ec68b655e 100644 --- a/src/types/qualification.rs +++ b/src/types/qualification.rs @@ -87,6 +87,21 @@ where } } +/// Construct a [`TypeName`] from 1-3 parts (`(database?).(schema?).type`) +impl From for TypeName +where + T: MaybeQualifiedTwice, +{ + fn from(value: T) -> Self { + let (schema_parts, r#type) = value.into_3_parts(); + let schema_name = schema_parts.map(|schema_parts| match schema_parts { + (Some(db), schema) => SchemaName(Some(DatabaseName(db)), schema), + (None, schema) => SchemaName(None, schema), + }); + TypeName(schema_name, r#type) + } +} + /// Construct a [`TableName`] from 1-3 parts (`(database?).(schema?).table`) impl From for TableName where