From 0c2b0d608d647fa5bd6a22cb866872fcc90f9d16 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 23 Sep 2021 22:00:30 +1000 Subject: [PATCH 01/29] Port actix example to Actix 4 Beta --- examples/actix_web_4_beta_example/.env | 3 + examples/actix_web_4_beta_example/Cargo.toml | 32 ++ examples/actix_web_4_beta_example/README.md | 10 + examples/actix_web_4_beta_example/src/main.rs | 200 ++++++++ examples/actix_web_4_beta_example/src/post.rs | 18 + .../actix_web_4_beta_example/src/setup.rs | 33 ++ .../static/css/normalize.css | 427 ++++++++++++++++++ .../static/css/skeleton.css | 421 +++++++++++++++++ .../static/css/style.css | 73 +++ .../static/images/favicon.png | Bin 0 -> 1155 bytes .../templates/edit.html.tera | 49 ++ .../templates/error/404.html.tera | 11 + .../templates/index.html.tera | 52 +++ .../templates/layout.html.tera | 26 ++ .../templates/new.html.tera | 38 ++ 15 files changed, 1393 insertions(+) create mode 100644 examples/actix_web_4_beta_example/.env create mode 100644 examples/actix_web_4_beta_example/Cargo.toml create mode 100644 examples/actix_web_4_beta_example/README.md create mode 100644 examples/actix_web_4_beta_example/src/main.rs create mode 100644 examples/actix_web_4_beta_example/src/post.rs create mode 100644 examples/actix_web_4_beta_example/src/setup.rs create mode 100644 examples/actix_web_4_beta_example/static/css/normalize.css create mode 100644 examples/actix_web_4_beta_example/static/css/skeleton.css create mode 100644 examples/actix_web_4_beta_example/static/css/style.css create mode 100644 examples/actix_web_4_beta_example/static/images/favicon.png create mode 100644 examples/actix_web_4_beta_example/templates/edit.html.tera create mode 100644 examples/actix_web_4_beta_example/templates/error/404.html.tera create mode 100644 examples/actix_web_4_beta_example/templates/index.html.tera create mode 100644 examples/actix_web_4_beta_example/templates/layout.html.tera create mode 100644 examples/actix_web_4_beta_example/templates/new.html.tera diff --git a/examples/actix_web_4_beta_example/.env b/examples/actix_web_4_beta_example/.env new file mode 100644 index 00000000..c4acf8a0 --- /dev/null +++ b/examples/actix_web_4_beta_example/.env @@ -0,0 +1,3 @@ +HOST=127.0.0.1 +PORT=8000 +DATABASE_URL="mysql://root:@localhost/rocket_example" diff --git a/examples/actix_web_4_beta_example/Cargo.toml b/examples/actix_web_4_beta_example/Cargo.toml new file mode 100644 index 00000000..7a0fbbf6 --- /dev/null +++ b/examples/actix_web_4_beta_example/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "sea-orm-actix-4-beta-example" +version = "0.1.0" +authors = ["Sam Samai "] +edition = "2018" +publish = false + +[workspace] + +[dependencies] +actix-files = "0.6.0-beta.4" +actix-http = "=3.0.0-beta.5" +actix-rt = "2.2.0" +actix-service = "=2.0.0-beta.5" +actix-web = "=4.0.0-beta.5" + +tera = "1.8.0" +dotenv = "0.15" +listenfd = "0.3.3" +# remove `path = ""` in your own project +sea-orm = { path = "../../", version = "^0.2.3", features = [ + "macros", + "runtime-tokio-native-tls", + # "runtime-actix-native-tls", +], default-features = false } +serde = "1" +env_logger = "0.8" + +[features] +default = ["sqlx-mysql"] +sqlx-mysql = ["sea-orm/sqlx-mysql"] +sqlx-postgres = ["sea-orm/sqlx-postgres"] diff --git a/examples/actix_web_4_beta_example/README.md b/examples/actix_web_4_beta_example/README.md new file mode 100644 index 00000000..ffd2830b --- /dev/null +++ b/examples/actix_web_4_beta_example/README.md @@ -0,0 +1,10 @@ +# Actix 4 Beta with SeaORM example app + +Edit `.env` to point to your database. + +Run server with auto-reloading: + +```bash +cargo install systemfd +systemfd --no-pid -s http::8000 -- cargo watch -x run +``` diff --git a/examples/actix_web_4_beta_example/src/main.rs b/examples/actix_web_4_beta_example/src/main.rs new file mode 100644 index 00000000..6a52b81b --- /dev/null +++ b/examples/actix_web_4_beta_example/src/main.rs @@ -0,0 +1,200 @@ +use actix_files::Files as Fs; +use actix_web::{ + error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, Result, +}; + +use listenfd::ListenFd; +use sea_orm::DatabaseConnection; +use sea_orm::{entity::*, query::*}; +use serde::{Deserialize, Serialize}; +use std::env; +use tera::Tera; + +mod post; +pub use post::Entity as Post; +mod setup; + +const DEFAULT_POSTS_PER_PAGE: usize = 5; + +#[derive(Debug, Clone)] +struct AppState { + templates: tera::Tera, + conn: DatabaseConnection, +} +#[derive(Debug, Deserialize)] +pub struct Params { + page: Option, + posts_per_page: Option, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +struct FlashData { + kind: String, + message: String, +} + +#[get("/")] +async fn list(req: HttpRequest, data: web::Data) -> Result { + let template = &data.templates; + let conn = &data.conn; + + // get params + let params = web::Query::::from_query(req.query_string()).unwrap(); + + let page = params.page.unwrap_or(1); + let posts_per_page = params.posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); + let paginator = Post::find() + .order_by_asc(post::Column::Id) + .paginate(conn, posts_per_page); + let num_pages = paginator.num_pages().await.ok().unwrap(); + + let posts = paginator + .fetch_page(page - 1) + .await + .expect("could not retrieve posts"); + let mut ctx = tera::Context::new(); + ctx.insert("posts", &posts); + ctx.insert("page", &page); + ctx.insert("posts_per_page", &posts_per_page); + ctx.insert("num_pages", &num_pages); + + let body = template + .render("index.html.tera", &ctx) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; + Ok(HttpResponse::Ok().content_type("text/html").body(body)) +} + +#[get("/new")] +async fn new(data: web::Data) -> Result { + let template = &data.templates; + let ctx = tera::Context::new(); + let body = template + .render("new.html.tera", &ctx) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; + Ok(HttpResponse::Ok().content_type("text/html").body(body)) +} + +#[post("/")] +async fn create( + data: web::Data, + post_form: web::Form, +) -> Result { + let conn = &data.conn; + + let form = post_form.into_inner(); + + post::ActiveModel { + title: Set(form.title.to_owned()), + text: Set(form.text.to_owned()), + ..Default::default() + } + .save(conn) + .await + .expect("could not insert post"); + + Ok(HttpResponse::Found().append_header(("location", "/")).finish()) +} + +#[get("/{id}")] +async fn edit(data: web::Data, id: web::Path) -> Result { + let conn = &data.conn; + let template = &data.templates; + + let post: post::Model = Post::find_by_id(id.into_inner()) + .one(conn) + .await + .expect("could not find post") + .unwrap(); + + let mut ctx = tera::Context::new(); + ctx.insert("post", &post); + + let body = template + .render("edit.html.tera", &ctx) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; + Ok(HttpResponse::Ok().content_type("text/html").body(body)) +} + +#[post("/{id}")] +async fn update( + data: web::Data, + id: web::Path, + post_form: web::Form, +) -> Result { + let conn = &data.conn; + let form = post_form.into_inner(); + + post::ActiveModel { + id: Set(id.into_inner()), + title: Set(form.title.to_owned()), + text: Set(form.text.to_owned()), + } + .save(conn) + .await + .expect("could not edit post"); + + Ok(HttpResponse::Found().append_header(("location", "/")).finish()) +} + +#[post("/delete/{id}")] +async fn delete(data: web::Data, id: web::Path) -> Result { + let conn = &data.conn; + + let post: post::ActiveModel = Post::find_by_id(id.into_inner()) + .one(conn) + .await + .unwrap() + .unwrap() + .into(); + + post.delete(conn).await.unwrap(); + + Ok(HttpResponse::Found().append_header(("location", "/")).finish()) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "debug"); + env_logger::init(); + + // get env vars + dotenv::dotenv().ok(); + let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); + let host = env::var("HOST").expect("HOST is not set in .env file"); + let port = env::var("PORT").expect("PORT is not set in .env file"); + let server_url = format!("{}:{}", host, port); + + // create post table if not exists + let conn = sea_orm::Database::connect(&db_url).await.unwrap(); + let _ = setup::create_post_table(&conn).await; + let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap(); + let state = AppState { templates, conn }; + + let mut listenfd = ListenFd::from_env(); + let mut server = HttpServer::new(move || { + App::new() + .service(Fs::new("/static", "./static")) + .data(state.clone()) + .wrap(middleware::Logger::default()) // enable logger + .configure(init) + }); + + server = match listenfd.take_tcp_listener(0)? { + Some(listener) => server.listen(listener)?, + None => server.bind(&server_url)?, + }; + + println!("Starting server at {}", server_url); + server.run().await?; + + Ok(()) +} + +pub fn init(cfg: &mut web::ServiceConfig) { + cfg.service(list); + cfg.service(new); + cfg.service(create); + cfg.service(edit); + cfg.service(update); + cfg.service(delete); +} diff --git a/examples/actix_web_4_beta_example/src/post.rs b/examples/actix_web_4_beta_example/src/post.rs new file mode 100644 index 00000000..37a99c2f --- /dev/null +++ b/examples/actix_web_4_beta_example/src/post.rs @@ -0,0 +1,18 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "posts")] +pub struct Model { + #[sea_orm(primary_key)] + #[serde(skip_deserializing)] + pub id: i32, + pub title: String, + #[sea_orm(column_type = "Text")] + pub text: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/actix_web_4_beta_example/src/setup.rs b/examples/actix_web_4_beta_example/src/setup.rs new file mode 100644 index 00000000..034e8b53 --- /dev/null +++ b/examples/actix_web_4_beta_example/src/setup.rs @@ -0,0 +1,33 @@ +use sea_orm::sea_query::{ColumnDef, TableCreateStatement}; +use sea_orm::{error::*, sea_query, DbConn, ExecResult}; + +async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { + let builder = db.get_database_backend(); + db.execute(builder.build(stmt)).await +} + +pub async fn create_post_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(super::post::Entity) + .if_not_exists() + .col( + ColumnDef::new(super::post::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(super::post::Column::Title) + .string() + .not_null(), + ) + .col( + ColumnDef::new(super::post::Column::Text) + .string() + .not_null(), + ) + .to_owned(); + + create_table(db, &stmt).await +} diff --git a/examples/actix_web_4_beta_example/static/css/normalize.css b/examples/actix_web_4_beta_example/static/css/normalize.css new file mode 100644 index 00000000..458eea1e --- /dev/null +++ b/examples/actix_web_4_beta_example/static/css/normalize.css @@ -0,0 +1,427 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/examples/actix_web_4_beta_example/static/css/skeleton.css b/examples/actix_web_4_beta_example/static/css/skeleton.css new file mode 100644 index 00000000..cdc432a4 --- /dev/null +++ b/examples/actix_web_4_beta_example/static/css/skeleton.css @@ -0,0 +1,421 @@ +/* +* Skeleton V2.0.4 +* Copyright 2014, Dave Gamache +* www.getskeleton.com +* Free to use under the MIT license. +* https://opensource.org/licenses/mit-license.php +* 12/29/2014 +*/ + + +/* Table of contents +–––––––––––––––––––––––––––––––––––––––––––––––––– +- Grid +- Base Styles +- Typography +- Links +- Buttons +- Forms +- Lists +- Code +- Tables +- Spacing +- Utilities +- Clearing +- Media Queries +*/ + + +/* Grid +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; } +.column, +.columns { + width: 100%; + float: left; + box-sizing: border-box; } + +/* For devices larger than 400px */ +@media (min-width: 400px) { + .container { + width: 85%; + padding: 0; } +} + +/* For devices larger than 550px */ +@media (min-width: 550px) { + .container { + width: 80%; } + .column, + .columns { + margin-left: 4%; } + .column:first-child, + .columns:first-child { + margin-left: 0; } + + .one.column, + .one.columns { width: 4.66666666667%; } + .two.columns { width: 13.3333333333%; } + .three.columns { width: 22%; } + .four.columns { width: 30.6666666667%; } + .five.columns { width: 39.3333333333%; } + .six.columns { width: 48%; } + .seven.columns { width: 56.6666666667%; } + .eight.columns { width: 65.3333333333%; } + .nine.columns { width: 74.0%; } + .ten.columns { width: 82.6666666667%; } + .eleven.columns { width: 91.3333333333%; } + .twelve.columns { width: 100%; margin-left: 0; } + + .one-third.column { width: 30.6666666667%; } + .two-thirds.column { width: 65.3333333333%; } + + .one-half.column { width: 48%; } + + /* Offsets */ + .offset-by-one.column, + .offset-by-one.columns { margin-left: 8.66666666667%; } + .offset-by-two.column, + .offset-by-two.columns { margin-left: 17.3333333333%; } + .offset-by-three.column, + .offset-by-three.columns { margin-left: 26%; } + .offset-by-four.column, + .offset-by-four.columns { margin-left: 34.6666666667%; } + .offset-by-five.column, + .offset-by-five.columns { margin-left: 43.3333333333%; } + .offset-by-six.column, + .offset-by-six.columns { margin-left: 52%; } + .offset-by-seven.column, + .offset-by-seven.columns { margin-left: 60.6666666667%; } + .offset-by-eight.column, + .offset-by-eight.columns { margin-left: 69.3333333333%; } + .offset-by-nine.column, + .offset-by-nine.columns { margin-left: 78.0%; } + .offset-by-ten.column, + .offset-by-ten.columns { margin-left: 86.6666666667%; } + .offset-by-eleven.column, + .offset-by-eleven.columns { margin-left: 95.3333333333%; } + + .offset-by-one-third.column, + .offset-by-one-third.columns { margin-left: 34.6666666667%; } + .offset-by-two-thirds.column, + .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } + + .offset-by-one-half.column, + .offset-by-one-half.columns { margin-left: 52%; } + +} + + +/* Base Styles +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* NOTE +html is set to 62.5% so that all the REM measurements throughout Skeleton +are based on 10px sizing. So basically 1.5rem = 15px :) */ +html { + font-size: 62.5%; } +body { + font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ + line-height: 1.6; + font-weight: 400; + font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #222; } + + +/* Typography +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 2rem; + font-weight: 300; } +h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} +h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } +h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } +h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } +h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } +h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } + +/* Larger than phablet */ +@media (min-width: 550px) { + h1 { font-size: 5.0rem; } + h2 { font-size: 4.2rem; } + h3 { font-size: 3.6rem; } + h4 { font-size: 3.0rem; } + h5 { font-size: 2.4rem; } + h6 { font-size: 1.5rem; } +} + +p { + margin-top: 0; } + + +/* Links +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +a { + color: #1EAEDB; } +a:hover { + color: #0FA0CE; } + + +/* Buttons +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + display: inline-block; + height: 38px; + padding: 0 30px; + color: #555; + text-align: center; + font-size: 11px; + font-weight: 600; + line-height: 38px; + letter-spacing: .1rem; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border-radius: 4px; + border: 1px solid #bbb; + cursor: pointer; + box-sizing: border-box; } +.button:hover, +button:hover, +input[type="submit"]:hover, +input[type="reset"]:hover, +input[type="button"]:hover, +.button:focus, +button:focus, +input[type="submit"]:focus, +input[type="reset"]:focus, +input[type="button"]:focus { + color: #333; + border-color: #888; + outline: 0; } +.button.button-primary, +button.button-primary, +button.primary, +input[type="submit"].button-primary, +input[type="reset"].button-primary, +input[type="button"].button-primary { + color: #FFF; + background-color: #33C3F0; + border-color: #33C3F0; } +.button.button-primary:hover, +button.button-primary:hover, +button.primary:hover, +input[type="submit"].button-primary:hover, +input[type="reset"].button-primary:hover, +input[type="button"].button-primary:hover, +.button.button-primary:focus, +button.button-primary:focus, +button.primary:focus, +input[type="submit"].button-primary:focus, +input[type="reset"].button-primary:focus, +input[type="button"].button-primary:focus { + color: #FFF; + background-color: #1EAEDB; + border-color: #1EAEDB; } + + +/* Forms +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea, +select { + height: 38px; + padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ + background-color: #fff; + border: 1px solid #D1D1D1; + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; } +/* Removes awkward default styles on some inputs for iOS */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } +textarea { + min-height: 65px; + padding-top: 6px; + padding-bottom: 6px; } +input[type="email"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="text"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +input[type="password"]:focus, +textarea:focus, +select:focus { + border: 1px solid #33C3F0; + outline: 0; } +label, +legend { + display: block; + margin-bottom: .5rem; + font-weight: 600; } +fieldset { + padding: 0; + border-width: 0; } +input[type="checkbox"], +input[type="radio"] { + display: inline; } +label > .label-body { + display: inline-block; + margin-left: .5rem; + font-weight: normal; } + + +/* Lists +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul { + list-style: circle inside; } +ol { + list-style: decimal inside; } +ol, ul { + padding-left: 0; + margin-top: 0; } +ul ul, +ul ol, +ol ol, +ol ul { + margin: 1.5rem 0 1.5rem 3rem; + font-size: 90%; } +li { + margin-bottom: 1rem; } + + +/* Code +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +code { + padding: .2rem .5rem; + margin: 0 .2rem; + font-size: 90%; + white-space: nowrap; + background: #F1F1F1; + border: 1px solid #E1E1E1; + border-radius: 4px; } +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre; } + + +/* Tables +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +th, +td { + padding: 12px 15px; + text-align: left; + border-bottom: 1px solid #E1E1E1; } +th:first-child, +td:first-child { + padding-left: 0; } +th:last-child, +td:last-child { + padding-right: 0; } + + +/* Spacing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +button, +.button { + margin-bottom: 1rem; } +input, +textarea, +select, +fieldset { + margin-bottom: 1.5rem; } +pre, +blockquote, +dl, +figure, +table, +p, +ul, +ol, +form { + margin-bottom: 2.5rem; } + + +/* Utilities +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.u-full-width { + width: 100%; + box-sizing: border-box; } +.u-max-full-width { + max-width: 100%; + box-sizing: border-box; } +.u-pull-right { + float: right; } +.u-pull-left { + float: left; } + + +/* Misc +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +hr { + margin-top: 3rem; + margin-bottom: 3.5rem; + border-width: 0; + border-top: 1px solid #E1E1E1; } + + +/* Clearing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ + +/* Self Clearing Goodness */ +.container:after, +.row:after, +.u-cf { + content: ""; + display: table; + clear: both; } + + +/* Media Queries +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* +Note: The best way to structure the use of media queries is to create the queries +near the relevant code. For example, if you wanted to change the styles for buttons +on small devices, paste the mobile query code up in the buttons section and style it +there. +*/ + + +/* Larger than mobile */ +@media (min-width: 400px) {} + +/* Larger than phablet (also point when grid becomes active) */ +@media (min-width: 550px) {} + +/* Larger than tablet */ +@media (min-width: 750px) {} + +/* Larger than desktop */ +@media (min-width: 1000px) {} + +/* Larger than Desktop HD */ +@media (min-width: 1200px) {} diff --git a/examples/actix_web_4_beta_example/static/css/style.css b/examples/actix_web_4_beta_example/static/css/style.css new file mode 100644 index 00000000..ac2720d3 --- /dev/null +++ b/examples/actix_web_4_beta_example/static/css/style.css @@ -0,0 +1,73 @@ +.field-error { + border: 1px solid #ff0000 !important; +} + +.field-error-flash { + color: #ff0000; + display: block; + margin: -10px 0 10px 0; +} + +.field-success { + border: 1px solid #5ab953 !important; +} + +.field-success-flash { + color: #5ab953; + display: block; + margin: -10px 0 10px 0; +} + +span.completed { + text-decoration: line-through; +} + +form.inline { + display: inline; +} + +form.link, +button.link { + display: inline; + color: #1eaedb; + border: none; + outline: none; + background: none; + cursor: pointer; + padding: 0; + margin: 0 0 0 0; + height: inherit; + text-decoration: underline; + font-size: inherit; + text-transform: none; + font-weight: normal; + line-height: inherit; + letter-spacing: inherit; +} + +form.link:hover, +button.link:hover { + color: #0fa0ce; +} + +button.small { + height: 20px; + padding: 0 10px; + font-size: 10px; + line-height: 20px; + margin: 0 2.5px; +} + +.post:hover { + background-color: #bce2ee; +} + +.post td { + padding: 5px; + width: 150px; +} + +#delete-button { + color: red; + border-color: red; +} diff --git a/examples/actix_web_4_beta_example/static/images/favicon.png b/examples/actix_web_4_beta_example/static/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..02b7390443ae2877c186898f52da23f062adbdcc GIT binary patch literal 1155 zcmV-}1bq96P)(=Qx>xolP{L(>2x~5PFgqllLG?-XURWh zvn7|E20`C&yWRV!SI1Uhr?1K%v2UB0m}sFhe_PG5av(Pl2<+kH9bD;aRz}QZjK*Wi zzU=Ss53_$_q-W@WteT*|u)Sk6X{KXzp9O21Er;<5zg9bJJVN(-=Zy8l4qegdI#S(evShjLl#Pk=r9jtgPegJHb!|>nu2DZUh(FA*z*X!Ly#;=Z< zcY*h@wRV}ZUw{XTs%x<*&yAr6*O7QQIqlYkGx)Ptvae%$?Bex{(}9JyQ+)L}e`z0B>Y7?qR+gjq`2_(| z*YR1|dc+Lo=!ck$dL#l;$vC2h!$%RW3u(Hst`lh9wh`s^nV%ujFKnUV($0F52BLCWB8{a&>BRO_*@>CN}62 z(UjU)Dh1DMef+rt@phC|?U_NQ(5sA6dU2zmz~B@z{?iJlE7 z#AcOX=vSv4Lj8d7>Y@B!eoVBgSnmWY)={>fquK*F zBSk|8RK;;Ao#{j?9gxf#WYg+-BV)QTNo=ijU>!sLJNa>)EQ2-Nf((u4A6fZ5e+Gh* Ve)_7M0d)WX002ovPDHLkV1i9aF}45z literal 0 HcmV?d00001 diff --git a/examples/actix_web_4_beta_example/templates/edit.html.tera b/examples/actix_web_4_beta_example/templates/edit.html.tera new file mode 100644 index 00000000..5aeb22af --- /dev/null +++ b/examples/actix_web_4_beta_example/templates/edit.html.tera @@ -0,0 +1,49 @@ +{% extends "layout.html.tera" %} {% block content %} + +{% endblock content %} diff --git a/examples/actix_web_4_beta_example/templates/error/404.html.tera b/examples/actix_web_4_beta_example/templates/error/404.html.tera new file mode 100644 index 00000000..afda653d --- /dev/null +++ b/examples/actix_web_4_beta_example/templates/error/404.html.tera @@ -0,0 +1,11 @@ + + + + + 404 - tera + + +

