Merge branch 'master' into support-time-crate
This commit is contained in:
commit
c28d77df6d
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@ -293,7 +293,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
path: [basic, actix_example, actix4_example, axum_example, rocket_example, poem_example]
|
path: [basic, actix_example, actix4_example, axum_example, axum-graphql_example, rocket_example, poem_example]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* Codegen add serde derives to enums, if specified https://github.com/SeaQL/sea-orm/pull/463
|
* Codegen add serde derives to enums, if specified https://github.com/SeaQL/sea-orm/pull/463
|
||||||
|
* Codegen Unsigned Integer - 2 https://github.com/SeaQL/sea-orm/pull/397
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
[](https://GitHub.com/SeaQL/sea-orm/stargazers/)
|
[](https://github.com/SeaQL/sea-orm/stargazers/)
|
||||||
If you like what we do, consider starring, commenting, sharing and contributing!
|
If you like what we do, consider starring, commenting, sharing and contributing!
|
||||||
|
|
||||||
[](https://discord.com/invite/uCPdDXzbdv)
|
[](https://discord.com/invite/uCPdDXzbdv)
|
||||||
@ -29,6 +29,7 @@ Join our Discord server to chat with others in the SeaQL community!
|
|||||||
+ [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)
|
+ [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)
|
||||||
+ [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)
|
+ [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)
|
||||||
+ [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example)
|
+ [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example)
|
||||||
|
+ [Axum-GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum-graphql_example)
|
||||||
+ [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example)
|
+ [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
1
examples/axum-graphql_example/.env
Normal file
1
examples/axum-graphql_example/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
DATABASE_URL=sqlite:./db?mode=rwc
|
3
examples/axum-graphql_example/.gitignore
vendored
Normal file
3
examples/axum-graphql_example/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
db
|
||||||
|
db-shm
|
||||||
|
db-wal
|
17
examples/axum-graphql_example/Cargo.toml
Normal file
17
examples/axum-graphql_example/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "axum-graphql"
|
||||||
|
authors = ["Aaron Leopold <aaronleopold1221@gmail.com>"]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[workspace]
|
||||||
|
members = [".", "entity", "migration"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
axum = "0.4.8"
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
async-graphql-axum = "3.0.31"
|
||||||
|
entity = { path = "entity" }
|
||||||
|
migration = { path = "migration" }
|
13
examples/axum-graphql_example/README.md
Normal file
13
examples/axum-graphql_example/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Axum-GraphQL with SeaORM example app
|
||||||
|
|
||||||
|
1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database
|
||||||
|
|
||||||
|
1. Turn on the appropriate database feature for your chosen db in `entity/Cargo.toml` (the `"sqlx-sqlite",` line)
|
||||||
|
|
||||||
|
1. Execute `cargo run` to start the server
|
||||||
|
|
||||||
|
1. Visit [localhost:3000/api/graphql](http://localhost:3000/api/graphql) in browser
|
BIN
examples/axum-graphql_example/Screenshot1.png
Normal file
BIN
examples/axum-graphql_example/Screenshot1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 615 KiB |
BIN
examples/axum-graphql_example/Screenshot2.png
Normal file
BIN
examples/axum-graphql_example/Screenshot2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 570 KiB |
26
examples/axum-graphql_example/entity/Cargo.toml
Normal file
26
examples/axum-graphql_example/entity/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "entity"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "entity"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
[dependencies.async-graphql]
|
||||||
|
version = "3.0.12"
|
||||||
|
|
||||||
|
[dependencies.sea-orm]
|
||||||
|
version = "^0.6.0"
|
||||||
|
features = [
|
||||||
|
"macros",
|
||||||
|
"runtime-tokio-native-tls",
|
||||||
|
# "sqlx-postgres",
|
||||||
|
# "sqlx-mysql",
|
||||||
|
"sqlx-sqlite"
|
||||||
|
]
|
||||||
|
default-features = false
|
4
examples/axum-graphql_example/entity/src/lib.rs
Normal file
4
examples/axum-graphql_example/entity/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod note;
|
||||||
|
|
||||||
|
pub use async_graphql;
|
||||||
|
pub use sea_orm;
|
39
examples/axum-graphql_example/entity/src/note.rs
Normal file
39
examples/axum-graphql_example/entity/src/note.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use async_graphql::*;
|
||||||
|
use sea_orm::{entity::prelude::*, DeleteMany};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)]
|
||||||
|
#[sea_orm(table_name = "notes")]
|
||||||
|
#[graphql(concrete(name = "Note", params()))]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub id: i32,
|
||||||
|
pub title: String,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
panic!("No RelationDef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
impl Entity {
|
||||||
|
pub fn find_by_id(id: i32) -> Select<Entity> {
|
||||||
|
Self::find().filter(Column::Id.eq(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_by_title(title: &str) -> Select<Entity> {
|
||||||
|
Self::find().filter(Column::Title.eq(title))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_by_id(id: i32) -> DeleteMany<Entity> {
|
||||||
|
Self::delete_many().filter(Column::Id.eq(id))
|
||||||
|
}
|
||||||
|
}
|
14
examples/axum-graphql_example/migration/Cargo.toml
Normal file
14
examples/axum-graphql_example/migration/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "migration"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "migration"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sea-schema = { version = "0.5.0", default-features = false, features = [ "migration", "debug-print" ] }
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
entity = { path = "../entity" }
|
37
examples/axum-graphql_example/migration/README.md
Normal file
37
examples/axum-graphql_example/migration/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Running Migrator CLI
|
||||||
|
|
||||||
|
- Apply all pending migrations
|
||||||
|
```sh
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
```sh
|
||||||
|
cargo run -- up
|
||||||
|
```
|
||||||
|
- Apply first 10 pending migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- up -n 10
|
||||||
|
```
|
||||||
|
- Rollback last applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- down
|
||||||
|
```
|
||||||
|
- Rollback last 10 applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- down -n 10
|
||||||
|
```
|
||||||
|
- Drop all tables from the database, then reapply all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- fresh
|
||||||
|
```
|
||||||
|
- Rollback all applied migrations, then reapply all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- refresh
|
||||||
|
```
|
||||||
|
- Rollback all applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- reset
|
||||||
|
```
|
||||||
|
- Check the status of all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- status
|
||||||
|
```
|
12
examples/axum-graphql_example/migration/src/lib.rs
Normal file
12
examples/axum-graphql_example/migration/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
pub use sea_schema::migration::*;
|
||||||
|
|
||||||
|
mod m20220101_000001_create_table;
|
||||||
|
|
||||||
|
pub struct Migrator;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigratorTrait for Migrator {
|
||||||
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
|
vec![Box::new(m20220101_000001_create_table::Migration)]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
use entity::{
|
||||||
|
note,
|
||||||
|
sea_orm::{DbBackend, EntityTrait, Schema},
|
||||||
|
};
|
||||||
|
use sea_schema::migration::{
|
||||||
|
sea_query::{self, *},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
fn get_seaorm_create_stmt<E: EntityTrait>(e: E) -> TableCreateStatement {
|
||||||
|
let schema = Schema::new(DbBackend::Sqlite);
|
||||||
|
|
||||||
|
schema
|
||||||
|
.create_table_from_entity(e)
|
||||||
|
.if_not_exists()
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_seaorm_drop_stmt<E: EntityTrait>(e: E) -> TableDropStatement {
|
||||||
|
Table::drop().table(e).if_exists().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20220101_000001_create_table"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
let stmts = vec![get_seaorm_create_stmt(note::Entity)];
|
||||||
|
|
||||||
|
for stmt in stmts {
|
||||||
|
manager.create_table(stmt.to_owned()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
let stmts = vec![get_seaorm_drop_stmt(note::Entity)];
|
||||||
|
|
||||||
|
for stmt in stmts {
|
||||||
|
manager.drop_table(stmt.to_owned()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
26
examples/axum-graphql_example/migration/src/main.rs
Normal file
26
examples/axum-graphql_example/migration/src/main.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use migration::Migrator;
|
||||||
|
use sea_schema::migration::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
#[async_std::main]
|
||||||
|
async fn main() {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
dotenv().ok();
|
||||||
|
|
||||||
|
let fallback = "sqlite:./db?mode=rwc";
|
||||||
|
|
||||||
|
match std::env::var("DATABASE_URL") {
|
||||||
|
Ok(val) => {
|
||||||
|
println!("Using DATABASE_URL: {}", val);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
std::env::set_var("DATABASE_URL", fallback);
|
||||||
|
println!("Set DATABASE_URL: {}", fallback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cli::run_cli(Migrator).await;
|
||||||
|
}
|
20
examples/axum-graphql_example/src/db.rs
Normal file
20
examples/axum-graphql_example/src/db.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use entity::sea_orm;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
pub struct Database {
|
||||||
|
pub connection: DatabaseConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
pub async fn new() -> Self {
|
||||||
|
let connection = sea_orm::Database::connect(std::env::var("DATABASE_URL").unwrap())
|
||||||
|
.await
|
||||||
|
.expect("Could not connect to database");
|
||||||
|
|
||||||
|
Database { connection }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_connection(&self) -> &DatabaseConnection {
|
||||||
|
&self.connection
|
||||||
|
}
|
||||||
|
}
|
3
examples/axum-graphql_example/src/graphql/mod.rs
Normal file
3
examples/axum-graphql_example/src/graphql/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod mutation;
|
||||||
|
pub mod query;
|
||||||
|
pub mod schema;
|
10
examples/axum-graphql_example/src/graphql/mutation/mod.rs
Normal file
10
examples/axum-graphql_example/src/graphql/mutation/mod.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use entity::async_graphql;
|
||||||
|
|
||||||
|
pub mod note;
|
||||||
|
|
||||||
|
pub use note::NoteMutation;
|
||||||
|
|
||||||
|
// Add your other ones here to create a unified Mutation object
|
||||||
|
// e.x. Mutation(NoteMutation, OtherMutation, OtherOtherMutation)
|
||||||
|
#[derive(async_graphql::MergedObject, Default)]
|
||||||
|
pub struct Mutation(NoteMutation);
|
60
examples/axum-graphql_example/src/graphql/mutation/note.rs
Normal file
60
examples/axum-graphql_example/src/graphql/mutation/note.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use async_graphql::{Context, Object, Result};
|
||||||
|
use entity::async_graphql::{self, InputObject, SimpleObject};
|
||||||
|
use entity::note;
|
||||||
|
use entity::sea_orm::{ActiveModelTrait, Set};
|
||||||
|
|
||||||
|
use crate::db::Database;
|
||||||
|
|
||||||
|
// I normally separate the input types into separate files/modules, but this is just
|
||||||
|
// a quick example.
|
||||||
|
|
||||||
|
#[derive(InputObject)]
|
||||||
|
pub struct CreateNoteInput {
|
||||||
|
pub title: String,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SimpleObject)]
|
||||||
|
pub struct DeleteResult {
|
||||||
|
pub success: bool,
|
||||||
|
pub rows_affected: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoteMutation;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl NoteMutation {
|
||||||
|
pub async fn create_note(
|
||||||
|
&self,
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
input: CreateNoteInput,
|
||||||
|
) -> Result<note::Model> {
|
||||||
|
let db = ctx.data::<Database>().unwrap();
|
||||||
|
|
||||||
|
let note = note::ActiveModel {
|
||||||
|
title: Set(input.title),
|
||||||
|
text: Set(input.text),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(note.insert(db.get_connection()).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_note(&self, ctx: &Context<'_>, id: i32) -> Result<DeleteResult> {
|
||||||
|
let db = ctx.data::<Database>().unwrap();
|
||||||
|
|
||||||
|
let res = note::Entity::delete_by_id(id)
|
||||||
|
.exec(db.get_connection())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if res.rows_affected <= 1 {
|
||||||
|
Ok(DeleteResult {
|
||||||
|
success: true,
|
||||||
|
rows_affected: res.rows_affected,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
examples/axum-graphql_example/src/graphql/query/mod.rs
Normal file
10
examples/axum-graphql_example/src/graphql/query/mod.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use entity::async_graphql;
|
||||||
|
|
||||||
|
pub mod note;
|
||||||
|
|
||||||
|
pub use note::NoteQuery;
|
||||||
|
|
||||||
|
// Add your other ones here to create a unified Query object
|
||||||
|
// e.x. Query(NoteQuery, OtherQuery, OtherOtherQuery)
|
||||||
|
#[derive(async_graphql::MergedObject, Default)]
|
||||||
|
pub struct Query(NoteQuery);
|
28
examples/axum-graphql_example/src/graphql/query/note.rs
Normal file
28
examples/axum-graphql_example/src/graphql/query/note.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use async_graphql::{Context, Object, Result};
|
||||||
|
use entity::{async_graphql, note, sea_orm::EntityTrait};
|
||||||
|
|
||||||
|
use crate::db::Database;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoteQuery;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl NoteQuery {
|
||||||
|
async fn get_notes(&self, ctx: &Context<'_>) -> Result<Vec<note::Model>> {
|
||||||
|
let db = ctx.data::<Database>().unwrap();
|
||||||
|
|
||||||
|
Ok(note::Entity::find()
|
||||||
|
.all(db.get_connection())
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_note_by_id(&self, ctx: &Context<'_>, id: i32) -> Result<Option<note::Model>> {
|
||||||
|
let db = ctx.data::<Database>().unwrap();
|
||||||
|
|
||||||
|
Ok(note::Entity::find_by_id(id)
|
||||||
|
.one(db.get_connection())
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?)
|
||||||
|
}
|
||||||
|
}
|
21
examples/axum-graphql_example/src/graphql/schema.rs
Normal file
21
examples/axum-graphql_example/src/graphql/schema.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use async_graphql::{EmptySubscription, Schema};
|
||||||
|
use entity::async_graphql;
|
||||||
|
use migration::{Migrator, MigratorTrait};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
db::Database,
|
||||||
|
graphql::{mutation::Mutation, query::Query},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type AppSchema = Schema<Query, Mutation, EmptySubscription>;
|
||||||
|
|
||||||
|
/// Builds the GraphQL Schema, attaching the Database to the context
|
||||||
|
pub async fn build_schema() -> AppSchema {
|
||||||
|
let db = Database::new().await;
|
||||||
|
|
||||||
|
Migrator::up(db.get_connection(), None).await.unwrap();
|
||||||
|
|
||||||
|
Schema::build(Query::default(), Mutation::default(), EmptySubscription)
|
||||||
|
.data(db)
|
||||||
|
.finish()
|
||||||
|
}
|
49
examples/axum-graphql_example/src/main.rs
Normal file
49
examples/axum-graphql_example/src/main.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
mod db;
|
||||||
|
mod graphql;
|
||||||
|
|
||||||
|
use entity::async_graphql;
|
||||||
|
|
||||||
|
use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
|
||||||
|
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
|
||||||
|
use axum::{
|
||||||
|
extract::Extension,
|
||||||
|
response::{Html, IntoResponse},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use graphql::schema::{build_schema, AppSchema};
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
async fn graphql_handler(schema: Extension<AppSchema>, req: GraphQLRequest) -> GraphQLResponse {
|
||||||
|
schema.execute(req.into_inner()).await.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn graphql_playground() -> impl IntoResponse {
|
||||||
|
Html(playground_source(GraphQLPlaygroundConfig::new(
|
||||||
|
"/api/graphql",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
dotenv().ok();
|
||||||
|
|
||||||
|
let schema = build_schema().await;
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route(
|
||||||
|
"/api/graphql",
|
||||||
|
get(graphql_playground).post(graphql_handler),
|
||||||
|
)
|
||||||
|
.layer(Extension(schema));
|
||||||
|
|
||||||
|
println!("Playground: http://localhost:3000/api/graphql");
|
||||||
|
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
@ -22,7 +22,7 @@ clap = { version = "^2.33.3" }
|
|||||||
dotenv = { version = "^0.15" }
|
dotenv = { version = "^0.15" }
|
||||||
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
|
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
|
||||||
sea-orm-codegen = { version = "^0.6.0", path = "../sea-orm-codegen" }
|
sea-orm-codegen = { version = "^0.6.0", path = "../sea-orm-codegen" }
|
||||||
sea-schema = { version = "^0.5.0", default-features = false, features = [
|
sea-schema = { version = "^0.6.0", default-features = false, features = [
|
||||||
"debug-print",
|
"debug-print",
|
||||||
"sqlx-mysql",
|
"sqlx-mysql",
|
||||||
"sqlx-sqlite",
|
"sqlx-sqlite",
|
||||||
|
@ -15,7 +15,7 @@ name = "sea_orm_codegen"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sea-query = { version = "0.21.0" }
|
sea-query = { version = "0.22.0" }
|
||||||
syn = { version = "^1", default-features = false, features = [
|
syn = { version = "^1", default-features = false, features = [
|
||||||
"derive",
|
"derive",
|
||||||
"parsing",
|
"parsing",
|
||||||
|
@ -37,6 +37,10 @@ impl Column {
|
|||||||
ColumnType::SmallInteger(_) => "i16".to_owned(),
|
ColumnType::SmallInteger(_) => "i16".to_owned(),
|
||||||
ColumnType::Integer(_) => "i32".to_owned(),
|
ColumnType::Integer(_) => "i32".to_owned(),
|
||||||
ColumnType::BigInteger(_) => "i64".to_owned(),
|
ColumnType::BigInteger(_) => "i64".to_owned(),
|
||||||
|
ColumnType::TinyUnsigned(_) => "u8".to_owned(),
|
||||||
|
ColumnType::SmallUnsigned(_) => "u16".to_owned(),
|
||||||
|
ColumnType::Unsigned(_) => "u32".to_owned(),
|
||||||
|
ColumnType::BigUnsigned(_) => "u64".to_owned(),
|
||||||
ColumnType::Float(_) => "f32".to_owned(),
|
ColumnType::Float(_) => "f32".to_owned(),
|
||||||
ColumnType::Double(_) => "f64".to_owned(),
|
ColumnType::Double(_) => "f64".to_owned(),
|
||||||
ColumnType::Json | ColumnType::JsonBinary => "Json".to_owned(),
|
ColumnType::Json | ColumnType::JsonBinary => "Json".to_owned(),
|
||||||
@ -90,6 +94,10 @@ impl Column {
|
|||||||
ColumnType::SmallInteger(_) => quote! { ColumnType::SmallInteger.def() },
|
ColumnType::SmallInteger(_) => quote! { ColumnType::SmallInteger.def() },
|
||||||
ColumnType::Integer(_) => quote! { ColumnType::Integer.def() },
|
ColumnType::Integer(_) => quote! { ColumnType::Integer.def() },
|
||||||
ColumnType::BigInteger(_) => quote! { ColumnType::BigInteger.def() },
|
ColumnType::BigInteger(_) => quote! { ColumnType::BigInteger.def() },
|
||||||
|
ColumnType::TinyUnsigned(_) => quote! { ColumnType::TinyUnsigned.def() },
|
||||||
|
ColumnType::SmallUnsigned(_) => quote! { ColumnType::SmallUnsigned.def() },
|
||||||
|
ColumnType::Unsigned(_) => quote! { ColumnType::Unsigned.def() },
|
||||||
|
ColumnType::BigUnsigned(_) => quote! { ColumnType::BigUnsigned.def() },
|
||||||
ColumnType::Float(_) => quote! { ColumnType::Float.def() },
|
ColumnType::Float(_) => quote! { ColumnType::Float.def() },
|
||||||
ColumnType::Double(_) => quote! { ColumnType::Double.def() },
|
ColumnType::Double(_) => quote! { ColumnType::Double.def() },
|
||||||
ColumnType::Decimal(s) => match s {
|
ColumnType::Decimal(s) => match s {
|
||||||
@ -194,9 +202,13 @@ mod tests {
|
|||||||
ColumnType::Custom(SeaRc::new(Alias::new("cus_col")))
|
ColumnType::Custom(SeaRc::new(Alias::new("cus_col")))
|
||||||
),
|
),
|
||||||
make_col!("CakeId", ColumnType::TinyInteger(None)),
|
make_col!("CakeId", ColumnType::TinyInteger(None)),
|
||||||
|
make_col!("CakeId", ColumnType::TinyUnsigned(Some(9))),
|
||||||
make_col!("CakeId", ColumnType::SmallInteger(None)),
|
make_col!("CakeId", ColumnType::SmallInteger(None)),
|
||||||
make_col!("CakeId", ColumnType::Integer(Some(11))),
|
make_col!("CakeId", ColumnType::SmallUnsigned(Some(10))),
|
||||||
|
make_col!("CakeId", ColumnType::Integer(None)),
|
||||||
|
make_col!("CakeId", ColumnType::Unsigned(Some(11))),
|
||||||
make_col!("CakeFillingId", ColumnType::BigInteger(None)),
|
make_col!("CakeFillingId", ColumnType::BigInteger(None)),
|
||||||
|
make_col!("CakeFillingId", ColumnType::BigUnsigned(Some(12))),
|
||||||
make_col!("cake-filling-id", ColumnType::Float(None)),
|
make_col!("cake-filling-id", ColumnType::Float(None)),
|
||||||
make_col!("CAKE_FILLING_ID", ColumnType::Double(None)),
|
make_col!("CAKE_FILLING_ID", ColumnType::Double(None)),
|
||||||
make_col!("CAKE-FILLING-ID", ColumnType::Binary(None)),
|
make_col!("CAKE-FILLING-ID", ColumnType::Binary(None)),
|
||||||
@ -218,6 +230,10 @@ mod tests {
|
|||||||
"cake_id",
|
"cake_id",
|
||||||
"cake_id",
|
"cake_id",
|
||||||
"cake_id",
|
"cake_id",
|
||||||
|
"cake_id",
|
||||||
|
"cake_id",
|
||||||
|
"cake_id",
|
||||||
|
"cake_filling_id",
|
||||||
"cake_filling_id",
|
"cake_filling_id",
|
||||||
"cake_filling_id",
|
"cake_filling_id",
|
||||||
"cake_filling_id",
|
"cake_filling_id",
|
||||||
@ -243,6 +259,10 @@ mod tests {
|
|||||||
"CakeId",
|
"CakeId",
|
||||||
"CakeId",
|
"CakeId",
|
||||||
"CakeId",
|
"CakeId",
|
||||||
|
"CakeId",
|
||||||
|
"CakeId",
|
||||||
|
"CakeId",
|
||||||
|
"CakeFillingId",
|
||||||
"CakeFillingId",
|
"CakeFillingId",
|
||||||
"CakeFillingId",
|
"CakeFillingId",
|
||||||
"CakeFillingId",
|
"CakeFillingId",
|
||||||
@ -266,9 +286,13 @@ mod tests {
|
|||||||
"String",
|
"String",
|
||||||
"String",
|
"String",
|
||||||
"i8",
|
"i8",
|
||||||
|
"u8",
|
||||||
"i16",
|
"i16",
|
||||||
|
"u16",
|
||||||
"i32",
|
"i32",
|
||||||
|
"u32",
|
||||||
"i64",
|
"i64",
|
||||||
|
"u64",
|
||||||
"f32",
|
"f32",
|
||||||
"f64",
|
"f64",
|
||||||
"Vec<u8>",
|
"Vec<u8>",
|
||||||
@ -300,9 +324,13 @@ mod tests {
|
|||||||
"ColumnType::String(Some(255u32)).def()",
|
"ColumnType::String(Some(255u32)).def()",
|
||||||
"ColumnType::Custom(\"cus_col\".to_owned()).def()",
|
"ColumnType::Custom(\"cus_col\".to_owned()).def()",
|
||||||
"ColumnType::TinyInteger.def()",
|
"ColumnType::TinyInteger.def()",
|
||||||
|
"ColumnType::TinyUnsigned.def()",
|
||||||
"ColumnType::SmallInteger.def()",
|
"ColumnType::SmallInteger.def()",
|
||||||
|
"ColumnType::SmallUnsigned.def()",
|
||||||
"ColumnType::Integer.def()",
|
"ColumnType::Integer.def()",
|
||||||
|
"ColumnType::Unsigned.def()",
|
||||||
"ColumnType::BigInteger.def()",
|
"ColumnType::BigInteger.def()",
|
||||||
|
"ColumnType::BigUnsigned.def()",
|
||||||
"ColumnType::Float.def()",
|
"ColumnType::Float.def()",
|
||||||
"ColumnType::Double.def()",
|
"ColumnType::Double.def()",
|
||||||
"ColumnType::Binary.def()",
|
"ColumnType::Binary.def()",
|
||||||
|
@ -778,28 +778,28 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Column {
|
Column {
|
||||||
name: "testing".to_owned(),
|
name: "testing".to_owned(),
|
||||||
col_type: ColumnType::Integer(Some(11)),
|
col_type: ColumnType::TinyInteger(Some(11)),
|
||||||
auto_increment: false,
|
auto_increment: false,
|
||||||
not_null: true,
|
not_null: true,
|
||||||
unique: false,
|
unique: false,
|
||||||
},
|
},
|
||||||
Column {
|
Column {
|
||||||
name: "rust".to_owned(),
|
name: "rust".to_owned(),
|
||||||
col_type: ColumnType::Integer(Some(11)),
|
col_type: ColumnType::TinyUnsigned(Some(11)),
|
||||||
auto_increment: false,
|
auto_increment: false,
|
||||||
not_null: true,
|
not_null: true,
|
||||||
unique: false,
|
unique: false,
|
||||||
},
|
},
|
||||||
Column {
|
Column {
|
||||||
name: "keywords".to_owned(),
|
name: "keywords".to_owned(),
|
||||||
col_type: ColumnType::Integer(Some(11)),
|
col_type: ColumnType::SmallInteger(Some(11)),
|
||||||
auto_increment: false,
|
auto_increment: false,
|
||||||
not_null: true,
|
not_null: true,
|
||||||
unique: false,
|
unique: false,
|
||||||
},
|
},
|
||||||
Column {
|
Column {
|
||||||
name: "type".to_owned(),
|
name: "type".to_owned(),
|
||||||
col_type: ColumnType::Integer(Some(11)),
|
col_type: ColumnType::SmallUnsigned(Some(11)),
|
||||||
auto_increment: false,
|
auto_increment: false,
|
||||||
not_null: true,
|
not_null: true,
|
||||||
unique: false,
|
unique: false,
|
||||||
@ -813,21 +813,21 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Column {
|
Column {
|
||||||
name: "crate".to_owned(),
|
name: "crate".to_owned(),
|
||||||
col_type: ColumnType::Integer(Some(11)),
|
col_type: ColumnType::Unsigned(Some(11)),
|
||||||
auto_increment: false,
|
auto_increment: false,
|
||||||
not_null: true,
|
not_null: true,
|
||||||
unique: false,
|
unique: false,
|
||||||
},
|
},
|
||||||
Column {
|
Column {
|
||||||
name: "self".to_owned(),
|
name: "self".to_owned(),
|
||||||
col_type: ColumnType::Integer(Some(11)),
|
col_type: ColumnType::BigInteger(Some(11)),
|
||||||
auto_increment: false,
|
auto_increment: false,
|
||||||
not_null: true,
|
not_null: true,
|
||||||
unique: false,
|
unique: false,
|
||||||
},
|
},
|
||||||
Column {
|
Column {
|
||||||
name: "self_id1".to_owned(),
|
name: "self_id1".to_owned(),
|
||||||
col_type: ColumnType::Integer(Some(11)),
|
col_type: ColumnType::BigUnsigned(Some(11)),
|
||||||
auto_increment: false,
|
auto_increment: false,
|
||||||
not_null: true,
|
not_null: true,
|
||||||
unique: false,
|
unique: false,
|
||||||
|
@ -7,14 +7,14 @@ use sea_orm::entity::prelude::*;
|
|||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub testing: i32,
|
pub testing: i8,
|
||||||
pub rust: i32,
|
pub rust: u8,
|
||||||
pub keywords: i32,
|
pub keywords: i16,
|
||||||
pub r#type: i32,
|
pub r#type: u16,
|
||||||
pub r#typeof: i32,
|
pub r#typeof: i32,
|
||||||
pub crate_: i32,
|
pub crate_: u32,
|
||||||
pub self_: i32,
|
pub self_: i64,
|
||||||
pub self_id1: i32,
|
pub self_id1: u64,
|
||||||
pub self_id2: i32,
|
pub self_id2: i32,
|
||||||
pub fruit_id1: i32,
|
pub fruit_id1: i32,
|
||||||
pub fruit_id2: i32,
|
pub fruit_id2: i32,
|
||||||
|
@ -14,14 +14,14 @@ impl EntityName for Entity {
|
|||||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub testing: i32,
|
pub testing: i8,
|
||||||
pub rust: i32,
|
pub rust: u8,
|
||||||
pub keywords: i32,
|
pub keywords: i16,
|
||||||
pub r#type: i32,
|
pub r#type: u16,
|
||||||
pub r#typeof: i32,
|
pub r#typeof: i32,
|
||||||
pub crate_: i32,
|
pub crate_: u32,
|
||||||
pub self_: i32,
|
pub self_: i64,
|
||||||
pub self_id1: i32,
|
pub self_id1: u64,
|
||||||
pub self_id2: i32,
|
pub self_id2: i32,
|
||||||
pub fruit_id1: i32,
|
pub fruit_id1: i32,
|
||||||
pub fruit_id2: i32,
|
pub fruit_id2: i32,
|
||||||
@ -73,14 +73,14 @@ impl ColumnTrait for Column {
|
|||||||
fn def(&self) -> ColumnDef {
|
fn def(&self) -> ColumnDef {
|
||||||
match self {
|
match self {
|
||||||
Self::Id => ColumnType::Integer.def(),
|
Self::Id => ColumnType::Integer.def(),
|
||||||
Self::Testing => ColumnType::Integer.def(),
|
Self::Testing => ColumnType::TinyInteger.def(),
|
||||||
Self::Rust => ColumnType::Integer.def(),
|
Self::Rust => ColumnType::TinyUnsigned.def(),
|
||||||
Self::Keywords => ColumnType::Integer.def(),
|
Self::Keywords => ColumnType::SmallInteger.def(),
|
||||||
Self::Type => ColumnType::Integer.def(),
|
Self::Type => ColumnType::SmallUnsigned.def(),
|
||||||
Self::Typeof => ColumnType::Integer.def(),
|
Self::Typeof => ColumnType::Integer.def(),
|
||||||
Self::Crate => ColumnType::Integer.def(),
|
Self::Crate => ColumnType::Unsigned.def(),
|
||||||
Self::Self_ => ColumnType::Integer.def(),
|
Self::Self_ => ColumnType::BigInteger.def(),
|
||||||
Self::SelfId1 => ColumnType::Integer.def(),
|
Self::SelfId1 => ColumnType::BigUnsigned.def(),
|
||||||
Self::SelfId2 => ColumnType::Integer.def(),
|
Self::SelfId2 => ColumnType::Integer.def(),
|
||||||
Self::FruitId1 => ColumnType::Integer.def(),
|
Self::FruitId1 => ColumnType::Integer.def(),
|
||||||
Self::FruitId2 => ColumnType::Integer.def(),
|
Self::FruitId2 => ColumnType::Integer.def(),
|
||||||
|
@ -230,10 +230,14 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
|
|||||||
let col_type = match temp {
|
let col_type = match temp {
|
||||||
"char" => quote! { Char(None) },
|
"char" => quote! { Char(None) },
|
||||||
"String" | "&str" => quote! { String(None) },
|
"String" | "&str" => quote! { String(None) },
|
||||||
"u8" | "i8" => quote! { TinyInteger },
|
"i8" => quote! { TinyInteger },
|
||||||
"u16" | "i16" => quote! { SmallInteger },
|
"u8" => quote! { TinyUnsigned },
|
||||||
"u32" | "i32" => quote! { Integer },
|
"i16" => quote! { SmallInteger },
|
||||||
"u64" | "i64" => quote! { BigInteger },
|
"u16" => quote! { SmallUnsigned },
|
||||||
|
"i32" => quote! { Integer },
|
||||||
|
"u32" => quote! { Unsigned },
|
||||||
|
"i64" => quote! { BigInteger },
|
||||||
|
"u64" => quote! { BigUnsigned },
|
||||||
"f32" => quote! { Float },
|
"f32" => quote! { Float },
|
||||||
"f64" => quote! { Double },
|
"f64" => quote! { Double },
|
||||||
"bool" => quote! { Boolean },
|
"bool" => quote! { Boolean },
|
||||||
|
@ -483,6 +483,71 @@ pub trait ActiveModelTrait: Clone + Debug {
|
|||||||
ActiveModelBehavior::after_delete(am_clone)?;
|
ActiveModelBehavior::after_delete(am_clone)?;
|
||||||
Ok(delete_res)
|
Ok(delete_res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the corresponding attributes in the ActiveModel from a JSON value
|
||||||
|
///
|
||||||
|
/// Note that this method will not alter the primary key values in ActiveModel.
|
||||||
|
#[cfg(feature = "with-json")]
|
||||||
|
fn set_from_json(&mut self, json: serde_json::Value) -> Result<(), DbErr>
|
||||||
|
where
|
||||||
|
<<Self as ActiveModelTrait>::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
|
||||||
|
for<'de> <<Self as ActiveModelTrait>::Entity as EntityTrait>::Model:
|
||||||
|
serde::de::Deserialize<'de>,
|
||||||
|
{
|
||||||
|
use crate::Iterable;
|
||||||
|
|
||||||
|
// Backup primary key values
|
||||||
|
let primary_key_values: Vec<(<Self::Entity as EntityTrait>::Column, ActiveValue<Value>)> =
|
||||||
|
<<Self::Entity as EntityTrait>::PrimaryKey>::iter()
|
||||||
|
.map(|pk| (pk.into_column(), self.take(pk.into_column())))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Replace all values in ActiveModel
|
||||||
|
*self = Self::from_json(json)?;
|
||||||
|
|
||||||
|
// Restore primary key values
|
||||||
|
for (col, active_value) in primary_key_values {
|
||||||
|
match active_value {
|
||||||
|
ActiveValue::Unchanged(v) | ActiveValue::Set(v) => self.set(col, v),
|
||||||
|
NotSet => self.not_set(col),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create ActiveModel from a JSON value
|
||||||
|
#[cfg(feature = "with-json")]
|
||||||
|
fn from_json(json: serde_json::Value) -> Result<Self, DbErr>
|
||||||
|
where
|
||||||
|
<<Self as ActiveModelTrait>::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
|
||||||
|
for<'de> <<Self as ActiveModelTrait>::Entity as EntityTrait>::Model:
|
||||||
|
serde::de::Deserialize<'de>,
|
||||||
|
{
|
||||||
|
use crate::{Iden, Iterable};
|
||||||
|
|
||||||
|
// Mark down which attribute exists in the JSON object
|
||||||
|
let json_keys: Vec<(<Self::Entity as EntityTrait>::Column, bool)> =
|
||||||
|
<<Self::Entity as EntityTrait>::Column>::iter()
|
||||||
|
.map(|col| (col, json.get(col.to_string()).is_some()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Convert JSON object into ActiveModel via Model
|
||||||
|
let model: <Self::Entity as EntityTrait>::Model =
|
||||||
|
serde_json::from_value(json).map_err(|e| DbErr::Json(e.to_string()))?;
|
||||||
|
let mut am = model.into_active_model();
|
||||||
|
|
||||||
|
// Transform attribute that exists in JSON object into ActiveValue::Set, otherwise ActiveValue::NotSet
|
||||||
|
for (col, json_key_exists) in json_keys {
|
||||||
|
if json_key_exists && !am.is_not_set(col) {
|
||||||
|
am.set(col, am.get(col).unwrap());
|
||||||
|
} else {
|
||||||
|
am.not_set(col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(am)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Trait for overriding the ActiveModel behavior
|
/// A Trait for overriding the ActiveModel behavior
|
||||||
@ -768,13 +833,15 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests_cfg::*;
|
use crate::{entity::*, tests_cfg::*, DbErr};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[cfg(feature = "with-json")]
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn test_derive_into_active_model_1() {
|
fn test_derive_into_active_model_1() {
|
||||||
use crate::entity::*;
|
|
||||||
|
|
||||||
mod my_fruit {
|
mod my_fruit {
|
||||||
pub use super::fruit::*;
|
pub use super::fruit::*;
|
||||||
use crate as sea_orm;
|
use crate as sea_orm;
|
||||||
@ -806,8 +873,6 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn test_derive_into_active_model_2() {
|
fn test_derive_into_active_model_2() {
|
||||||
use crate::entity::*;
|
|
||||||
|
|
||||||
mod my_fruit {
|
mod my_fruit {
|
||||||
pub use super::fruit::*;
|
pub use super::fruit::*;
|
||||||
use crate as sea_orm;
|
use crate as sea_orm;
|
||||||
@ -852,4 +917,175 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "with-json")]
|
||||||
|
#[should_panic(
|
||||||
|
expected = r#"called `Result::unwrap()` on an `Err` value: Json("missing field `id`")"#
|
||||||
|
)]
|
||||||
|
fn test_active_model_set_from_json_1() {
|
||||||
|
let mut cake: cake::ActiveModel = Default::default();
|
||||||
|
|
||||||
|
cake.set_from_json(json!({
|
||||||
|
"name": "Apple Pie",
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "with-json")]
|
||||||
|
fn test_active_model_set_from_json_2() -> Result<(), DbErr> {
|
||||||
|
let mut fruit: fruit::ActiveModel = Default::default();
|
||||||
|
|
||||||
|
fruit.set_from_json(json!({
|
||||||
|
"name": "Apple",
|
||||||
|
}))?;
|
||||||
|
assert_eq!(
|
||||||
|
fruit,
|
||||||
|
fruit::ActiveModel {
|
||||||
|
id: ActiveValue::NotSet,
|
||||||
|
name: ActiveValue::Set("Apple".to_owned()),
|
||||||
|
cake_id: ActiveValue::NotSet,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fruit::ActiveModel::from_json(json!({
|
||||||
|
"name": "Apple",
|
||||||
|
}))?,
|
||||||
|
fruit::ActiveModel {
|
||||||
|
id: ActiveValue::NotSet,
|
||||||
|
name: ActiveValue::Set("Apple".to_owned()),
|
||||||
|
cake_id: ActiveValue::NotSet,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fruit.set_from_json(json!({
|
||||||
|
"name": "Apple",
|
||||||
|
"cake_id": null,
|
||||||
|
}))?;
|
||||||
|
assert_eq!(
|
||||||
|
fruit,
|
||||||
|
fruit::ActiveModel {
|
||||||
|
id: ActiveValue::NotSet,
|
||||||
|
name: ActiveValue::Set("Apple".to_owned()),
|
||||||
|
cake_id: ActiveValue::Set(None),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fruit.set_from_json(json!({
|
||||||
|
"id": null,
|
||||||
|
"name": "Apple",
|
||||||
|
"cake_id": 1,
|
||||||
|
}))?;
|
||||||
|
assert_eq!(
|
||||||
|
fruit,
|
||||||
|
fruit::ActiveModel {
|
||||||
|
id: ActiveValue::NotSet,
|
||||||
|
name: ActiveValue::Set("Apple".to_owned()),
|
||||||
|
cake_id: ActiveValue::Set(Some(1)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fruit.set_from_json(json!({
|
||||||
|
"id": 2,
|
||||||
|
"name": "Apple",
|
||||||
|
"cake_id": 1,
|
||||||
|
}))?;
|
||||||
|
assert_eq!(
|
||||||
|
fruit,
|
||||||
|
fruit::ActiveModel {
|
||||||
|
id: ActiveValue::NotSet,
|
||||||
|
name: ActiveValue::Set("Apple".to_owned()),
|
||||||
|
cake_id: ActiveValue::Set(Some(1)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut fruit = fruit::ActiveModel {
|
||||||
|
id: ActiveValue::Set(1),
|
||||||
|
name: ActiveValue::NotSet,
|
||||||
|
cake_id: ActiveValue::NotSet,
|
||||||
|
};
|
||||||
|
fruit.set_from_json(json!({
|
||||||
|
"id": 8,
|
||||||
|
"name": "Apple",
|
||||||
|
"cake_id": 1,
|
||||||
|
}))?;
|
||||||
|
assert_eq!(
|
||||||
|
fruit,
|
||||||
|
fruit::ActiveModel {
|
||||||
|
id: ActiveValue::Set(1),
|
||||||
|
name: ActiveValue::Set("Apple".to_owned()),
|
||||||
|
cake_id: ActiveValue::Set(Some(1)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[smol_potat::test]
|
||||||
|
#[cfg(feature = "with-json")]
|
||||||
|
async fn test_active_model_set_from_json_3() -> Result<(), DbErr> {
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
let db = MockDatabase::new(DbBackend::Postgres)
|
||||||
|
.append_exec_results(vec![
|
||||||
|
MockExecResult {
|
||||||
|
last_insert_id: 1,
|
||||||
|
rows_affected: 1,
|
||||||
|
},
|
||||||
|
MockExecResult {
|
||||||
|
last_insert_id: 1,
|
||||||
|
rows_affected: 1,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.append_query_results(vec![
|
||||||
|
vec![fruit::Model {
|
||||||
|
id: 1,
|
||||||
|
name: "Apple".to_owned(),
|
||||||
|
cake_id: None,
|
||||||
|
}],
|
||||||
|
vec![fruit::Model {
|
||||||
|
id: 2,
|
||||||
|
name: "Orange".to_owned(),
|
||||||
|
cake_id: Some(1),
|
||||||
|
}],
|
||||||
|
])
|
||||||
|
.into_connection();
|
||||||
|
|
||||||
|
let mut fruit: fruit::ActiveModel = Default::default();
|
||||||
|
fruit.set_from_json(json!({
|
||||||
|
"name": "Apple",
|
||||||
|
}))?;
|
||||||
|
fruit.save(&db).await?;
|
||||||
|
|
||||||
|
let mut fruit = fruit::ActiveModel {
|
||||||
|
id: Set(2),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
fruit.set_from_json(json!({
|
||||||
|
"id": 9,
|
||||||
|
"name": "Orange",
|
||||||
|
"cake_id": 1,
|
||||||
|
}))?;
|
||||||
|
fruit.save(&db).await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.into_transaction_log(),
|
||||||
|
vec![
|
||||||
|
Transaction::from_sql_and_values(
|
||||||
|
DbBackend::Postgres,
|
||||||
|
r#"INSERT INTO "fruit" ("name") VALUES ($1) RETURNING "id", "name", "cake_id""#,
|
||||||
|
vec!["Apple".into()]
|
||||||
|
),
|
||||||
|
Transaction::from_sql_and_values(
|
||||||
|
DbBackend::Postgres,
|
||||||
|
r#"UPDATE "fruit" SET "name" = $1, "cake_id" = $2 WHERE "fruit"."id" = $3 RETURNING "id", "name", "cake_id""#,
|
||||||
|
vec!["Orange".into(), 1i32.into(), 2i32.into()]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,14 @@ pub enum ColumnType {
|
|||||||
/// `BIGINT` is a 64-bit representation of an integer taking up 8 bytes of storage and
|
/// `BIGINT` is a 64-bit representation of an integer taking up 8 bytes of storage and
|
||||||
/// ranging from -2^63 (-9,223,372,036,854,775,808) to 2^63 (9,223,372,036,854,775,807).
|
/// ranging from -2^63 (-9,223,372,036,854,775,808) to 2^63 (9,223,372,036,854,775,807).
|
||||||
BigInteger,
|
BigInteger,
|
||||||
|
/// `TINYINT UNSIGNED` data type
|
||||||
|
TinyUnsigned,
|
||||||
|
/// `SMALLINT UNSIGNED` data type
|
||||||
|
SmallUnsigned,
|
||||||
|
/// `INTEGER UNSIGNED` data type
|
||||||
|
Unsigned,
|
||||||
|
/// `BIGINT UNSIGNED` data type
|
||||||
|
BigUnsigned,
|
||||||
/// `FLOAT` an approximate-number data type, where values range cannot be represented exactly.
|
/// `FLOAT` an approximate-number data type, where values range cannot be represented exactly.
|
||||||
Float,
|
Float,
|
||||||
/// `DOUBLE` is a normal-size floating point number where the
|
/// `DOUBLE` is a normal-size floating point number where the
|
||||||
@ -362,6 +370,10 @@ impl From<ColumnType> for sea_query::ColumnType {
|
|||||||
ColumnType::SmallInteger => sea_query::ColumnType::SmallInteger(None),
|
ColumnType::SmallInteger => sea_query::ColumnType::SmallInteger(None),
|
||||||
ColumnType::Integer => sea_query::ColumnType::Integer(None),
|
ColumnType::Integer => sea_query::ColumnType::Integer(None),
|
||||||
ColumnType::BigInteger => sea_query::ColumnType::BigInteger(None),
|
ColumnType::BigInteger => sea_query::ColumnType::BigInteger(None),
|
||||||
|
ColumnType::TinyUnsigned => sea_query::ColumnType::TinyUnsigned(None),
|
||||||
|
ColumnType::SmallUnsigned => sea_query::ColumnType::SmallUnsigned(None),
|
||||||
|
ColumnType::Unsigned => sea_query::ColumnType::Unsigned(None),
|
||||||
|
ColumnType::BigUnsigned => sea_query::ColumnType::BigUnsigned(None),
|
||||||
ColumnType::Float => sea_query::ColumnType::Float(None),
|
ColumnType::Float => sea_query::ColumnType::Float(None),
|
||||||
ColumnType::Double => sea_query::ColumnType::Double(None),
|
ColumnType::Double => sea_query::ColumnType::Double(None),
|
||||||
ColumnType::Decimal(s) => sea_query::ColumnType::Decimal(s),
|
ColumnType::Decimal(s) => sea_query::ColumnType::Decimal(s),
|
||||||
@ -395,6 +407,10 @@ impl From<sea_query::ColumnType> for ColumnType {
|
|||||||
sea_query::ColumnType::SmallInteger(_) => Self::SmallInteger,
|
sea_query::ColumnType::SmallInteger(_) => Self::SmallInteger,
|
||||||
sea_query::ColumnType::Integer(_) => Self::Integer,
|
sea_query::ColumnType::Integer(_) => Self::Integer,
|
||||||
sea_query::ColumnType::BigInteger(_) => Self::BigInteger,
|
sea_query::ColumnType::BigInteger(_) => Self::BigInteger,
|
||||||
|
sea_query::ColumnType::TinyUnsigned(_) => Self::TinyUnsigned,
|
||||||
|
sea_query::ColumnType::SmallUnsigned(_) => Self::SmallUnsigned,
|
||||||
|
sea_query::ColumnType::Unsigned(_) => Self::Unsigned,
|
||||||
|
sea_query::ColumnType::BigUnsigned(_) => Self::BigUnsigned,
|
||||||
sea_query::ColumnType::Float(_) => Self::Float,
|
sea_query::ColumnType::Float(_) => Self::Float,
|
||||||
sea_query::ColumnType::Double(_) => Self::Double,
|
sea_query::ColumnType::Double(_) => Self::Double,
|
||||||
sea_query::ColumnType::Decimal(s) => Self::Decimal(s),
|
sea_query::ColumnType::Decimal(s) => Self::Decimal(s),
|
||||||
@ -513,13 +529,21 @@ mod tests {
|
|||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub one: i32,
|
pub one: i32,
|
||||||
#[sea_orm(unique)]
|
#[sea_orm(unique)]
|
||||||
pub two: i32,
|
pub two: i8,
|
||||||
#[sea_orm(indexed)]
|
#[sea_orm(indexed)]
|
||||||
pub three: i32,
|
pub three: i16,
|
||||||
#[sea_orm(nullable)]
|
#[sea_orm(nullable)]
|
||||||
pub four: i32,
|
pub four: i32,
|
||||||
#[sea_orm(unique, indexed, nullable)]
|
#[sea_orm(unique, indexed, nullable)]
|
||||||
pub five: i32,
|
pub five: i64,
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub six: u8,
|
||||||
|
#[sea_orm(indexed)]
|
||||||
|
pub seven: u16,
|
||||||
|
#[sea_orm(nullable)]
|
||||||
|
pub eight: u32,
|
||||||
|
#[sea_orm(unique, indexed, nullable)]
|
||||||
|
pub nine: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
@ -529,10 +553,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(hello::Column::One.def(), ColumnType::Integer.def());
|
assert_eq!(hello::Column::One.def(), ColumnType::Integer.def());
|
||||||
assert_eq!(hello::Column::Two.def(), ColumnType::Integer.def().unique());
|
assert_eq!(
|
||||||
|
hello::Column::Two.def(),
|
||||||
|
ColumnType::TinyInteger.def().unique()
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hello::Column::Three.def(),
|
hello::Column::Three.def(),
|
||||||
ColumnType::Integer.def().indexed()
|
ColumnType::SmallInteger.def().indexed()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hello::Column::Four.def(),
|
hello::Column::Four.def(),
|
||||||
@ -540,7 +567,23 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hello::Column::Five.def(),
|
hello::Column::Five.def(),
|
||||||
ColumnType::Integer.def().unique().indexed().nullable()
|
ColumnType::BigInteger.def().unique().indexed().nullable()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
hello::Column::Six.def(),
|
||||||
|
ColumnType::TinyUnsigned.def().unique()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
hello::Column::Seven.def(),
|
||||||
|
ColumnType::SmallUnsigned.def().indexed()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
hello::Column::Eight.def(),
|
||||||
|
ColumnType::Unsigned.def().nullable()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
hello::Column::Nine.def(),
|
||||||
|
ColumnType::BigUnsigned.def().unique().indexed().nullable()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ pub enum DbErr {
|
|||||||
Custom(String),
|
Custom(String),
|
||||||
/// Error occurred while parsing value as target type
|
/// Error occurred while parsing value as target type
|
||||||
Type(String),
|
Type(String),
|
||||||
|
/// Error occurred while parsing json value as target type
|
||||||
|
Json(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for DbErr {}
|
impl std::error::Error for DbErr {}
|
||||||
@ -26,6 +28,7 @@ impl std::fmt::Display for DbErr {
|
|||||||
Self::RecordNotFound(s) => write!(f, "RecordNotFound Error: {}", s),
|
Self::RecordNotFound(s) => write!(f, "RecordNotFound Error: {}", s),
|
||||||
Self::Custom(s) => write!(f, "Custom Error: {}", s),
|
Self::Custom(s) => write!(f, "Custom Error: {}", s),
|
||||||
Self::Type(s) => write!(f, "Type Error: {}", s),
|
Self::Type(s) => write!(f, "Type Error: {}", s),
|
||||||
|
Self::Json(s) => write!(f, "Json Error: {}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Getting Started
|
//! ## Getting Started
|
||||||
//!
|
//!
|
||||||
//! [](https://GitHub.com/SeaQL/sea-orm/stargazers/)
|
//! [](https://github.com/SeaQL/sea-orm/stargazers/)
|
||||||
//! If you like what we do, consider starring, commenting, sharing and contributing!
|
//! If you like what we do, consider starring, commenting, sharing and contributing!
|
||||||
//!
|
//!
|
||||||
//! [](https://discord.com/invite/uCPdDXzbdv)
|
//! [](https://discord.com/invite/uCPdDXzbdv)
|
||||||
@ -37,6 +37,7 @@
|
|||||||
//! + [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)
|
//! + [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)
|
||||||
//! + [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)
|
//! + [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)
|
||||||
//! + [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example)
|
//! + [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example)
|
||||||
|
//! + [Axum-GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum-graphql_example)
|
||||||
//! + [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example)
|
//! + [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example)
|
||||||
//!
|
//!
|
||||||
//! ## Features
|
//! ## Features
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
use crate as sea_orm;
|
use crate as sea_orm;
|
||||||
use crate::entity::prelude::*;
|
use crate::entity::prelude::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "with-json")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[cfg_attr(feature = "with-json", derive(Serialize, Deserialize))]
|
||||||
#[sea_orm(table_name = "cake")]
|
#[sea_orm(table_name = "cake")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
use crate as sea_orm;
|
use crate as sea_orm;
|
||||||
use crate::entity::prelude::*;
|
use crate::entity::prelude::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "with-json")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[cfg_attr(feature = "with-json", derive(Serialize, Deserialize))]
|
||||||
#[sea_orm(table_name = "fruit")]
|
#[sea_orm(table_name = "fruit")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub cake_id: Option<i32>,
|
pub cake_id: Option<i32>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user