Repository: SeaQL/sea-orm (rust)

Issue:
Generate entities: RcOrArc<ColumnDef> passed for ColumnType::Array
<!--

Welcome! Thank you for reporting bugs!

First of all, please star our repo. Your support is vital to the continued maintenance of SeaORM.

Want to ask a question? You can reach us via:

- Discord: https://discord.com/invite/uCPdDXzbdv
- GitHub Discussions: https://github.com/SeaQL/sea-orm/discussions/new

Please make sure that you are not asking for a missing feature; a bug is incorrect behavior -
either in the feature specification or implementation. Feature requests should be first raised in discussions.

Please also make sure your description is clear and precise - maintainers don't have access to your
code and can't see what you have seen. Please avoid vague descriptions like "they are different"
or "the program crashes" - in either case, provide exact information.

If you are certain there is a bug, please provide a reproducible example, which helps the investigator
to pin-point the bug and the implementor to verify that a solution is satisfactory. Bug reports without 
reproducible example may be closed immediately or dangle forever.

Finally, please search for existing issues and discussions before submission. Feel free to revive old
threads if you have new information to add, but please don't ask for ETA or "+1".

-->

## Description

I ran `sea-orm-cli generate entity` and generated Rust code does not compile:
```shell
error[E0308]: mismatched types
   --> entities/src/gen/foo.rs:<line>:<col>
    |
69  |             Self::FooBar => ColumnType::Array(RcOrArc::new(BarBaz::db_type())).def(),
    |                                              ------------ ^^^^^^^^^^^^^^^^^^^^^ expected `ColumnType`, found `ColumnDef`
    |                                              |
    |                                              arguments to this function are incorrect
    |
```

<!-- Briefly describe the bug -->

## Steps to Reproduce

1. Have a enum of any type
2. Have a table with column of type `enum[]`
3. Generate entities from the table
4. Try to compile the generated Rust code

### Expected Behavior

Compiles

<!-- What is expected to happen? -->

### Actual Behavior

Not compiles

<!-- What actually happened? -->

### Reproduces How Often

Consistent

<!-- Is it always reproducible? -->

### Workarounds

Edit generated code like this:

```diff
-use sea_orm::entity::prelude::*;
+use sea_orm::{entity::prelude::*, sea_query::ValueType};

 #[derive(Copy, Clone, Default, Debug, DeriveEntity)]
@@ -66,7 +66,7 @@ impl ColumnTrait for Column {
-            Self::FooBar => ColumnType::Array(RcOrArc::new(BarBaz::db_type())).def(),
+            Self::FooBar => ColumnType::Array(RcOrArc::new(BarBaz::column_type())).def(),

```