404: Hey! There's nothing here.

+ The page at {{ uri }} does not exist! + + diff --git a/examples/actix_web_4_beta_example/templates/index.html.tera b/examples/actix_web_4_beta_example/templates/index.html.tera new file mode 100644 index 00000000..1dccdeba --- /dev/null +++ b/examples/actix_web_4_beta_example/templates/index.html.tera @@ -0,0 +1,52 @@ +{% extends "layout.html.tera" %} {% block content %} +
+

+

Posts

+ {% if flash %} + + {{ flash.message }} + + {% endif %} + + + + + + + + + + {% for post in posts %} + + + + + + {% endfor %} + + + + + + + + +
IDTitleText
{{ post.id }}{{ post.title }}{{ post.text }}
+ {% if page == 1 %} Previous {% else %} + Previous + {% endif %} | {% if page == num_pages %} Next {% else %} + Next + {% endif %} +
+ +
+ + + +
+
+{% endblock content %} diff --git a/examples/actix_web_4_beta_example/templates/layout.html.tera b/examples/actix_web_4_beta_example/templates/layout.html.tera new file mode 100644 index 00000000..c930afcc --- /dev/null +++ b/examples/actix_web_4_beta_example/templates/layout.html.tera @@ -0,0 +1,26 @@ + + + + + Actix Example + + + + + + + + + + + +
+

+ {% block content %}{% endblock content %} +
+ + diff --git a/examples/actix_web_4_beta_example/templates/new.html.tera b/examples/actix_web_4_beta_example/templates/new.html.tera new file mode 100644 index 00000000..dee19565 --- /dev/null +++ b/examples/actix_web_4_beta_example/templates/new.html.tera @@ -0,0 +1,38 @@ +{% extends "layout.html.tera" %} {% block content %} +
+

New Post