Of course it will be overwritten when we re-generate Rust code :(

<!-- What experiments have you done to understand / workaround the bug? -->

## Reproducible Example

migration file:

```rust
#[derive(Iden)]
pub enum BarBaz {
  #[iden = "bar_baz"]
  Enum,
  #[iden = "variant_1"]
  Variant1,
}

manager
    .create_type(
        extension::postgres::Type::create()
            .as_enum(BarBaz::Enum)
            .values([
                BarBaz::Variant1,
            ])
            .to_owned(),
    )
    .await?;

manager
    .create_table(
        Table::create()
            .table(Foo::Table)
            .if_not_exists()
            .col(
                ColumnDef::new(Foo::FooBar)
                    .custom(Alias::new("bar_baz[]"))
                    .not_null(),
            )
            .to_owned(),
    )
    .await?;
```

<!-- Please add a minimal reproducible example under https://github.com/SeaQL/sea-orm/tree/master/issues, and open a PR subsequently. -->

## Versions

```
$ cargo tree | grep sea-orm
│   ├── sea-orm v1.1.1
│   │   ├── sea-orm-macros v1.1.1 (proc-macro)
│   ├── sea-orm v1.1.1 (*)
│   ├── sea-orm v1.1.1 (*)
│   │   └── sea-orm-migration v1.1.1
│   │       ├── sea-orm v1.1.1 (*)
│   │       ├── sea-orm-cli v1.1.1
│   │   ├── sea-orm v1.1.1 (*)
├── sea-orm v1.1.1 (*)
```

<!-- You can get this information from the output of `cargo tree | grep sea-` from the console. Also, please include the database and OS that you are running. -->


Pull request:
fix #2409
## PR Info

- Closes #2409

## Bug Fixes

- [x] generating entity with `--expanded-format` for array of enum got a type mismatch error

```rust
error[E0308]: mismatched types
   --> entities/src/gen/foo.rs:<line>:<col>
    |
69  |             Self::FooBar => ColumnType::Array(RcOrArc::new(BarBaz::db_type())).def(),
    |                                              ------------ ^^^^^^^^^^^^^^^^^^^^^ expected `ColumnType`, found `ColumnDef`
    |                                              |
    |                                              arguments to this function are incorrect
```

## Changes

@pranc1ngpegasus said `db_type()` should be `column_type()`, but `column_type()` needs to import `ValueType` trait. So I find `column_type`:

```rust
fn column_type() -> sea_orm::sea_query::ColumnType {
    <Self as sea_orm::ActiveEnum>::db_type()
        .get_column_type()
        .to_owned()
        .into()
}
```

So, I modified the code in `sea-orm-codegen` from
```rust
quote! { #enum_ident::db_type() }
```
to
```rust
quote! {
    #enum_ident::db_type()
        .get_column_type()
        .to_owned()
}
```

Hope this change did not influence other components...

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index e69e272f..4fe3434d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -42,6 +42,7 @@ serde_json = { version = "1.0", default-features = false, optional = true }
 sqlx = { version = "0.8.2", default-features = false, optional = true }
 uuid = { version = "1", default-features = false, optional = true }
 pgvector = { version = "~0.4", default-features = false, optional = true }
+ipnetwork = { version = "0.20", default-features = false, optional = true }
 ouroboros = { version = "0.18", default-features = false }
 url = { version = "2.2", default-features = false }
 thiserror = { version = "2", default-features = false }
@@ -59,7 +60,6 @@ sea-orm = { path = ".", features = ["mock", "debug-print", "tests-cfg", "postgre
 pretty_assertions = { version = "0.7" }
 time = { version = "0.3.36", features = ["macros"] }
 uuid = { version = "1", features = ["v4"] }
-once_cell = "1.8"
 arraystring = "0.3"
 dotenv = "0.15"
 
@@ -73,6 +73,7 @@ default = [
     "with-bigdecimal",
     "with-uuid",
     "with-time",
+    "with-ipnetwork",
 ]
 macros = ["sea-orm-macros/derive"]
 mock = []
@@ -83,6 +84,7 @@ with-rust_decimal = ["rust_decimal", "sea-query/with-rust_decimal", "sea-query-b
 with-bigdecimal = ["bigdecimal", "sea-query/with-bigdecimal", "sea-query-binder?/with-bigdecimal", "sqlx?/bigdecimal"]
 with-uuid = ["uuid", "sea-query/with-uuid", "sea-query-binder?/with-uuid", "sqlx?/uuid"]
 with-time = ["time", "sea-query/with-time", "sea-query-binder?/with-time", "sqlx?/time"]
+with-ipnetwork = ["ipnetwork", "sea-query/with-ipnetwork", "sea-query-binder?/with-ipnetwork", "sqlx?/ipnetwork"]
 postgres-array = ["sea-query/postgres-array", "sea-query-binder?/postgres-array", "sea-orm-macros/postgres-array"]
 postgres-vector = ["pgvector", "sea-query/postgres-vector", "sea-query-binder?/postgres-vector"]
 json-array = ["postgres-array"] # this does not actually enable sqlx-postgres, but only a few traits to support array in sea-query
diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs
index 3e722933..18625fd1 100644
--- a/sea-orm-codegen/src/entity/column.rs
+++ b/sea-orm-codegen/src/entity/column.rs
@@ -80,11 +80,10 @@ impl Column {
                 ColumnType::Bit(None | Some(1)) => "bool".to_owned(),
                 ColumnType::Bit(_) | ColumnType::VarBit(_) => "Vec<u8>".to_owned(),
                 ColumnType::Year => "i32".to_owned(),
-                ColumnType::Interval(_, _)
-                | ColumnType::Cidr
-                | ColumnType::Inet
-                | ColumnType::MacAddr
-                | ColumnType::LTree => "String".to_owned(),
+                ColumnType::Cidr | ColumnType::Inet => "IpNetwork".to_owned(),
+                ColumnType::Interval(_, _) | ColumnType::MacAddr | ColumnType::LTree => {
+                    "String".to_owned()
+                }
                 _ => unimplemented!(),
             }
         }
@@ -113,6 +112,7 @@ impl Column {
                 StringLen::Max => Some("VarBinary(StringLen::Max)".to_owned()),
             },
             ColumnType::Blob => Some("Blob".to_owned()),
+            ColumnType::Cidr => Some("Cidr".to_owned()),
             _ => None,
         };
         col_type.map(|ty| quote! { column_type = #ty })
@@ -169,13 +169,19 @@ impl Column {
                 ColumnType::Json => quote! { ColumnType::Json },
                 ColumnType::JsonBinary => quote! { ColumnType::JsonBinary },
                 ColumnType::Uuid => quote! { ColumnType::Uuid },
+                ColumnType::Cidr => quote! { ColumnType::Cidr },
+                ColumnType::Inet => quote! { ColumnType::Inet },
                 ColumnType::Custom(s) => {
                     let s = s.to_string();
                     quote! { ColumnType::custom(#s) }
                 }
                 ColumnType::Enum { name, .. } => {
                     let enum_ident = format_ident!("{}", name.to_string().to_upper_camel_case());
-                    quote! { #enum_ident::db_type() }
+                    quote! {
+                        #enum_ident::db_type()
+                            .get_column_type()
+                            .to_owned()
+                    }
                 }
                 ColumnType::Array(column_type) => {
                     let column_type = write_col_def(column_type);
diff --git a/sea-orm-migration/Cargo.toml b/sea-orm-migration/Cargo.toml
index bf55b2e9..d5e47bbe 100644
--- a/sea-orm-migration/Cargo.toml
+++ b/sea-orm-migration/Cargo.toml
@@ -54,3 +54,4 @@ with-rust_decimal = ["sea-orm/with-rust_decimal"]
 with-bigdecimal = ["sea-orm/with-bigdecimal"]
 with-uuid = ["sea-orm/with-uuid"]
 with-time = ["sea-orm/with-time"]
+with-ipnetwork = ["sea-orm/with-ipnetwork"]
diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs
index 9cc8958d..e92fda2f 100644
--- a/src/entity/active_model.rs
+++ b/src/entity/active_model.rs
@@ -742,6 +742,10 @@ impl_into_active_value!(crate::prelude::TimeDateTime);
 #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))]
 impl_into_active_value!(crate::prelude::TimeDateTimeWithTimeZone);
 
+#[cfg(feature = "with-ipnetwork")]
+#[cfg_attr(docsrs, doc(cfg(feature = "with-ipnetwork")))]
+impl_into_active_value!(crate::prelude::IpNetwork);
+
 impl<V> Default for ActiveValue<V>
 where
     V: Into<Value>,
diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs
index 232a89a7..260bd073 100644
--- a/src/entity/prelude.rs
+++ b/src/entity/prelude.rs
@@ -86,3 +86,6 @@ pub use uuid::Uuid;
 
 #[cfg(feature = "postgres-vector")]
 pub use pgvector::Vector as PgVector;
+
+#[cfg(feature = "with-ipnetwork")]
+pub use ipnetwork::IpNetwork;
diff --git a/src/executor/paginator.rs b/src/executor/paginator.rs
index aad69172..5b910fbb 100644
--- a/src/executor/paginator.rs
+++ b/src/executor/paginator.rs
@@ -325,11 +325,11 @@ mod tests {
     use crate::{tests_cfg::*, ConnectionTrait, Statement};
     use crate::{DatabaseConnection, DbBackend, MockDatabase, Transaction};
     use futures_util::TryStreamExt;
-    use once_cell::sync::Lazy;
     use pretty_assertions::assert_eq;
     use sea_query::{Alias, Expr, SelectStatement, Value};
+    use std::sync::LazyLock;
 
-    static RAW_STMT: Lazy<Statement> = Lazy::new(|| {
+    static RAW_STMT: LazyLock<Statement> = LazyLock::new(|| {
         Statement::from_sql_and_values(
             DbBackend::Postgres,
             r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#,
diff --git a/src/executor/query.rs b/src/executor/query.rs
index f8be72bf..1afe3f47 100644
--- a/src/executor/query.rs
+++ b/src/executor/query.rs
@@ -477,6 +477,48 @@ macro_rules! try_getable_mysql {
     };
 }
 
+#[allow(unused_macros)]
+macro_rules! try_getable_postgres {
+    ( $type: ty ) => {
+        impl TryGetable for $type {
+            #[allow(unused_variables)]
+            fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
+                match &res.row {
+                    #[cfg(feature = "sqlx-mysql")]
+                    QueryResultRow::SqlxMySql(_) => Err(type_err(format!(
+                        "{} unsupported by sqlx-mysql",
+                        stringify!($type)
+                    ))
+                    .into()),
+                    #[cfg(feature = "sqlx-postgres")]
+                    QueryResultRow::SqlxPostgres(row) => row
+                        .try_get::<Option<$type>, _>(idx.as_sqlx_postgres_index())
+                        .map_err(|e| sqlx_error_to_query_err(e).into())
+                        .and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
+                    #[cfg(feature = "sqlx-sqlite")]
+                    QueryResultRow::SqlxSqlite(_) => Err(type_err(format!(
+                        "{} unsupported by sqlx-sqlite",
+                        stringify!($type)
+                    ))
+                    .into()),
+                    #[cfg(feature = "mock")]
+                    QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
+                        debug_print!("{:#?}", e.to_string());
+                        err_null_idx_col(idx)
+                    }),
+                    #[cfg(feature = "proxy")]
+                    QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
+                        debug_print!("{:#?}", e.to_string());
+                        err_null_idx_col(idx)
+                    }),
+                    #[allow(unreachable_patterns)]
+                    _ => unreachable!(),
+                }
+            }
+        }
+    };
+}
+
 #[allow(unused_macros)]
 macro_rules! try_getable_date_time {
     ( $type: ty ) => {
@@ -759,6 +801,9 @@ try_getable_uuid!(uuid::fmt::Simple, uuid::Uuid::simple);
 #[cfg(feature = "with-uuid")]
 try_getable_uuid!(uuid::fmt::Urn, uuid::Uuid::urn);
 
+#[cfg(feature = "with-ipnetwork")]
+try_getable_postgres!(ipnetwork::IpNetwork);
+
 impl TryGetable for u32 {
     #[allow(unused_variables)]
     fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
@@ -907,6 +952,7 @@ mod postgres_array {
     try_getable_postgres_array!(f32);
     try_getable_postgres_array!(f64);
     try_getable_postgres_array!(String);
+    try_getable_postgres_array!(Vec<u8>);
 
     #[cfg(feature = "with-json")]
     try_getable_postgres_array!(serde_json::Value);
@@ -947,6 +993,9 @@ mod postgres_array {
     #[cfg(feature = "with-bigdecimal")]
     try_getable_postgres_array!(bigdecimal::BigDecimal);
 
+    #[cfg(feature = "with-ipnetwork")]
+    try_getable_postgres_array!(ipnetwork::IpNetwork);
+
     #[allow(unused_macros)]
     macro_rules! try_getable_postgres_array_uuid {
         ( $type: ty, $conversion_fn: expr ) => {
@@ -1469,6 +1518,9 @@ try_from_u64_err!(rust_decimal::Decimal);
 #[cfg(feature = "with-uuid")]
 try_from_u64_err!(uuid::Uuid);
 
+#[cfg(feature = "with-ipnetwork")]
+try_from_u64_err!(ipnetwork::IpNetwork);
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/src/query/json.rs b/src/query/json.rs
index 2ad92197..41e5b7b9 100644
--- a/src/query/json.rs
+++ b/src/query/json.rs
@@ -157,6 +157,10 @@ impl FromQueryResult for JsonValue {
                     try_get_type!(uuid::Uuid, col);
                     #[cfg(all(feature = "with-uuid", feature = "postgres-array"))]
                     try_get_type!(Vec<uuid::Uuid>, col);
+                    #[cfg(feature = "with-ipnetwork")]
+                    try_get_type!(ipnetwork::IpNetwork, col);
+                    #[cfg(all(feature = "with-ipnetwork", feature = "postgres-array"))]
+                    try_get_type!(Vec<ipnetwork::IpNetwork>, col);
                     try_get_type!(Vec<u8>, col);
                 }
                 Ok(JsonValue::Object(map))


Test patch:
diff --git a/tests/common/features/host_network.rs b/tests/common/features/host_network.rs
new file mode 100644
index 00000000..8d0ed92b
--- /dev/null
+++ b/tests/common/features/host_network.rs
@@ -0,0 +1,17 @@
+use sea_orm::entity::prelude::*;
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "host_network")]
+pub struct Model {
+    #[sea_orm(primary_key)]
+    pub id: i32,
+    pub hostname: String,
+    pub ipaddress: IpNetwork,
+    #[sea_orm(column_type = "Cidr")]
+    pub network: IpNetwork,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/tests/common/features/mod.rs b/tests/common/features/mod.rs
index 37f057fa..953ac018 100644
--- a/tests/common/features/mod.rs
+++ b/tests/common/features/mod.rs
@@ -14,6 +14,7 @@ pub mod edit_log;
 #[cfg(feature = "postgres-vector")]
 pub mod embedding;
 pub mod event_trigger;
+pub mod host_network;
 pub mod insert_default;
 pub mod json_struct;
 pub mod json_vec;
@@ -45,6 +46,7 @@ pub use edit_log::Entity as EditLog;
 #[cfg(feature = "postgres-vector")]
 pub use embedding::Entity as Embedding;
 pub use event_trigger::Entity as EventTrigger;
+pub use host_network::Entity as HostNetwork;
 pub use insert_default::Entity as InsertDefault;
 pub use json_struct::Entity as JsonStruct;
 pub use json_vec::Entity as JsonVec;
diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs
index 5d03bd60..08f6d98b 100644
--- a/tests/common/features/schema.rs
+++ b/tests/common/features/schema.rs
@@ -63,6 +63,7 @@ pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> {
         create_categories_table(db).await?;
         #[cfg(feature = "postgres-vector")]
         create_embedding_table(db).await?;
+        create_host_network_table(db).await?;
     }
 
     Ok(())
@@ -473,6 +474,36 @@ pub async fn create_collection_table(db: &DbConn) -> Result<ExecResult, DbErr> {
     create_table(db, &stmt, Collection).await
 }
 
+pub async fn create_host_network_table(db: &DbConn) -> Result<ExecResult, DbErr> {
+    let stmt = sea_query::Table::create()
+        .table(host_network::Entity)
+        .col(
+            ColumnDef::new(host_network::Column::Id)
+                .integer()
+                .not_null()
+                .auto_increment()
+                .primary_key(),
+        )
+        .col(
+            ColumnDef::new(host_network::Column::Hostname)
+                .string()
+                .not_null(),
+        )
+        .col(
+            ColumnDef::new(host_network::Column::Ipaddress)
+                .inet()
+                .not_null(),
+        )
+        .col(
+            ColumnDef::new(host_network::Column::Network)
+                .cidr()
+                .not_null(),
+        )
+        .to_owned();
+
+    create_table(db, &stmt, HostNetwork).await
+}
+
 pub async fn create_pi_table(db: &DbConn) -> Result<ExecResult, DbErr> {
     let stmt = sea_query::Table::create()
         .table(pi::Entity)
diff --git a/tests/host_network_tests.rs b/tests/host_network_tests.rs
new file mode 100644
index 00000000..f7423de3
--- /dev/null
+++ b/tests/host_network_tests.rs
@@ -0,0 +1,64 @@
+#![allow(unused_imports, dead_code)]
+
+pub mod common;
+
+use common::{features::*, setup::*, TestContext};
+use pretty_assertions::assert_eq;
+use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection};
+use std::net::{Ipv4Addr, Ipv6Addr};
+
+#[sea_orm_macros::test]
+#[cfg(feature = "sqlx-postgres")]
+async fn main() -> Result<(), DbErr> {
+    let ctx = TestContext::new("host_network_tests").await;
+    create_tables(&ctx.db).await?;
+    create_and_update_host_network(&ctx.db).await?;
+    ctx.delete().await;
+
+    Ok(())
+}
+
+async fn create_and_update_host_network(db: &DatabaseConnection) -> Result<(), DbErr> {
+    let addr = IpNetwork::new(Ipv4Addr::new(192, 168, 0, 20).into(), 24).unwrap();
+    let net = IpNetwork::new(addr.network(), addr.prefix()).unwrap();
+
+    let host = host_network::Model {
+        id: 1,
+        hostname: "example.com".to_owned(),
+        ipaddress: addr,
+        network: net,
+    };
+    let res = host.clone().into_active_model().insert(db).await?;
+
+    let model = HostNetwork::find().one(db).await?.unwrap();
+    assert_eq!(model, res);
+    assert_eq!(model, host.clone());
+
+    let addrv6 = IpNetwork::new(
+        Ipv6Addr::new(0xfd89, 0x1926, 0x4cae, 0x8abd, 0, 0, 0, 0x6f52).into(),
+        64,
+    )
+    .unwrap();
+    let netv6 = IpNetwork::new(addr.network(), addr.prefix()).unwrap();
+
+    let res = host_network::ActiveModel {
+        id: Set(1),
+        ipaddress: Set(addrv6),
+        network: Set(netv6),
+        ..Default::default()
+    }
+    .update(db)
+    .await?;
+
+    assert_eq!(
+        res,
+        host_network::Model {
+            id: 1,
+            hostname: "example.com".to_owned(),
+            ipaddress: addrv6,
+            network: netv6,
+        }
+    );
+
+    Ok(())
+}
diff --git a/tests/type_tests.rs b/tests/type_tests.rs
index 252f74a7..e71d4366 100644
--- a/tests/type_tests.rs
+++ b/tests/type_tests.rs
@@ -65,4 +65,5 @@ fn main() {
     it_impl_traits!(time::OffsetDateTime);
     it_impl_traits!(rust_decimal::Decimal);
     it_impl_traits!(uuid::Uuid);
+    it_impl_traits!(ipnetwork::IpNetwork);
 }