+
+
+ + +
+
+
+ + + +
+
+
+ +
+
+
+
+{% endblock content %} From 7742eb2cec1fb09897c7a9245ab72d55ed0de1c1 Mon Sep 17 00:00:00 2001 From: Muhannad Alrusayni Date: Mon, 27 Sep 2021 12:47:14 +0300 Subject: [PATCH 02/29] Impl TryGetableMany for (T) --- src/executor/query.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/executor/query.rs b/src/executor/query.rs index ae09f97c..73b9aae4 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -314,6 +314,15 @@ where } } +impl TryGetableMany for (T,) +where + T: TryGetableMany, +{ + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + T::try_get_many(res, pre, cols).map(|r| (r,)) + } +} + impl TryGetableMany for (A, B) where A: TryGetable, From 5379a23630cfb47ba4167d8a486dffec13ec4697 Mon Sep 17 00:00:00 2001 From: Muhannad Alrusayni Date: Tue, 28 Sep 2021 00:10:13 +0300 Subject: [PATCH 03/29] Add `find_by_statement` for `T: TryGetableMany` Add `into_values` for `Select` --- src/executor/query.rs | 75 ++++++++++++++++++++++++- src/executor/select.rs | 124 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/src/executor/query.rs b/src/executor/query.rs index 73b9aae4..c39c2211 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -1,6 +1,6 @@ #[cfg(feature = "mock")] use crate::debug_print; -use crate::DbErr; +use crate::{DbErr, SelectGetableValue, SelectorRaw, Statement}; use std::fmt; #[derive(Debug)] @@ -302,6 +302,79 @@ try_getable_all!(uuid::Uuid); pub trait TryGetableMany: Sized { fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result; + + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![vec![ + /// # maplit::btreemap! { + /// # "name" => Into::::into("Chocolate Forest"), + /// # "num_of_cakes" => Into::::into(1), + /// # }, + /// # maplit::btreemap! { + /// # "name" => Into::::into("New York Cheese"), + /// # "num_of_cakes" => Into::::into(1), + /// # }, + /// # ]]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, TryGetableMany}; + /// + /// #[derive(EnumIter)] + /// enum ResultCol { + /// Name, + /// NumOfCakes, + /// } + /// + /// // this can be derived using derive_more crate + /// impl std::fmt::Display for ResultCol { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// match self { + /// Self::Name => write!(f, "name"), + /// Self::NumOfCakes => write!(f, "num_of_cakes"), + /// } + /// } + /// } + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let res: Vec<(String, i32)> = + /// <(String, i32)>::find_by_statement::(Statement::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, + /// vec![], + /// )) + /// .all(&db) + /// .await?; + /// + /// assert_eq!( + /// res, + /// vec![ + /// ("Chocolate Forest".to_owned(), 1), + /// ("New York Cheese".to_owned(), 1), + /// ] + /// ); + /// # + /// # Ok(()) + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, + /// vec![] + /// ),] + /// ); + /// ``` + fn find_by_statement(stmt: Statement) -> SelectorRaw> + where + C: sea_strum::IntoEnumIterator + ToString, + { + SelectorRaw::>::with_columns(stmt) + } } impl TryGetableMany for T diff --git a/src/executor/select.rs b/src/executor/select.rs index 959091c0..ebc5ad11 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -1,7 +1,7 @@ use crate::{ error::*, DatabaseConnection, EntityTrait, FromQueryResult, IdenStatic, Iterable, JsonValue, ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, - SelectTwoMany, Statement, + SelectTwoMany, Statement, TryGetableMany, }; use sea_query::SelectStatement; use std::marker::PhantomData; @@ -30,6 +30,16 @@ pub trait SelectorTrait { fn from_raw_query_result(res: QueryResult) -> Result; } +#[derive(Debug)] +pub struct SelectGetableValue +where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, +{ + columns: PhantomData, + model: PhantomData, +} + #[derive(Debug)] pub struct SelectModel where @@ -47,6 +57,19 @@ where model: PhantomData<(M, N)>, } +impl SelectorTrait for SelectGetableValue +where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, +{ + type Item = T; + + fn from_raw_query_result(res: QueryResult) -> Result { + let cols: Vec = C::iter().map(|col| col.to_string()).collect(); + T::try_get_many(&res, "", &cols).map_err(Into::into) + } +} + impl SelectorTrait for SelectModel where M: FromQueryResult + Sized, @@ -103,6 +126,73 @@ where } } + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![vec![ + /// # maplit::btreemap! { + /// # "name" => Into::::into("Chocolate Forest"), + /// # "num_of_cakes" => Into::::into(1), + /// # }, + /// # maplit::btreemap! { + /// # "name" => Into::::into("New York Cheese"), + /// # "num_of_cakes" => Into::::into(1), + /// # }, + /// # ]]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, TryGetableMany}; + /// + /// #[derive(EnumIter)] + /// enum ResultCol { + /// Name, + /// } + /// + /// // this can be derived using derive_more crate + /// impl std::fmt::Display for ResultCol { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// match self { + /// Self::Name => write!(f, "name"), + /// } + /// } + /// } + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let res: Vec = cake::Entity::find() + /// .select_only() + /// .column(cake::Column::Name) + /// .into_values::<_, ResultCol>() + /// .all(&db) + /// .await?; + /// + /// assert_eq!( + /// res, + /// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned(),] + /// ); + /// # + /// # Ok(()) + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name" FROM "cake""#, + /// vec![] + /// ),] + /// ); + /// ``` + pub fn into_values(self) -> Selector> + where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, + { + Selector::>::with_columns(self.query) + } + pub async fn one(self, db: &DatabaseConnection) -> Result, DbErr> { self.into_model().one(db).await } @@ -228,6 +318,22 @@ impl Selector where S: SelectorTrait, { + /// Create `Selector` from Statment and columns. Executing this `Selector` + /// will return a type `T` which implement `TryGetableMany`. + pub fn with_columns(query: SelectStatement) -> Selector> + where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, + { + Selector { + query, + selector: SelectGetableValue { + columns: PhantomData, + model: PhantomData, + }, + } + } + pub async fn one(mut self, db: &DatabaseConnection) -> Result, DbErr> { let builder = db.get_database_backend(); self.query.limit(1); @@ -275,6 +381,22 @@ where } } + /// Create `SelectorRaw` from Statment and columns. Executing this `SelectorRaw` will + /// return a type `T` which implement `TryGetableMany`. + pub fn with_columns(stmt: Statement) -> SelectorRaw> + where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, + { + SelectorRaw { + stmt, + selector: SelectGetableValue { + columns: PhantomData, + model: PhantomData, + }, + } + } + /// ``` /// # #[cfg(feature = "mock")] /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; From 88776a28ed5f5cf6f162f5ecaf12aa23a88e171c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 28 Sep 2021 14:27:19 +0800 Subject: [PATCH 04/29] Rename column name & column enum variant --- sea-orm-macros/src/derives/active_model.rs | 33 +++++++++++++- sea-orm-macros/src/derives/column.rs | 29 +++++++++++-- sea-orm-macros/src/derives/entity_model.rs | 50 +++++++++++++++++++--- sea-orm-macros/src/derives/model.rs | 31 ++++++++++++-- sea-orm-macros/src/lib.rs | 2 +- 5 files changed, 131 insertions(+), 14 deletions(-) diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 3a96860e..2227f09b 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -2,7 +2,7 @@ use crate::util::field_not_ignored; use heck::CamelCase; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, quote_spanned}; -use syn::{Data, DataStruct, Field, Fields, Type}; +use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Field, Fields, Lit, Meta, Type}; pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result { let fields = match data { @@ -28,7 +28,36 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = fields .clone() .into_iter() - .map(|Field { ident, .. }| format_ident!("{}", ident.unwrap().to_string().to_camel_case())) + .map(|field| { + let mut ident = format_ident!( + "{}", + field.ident.as_ref().unwrap().to_string().to_camel_case() + ); + for attr in field.attrs.iter() { + if let Some(ident) = attr.path.get_ident() { + if ident != "sea_orm" { + continue; + } + } else { + continue; + } + if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) + { + for meta in list.iter() { + if let Meta::NameValue(nv) = meta { + if let Some(name) = nv.path.get_ident() { + if name == "enum_name" { + if let Lit::Str(litstr) = &nv.lit { + ident = syn::parse_str(&litstr.value()).unwrap(); + } + } + } + } + } + } + } + ident + }) .collect(); let ty: Vec = fields.into_iter().map(|Field { ty, .. }| ty).collect(); diff --git a/sea-orm-macros/src/derives/column.rs b/sea-orm-macros/src/derives/column.rs index 16f9bb22..5fc471e9 100644 --- a/sea-orm-macros/src/derives/column.rs +++ b/sea-orm-macros/src/derives/column.rs @@ -1,7 +1,7 @@ use heck::{MixedCase, SnakeCase}; use proc_macro2::{Ident, TokenStream}; use quote::{quote, quote_spanned}; -use syn::{Data, DataEnum, Fields, Variant}; +use syn::{punctuated::Punctuated, token::Comma, Data, DataEnum, Fields, Lit, Meta, Variant}; pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result { let variants = match data { @@ -25,8 +25,31 @@ pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result = variants .iter() .map(|v| { - let ident = v.ident.to_string().to_snake_case(); - quote! { #ident } + let mut column_name = v.ident.to_string().to_snake_case(); + for attr in v.attrs.iter() { + if let Some(ident) = attr.path.get_ident() { + if ident != "sea_orm" { + continue; + } + } else { + continue; + } + if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) + { + for meta in list.iter() { + if let Meta::NameValue(nv) = meta { + if let Some(name) = nv.path.get_ident() { + if name == "column_name" { + if let Lit::Str(litstr) = &nv.lit { + column_name = litstr.value(); + } + } + } + } + } + } + } + quote! { #column_name } }) .collect(); diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index f0772763..e7a6a23a 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -60,9 +60,8 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res if let Fields::Named(fields) = item_struct.fields { for field in fields.named { if let Some(ident) = &field.ident { - let field_name = + let mut field_name = Ident::new(&ident.to_string().to_case(Case::Pascal), Span::call_site()); - columns_enum.push(quote! { #field_name }); let mut nullable = false; let mut default_value = None; @@ -71,7 +70,10 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res let mut ignore = false; let mut unique = false; let mut sql_type = None; - // search for #[sea_orm(primary_key, auto_increment = false, column_type = "String(Some(255))", default_value = "new user", default_expr = "gen_random_uuid()", nullable, indexed, unique)] + let mut column_name = None; + let mut enum_name = None; + let mut is_primary_key = false; + // search for #[sea_orm(primary_key, auto_increment = false, column_type = "String(Some(255))", default_value = "new user", default_expr = "gen_random_uuid()", column_name = "name", enum_name = "Name", nullable, indexed, unique)] for attr in field.attrs.iter() { if let Some(ident) = attr.path.get_ident() { if ident != "sea_orm" { @@ -116,6 +118,26 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res default_value = Some(nv.lit.to_owned()); } else if name == "default_expr" { default_expr = Some(nv.lit.to_owned()); + } else if name == "column_name" { + if let Lit::Str(litstr) = &nv.lit { + column_name = Some(litstr.value()); + } else { + return Err(Error::new( + field.span(), + format!("Invalid column_name {:?}", nv.lit), + )); + } + } else if name == "enum_name" { + if let Lit::Str(litstr) = &nv.lit { + let ty: Ident = + syn::parse_str(&litstr.value())?; + enum_name = Some(ty); + } else { + return Err(Error::new( + field.span(), + format!("Invalid enum_name {:?}", nv.lit), + )); + } } } } @@ -125,7 +147,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res ignore = true; break; } else if name == "primary_key" { - primary_keys.push(quote! { #field_name }); + is_primary_key = true; primary_key_types.push(field.ty.clone()); } else if name == "nullable" { nullable = true; @@ -142,9 +164,27 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res } } + if let Some(enum_name) = enum_name { + field_name = enum_name; + } + if ignore { - columns_enum.pop(); continue; + } else { + let variant_attrs = match &column_name { + Some(column_name) => quote! { + #[sea_orm(column_name = #column_name)] + }, + None => quote! {}, + }; + columns_enum.push(quote! { + #variant_attrs + #field_name + }); + } + + if is_primary_key { + primary_keys.push(quote! { #field_name }); } let field_type = match sql_type { diff --git a/sea-orm-macros/src/derives/model.rs b/sea-orm-macros/src/derives/model.rs index 9d619991..a43b487f 100644 --- a/sea-orm-macros/src/derives/model.rs +++ b/sea-orm-macros/src/derives/model.rs @@ -3,7 +3,7 @@ use heck::CamelCase; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; use std::iter::FromIterator; -use syn::Ident; +use syn::{punctuated::Punctuated, token::Comma, Ident, Lit, Meta}; enum Error { InputNotStruct, @@ -43,10 +43,35 @@ impl DeriveModel { let column_idents = fields .iter() .map(|field| { - format_ident!( + let mut ident = format_ident!( "{}", field.ident.as_ref().unwrap().to_string().to_camel_case() - ) + ); + for attr in field.attrs.iter() { + if let Some(ident) = attr.path.get_ident() { + if ident != "sea_orm" { + continue; + } + } else { + continue; + } + if let Ok(list) = + attr.parse_args_with(Punctuated::::parse_terminated) + { + for meta in list.iter() { + if let Meta::NameValue(nv) = meta { + if let Some(name) = nv.path.get_ident() { + if name == "enum_name" { + if let Lit::Str(litstr) = &nv.lit { + ident = syn::parse_str(&litstr.value()).unwrap(); + } + } + } + } + } + } + } + ident }) .collect(); diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index 629c5c18..c3f8a50f 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -46,7 +46,7 @@ pub fn derive_primary_key(input: TokenStream) -> TokenStream { } } -#[proc_macro_derive(DeriveColumn)] +#[proc_macro_derive(DeriveColumn, attributes(sea_orm))] pub fn derive_column(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); From 89806ef5223463bbb75c7907311905d0568307c4 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 28 Sep 2021 14:27:30 +0800 Subject: [PATCH 05/29] Add test cases --- src/tests_cfg/cake.rs | 1 + tests/common/bakery_chain/metadata.rs | 2 ++ tests/common/setup/schema.rs | 1 + tests/parallel_tests.rs | 3 +++ tests/uuid_tests.rs | 1 + 5 files changed, 8 insertions(+) diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index 920e1fea..90f529a5 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -6,6 +6,7 @@ use crate::entity::prelude::*; pub struct Model { #[sea_orm(primary_key)] pub id: i32, + #[sea_orm(column_name = "name", enum_name = "Name")] pub name: String, } diff --git a/tests/common/bakery_chain/metadata.rs b/tests/common/bakery_chain/metadata.rs index 95a7a48b..0811ce39 100644 --- a/tests/common/bakery_chain/metadata.rs +++ b/tests/common/bakery_chain/metadata.rs @@ -5,6 +5,8 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub uuid: Uuid, + #[sea_orm(column_name = "type", enum_name = "Type")] + pub ty: String, pub key: String, pub value: String, pub bytes: Vec, diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 64f31dfe..176cfc70 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -283,6 +283,7 @@ pub async fn create_metadata_table(db: &DbConn) -> Result { .not_null() .primary_key(), ) + .col(ColumnDef::new(metadata::Column::Type).string().not_null()) .col(ColumnDef::new(metadata::Column::Key).string().not_null()) .col(ColumnDef::new(metadata::Column::Value).string().not_null()) .col(ColumnDef::new(metadata::Column::Bytes).binary().not_null()) diff --git a/tests/parallel_tests.rs b/tests/parallel_tests.rs index 0ac09fd6..7b313d0c 100644 --- a/tests/parallel_tests.rs +++ b/tests/parallel_tests.rs @@ -22,18 +22,21 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { let metadata = vec![ metadata::Model { uuid: Uuid::new_v4(), + ty: "Type".to_owned(), key: "markup".to_owned(), value: "1.18".to_owned(), bytes: vec![1, 2, 3], }, metadata::Model { uuid: Uuid::new_v4(), + ty: "Type".to_owned(), key: "exchange_rate".to_owned(), value: "0.78".to_owned(), bytes: vec![1, 2, 3], }, metadata::Model { uuid: Uuid::new_v4(), + ty: "Type".to_owned(), key: "service_charge".to_owned(), value: "1.1".to_owned(), bytes: vec![1, 2, 3], diff --git a/tests/uuid_tests.rs b/tests/uuid_tests.rs index e58daca4..2bd83473 100644 --- a/tests/uuid_tests.rs +++ b/tests/uuid_tests.rs @@ -20,6 +20,7 @@ async fn main() -> Result<(), DbErr> { pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { let metadata = metadata::Model { uuid: Uuid::new_v4(), + ty: "Type".to_owned(), key: "markup".to_owned(), value: "1.18".to_owned(), bytes: vec![1, 2, 3], From f7e96b3d72dfff2d9764c97715d7c845f43e6515 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 27 Sep 2021 18:35:42 +0800 Subject: [PATCH 06/29] Support `chrono::NaiveDate` --- sea-orm-macros/src/derives/entity_model.rs | 2 +- src/entity/prelude.rs | 3 +++ src/executor/query.rs | 3 +++ tests/common/bakery_chain/metadata.rs | 1 + tests/common/setup/schema.rs | 1 + tests/parallel_tests.rs | 3 +++ tests/uuid_tests.rs | 1 + 7 files changed, 13 insertions(+), 1 deletion(-) diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index f0772763..ea9d05f5 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -170,7 +170,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res "f32" => quote! { Float }, "f64" => quote! { Double }, "bool" => quote! { Boolean }, - "NaiveDate" => quote! { Date }, + "Date" | "NaiveDate" => quote! { Date }, "NaiveTime" => quote! { Time }, "DateTime" | "NaiveDateTime" => { quote! { DateTime } diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 8d87a4b2..221736f7 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -9,6 +9,9 @@ pub use crate::{ #[cfg(feature = "with-json")] pub use serde_json::Value as Json; +#[cfg(feature = "with-chrono")] +pub use chrono::NaiveDate as Date; + #[cfg(feature = "with-chrono")] pub use chrono::NaiveDateTime as DateTime; diff --git a/src/executor/query.rs b/src/executor/query.rs index ae09f97c..483d7d54 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -241,6 +241,9 @@ try_getable_all!(Vec); #[cfg(feature = "with-json")] try_getable_all!(serde_json::Value); +#[cfg(feature = "with-chrono")] +try_getable_all!(chrono::NaiveDate); + #[cfg(feature = "with-chrono")] try_getable_all!(chrono::NaiveDateTime); diff --git a/tests/common/bakery_chain/metadata.rs b/tests/common/bakery_chain/metadata.rs index 95a7a48b..c7727fb8 100644 --- a/tests/common/bakery_chain/metadata.rs +++ b/tests/common/bakery_chain/metadata.rs @@ -8,6 +8,7 @@ pub struct Model { pub key: String, pub value: String, pub bytes: Vec, + pub date: Date, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 64f31dfe..0787e971 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -286,6 +286,7 @@ pub async fn create_metadata_table(db: &DbConn) -> Result { .col(ColumnDef::new(metadata::Column::Key).string().not_null()) .col(ColumnDef::new(metadata::Column::Value).string().not_null()) .col(ColumnDef::new(metadata::Column::Bytes).binary().not_null()) + .col(ColumnDef::new(metadata::Column::Date).date().not_null()) .to_owned(); create_table(db, &stmt, Metadata).await diff --git a/tests/parallel_tests.rs b/tests/parallel_tests.rs index 0ac09fd6..d83c7ab5 100644 --- a/tests/parallel_tests.rs +++ b/tests/parallel_tests.rs @@ -25,18 +25,21 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { key: "markup".to_owned(), value: "1.18".to_owned(), bytes: vec![1, 2, 3], + date: Date::from_ymd(2021, 9, 27), }, metadata::Model { uuid: Uuid::new_v4(), key: "exchange_rate".to_owned(), value: "0.78".to_owned(), bytes: vec![1, 2, 3], + date: Date::from_ymd(2021, 9, 27), }, metadata::Model { uuid: Uuid::new_v4(), key: "service_charge".to_owned(), value: "1.1".to_owned(), bytes: vec![1, 2, 3], + date: Date::from_ymd(2021, 9, 27), }, ]; diff --git a/tests/uuid_tests.rs b/tests/uuid_tests.rs index e58daca4..c6edd2aa 100644 --- a/tests/uuid_tests.rs +++ b/tests/uuid_tests.rs @@ -23,6 +23,7 @@ pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { key: "markup".to_owned(), value: "1.18".to_owned(), bytes: vec![1, 2, 3], + date: Date::from_ymd(2021, 9, 27), }; let res = Metadata::insert(metadata.clone().into_active_model()) From 02066cef6ed1e071b5181fec81cf0f8a062e1216 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 28 Sep 2021 11:40:40 +0800 Subject: [PATCH 07/29] Support `chrono::NaiveTime` --- sea-orm-macros/src/derives/entity_model.rs | 2 +- src/entity/prelude.rs | 3 +++ src/executor/query.rs | 3 +++ tests/common/bakery_chain/metadata.rs | 1 + tests/common/setup/schema.rs | 1 + tests/parallel_tests.rs | 3 +++ tests/uuid_tests.rs | 1 + 7 files changed, 13 insertions(+), 1 deletion(-) diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index ea9d05f5..ba448bd4 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -171,7 +171,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res "f64" => quote! { Double }, "bool" => quote! { Boolean }, "Date" | "NaiveDate" => quote! { Date }, - "NaiveTime" => quote! { Time }, + "Time" | "NaiveTime" => quote! { Time }, "DateTime" | "NaiveDateTime" => { quote! { DateTime } } diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 221736f7..fb89a454 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -12,6 +12,9 @@ pub use serde_json::Value as Json; #[cfg(feature = "with-chrono")] pub use chrono::NaiveDate as Date; +#[cfg(feature = "with-chrono")] +pub use chrono::NaiveTime as Time; + #[cfg(feature = "with-chrono")] pub use chrono::NaiveDateTime as DateTime; diff --git a/src/executor/query.rs b/src/executor/query.rs index 483d7d54..9fda50ff 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -244,6 +244,9 @@ try_getable_all!(serde_json::Value); #[cfg(feature = "with-chrono")] try_getable_all!(chrono::NaiveDate); +#[cfg(feature = "with-chrono")] +try_getable_all!(chrono::NaiveTime); + #[cfg(feature = "with-chrono")] try_getable_all!(chrono::NaiveDateTime); diff --git a/tests/common/bakery_chain/metadata.rs b/tests/common/bakery_chain/metadata.rs index c7727fb8..9130bbbd 100644 --- a/tests/common/bakery_chain/metadata.rs +++ b/tests/common/bakery_chain/metadata.rs @@ -9,6 +9,7 @@ pub struct Model { pub value: String, pub bytes: Vec, pub date: Date, + pub time: Time, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 0787e971..74e3d390 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -287,6 +287,7 @@ pub async fn create_metadata_table(db: &DbConn) -> Result { .col(ColumnDef::new(metadata::Column::Value).string().not_null()) .col(ColumnDef::new(metadata::Column::Bytes).binary().not_null()) .col(ColumnDef::new(metadata::Column::Date).date().not_null()) + .col(ColumnDef::new(metadata::Column::Time).time().not_null()) .to_owned(); create_table(db, &stmt, Metadata).await diff --git a/tests/parallel_tests.rs b/tests/parallel_tests.rs index d83c7ab5..6e18cb52 100644 --- a/tests/parallel_tests.rs +++ b/tests/parallel_tests.rs @@ -26,6 +26,7 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { value: "1.18".to_owned(), bytes: vec![1, 2, 3], date: Date::from_ymd(2021, 9, 27), + time: Time::from_hms(11, 32, 55), }, metadata::Model { uuid: Uuid::new_v4(), @@ -33,6 +34,7 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { value: "0.78".to_owned(), bytes: vec![1, 2, 3], date: Date::from_ymd(2021, 9, 27), + time: Time::from_hms(11, 32, 55), }, metadata::Model { uuid: Uuid::new_v4(), @@ -40,6 +42,7 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { value: "1.1".to_owned(), bytes: vec![1, 2, 3], date: Date::from_ymd(2021, 9, 27), + time: Time::from_hms(11, 32, 55), }, ]; diff --git a/tests/uuid_tests.rs b/tests/uuid_tests.rs index c6edd2aa..bf13f689 100644 --- a/tests/uuid_tests.rs +++ b/tests/uuid_tests.rs @@ -24,6 +24,7 @@ pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { value: "1.18".to_owned(), bytes: vec![1, 2, 3], date: Date::from_ymd(2021, 9, 27), + time: Time::from_hms(11, 32, 55), }; let res = Metadata::insert(metadata.clone().into_active_model()) From d9f63e8d170788db585f5eb44983ea2bf52134ef Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 28 Sep 2021 18:01:18 +0800 Subject: [PATCH 08/29] Fixup --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 37aa1b6e..753e59d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ sea-query = { version = "^0.16.3", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } -sqlx = { version = "^0.5", optional = true } +sqlx = { version = "^0.5", git = "https://github.com/billy1624/sqlx.git", branch = "fix-sqlite-naive-time", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } [dev-dependencies] From 05b370434b3765c1a1c695d8e8e951845f5a0236 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 30 Sep 2021 10:45:59 +0800 Subject: [PATCH 09/29] Without fixing SQLx source code --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 753e59d5..39003c5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,11 +30,11 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.2.3", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.16.3", features = ["thread-safe"] } +sea-query = { version = "^0.16.4", git = "https://github.com/SeaQL/sea-query.git", branch = "sqlite-time-temp-fixup", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } -sqlx = { version = "^0.5", git = "https://github.com/billy1624/sqlx.git", branch = "fix-sqlite-naive-time", optional = true } +sqlx = { version = "^0.5", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } [dev-dependencies] From 088bfd01e79e1e9da1ecbf29e21067bc9087c555 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 30 Sep 2021 11:19:17 +0800 Subject: [PATCH 10/29] Add unit tests --- src/entity/column.rs | 321 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) diff --git a/src/entity/column.rs b/src/entity/column.rs index 32500630..04cb835e 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -453,4 +453,325 @@ mod tests { ColumnType::Integer.def().unique().indexed().nullable() ); } + + #[test] + #[cfg(feature = "macros")] + fn column_name_1() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "hello")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_name = "ONE")] + pub one: i32, + pub two: i32, + #[sea_orm(column_name = "3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::One.to_string().as_str(), "ONE"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three.to_string().as_str(), "3"); + } + + #[test] + #[cfg(feature = "macros")] + fn column_name_2() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Copy, Clone, Default, Debug, DeriveEntity)] + pub struct Entity; + + impl EntityName for Entity { + fn table_name(&self) -> &str { + "hello" + } + } + + #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] + pub struct Model { + pub id: i32, + pub one: i32, + pub two: i32, + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + pub enum Column { + Id, + #[sea_orm(column_name = "ONE")] + One, + Two, + #[sea_orm(column_name = "3")] + Three, + } + + impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Column::Id => ColumnType::Integer.def(), + Column::One => ColumnType::Integer.def(), + Column::Two => ColumnType::Integer.def(), + Column::Three => ColumnType::Integer.def(), + } + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] + pub enum PrimaryKey { + Id, + } + + impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::One.to_string().as_str(), "ONE"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three.to_string().as_str(), "3"); + } + + #[test] + #[cfg(feature = "macros")] + fn enum_name_1() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "hello")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(enum_name = "One1")] + pub one: i32, + pub two: i32, + #[sea_orm(enum_name = "Three3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::One1.to_string().as_str(), "one1"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three3.to_string().as_str(), "three3"); + } + + #[test] + #[cfg(feature = "macros")] + fn enum_name_2() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Copy, Clone, Default, Debug, DeriveEntity)] + pub struct Entity; + + impl EntityName for Entity { + fn table_name(&self) -> &str { + "hello" + } + } + + #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] + pub struct Model { + pub id: i32, + #[sea_orm(enum_name = "One1")] + pub one: i32, + pub two: i32, + #[sea_orm(enum_name = "Three3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + pub enum Column { + Id, + One1, + Two, + Three3, + } + + impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Column::Id => ColumnType::Integer.def(), + Column::One1 => ColumnType::Integer.def(), + Column::Two => ColumnType::Integer.def(), + Column::Three3 => ColumnType::Integer.def(), + } + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] + pub enum PrimaryKey { + Id, + } + + impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::One1.to_string().as_str(), "one1"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three3.to_string().as_str(), "three3"); + } + + #[test] + #[cfg(feature = "macros")] + fn column_name_enum_name_1() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "hello")] + pub struct Model { + #[sea_orm(primary_key, column_name = "ID", enum_name = "IdentityColumn")] + pub id: i32, + #[sea_orm(column_name = "ONE", enum_name = "One1")] + pub one: i32, + pub two: i32, + #[sea_orm(column_name = "THREE", enum_name = "Three3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::IdentityColumn.to_string().as_str(), "ID"); + assert_eq!(hello::Column::One1.to_string().as_str(), "ONE"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three3.to_string().as_str(), "THREE"); + } + + #[test] + #[cfg(feature = "macros")] + fn column_name_enum_name_2() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Copy, Clone, Default, Debug, DeriveEntity)] + pub struct Entity; + + impl EntityName for Entity { + fn table_name(&self) -> &str { + "hello" + } + } + + #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] + pub struct Model { + #[sea_orm(enum_name = "IdentityCol")] + pub id: i32, + #[sea_orm(enum_name = "One1")] + pub one: i32, + pub two: i32, + #[sea_orm(enum_name = "Three3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + pub enum Column { + #[sea_orm(column_name = "ID")] + IdentityCol, + #[sea_orm(column_name = "ONE")] + One1, + Two, + #[sea_orm(column_name = "THREE")] + Three3, + } + + impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Column::IdentityCol => ColumnType::Integer.def(), + Column::One1 => ColumnType::Integer.def(), + Column::Two => ColumnType::Integer.def(), + Column::Three3 => ColumnType::Integer.def(), + } + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] + pub enum PrimaryKey { + IdentityCol, + } + + impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::IdentityCol.to_string().as_str(), "ID"); + assert_eq!(hello::Column::One1.to_string().as_str(), "ONE"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three3.to_string().as_str(), "THREE"); + } } From b716c9ed57ceae85dad4ca5b2ac1addef869ff7f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 30 Sep 2021 11:19:26 +0800 Subject: [PATCH 11/29] cargo fmt --- src/executor/insert.rs | 4 ++-- src/query/helper.rs | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index d580f110..a44867f7 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,6 +1,6 @@ use crate::{ - error::*, ActiveModelTrait, DatabaseConnection, DbBackend, EntityTrait, Insert, PrimaryKeyTrait, - Statement, TryFromU64, + error::*, ActiveModelTrait, DatabaseConnection, DbBackend, EntityTrait, Insert, + PrimaryKeyTrait, Statement, TryFromU64, }; use sea_query::InsertStatement; use std::{future::Future, marker::PhantomData}; diff --git a/src/query/helper.rs b/src/query/helper.rs index 43fed28a..5b8053de 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -276,7 +276,9 @@ pub trait QueryFilter: Sized { /// struct Input { /// name: Option, /// } - /// let input = Input { name: Some("cheese".to_owned()) }; + /// let input = Input { + /// name: Some("cheese".to_owned()), + /// }; /// /// let mut conditions = Condition::all(); /// if let Some(name) = input.name { @@ -298,13 +300,14 @@ pub trait QueryFilter: Sized { /// struct Input { /// name: Option, /// } - /// let input = Input { name: Some("cheese".to_owned()) }; + /// let input = Input { + /// name: Some("cheese".to_owned()), + /// }; /// /// assert_eq!( /// cake::Entity::find() /// .filter( - /// Condition::all() - /// .add_option(input.name.map(|n| cake::Column::Name.contains(&n))) + /// Condition::all().add_option(input.name.map(|n| cake::Column::Name.contains(&n))) /// ) /// .build(DbBackend::MySql) /// .to_string(), From 33f0cfaa7728444f30fe2eca144a01f1ec7c579d Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 30 Sep 2021 18:27:05 +0800 Subject: [PATCH 12/29] Readme --- README.md | 25 ++++++++++++++++++++++++ src/lib.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/README.md b/README.md index 2285c77c..28b9747c 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,31 @@ async fn list( ## A quick taste of SeaORM +### Entity +```rust +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "cake")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} +``` + ### Select ```rust // find all models diff --git a/src/lib.rs b/src/lib.rs index a1dc83f7..e4897d13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,6 +203,63 @@ //! //! ## A quick taste of SeaORM //! +//! ### Entity +//! ``` +//! # #[cfg(feature = "macros")] +//! # mod entities { +//! # mod fruit { +//! # use sea_orm::entity::prelude::*; +//! # #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +//! # #[sea_orm(table_name = "fruit")] +//! # pub struct Model { +//! # #[sea_orm(primary_key)] +//! # pub id: i32, +//! # pub name: String, +//! # pub cake_id: Option, +//! # } +//! # #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +//! # pub enum Relation { +//! # #[sea_orm( +//! # belongs_to = "super::cake::Entity", +//! # from = "Column::CakeId", +//! # to = "super::cake::Column::Id" +//! # )] +//! # Cake, +//! # } +//! # impl Related for Entity { +//! # fn to() -> RelationDef { +//! # Relation::Cake.def() +//! # } +//! # } +//! # impl ActiveModelBehavior for ActiveModel {} +//! # } +//! # mod cake { +//! use sea_orm::entity::prelude::*; +//! +//! #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +//! #[sea_orm(table_name = "cake")] +//! pub struct Model { +//! #[sea_orm(primary_key)] +//! pub id: i32, +//! pub name: String, +//! } +//! +//! #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +//! pub enum Relation { +//! #[sea_orm(has_many = "super::fruit::Entity")] +//! Fruit, +//! } +//! +//! impl Related for Entity { +//! fn to() -> RelationDef { +//! Relation::Fruit.def() +//! } +//! } +//! # impl ActiveModelBehavior for ActiveModel {} +//! # } +//! # } +//! ``` +//! //! ### Select //! ``` //! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; From fcd969e57ce4fbc71b85240b7ebb1d248eb1e071 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 30 Sep 2021 22:05:23 +0800 Subject: [PATCH 13/29] Use DeriveIden to replace ToString --- src/executor/query.rs | 16 +++------------- src/executor/select.rs | 23 +++++++---------------- src/lib.rs | 2 ++ 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src/executor/query.rs b/src/executor/query.rs index c39c2211..f2f29137 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -320,24 +320,14 @@ pub trait TryGetableMany: Sized { /// # ]]) /// # .into_connection(); /// # - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, TryGetableMany}; + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, DeriveIden, TryGetableMany}; /// - /// #[derive(EnumIter)] + /// #[derive(EnumIter, DeriveIden)] /// enum ResultCol { /// Name, /// NumOfCakes, /// } /// - /// // this can be derived using derive_more crate - /// impl std::fmt::Display for ResultCol { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// match self { - /// Self::Name => write!(f, "name"), - /// Self::NumOfCakes => write!(f, "num_of_cakes"), - /// } - /// } - /// } - /// /// # let _: Result<(), DbErr> = smol::block_on(async { /// # /// let res: Vec<(String, i32)> = @@ -371,7 +361,7 @@ pub trait TryGetableMany: Sized { /// ``` fn find_by_statement(stmt: Statement) -> SelectorRaw> where - C: sea_strum::IntoEnumIterator + ToString, + C: sea_strum::IntoEnumIterator + sea_query::Iden, { SelectorRaw::>::with_columns(stmt) } diff --git a/src/executor/select.rs b/src/executor/select.rs index ebc5ad11..879ab26e 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -34,7 +34,7 @@ pub trait SelectorTrait { pub struct SelectGetableValue where T: TryGetableMany, - C: sea_strum::IntoEnumIterator + ToString, + C: sea_strum::IntoEnumIterator + sea_query::Iden, { columns: PhantomData, model: PhantomData, @@ -60,7 +60,7 @@ where impl SelectorTrait for SelectGetableValue where T: TryGetableMany, - C: sea_strum::IntoEnumIterator + ToString, + C: sea_strum::IntoEnumIterator + sea_query::Iden, { type Item = T; @@ -143,22 +143,13 @@ where /// # ]]) /// # .into_connection(); /// # - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, TryGetableMany}; + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, DeriveIden, TryGetableMany}; /// - /// #[derive(EnumIter)] + /// #[derive(EnumIter, DeriveIden)] /// enum ResultCol { /// Name, /// } /// - /// // this can be derived using derive_more crate - /// impl std::fmt::Display for ResultCol { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// match self { - /// Self::Name => write!(f, "name"), - /// } - /// } - /// } - /// /// # let _: Result<(), DbErr> = smol::block_on(async { /// # /// let res: Vec = cake::Entity::find() @@ -188,7 +179,7 @@ where pub fn into_values(self) -> Selector> where T: TryGetableMany, - C: sea_strum::IntoEnumIterator + ToString, + C: sea_strum::IntoEnumIterator + sea_query::Iden, { Selector::>::with_columns(self.query) } @@ -323,7 +314,7 @@ where pub fn with_columns(query: SelectStatement) -> Selector> where T: TryGetableMany, - C: sea_strum::IntoEnumIterator + ToString, + C: sea_strum::IntoEnumIterator + sea_query::Iden, { Selector { query, @@ -386,7 +377,7 @@ where pub fn with_columns(stmt: Statement) -> SelectorRaw> where T: TryGetableMany, - C: sea_strum::IntoEnumIterator + ToString, + C: sea_strum::IntoEnumIterator + sea_query::Iden, { SelectorRaw { stmt, diff --git a/src/lib.rs b/src/lib.rs index e4897d13..90ffe214 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -440,5 +440,7 @@ pub use sea_orm_macros::{ pub use sea_query; pub use sea_query::Iden; +pub use sea_query::Iden as DeriveIden; + pub use sea_strum; pub use sea_strum::EnumIter; From ca296bc941196e8e3398e8a5a8d34a93095a714e Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 30 Sep 2021 22:25:31 +0800 Subject: [PATCH 14/29] Feature guard macros --- src/entity/prelude.rs | 11 ++++++++--- src/executor/query.rs | 2 +- src/executor/select.rs | 2 +- src/lib.rs | 7 +++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 8d87a4b2..ad8cafc6 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -1,9 +1,14 @@ pub use crate::{ error::*, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType, + EntityName, EntityTrait, EnumIter, ForeignKeyAction, Iden, IdenStatic, Linked, ModelTrait, + PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, + RelationTrait, Select, Value, +}; + +#[cfg(feature = "macros")] +pub use crate::{ DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, - DeriveEntityModel, DeriveModel, DerivePrimaryKey, DeriveRelation, EntityName, EntityTrait, - EnumIter, ForeignKeyAction, Iden, IdenStatic, Linked, ModelTrait, PrimaryKeyToColumn, - PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, RelationTrait, Select, Value, + DeriveEntityModel, DeriveModel, DerivePrimaryKey, DeriveRelation, }; #[cfg(feature = "with-json")] diff --git a/src/executor/query.rs b/src/executor/query.rs index f2f29137..d51cdaba 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -304,7 +304,7 @@ pub trait TryGetableMany: Sized { fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result; /// ``` - /// # #[cfg(feature = "mock")] + /// # #[cfg(all(feature = "mock", feature = "macros"))] /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; /// # /// # let db = MockDatabase::new(DbBackend::Postgres) diff --git a/src/executor/select.rs b/src/executor/select.rs index 879ab26e..1285fe96 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -127,7 +127,7 @@ where } /// ``` - /// # #[cfg(feature = "mock")] + /// # #[cfg(all(feature = "mock", feature = "macros"))] /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; /// # /// # let db = MockDatabase::new(DbBackend::Postgres) diff --git a/src/lib.rs b/src/lib.rs index 90ffe214..c0c4f670 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -243,13 +243,13 @@ //! pub id: i32, //! pub name: String, //! } -//! +//! //! #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] //! pub enum Relation { //! #[sea_orm(has_many = "super::fruit::Entity")] //! Fruit, //! } -//! +//! //! impl Related for Entity { //! fn to() -> RelationDef { //! Relation::Fruit.def() @@ -433,6 +433,7 @@ pub use executor::*; pub use query::*; pub use schema::*; +#[cfg(feature = "macros")] pub use sea_orm_macros::{ DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveModel, DerivePrimaryKey, DeriveRelation, FromQueryResult, @@ -440,7 +441,9 @@ pub use sea_orm_macros::{ pub use sea_query; pub use sea_query::Iden; +#[cfg(feature = "macros")] pub use sea_query::Iden as DeriveIden; pub use sea_strum; +#[cfg(feature = "macros")] pub use sea_strum::EnumIter; From cb5445b035fe121c70edc44ad5d0b07f96b20ff6 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 30 Sep 2021 22:42:31 +0800 Subject: [PATCH 15/29] Bump sea-query to 0.16.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 39003c5f..d1ddf12e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.2.3", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.16.4", git = "https://github.com/SeaQL/sea-query.git", branch = "sqlite-time-temp-fixup", features = ["thread-safe"] } +sea-query = { version = "^0.16.5", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } From 50a8ae41c026149e919112fc8907cd050a23d92f Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 11:11:48 +0800 Subject: [PATCH 16/29] Tweaks --- examples/actix_example/.env | 2 +- examples/actix_web_4_beta_example/.env | 2 +- examples/actix_web_4_beta_example/Cargo.toml | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/actix_example/.env b/examples/actix_example/.env index f6b73c71..94ec74e3 100644 --- a/examples/actix_example/.env +++ b/examples/actix_example/.env @@ -1,3 +1,3 @@ HOST=127.0.0.1 PORT=8000 -DATABASE_URL="sql://root:root@localhost/actix_example" \ No newline at end of file +DATABASE_URL="sql://root:@localhost/actix_example" \ No newline at end of file diff --git a/examples/actix_web_4_beta_example/.env b/examples/actix_web_4_beta_example/.env index c4acf8a0..d89d9e63 100644 --- a/examples/actix_web_4_beta_example/.env +++ b/examples/actix_web_4_beta_example/.env @@ -1,3 +1,3 @@ HOST=127.0.0.1 PORT=8000 -DATABASE_URL="mysql://root:@localhost/rocket_example" +DATABASE_URL="mysql://root:@localhost/actix_example" diff --git a/examples/actix_web_4_beta_example/Cargo.toml b/examples/actix_web_4_beta_example/Cargo.toml index 7a0fbbf6..b03b7e85 100644 --- a/examples/actix_web_4_beta_example/Cargo.toml +++ b/examples/actix_web_4_beta_example/Cargo.toml @@ -20,8 +20,7 @@ listenfd = "0.3.3" # remove `path = ""` in your own project sea-orm = { path = "../../", version = "^0.2.3", features = [ "macros", - "runtime-tokio-native-tls", - # "runtime-actix-native-tls", + "runtime-actix-native-tls", ], default-features = false } serde = "1" env_logger = "0.8" From 399715521ec638526ccda82a203e4c04b6cdf323 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 11:14:59 +0800 Subject: [PATCH 17/29] Rename folder --- .../.env | 0 .../Cargo.toml | 0 .../README.md | 0 .../src/main.rs | 0 .../src/post.rs | 0 .../src/setup.rs | 0 .../static/css/normalize.css | 0 .../static/css/skeleton.css | 0 .../static/css/style.css | 0 .../static/images/favicon.png | Bin .../templates/edit.html.tera | 0 .../templates/error/404.html.tera | 0 .../templates/index.html.tera | 0 .../templates/layout.html.tera | 0 .../templates/new.html.tera | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename examples/{actix_web_4_beta_example => actix4_example}/.env (100%) rename examples/{actix_web_4_beta_example => actix4_example}/Cargo.toml (100%) rename examples/{actix_web_4_beta_example => actix4_example}/README.md (100%) rename examples/{actix_web_4_beta_example => actix4_example}/src/main.rs (100%) rename examples/{actix_web_4_beta_example => actix4_example}/src/post.rs (100%) rename examples/{actix_web_4_beta_example => actix4_example}/src/setup.rs (100%) rename examples/{actix_web_4_beta_example => actix4_example}/static/css/normalize.css (100%) rename examples/{actix_web_4_beta_example => actix4_example}/static/css/skeleton.css (100%) rename examples/{actix_web_4_beta_example => actix4_example}/static/css/style.css (100%) rename examples/{actix_web_4_beta_example => actix4_example}/static/images/favicon.png (100%) rename examples/{actix_web_4_beta_example => actix4_example}/templates/edit.html.tera (100%) rename examples/{actix_web_4_beta_example => actix4_example}/templates/error/404.html.tera (100%) rename examples/{actix_web_4_beta_example => actix4_example}/templates/index.html.tera (100%) rename examples/{actix_web_4_beta_example => actix4_example}/templates/layout.html.tera (100%) rename examples/{actix_web_4_beta_example => actix4_example}/templates/new.html.tera (100%) diff --git a/examples/actix_web_4_beta_example/.env b/examples/actix4_example/.env similarity index 100% rename from examples/actix_web_4_beta_example/.env rename to examples/actix4_example/.env diff --git a/examples/actix_web_4_beta_example/Cargo.toml b/examples/actix4_example/Cargo.toml similarity index 100% rename from examples/actix_web_4_beta_example/Cargo.toml rename to examples/actix4_example/Cargo.toml diff --git a/examples/actix_web_4_beta_example/README.md b/examples/actix4_example/README.md similarity index 100% rename from examples/actix_web_4_beta_example/README.md rename to examples/actix4_example/README.md diff --git a/examples/actix_web_4_beta_example/src/main.rs b/examples/actix4_example/src/main.rs similarity index 100% rename from examples/actix_web_4_beta_example/src/main.rs rename to examples/actix4_example/src/main.rs diff --git a/examples/actix_web_4_beta_example/src/post.rs b/examples/actix4_example/src/post.rs similarity index 100% rename from examples/actix_web_4_beta_example/src/post.rs rename to examples/actix4_example/src/post.rs diff --git a/examples/actix_web_4_beta_example/src/setup.rs b/examples/actix4_example/src/setup.rs similarity index 100% rename from examples/actix_web_4_beta_example/src/setup.rs rename to examples/actix4_example/src/setup.rs diff --git a/examples/actix_web_4_beta_example/static/css/normalize.css b/examples/actix4_example/static/css/normalize.css similarity index 100% rename from examples/actix_web_4_beta_example/static/css/normalize.css rename to examples/actix4_example/static/css/normalize.css diff --git a/examples/actix_web_4_beta_example/static/css/skeleton.css b/examples/actix4_example/static/css/skeleton.css similarity index 100% rename from examples/actix_web_4_beta_example/static/css/skeleton.css rename to examples/actix4_example/static/css/skeleton.css diff --git a/examples/actix_web_4_beta_example/static/css/style.css b/examples/actix4_example/static/css/style.css similarity index 100% rename from examples/actix_web_4_beta_example/static/css/style.css rename to examples/actix4_example/static/css/style.css diff --git a/examples/actix_web_4_beta_example/static/images/favicon.png b/examples/actix4_example/static/images/favicon.png similarity index 100% rename from examples/actix_web_4_beta_example/static/images/favicon.png rename to examples/actix4_example/static/images/favicon.png diff --git a/examples/actix_web_4_beta_example/templates/edit.html.tera b/examples/actix4_example/templates/edit.html.tera similarity index 100% rename from examples/actix_web_4_beta_example/templates/edit.html.tera rename to examples/actix4_example/templates/edit.html.tera diff --git a/examples/actix_web_4_beta_example/templates/error/404.html.tera b/examples/actix4_example/templates/error/404.html.tera similarity index 100% rename from examples/actix_web_4_beta_example/templates/error/404.html.tera rename to examples/actix4_example/templates/error/404.html.tera diff --git a/examples/actix_web_4_beta_example/templates/index.html.tera b/examples/actix4_example/templates/index.html.tera similarity index 100% rename from examples/actix_web_4_beta_example/templates/index.html.tera rename to examples/actix4_example/templates/index.html.tera diff --git a/examples/actix_web_4_beta_example/templates/layout.html.tera b/examples/actix4_example/templates/layout.html.tera similarity index 100% rename from examples/actix_web_4_beta_example/templates/layout.html.tera rename to examples/actix4_example/templates/layout.html.tera diff --git a/examples/actix_web_4_beta_example/templates/new.html.tera b/examples/actix4_example/templates/new.html.tera similarity index 100% rename from examples/actix_web_4_beta_example/templates/new.html.tera rename to examples/actix4_example/templates/new.html.tera From f0dda202b20c36fafba2239ca5d131c977ba20aa Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 11:15:38 +0800 Subject: [PATCH 18/29] Add to workflow --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 236a668e..53842ae7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -170,7 +170,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - path: [async-std, tokio, actix_example, rocket_example] + path: [async-std, tokio, actix_example, actix4_example, rocket_example] steps: - uses: actions/checkout@v2 From dc1bf2c066371473c981131a7c660fd962626507 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 12:37:10 +0800 Subject: [PATCH 19/29] sea-orm-macros 0.2.4 --- sea-orm-macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sea-orm-macros/Cargo.toml b/sea-orm-macros/Cargo.toml index 6ecc2b40..e37c8053 100644 --- a/sea-orm-macros/Cargo.toml +++ b/sea-orm-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sea-orm-macros" -version = "0.2.3" +version = "0.2.4" authors = [ "Billy Chan " ] edition = "2018" description = "Derive macros for SeaORM" From d79419f0d3b66533c39dadb7dcd1d02252f08eb3 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 12:35:55 +0800 Subject: [PATCH 20/29] 0.2.4 --- CHANGELOG.md | 13 ++++++++++++- Cargo.toml | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5038563..0398854a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## 0.2.4 - Prerelease +## 0.2.4 - 2021-10-01 - [[#186]] [sea-orm-cli] Foreign key handling - [[#191]] [sea-orm-cli] Unique key handling +- [[#182]] `find_linked` join with alias +- [[#202]] Accept both `postgres://` and `postgresql://` +- [[#208]] Support feteching T, (T, U), (T, U, P) etc +- [[#209]] Rename column name & column enum variant +- [[#207]] Support `chrono::NaiveDate` & `chrono::NaiveTime` +- Support `Condition::not` (from sea-query) [#186]: https://github.com/SeaQL/sea-orm/issues/186 [#191]: https://github.com/SeaQL/sea-orm/issues/191 +[#182]: https://github.com/SeaQL/sea-orm/pull/182 +[#202]: https://github.com/SeaQL/sea-orm/pull/202 +[#208]: https://github.com/SeaQL/sea-orm/pull/208 +[#209]: https://github.com/SeaQL/sea-orm/pull/209 +[#207]: https://github.com/SeaQL/sea-orm/pull/207 ## 0.2.3 - 2021-09-22 diff --git a/Cargo.toml b/Cargo.toml index d1ddf12e..a4466152 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "sea-orm-macros", "sea-orm-codegen"] [package] name = "sea-orm" -version = "0.2.3" +version = "0.2.4" authors = ["Chris Tsang "] edition = "2018" description = "🐚 An async & dynamic ORM for Rust" @@ -29,7 +29,7 @@ futures = { version = "^0.3" } futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } -sea-orm-macros = { version = "^0.2.3", path = "sea-orm-macros", optional = true } +sea-orm-macros = { version = "^0.2.4", path = "sea-orm-macros", optional = true } sea-query = { version = "^0.16.5", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } From 0c05b4c34ef69ce2aac08dd1bfeb867dd6a28b47 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 13:24:11 +0800 Subject: [PATCH 21/29] Improve example for `Select::into_values` --- src/executor/select.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/executor/select.rs b/src/executor/select.rs index 1285fe96..7a10488e 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -133,29 +133,29 @@ where /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ /// # maplit::btreemap! { - /// # "name" => Into::::into("Chocolate Forest"), + /// # "cake_name" => Into::::into("Chocolate Forest"), /// # "num_of_cakes" => Into::::into(1), /// # }, /// # maplit::btreemap! { - /// # "name" => Into::::into("New York Cheese"), + /// # "cake_name" => Into::::into("New York Cheese"), /// # "num_of_cakes" => Into::::into(1), /// # }, /// # ]]) /// # .into_connection(); /// # - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, DeriveIden, TryGetableMany}; + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter, TryGetableMany}; /// - /// #[derive(EnumIter, DeriveIden)] - /// enum ResultCol { - /// Name, + /// #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + /// enum QueryAs { + /// CakeName, /// } /// /// # let _: Result<(), DbErr> = smol::block_on(async { /// # /// let res: Vec = cake::Entity::find() /// .select_only() - /// .column(cake::Column::Name) - /// .into_values::<_, ResultCol>() + /// .column_as(cake::Column::Name, QueryAs::CakeName) + /// .into_values::<_, QueryAs>() /// .all(&db) /// .await?; /// @@ -171,7 +171,7 @@ where /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( /// DbBackend::Postgres, - /// r#"SELECT "cake"."name" FROM "cake""#, + /// r#"SELECT "cake"."name" AS "cake_name" FROM "cake""#, /// vec![] /// ),] /// ); From 656e503a520d90abb6977dac9a987ebc4e3660ef Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 13:33:05 +0800 Subject: [PATCH 22/29] Doc --- src/entity/model.rs | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/entity/model.rs b/src/entity/model.rs index ce4a3a4c..f71c1229 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -44,11 +44,7 @@ pub trait FromQueryResult: Sized { /// # .append_query_results(vec![vec![ /// # maplit::btreemap! { /// # "name" => Into::::into("Chocolate Forest"), - /// # "num_of_cakes" => Into::::into(1), - /// # }, - /// # maplit::btreemap! { - /// # "name" => Into::::into("New York Cheese"), - /// # "num_of_cakes" => Into::::into(1), + /// # "num_of_cakes" => Into::::into(2), /// # }, /// # ]]) /// # .into_connection(); @@ -65,7 +61,7 @@ pub trait FromQueryResult: Sized { /// # /// let res: Vec = SelectResult::find_by_statement(Statement::from_sql_and_values( /// DbBackend::Postgres, - /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, + /// r#"SELECT "name", COUNT(*) AS "num_of_cakes" FROM "cake" GROUP BY("name")"#, /// vec![], /// )) /// .all(&db) @@ -76,26 +72,21 @@ pub trait FromQueryResult: Sized { /// vec![ /// SelectResult { /// name: "Chocolate Forest".to_owned(), - /// num_of_cakes: 1, - /// }, - /// SelectResult { - /// name: "New York Cheese".to_owned(), - /// num_of_cakes: 1, + /// num_of_cakes: 2, /// }, /// ] /// ); /// # /// # Ok(()) /// # }); - /// - /// assert_eq!( - /// db.into_transaction_log(), - /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, - /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, - /// vec![] - /// ),] - /// ); + /// # assert_eq!( + /// # db.into_transaction_log(), + /// # vec![Transaction::from_sql_and_values( + /// # DbBackend::Postgres, + /// # r#"SELECT "name", COUNT(*) AS "num_of_cakes" FROM "cake" GROUP BY("name")"#, + /// # vec![] + /// # ),] + /// # ); /// ``` fn find_by_statement(stmt: Statement) -> SelectorRaw> { SelectorRaw::>::from_statement(stmt) From 7163f517881595f75ce2b51bd981d969cf745a59 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 21:40:01 +0800 Subject: [PATCH 23/29] Readme --- README.md | 5 +++-- src/lib.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 28b9747c..43daa1d6 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust. -[![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) -[![Usage Example](https://img.shields.io/badge/Usage%20Example-green)](https://github.com/SeaQL/sea-orm/tree/master/examples/async-std) +[![Getting Started](https://img.shields.io/badge/Getting%20Started-brightgreen)](https://www.sea-ql.org/SeaORM/docs/index) +[![Usage Example](https://img.shields.io/badge/Usage%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/async-std) +[![Actix Example](https://img.shields.io/badge/Actix%20Example-blue)](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) diff --git a/src/lib.rs b/src/lib.rs index c0c4f670..734663a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,8 +27,9 @@ //! //! SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust. //! -//! [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) -//! [![Usage Example](https://img.shields.io/badge/Usage%20Example-green)](https://github.com/SeaQL/sea-orm/tree/master/examples/async-std) +//! [![Getting Started](https://img.shields.io/badge/Getting%20Started-brightgreen)](https://www.sea-ql.org/SeaORM/docs/index) +//! [![Usage Example](https://img.shields.io/badge/Usage%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/async-std) +//! [![Actix Example](https://img.shields.io/badge/Actix%20Example-blue)](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) //! [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) //! [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) //! From a0be9239bd67c17998d947108024c1b4bc906a8e Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 1 Oct 2021 23:00:55 +0800 Subject: [PATCH 24/29] Doc --- src/entity/model.rs | 2 +- src/executor/select.rs | 61 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/entity/model.rs b/src/entity/model.rs index f71c1229..318b8e70 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -49,7 +49,7 @@ pub trait FromQueryResult: Sized { /// # ]]) /// # .into_connection(); /// # - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, FromQueryResult}; + /// use sea_orm::{query::*, FromQueryResult}; /// /// #[derive(Debug, PartialEq, FromQueryResult)] /// struct SelectResult { diff --git a/src/executor/select.rs b/src/executor/select.rs index 7a10488e..bb386722 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -134,16 +134,14 @@ where /// # .append_query_results(vec![vec![ /// # maplit::btreemap! { /// # "cake_name" => Into::::into("Chocolate Forest"), - /// # "num_of_cakes" => Into::::into(1), /// # }, /// # maplit::btreemap! { /// # "cake_name" => Into::::into("New York Cheese"), - /// # "num_of_cakes" => Into::::into(1), /// # }, /// # ]]) /// # .into_connection(); /// # - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter, TryGetableMany}; + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter}; /// /// #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// enum QueryAs { @@ -161,7 +159,7 @@ where /// /// assert_eq!( /// res, - /// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned(),] + /// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned()] /// ); /// # /// # Ok(()) @@ -173,7 +171,60 @@ where /// DbBackend::Postgres, /// r#"SELECT "cake"."name" AS "cake_name" FROM "cake""#, /// vec![] - /// ),] + /// )] + /// ); + /// ``` + /// + /// ``` + /// # #[cfg(all(feature = "mock", feature = "macros"))] + /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![vec![ + /// # maplit::btreemap! { + /// # "cake_name" => Into::::into("Chocolate Forest"), + /// # "num_of_cakes" => Into::::into(2i64), + /// # }, + /// # ]]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter}; + /// + /// #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + /// enum QueryAs { + /// CakeName, + /// NumOfCakes, + /// } + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let res: Vec<(String, i64)> = cake::Entity::find() + /// .select_only() + /// .column_as(cake::Column::Name, QueryAs::CakeName) + /// .column_as(cake::Column::Id.count(), QueryAs::NumOfCakes) + /// .group_by(cake::Column::Name) + /// .into_values::<_, QueryAs>() + /// .all(&db) + /// .await?; + /// + /// assert_eq!( + /// res, + /// vec![("Chocolate Forest".to_owned(), 2i64)] + /// ); + /// # + /// # Ok(()) + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// vec![ + /// r#"SELECT "cake"."name" AS "cake_name", COUNT("cake"."id") AS "num_of_cakes""#, + /// r#"FROM "cake" GROUP BY "cake"."name""#, + /// ].join(" ").as_str(), + /// vec![] + /// )] /// ); /// ``` pub fn into_values(self) -> Selector> From 7c82670d12c19ef64029412904ee9d26c4983000 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 2 Oct 2021 00:02:17 +0800 Subject: [PATCH 25/29] Doc --- src/entity/column.rs | 28 ++++++++++++++++++++++++++++ src/query/helper.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/entity/column.rs b/src/entity/column.rs index 04cb835e..a27215e5 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -774,4 +774,32 @@ mod tests { assert_eq!(hello::Column::Two.to_string().as_str(), "two"); assert_eq!(hello::Column::Three3.to_string().as_str(), "THREE"); } + + #[test] + #[cfg(feature = "macros")] + fn column_name_enum_name_3() { + use sea_query::Iden; + + mod my_entity { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "my_entity")] + pub struct Model { + #[sea_orm(primary_key, enum_name = "IdentityColumn", column_name = "id")] + pub id: i32, + #[sea_orm(column_name = "type")] + pub type_: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(my_entity::Column::IdentityColumn.to_string().as_str(), "id"); + assert_eq!(my_entity::Column::Type.to_string().as_str(), "type"); + } } diff --git a/src/query/helper.rs b/src/query/helper.rs index 5b8053de..7efe0ca1 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -314,6 +314,32 @@ pub trait QueryFilter: Sized { /// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%cheese%'" /// ); /// ``` + /// + /// A slightly more complex example. + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::Expr, DbBackend}; + /// + /// assert_eq!( + /// cake::Entity::find() + /// .filter( + /// Condition::all() + /// .add( + /// Condition::all() + /// .not() + /// .add(Expr::val(1).eq(1)) + /// .add(Expr::val(2).eq(2)) + /// ) + /// .add( + /// Condition::any() + /// .add(Expr::val(3).eq(3)) + /// .add(Expr::val(4).eq(4)) + /// ) + /// ) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE (NOT (1 = 1 AND 2 = 2)) AND (3 = 3 OR 4 = 4)"# + /// ); + /// ``` fn filter(mut self, filter: F) -> Self where F: IntoCondition, From ea88d73e84d3fa8f2946f8a500508e86db9f9f39 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 2 Oct 2021 14:17:40 +0800 Subject: [PATCH 26/29] Cleanup Readme --- README.md | 94 ----------------------------- src/docs.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 152 +---------------------------------------------- 3 files changed, 167 insertions(+), 245 deletions(-) create mode 100644 src/docs.rs diff --git a/README.md b/README.md index 43daa1d6..b4525b31 100644 --- a/README.md +++ b/README.md @@ -32,112 +32,18 @@ SeaORM is a relational ORM to help you build light weight and concurrent web ser Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. -```rust -// execute multiple queries in parallel -let cakes_and_fruits: (Vec, Vec) = - futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?; -``` - 2. Dynamic Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. -```rust -// build subquery with ease -let cakes_with_filling: Vec = cake::Entity::find() - .filter( - Condition::any().add( - cake::Column::Id.in_subquery( - Query::select() - .column(cake_filling::Column::CakeId) - .from(cake_filling::Entity) - .to_owned(), - ), - ), - ) - .all(&db) - .await?; - -``` - 3. Testable Use mock connections to write unit tests for your logic. -```rust -// Setup mock connection -let db = MockDatabase::new(DbBackend::Postgres) - .append_query_results(vec![ - vec![ - cake::Model { - id: 1, - name: "New York Cheese".to_owned(), - }, - ], - ]) - .into_connection(); - -// Perform your application logic -assert_eq!( - cake::Entity::find().one(&db).await?, - Some(cake::Model { - id: 1, - name: "New York Cheese".to_owned(), - }) -); - -// Compare it against the expected transaction log -assert_eq!( - db.into_transaction_log(), - vec![ - Transaction::from_sql_and_values( - DbBackend::Postgres, - r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, - vec![1u64.into()] - ), - ] -); -``` - 4. Service Oriented Quickly build services that join, filter, sort and paginate data in APIs. -```rust -#[get("/?&")] -async fn list( - conn: Connection, - page: Option, - per_page: Option, -) -> Template { - // Set page number and items per page - let page = page.unwrap_or(1); - let per_page = per_page.unwrap_or(10); - - // Setup paginator - let paginator = Post::find() - .order_by_asc(post::Column::Id) - .paginate(&conn, per_page); - let num_pages = paginator.num_pages().await.unwrap(); - - // Fetch paginated posts - let posts = paginator - .fetch_page(page - 1) - .await - .expect("could not retrieve posts"); - - Template::render( - "index", - context! { - page: page, - per_page: per_page, - posts: posts, - num_pages: num_pages, - }, - ) -} -``` - ## A quick taste of SeaORM ### Entity diff --git a/src/docs.rs b/src/docs.rs new file mode 100644 index 00000000..bab054ef --- /dev/null +++ b/src/docs.rs @@ -0,0 +1,166 @@ +//! 1. Async +//! +//! Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. +//! +//! ``` +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*, DatabaseConnection, DbBackend, MockDatabase, Transaction, IntoMockRow}; +//! # let db = MockDatabase::new(DbBackend::Postgres) +//! # .append_query_results(vec![ +//! # vec![cake::Model { +//! # id: 1, +//! # name: "New York Cheese".to_owned(), +//! # } +//! # .into_mock_row()], +//! # vec![fruit::Model { +//! # id: 1, +//! # name: "Apple".to_owned(), +//! # cake_id: Some(1), +//! # } +//! # .into_mock_row()], +//! # ]) +//! # .into_connection(); +//! # let _: Result<(), DbErr> = smol::block_on(async { +//! // execute multiple queries in parallel +//! let cakes_and_fruits: (Vec, Vec) = +//! futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?; +//! # assert_eq!( +//! # cakes_and_fruits, +//! # ( +//! # vec![cake::Model { +//! # id: 1, +//! # name: "New York Cheese".to_owned(), +//! # }], +//! # vec![fruit::Model { +//! # id: 1, +//! # name: "Apple".to_owned(), +//! # cake_id: Some(1), +//! # }] +//! # ) +//! # ); +//! # assert_eq!( +//! # db.into_transaction_log(), +//! # vec![ +//! # Transaction::from_sql_and_values( +//! # DbBackend::Postgres, +//! # r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, +//! # vec![] +//! # ), +//! # Transaction::from_sql_and_values( +//! # DbBackend::Postgres, +//! # r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#, +//! # vec![] +//! # ), +//! # ] +//! # ); +//! # Ok(()) +//! # }); +//! ``` +//! +//! 2. Dynamic +//! +//! Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. +//! +//! ``` +//! # use sea_query::Query; +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: DbConn) -> Result<(), DbErr> { +//! // build subquery with ease +//! let cakes_with_filling: Vec = cake::Entity::find() +//! .filter( +//! Condition::any().add( +//! cake::Column::Id.in_subquery( +//! Query::select() +//! .column(cake_filling::Column::CakeId) +//! .from(cake_filling::Entity) +//! .to_owned(), +//! ), +//! ), +//! ) +//! .all(&db) +//! .await?; +//! +//! # Ok(()) +//! # } +//! ``` +//! +//! 3. Testable +//! +//! Use mock connections to write unit tests for your logic. +//! +//! ``` +//! # use sea_orm::{error::*, entity::*, query::*, tests_cfg::*, DbConn, MockDatabase, Transaction, DbBackend}; +//! # async fn function(db: DbConn) -> Result<(), DbErr> { +//! // Setup mock connection +//! let db = MockDatabase::new(DbBackend::Postgres) +//! .append_query_results(vec![ +//! vec![ +//! cake::Model { +//! id: 1, +//! name: "New York Cheese".to_owned(), +//! }, +//! ], +//! ]) +//! .into_connection(); +//! +//! // Perform your application logic +//! assert_eq!( +//! cake::Entity::find().one(&db).await?, +//! Some(cake::Model { +//! id: 1, +//! name: "New York Cheese".to_owned(), +//! }) +//! ); +//! +//! // Compare it against the expected transaction log +//! assert_eq!( +//! db.into_transaction_log(), +//! vec![ +//! Transaction::from_sql_and_values( +//! DbBackend::Postgres, +//! r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, +//! vec![1u64.into()] +//! ), +//! ] +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! 4. Service Oriented +//! +//! Quickly build services that join, filter, sort and paginate data in APIs. +//! +//! ```ignore +//! #[get("/?&")] +//! async fn list( +//! conn: Connection, +//! page: Option, +//! per_page: Option, +//! ) -> Template { +//! // Set page number and items per page +//! let page = page.unwrap_or(1); +//! let per_page = per_page.unwrap_or(10); +//! +//! // Setup paginator +//! let paginator = Post::find() +//! .order_by_asc(post::Column::Id) +//! .paginate(&conn, per_page); +//! let num_pages = paginator.num_pages().await.unwrap(); +//! +//! // Fetch paginated posts +//! let posts = paginator +//! .fetch_page(page - 1) +//! .await +//! .expect("could not retrieve posts"); +//! +//! Template::render( +//! "index", +//! context! { +//! page: page, +//! per_page: per_page, +//! posts: posts, +//! num_pages: num_pages, +//! }, +//! ) +//! } +//! ``` \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 734663a4..910044a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,169 +39,18 @@ //! //! Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. //! -//! ``` -//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*, DatabaseConnection, DbBackend, MockDatabase, Transaction, IntoMockRow}; -//! # let db = MockDatabase::new(DbBackend::Postgres) -//! # .append_query_results(vec![ -//! # vec![cake::Model { -//! # id: 1, -//! # name: "New York Cheese".to_owned(), -//! # } -//! # .into_mock_row()], -//! # vec![fruit::Model { -//! # id: 1, -//! # name: "Apple".to_owned(), -//! # cake_id: Some(1), -//! # } -//! # .into_mock_row()], -//! # ]) -//! # .into_connection(); -//! # let _: Result<(), DbErr> = smol::block_on(async { -//! // execute multiple queries in parallel -//! let cakes_and_fruits: (Vec, Vec) = -//! futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?; -//! # assert_eq!( -//! # cakes_and_fruits, -//! # ( -//! # vec![cake::Model { -//! # id: 1, -//! # name: "New York Cheese".to_owned(), -//! # }], -//! # vec![fruit::Model { -//! # id: 1, -//! # name: "Apple".to_owned(), -//! # cake_id: Some(1), -//! # }] -//! # ) -//! # ); -//! # assert_eq!( -//! # db.into_transaction_log(), -//! # vec![ -//! # Transaction::from_sql_and_values( -//! # DbBackend::Postgres, -//! # r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, -//! # vec![] -//! # ), -//! # Transaction::from_sql_and_values( -//! # DbBackend::Postgres, -//! # r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#, -//! # vec![] -//! # ), -//! # ] -//! # ); -//! # Ok(()) -//! # }); -//! ``` -//! //! 2. Dynamic //! //! Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. //! -//! ``` -//! # use sea_query::Query; -//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; -//! # async fn function(db: DbConn) -> Result<(), DbErr> { -//! // build subquery with ease -//! let cakes_with_filling: Vec = cake::Entity::find() -//! .filter( -//! Condition::any().add( -//! cake::Column::Id.in_subquery( -//! Query::select() -//! .column(cake_filling::Column::CakeId) -//! .from(cake_filling::Entity) -//! .to_owned(), -//! ), -//! ), -//! ) -//! .all(&db) -//! .await?; -//! -//! # Ok(()) -//! # } -//! ``` -//! //! 3. Testable //! //! Use mock connections to write unit tests for your logic. //! -//! ``` -//! # use sea_orm::{error::*, entity::*, query::*, tests_cfg::*, DbConn, MockDatabase, Transaction, DbBackend}; -//! # async fn function(db: DbConn) -> Result<(), DbErr> { -//! // Setup mock connection -//! let db = MockDatabase::new(DbBackend::Postgres) -//! .append_query_results(vec![ -//! vec![ -//! cake::Model { -//! id: 1, -//! name: "New York Cheese".to_owned(), -//! }, -//! ], -//! ]) -//! .into_connection(); -//! -//! // Perform your application logic -//! assert_eq!( -//! cake::Entity::find().one(&db).await?, -//! Some(cake::Model { -//! id: 1, -//! name: "New York Cheese".to_owned(), -//! }) -//! ); -//! -//! // Compare it against the expected transaction log -//! assert_eq!( -//! db.into_transaction_log(), -//! vec![ -//! Transaction::from_sql_and_values( -//! DbBackend::Postgres, -//! r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, -//! vec![1u64.into()] -//! ), -//! ] -//! ); -//! # Ok(()) -//! # } -//! ``` -//! //! 4. Service Oriented //! //! Quickly build services that join, filter, sort and paginate data in APIs. //! -//! ```ignore -//! #[get("/?&")] -//! async fn list( -//! conn: Connection, -//! page: Option, -//! per_page: Option, -//! ) -> Template { -//! // Set page number and items per page -//! let page = page.unwrap_or(1); -//! let per_page = per_page.unwrap_or(10); -//! -//! // Setup paginator -//! let paginator = Post::find() -//! .order_by_asc(post::Column::Id) -//! .paginate(&conn, per_page); -//! let num_pages = paginator.num_pages().await.unwrap(); -//! -//! // Fetch paginated posts -//! let posts = paginator -//! .fetch_page(page - 1) -//! .await -//! .expect("could not retrieve posts"); -//! -//! Template::render( -//! "index", -//! context! { -//! page: page, -//! per_page: per_page, -//! posts: posts, -//! num_pages: num_pages, -//! }, -//! ) -//! } -//! ``` -//! //! ## A quick taste of SeaORM //! //! ### Entity @@ -424,6 +273,7 @@ pub mod query; pub mod schema; #[doc(hidden)] pub mod tests_cfg; +mod docs; mod util; pub use database::*; From 5c0a5be9adb545576367f63972a8cf5b831aaf8e Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 2 Oct 2021 14:20:49 +0800 Subject: [PATCH 27/29] Readme --- examples/actix4_example/README.md | 7 +++++++ examples/actix_example/README.md | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/examples/actix4_example/README.md b/examples/actix4_example/README.md index ffd2830b..9eb73ea6 100644 --- a/examples/actix4_example/README.md +++ b/examples/actix4_example/README.md @@ -1,5 +1,12 @@ # Actix 4 Beta with SeaORM example app +Edit `Cargo.toml` to use `sqlx-mysql` or `sqlx-postgres`. + +```toml +[features] +default = ["sqlx-$DATABASE"] +``` + Edit `.env` to point to your database. Run server with auto-reloading: diff --git a/examples/actix_example/README.md b/examples/actix_example/README.md index 3785c036..d1ba90bf 100644 --- a/examples/actix_example/README.md +++ b/examples/actix_example/README.md @@ -1,10 +1,17 @@ # Actix with SeaORM example app +Edit `Cargo.toml` to use `sqlx-mysql` or `sqlx-postgres`. + +```toml +[features] +default = ["sqlx-$DATABASE"] +``` + Edit `.env` to point to your database. Run server with auto-reloading: ```bash cargo install systemfd -systemfd --no-pid -s http::5000 -- cargo watch -x run +systemfd --no-pid -s http::8000 -- cargo watch -x run ``` From 7274f5c6241079ed79e5b8fd0cb64e9c86641ac4 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 2 Oct 2021 15:16:43 +0800 Subject: [PATCH 28/29] Fix test failure --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 53842ae7..6b11432a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -282,7 +282,7 @@ jobs: matrix: version: [10.6] runtime: [async-std, actix, tokio] - tls: [rustls] + tls: [native-tls] services: mysql: image: mariadb:${{ matrix.version }} From ba96917a18be72fb32a0609b0643ce827175c9c5 Mon Sep 17 00:00:00 2001 From: Jordy Ruiter Date: Sun, 3 Oct 2021 15:45:07 +0900 Subject: [PATCH 29/29] Add tables option to CLI generate entity --- sea-orm-cli/src/cli.rs | 9 +++++++++ sea-orm-cli/src/main.rs | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index 100c747c..8fe9c4a4 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -41,6 +41,15 @@ pub fn build_cli() -> App<'static, 'static> { .help("Generate entity file for hidden tables (i.e. table name starts with an underscore)") .takes_value(false), ) + .arg( + Arg::with_name("TABLES") + .long("tables") + .short("t") + .use_delimiter(true) + .help("Generate entity file for specified tables only (comma seperated)") + .takes_value(true) + .conflicts_with("INCLUDE_HIDDEN_TABLES"), + ) .arg( Arg::with_name("EXPANDED_FORMAT") .long("expanded-format") diff --git a/sea-orm-cli/src/main.rs b/sea-orm-cli/src/main.rs index 527e1264..c318a510 100644 --- a/sea-orm-cli/src/main.rs +++ b/sea-orm-cli/src/main.rs @@ -26,7 +26,15 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box>(); let expanded_format = args.is_present("EXPANDED_FORMAT"); + let filter_tables = |table: &str| -> bool { + if tables.len() > 0 { + return tables.contains(&table); + } + + true + }; let filter_hidden_tables = |table: &str| -> bool { if include_hidden_tables { true @@ -53,6 +61,7 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box) -> Result<(), Box