From b876009b22dc4c8067b7846de1a6928a36d2168c Mon Sep 17 00:00:00 2001 From: Huliiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:01:57 +0800 Subject: [PATCH 1/6] Init --- SeaORM/docusaurus.config.js | 8 + SeaORM/i18n/zh-Hans/code.json | 456 ++++++++++++++++++ .../current.json | 58 +++ .../current/01-basic-schema.md | 9 + .../current/01-create-table.md | 125 +++++ .../current/01-custom-select.md | 232 +++++++++ .../current/01-database-and-async-runtime.md | 63 +++ .../current/01-index.md | 149 ++++++ .../current/01-introduction/01-orm.md | 13 + .../current/01-introduction/02-async.md | 11 + .../current/01-introduction/03-sea-orm.md | 15 + .../current/01-introduction/04-tutorial.md | 19 + .../current/01-introduction/_category_.json | 4 + .../current/01-one-to-one.md | 118 +++++ .../current/01-orm.md | 13 + .../current/01-sea-orm-cli.md | 73 +++ .../current/01-sea-orm-pro-intro.md | 13 + .../current/01-seaography-intro.md | 9 + .../current/01-setting-up-migration.md | 180 +++++++ .../current/01-testing.md | 64 +++ .../current/01-trait-and-type.md | 66 +++ .../current/01-whats-next.md | 7 + .../current/02-async.md | 11 + .../current/02-conditional-expression.md | 95 ++++ .../current/02-connection.md | 103 ++++ .../current/02-create-enum.md | 221 +++++++++ .../current/02-derive-macro.md | 53 ++ .../current/02-entity-format.md | 262 ++++++++++ .../current/02-getting-started.md | 183 +++++++ .../01-database-and-async-runtime.md | 63 +++ .../02-install-and-config/02-connection.md | 103 ++++ .../02-install-and-config/03-debug-log.md | 39 ++ .../02-install-and-config/_category_.json | 4 + .../current/02-mock.md | 218 +++++++++ .../current/02-one-to-many.md | 96 ++++ .../current/02-select.md | 164 +++++++ .../current/02-writing-migration.md | 382 +++++++++++++++ .../current/03-active-model.md | 127 +++++ .../current/03-aggregate-function.md | 63 +++ .../current/03-column-types.md | 150 ++++++ .../current/03-create-index.md | 52 ++ .../current/03-debug-log.md | 39 ++ .../current/03-diesel.md | 41 ++ .../current/03-many-to-many.md | 136 ++++++ .../03-migration/01-setting-up-migration.md | 180 +++++++ .../03-migration/02-writing-migration.md | 382 +++++++++++++++ .../03-migration/03-running-migration.md | 113 +++++ .../current/03-migration/04-seeding-data.md | 86 ++++ .../current/03-migration/_category_.json | 5 + .../current/03-running-migration.md | 113 +++++ .../current/03-sea-orm.md | 15 + .../current/03-sqlite.md | 83 ++++ .../current/04-advanced-joins.md | 360 ++++++++++++++ .../current/04-architecture.md | 43 ++ .../current/04-complex-relations.md | 194 ++++++++ .../current/04-enumeration.md | 299 ++++++++++++ .../04-generate-entity/01-sea-orm-cli.md | 73 +++ .../04-generate-entity/02-entity-format.md | 262 ++++++++++ .../04-generate-entity/03-column-types.md | 150 ++++++ .../04-generate-entity/04-enumeration.md | 299 ++++++++++++ .../current/04-generate-entity/05-newtype.md | 311 ++++++++++++ .../04-generate-entity/_category_.json | 4 + .../current/04-insert.md | 257 ++++++++++ .../current/04-seeding-data.md | 86 ++++ .../current/04-tutorial.md | 19 + .../current/05-basic-crud/01-basic-schema.md | 9 + .../current/05-basic-crud/02-select.md | 164 +++++++ .../current/05-basic-crud/03-active-model.md | 127 +++++ .../current/05-basic-crud/04-insert.md | 257 ++++++++++ .../current/05-basic-crud/05-update.md | 77 +++ .../current/05-basic-crud/06-save.md | 32 ++ .../current/05-basic-crud/07-delete.md | 66 +++ .../current/05-basic-crud/08-json.md | 131 +++++ .../current/05-basic-crud/09-raw-sql.md | 138 ++++++ .../current/05-basic-crud/_category_.json | 4 + .../current/05-expanded-entity-format.md | 220 +++++++++ .../current/05-newtype.md | 311 ++++++++++++ .../current/05-subquery.md | 31 ++ .../current/05-update.md | 77 +++ .../current/06-custom-join-condition.md | 240 +++++++++ .../current/06-relation/01-one-to-one.md | 118 +++++ .../current/06-relation/02-one-to-many.md | 96 ++++ .../current/06-relation/03-many-to-many.md | 136 ++++++ .../06-relation/04-complex-relations.md | 194 ++++++++ .../06-relation/06-custom-join-condition.md | 240 +++++++++ .../current/06-relation/07-data-loader.md | 59 +++ .../current/06-relation/08-bakery-schema.md | 5 + .../current/06-relation/09-nested-selects.md | 378 +++++++++++++++ .../current/06-relation/_category_.json | 4 + .../current/06-save.md | 32 ++ .../current/06-transaction.md | 147 ++++++ .../current/07-data-loader.md | 59 +++ .../current/07-delete.md | 66 +++ .../current/07-streaming.md | 36 ++ .../current/07-write-test/01-testing.md | 64 +++ .../current/07-write-test/02-mock.md | 218 +++++++++ .../current/07-write-test/03-sqlite.md | 83 ++++ .../current/07-write-test/_category_.json | 4 + .../08-advanced-query/01-custom-select.md | 232 +++++++++ .../02-conditional-expression.md | 95 ++++ .../03-aggregate-function.md | 63 +++ .../08-advanced-query/04-advanced-joins.md | 360 ++++++++++++++ .../current/08-advanced-query/05-subquery.md | 31 ++ .../08-advanced-query/06-transaction.md | 147 ++++++ .../current/08-advanced-query/07-streaming.md | 36 ++ .../08-custom-active-model.md | 91 ++++ .../08-advanced-query/09-error-handling.md | 53 ++ .../current/08-advanced-query/_category_.json | 4 + .../current/08-bakery-schema.md | 5 + .../current/08-custom-active-model.md | 91 ++++ .../current/08-json.md | 131 +++++ .../current/09-error-handling.md | 53 ++ .../current/09-nested-selects.md | 378 +++++++++++++++ .../current/09-raw-sql.md | 138 ++++++ .../09-schema-statement/01-create-table.md | 125 +++++ .../09-schema-statement/02-create-enum.md | 221 +++++++++ .../09-schema-statement/03-create-index.md | 52 ++ .../09-schema-statement/_category_.json | 4 + .../10-graph-ql/01-seaography-intro.md | 9 + .../current/10-graph-ql/02-getting-started.md | 183 +++++++ .../current/10-graph-ql/_category_.json | 4 + .../11-sea-orm-pro/01-sea-orm-pro-intro.md | 13 + .../11-sea-orm-pro/02-getting-started.md | 5 + .../current/11-sea-orm-pro/_category_.json | 4 + .../12-internal-design/01-trait-and-type.md | 66 +++ .../12-internal-design/02-derive-macro.md | 53 ++ .../current/12-internal-design/03-diesel.md | 41 ++ .../12-internal-design/04-architecture.md | 43 ++ .../05-expanded-entity-format.md | 220 +++++++++ .../12-internal-design/_category_.json | 4 + .../current/13-whats-next/01-whats-next.md | 7 + .../current/13-whats-next/_category_.json | 4 + .../current/_category_.json | 4 + .../version-0.10.x.json | 46 ++ .../version-0.11.x.json | 46 ++ .../version-0.12.x.json | 54 +++ .../version-0.4.x.json | 42 ++ .../version-0.5.x.json | 42 ++ .../version-0.6.x.json | 46 ++ .../version-0.7.x.json | 46 ++ .../version-0.8.x.json | 46 ++ .../version-0.9.x.json | 46 ++ .../version-1.0.x.json | 54 +++ .../version-1.1.x.json | 58 +++ .../about-us.md | 11 + .../docusaurus-theme-classic/footer.json | 58 +++ .../docusaurus-theme-classic/navbar.json | 34 ++ 147 files changed, 15066 insertions(+) create mode 100644 SeaORM/i18n/zh-Hans/code.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-basic-schema.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-create-table.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-custom-select.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-database-and-async-runtime.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-index.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/01-orm.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/02-async.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/03-sea-orm.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/04-tutorial.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-one-to-one.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-orm.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-sea-orm-cli.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-sea-orm-pro-intro.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-seaography-intro.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-setting-up-migration.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-testing.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-trait-and-type.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-whats-next.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-async.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-conditional-expression.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-connection.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-create-enum.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-derive-macro.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-entity-format.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-getting-started.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/01-database-and-async-runtime.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/02-connection.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/03-debug-log.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-mock.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-one-to-many.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-select.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-writing-migration.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-active-model.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-aggregate-function.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-column-types.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-create-index.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-debug-log.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-diesel.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-many-to-many.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/01-setting-up-migration.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/02-writing-migration.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/03-running-migration.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/04-seeding-data.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-running-migration.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-sea-orm.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-sqlite.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-advanced-joins.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-architecture.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-complex-relations.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-enumeration.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/01-sea-orm-cli.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/02-entity-format.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/03-column-types.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/04-enumeration.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/05-newtype.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-insert.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-seeding-data.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-tutorial.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/01-basic-schema.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/02-select.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/03-active-model.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/04-insert.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/05-update.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/06-save.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/07-delete.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/08-json.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/09-raw-sql.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-expanded-entity-format.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-newtype.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-subquery.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-update.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-custom-join-condition.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/01-one-to-one.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/02-one-to-many.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/03-many-to-many.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/04-complex-relations.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/06-custom-join-condition.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/07-data-loader.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/08-bakery-schema.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/09-nested-selects.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-save.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-transaction.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/07-data-loader.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/07-delete.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/07-streaming.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/07-write-test/01-testing.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/07-write-test/02-mock.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/07-write-test/03-sqlite.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/07-write-test/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/01-custom-select.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/02-conditional-expression.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/03-aggregate-function.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/04-advanced-joins.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/05-subquery.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/06-transaction.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/07-streaming.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/08-custom-active-model.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/09-error-handling.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-advanced-query/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-bakery-schema.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-custom-active-model.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/08-json.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/09-error-handling.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/09-nested-selects.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/09-raw-sql.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/09-schema-statement/01-create-table.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/09-schema-statement/02-create-enum.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/09-schema-statement/03-create-index.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/09-schema-statement/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/10-graph-ql/01-seaography-intro.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/10-graph-ql/02-getting-started.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/10-graph-ql/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/11-sea-orm-pro/01-sea-orm-pro-intro.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/11-sea-orm-pro/02-getting-started.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/11-sea-orm-pro/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/12-internal-design/01-trait-and-type.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/12-internal-design/02-derive-macro.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/12-internal-design/03-diesel.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/12-internal-design/04-architecture.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/12-internal-design/05-expanded-entity-format.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/12-internal-design/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/13-whats-next/01-whats-next.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/13-whats-next/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/_category_.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.10.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.11.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.12.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.4.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.5.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.6.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.7.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.8.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.9.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-1.0.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/version-1.1.x.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-plugin-content-pages/about-us.md create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-theme-classic/footer.json create mode 100644 SeaORM/i18n/zh-Hans/docusaurus-theme-classic/navbar.json diff --git a/SeaORM/docusaurus.config.js b/SeaORM/docusaurus.config.js index e1fcade6241..a1e1108d9f4 100644 --- a/SeaORM/docusaurus.config.js +++ b/SeaORM/docusaurus.config.js @@ -28,6 +28,10 @@ module.exports = { organizationName: 'SeaQL', projectName: 'sea-orm', trailingSlash: true, + i18n: { + defaultLocale: 'en', + locales: ['en', 'zh-Hans'], + }, themeConfig: { colorMode: { respectPrefersColorScheme: true, @@ -80,6 +84,10 @@ module.exports = { position: 'right', dropdownActiveClassDisabled: true, }, + { + type: 'localeDropdown', + position: 'right', + }, ], }, footer: { diff --git a/SeaORM/i18n/zh-Hans/code.json b/SeaORM/i18n/zh-Hans/code.json new file mode 100644 index 00000000000..85bb87b3029 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/code.json @@ -0,0 +1,456 @@ +{ + "theme.ErrorPageContent.title": { + "message": "页面已崩溃。", + "description": "The title of the fallback page when the page crashed" + }, + "theme.BackToTopButton.buttonAriaLabel": { + "message": "回到顶部", + "description": "The ARIA label for the back to top button" + }, + "theme.blog.archive.title": { + "message": "历史博文", + "description": "The page & hero title of the blog archive page" + }, + "theme.blog.archive.description": { + "message": "历史博文", + "description": "The page & hero description of the blog archive page" + }, + "theme.blog.paginator.navAriaLabel": { + "message": "博文列表分页导航", + "description": "The ARIA label for the blog pagination" + }, + "theme.blog.paginator.newerEntries": { + "message": "较新的博文", + "description": "The label used to navigate to the newer blog posts page (previous page)" + }, + "theme.blog.paginator.olderEntries": { + "message": "较旧的博文", + "description": "The label used to navigate to the older blog posts page (next page)" + }, + "theme.blog.post.paginator.navAriaLabel": { + "message": "博文分页导航", + "description": "The ARIA label for the blog posts pagination" + }, + "theme.blog.post.paginator.newerPost": { + "message": "较新一篇", + "description": "The blog post button label to navigate to the newer/previous post" + }, + "theme.blog.post.paginator.olderPost": { + "message": "较旧一篇", + "description": "The blog post button label to navigate to the older/next post" + }, + "theme.tags.tagsPageLink": { + "message": "查看所有标签", + "description": "The label of the link targeting the tag list page" + }, + "theme.colorToggle.ariaLabel.mode.system": { + "message": "system mode", + "description": "The name for the system color mode" + }, + "theme.colorToggle.ariaLabel.mode.light": { + "message": "浅色模式", + "description": "The name for the light color mode" + }, + "theme.colorToggle.ariaLabel.mode.dark": { + "message": "暗黑模式", + "description": "The name for the dark color mode" + }, + "theme.colorToggle.ariaLabel": { + "message": "切换浅色/暗黑模式(当前为{mode})", + "description": "The ARIA label for the color mode toggle" + }, + "theme.docs.breadcrumbs.navAriaLabel": { + "message": "页面路径", + "description": "The ARIA label for the breadcrumbs" + }, + "theme.docs.DocCard.categoryDescription.plurals": { + "message": "{count} 个项目", + "description": "The default description for a category card in the generated index about how many items this category includes" + }, + "theme.docs.paginator.navAriaLabel": { + "message": "文件选项卡", + "description": "The ARIA label for the docs pagination" + }, + "theme.docs.paginator.previous": { + "message": "上一页", + "description": "The label used to navigate to the previous doc" + }, + "theme.docs.paginator.next": { + "message": "下一页", + "description": "The label used to navigate to the next doc" + }, + "theme.docs.tagDocListPageTitle.nDocsTagged": { + "message": "{count} 篇文档带有标签", + "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.docs.tagDocListPageTitle": { + "message": "{nDocsTagged}「{tagName}」", + "description": "The title of the page for a docs tag" + }, + "theme.docs.versions.unreleasedVersionLabel": { + "message": "此为 {siteTitle} {versionLabel} 版尚未发行的文档。", + "description": "The label used to tell the user that he's browsing an unreleased doc version" + }, + "theme.docs.versions.unmaintainedVersionLabel": { + "message": "此为 {siteTitle} {versionLabel} 版的文档,现已不再积极维护。", + "description": "The label used to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionSuggestionLabel": { + "message": "最新的文档请参阅 {latestVersionLink} ({versionLabel})。", + "description": "The label used to tell the user to check the latest version" + }, + "theme.docs.versions.latestVersionLinkLabel": { + "message": "最新版本", + "description": "The label used for the latest version suggestion link label" + }, + "theme.docs.versionBadge.label": { + "message": "版本:{versionLabel}" + }, + "theme.common.editThisPage": { + "message": "编辑此页", + "description": "The link label to edit the current page" + }, + "theme.common.headingLinkTitle": { + "message": "{heading}的直接链接", + "description": "Title for link to heading" + }, + "theme.lastUpdated.atDate": { + "message": "于 {date} ", + "description": "The words used to describe on which date a page has been last updated" + }, + "theme.lastUpdated.byUser": { + "message": "由 {user} ", + "description": "The words used to describe by who the page has been last updated" + }, + "theme.lastUpdated.lastUpdatedAtBy": { + "message": "最后{byUser}{atDate}更新", + "description": "The sentence used to display when a page has been last updated, and by who" + }, + "theme.navbar.mobileVersionsDropdown.label": { + "message": "选择版本", + "description": "The label for the navbar versions dropdown on mobile view" + }, + "theme.NotFound.title": { + "message": "找不到页面", + "description": "The title of the 404 page" + }, + "theme.tags.tagsListLabel": { + "message": "标签:", + "description": "The label alongside a tag list" + }, + "theme.admonition.caution": { + "message": "警告", + "description": "The default label used for the Caution admonition (:::caution)" + }, + "theme.admonition.danger": { + "message": "危险", + "description": "The default label used for the Danger admonition (:::danger)" + }, + "theme.admonition.info": { + "message": "信息", + "description": "The default label used for the Info admonition (:::info)" + }, + "theme.admonition.note": { + "message": "备注", + "description": "The default label used for the Note admonition (:::note)" + }, + "theme.admonition.tip": { + "message": "提示", + "description": "The default label used for the Tip admonition (:::tip)" + }, + "theme.admonition.warning": { + "message": "注意", + "description": "The default label used for the Warning admonition (:::warning)" + }, + "theme.AnnouncementBar.closeButtonAriaLabel": { + "message": "关闭", + "description": "The ARIA label for close button of announcement bar" + }, + "theme.blog.sidebar.navAriaLabel": { + "message": "最近博文导航", + "description": "The ARIA label for recent posts in the blog sidebar" + }, + "theme.DocSidebarItem.expandCategoryAriaLabel": { + "message": "展开侧边栏分类 '{label}'", + "description": "The ARIA label to expand the sidebar category" + }, + "theme.DocSidebarItem.collapseCategoryAriaLabel": { + "message": "折叠侧边栏分类 '{label}'", + "description": "The ARIA label to collapse the sidebar category" + }, + "theme.NavBar.navAriaLabel": { + "message": "主导航", + "description": "The ARIA label for the main navigation" + }, + "theme.navbar.mobileLanguageDropdown.label": { + "message": "选择语言", + "description": "The label for the mobile language switcher dropdown" + }, + "theme.NotFound.p1": { + "message": "我们找不到您要找的页面。", + "description": "The first paragraph of the 404 page" + }, + "theme.NotFound.p2": { + "message": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。", + "description": "The 2nd paragraph of the 404 page" + }, + "theme.TOCCollapsible.toggleButtonLabel": { + "message": "本页总览", + "description": "The label used by the button on the collapsible TOC component" + }, + "theme.blog.post.readMore": { + "message": "阅读更多", + "description": "The label used in blog post item excerpts to link to full blog posts" + }, + "theme.blog.post.readMoreLabel": { + "message": "阅读 {title} 的全文", + "description": "The ARIA label for the link to full blog posts from excerpts" + }, + "theme.blog.post.readingTime.plurals": { + "message": "阅读需 {readingTime} 分钟", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.CodeBlock.copy": { + "message": "复制", + "description": "The copy button label on code blocks" + }, + "theme.CodeBlock.copied": { + "message": "复制成功", + "description": "The copied button label on code blocks" + }, + "theme.CodeBlock.copyButtonAriaLabel": { + "message": "复制代码到剪贴板", + "description": "The ARIA label for copy code blocks button" + }, + "theme.CodeBlock.wordWrapToggle": { + "message": "切换自动换行", + "description": "The title attribute for toggle word wrapping button of code block lines" + }, + "theme.docs.breadcrumbs.home": { + "message": "主页面", + "description": "The ARIA label for the home page in the breadcrumbs" + }, + "theme.docs.sidebar.collapseButtonTitle": { + "message": "收起侧边栏", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.collapseButtonAriaLabel": { + "message": "收起侧边栏", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.navAriaLabel": { + "message": "文档侧边栏", + "description": "The ARIA label for the sidebar navigation" + }, + "theme.docs.sidebar.closeSidebarButtonAriaLabel": { + "message": "关闭导航栏", + "description": "The ARIA label for close button of mobile sidebar" + }, + "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { + "message": "← 回到主菜单", + "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" + }, + "theme.docs.sidebar.toggleSidebarButtonAriaLabel": { + "message": "切换导航栏", + "description": "The ARIA label for hamburger menu button of mobile navigation" + }, + "theme.navbar.mobileDropdown.collapseButton.expandAriaLabel": { + "message": "Expand the dropdown", + "description": "The ARIA label of the button to expand the mobile dropdown navbar item" + }, + "theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": { + "message": "Collapse the dropdown", + "description": "The ARIA label of the button to collapse the mobile dropdown navbar item" + }, + "theme.docs.sidebar.expandButtonTitle": { + "message": "展开侧边栏", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.sidebar.expandButtonAriaLabel": { + "message": "展开侧边栏", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.SearchBar.seeAll": { + "message": "查看全部 {count} 个结果" + }, + "theme.SearchPage.documentsFound.plurals": { + "message": "找到 {count} 份文件", + "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.SearchPage.existingResultsTitle": { + "message": "「{query}」的搜索结果", + "description": "The search page title for non-empty query" + }, + "theme.SearchPage.emptyResultsTitle": { + "message": "在文档中搜索", + "description": "The search page title for empty query" + }, + "theme.SearchPage.inputPlaceholder": { + "message": "在此输入搜索字词", + "description": "The placeholder for search page input" + }, + "theme.SearchPage.inputLabel": { + "message": "搜索", + "description": "The ARIA label for search page input" + }, + "theme.SearchPage.algoliaLabel": { + "message": "通过 Algolia 搜索", + "description": "The ARIA label for Algolia mention" + }, + "theme.SearchPage.noResultsText": { + "message": "未找到任何结果", + "description": "The paragraph for empty search result" + }, + "theme.SearchPage.fetchingNewResults": { + "message": "正在获取新的搜索结果...", + "description": "The paragraph for fetching new search results" + }, + "theme.SearchBar.label": { + "message": "搜索", + "description": "The ARIA label and placeholder for search button" + }, + "theme.SearchModal.searchBox.resetButtonTitle": { + "message": "清除查询", + "description": "The label and ARIA label for search box reset button" + }, + "theme.SearchModal.searchBox.cancelButtonText": { + "message": "取消", + "description": "The label and ARIA label for search box cancel button" + }, + "theme.SearchModal.startScreen.recentSearchesTitle": { + "message": "最近搜索", + "description": "The title for recent searches" + }, + "theme.SearchModal.startScreen.noRecentSearchesText": { + "message": "没有最近搜索", + "description": "The text when no recent searches" + }, + "theme.SearchModal.startScreen.saveRecentSearchButtonTitle": { + "message": "保存这个搜索", + "description": "The label for save recent search button" + }, + "theme.SearchModal.startScreen.removeRecentSearchButtonTitle": { + "message": "从历史记录中删除这个搜索", + "description": "The label for remove recent search button" + }, + "theme.SearchModal.startScreen.favoriteSearchesTitle": { + "message": "收藏", + "description": "The title for favorite searches" + }, + "theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": { + "message": "从收藏列表中删除这个搜索", + "description": "The label for remove favorite search button" + }, + "theme.SearchModal.errorScreen.titleText": { + "message": "无法获取结果", + "description": "The title for error screen of search modal" + }, + "theme.SearchModal.errorScreen.helpText": { + "message": "你可能需要检查网络连接。", + "description": "The help text for error screen of search modal" + }, + "theme.SearchModal.footer.selectText": { + "message": "选中", + "description": "The explanatory text of the action for the enter key" + }, + "theme.SearchModal.footer.selectKeyAriaLabel": { + "message": "Enter 键", + "description": "The ARIA label for the Enter key button that makes the selection" + }, + "theme.SearchModal.footer.navigateText": { + "message": "导航", + "description": "The explanatory text of the action for the Arrow up and Arrow down key" + }, + "theme.SearchModal.footer.navigateUpKeyAriaLabel": { + "message": "向上键", + "description": "The ARIA label for the Arrow up key button that makes the navigation" + }, + "theme.SearchModal.footer.navigateDownKeyAriaLabel": { + "message": "向下键", + "description": "The ARIA label for the Arrow down key button that makes the navigation" + }, + "theme.SearchModal.footer.closeText": { + "message": "关闭", + "description": "The explanatory text of the action for Escape key" + }, + "theme.SearchModal.footer.closeKeyAriaLabel": { + "message": "Esc 键", + "description": "The ARIA label for the Escape key button that close the modal" + }, + "theme.SearchModal.footer.searchByText": { + "message": "搜索提供", + "description": "The text explain that the search is making by Algolia" + }, + "theme.SearchModal.noResultsScreen.noResultsText": { + "message": "没有结果:", + "description": "The text explains that there are no results for the following search" + }, + "theme.SearchModal.noResultsScreen.suggestedQueryText": { + "message": "试试搜索", + "description": "The text for the suggested query when no results are found for the following search" + }, + "theme.SearchModal.noResultsScreen.reportMissingResultsText": { + "message": "认为这个查询应该有结果?", + "description": "The text for the question where the user thinks there are missing results" + }, + "theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": { + "message": "请告知我们。", + "description": "The text for the link to report missing results" + }, + "theme.SearchModal.placeholder": { + "message": "搜索文档", + "description": "The placeholder of the input of the DocSearch pop-up modal" + }, + "theme.blog.post.plurals": { + "message": "{count} 篇博文", + "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.blog.tagTitle": { + "message": "{nPosts} 含有标签「{tagName}」", + "description": "The title of the page for a blog tag" + }, + "theme.blog.author.pageTitle": { + "message": "{authorName} - {nPosts}", + "description": "The title of the page for a blog author" + }, + "theme.blog.authorsList.pageTitle": { + "message": "作者", + "description": "The title of the authors page" + }, + "theme.blog.authorsList.viewAll": { + "message": "查看所有作者", + "description": "The label of the link targeting the blog authors page" + }, + "theme.blog.author.noPosts": { + "message": "该作者尚未撰写任何文章。", + "description": "The text for authors with 0 blog post" + }, + "theme.contentVisibility.unlistedBanner.title": { + "message": "未列出页", + "description": "The unlisted content banner title" + }, + "theme.contentVisibility.unlistedBanner.message": { + "message": "此页面未列出。搜索引擎不会对其索引,只有拥有直接链接的用户才能访问。", + "description": "The unlisted content banner message" + }, + "theme.contentVisibility.draftBanner.title": { + "message": "草稿页", + "description": "The draft content banner title" + }, + "theme.contentVisibility.draftBanner.message": { + "message": "此页面是草稿,仅在开发环境中可见,不会包含在正式版本中。", + "description": "The draft content banner message" + }, + "theme.ErrorPageContent.tryAgain": { + "message": "重试", + "description": "The label of the button to try again rendering when the React error boundary captures an error" + }, + "theme.common.skipToMainContent": { + "message": "跳到主要内容", + "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + }, + "theme.tags.tagsPageTitle": { + "message": "标签", + "description": "The title of the tag list page" + } +} diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json new file mode 100644 index 00000000000..3b38d89397c --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,58 @@ +{ + "version.label": { + "message": "2.0.x 🚧", + "description": "The label for version current" + }, + "sidebar.tutorialSidebar.category.Introduction": { + "message": "Introduction", + "description": "The label for category Introduction in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Installation & Configuration": { + "message": "Installation & Configuration", + "description": "The label for category Installation & Configuration in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Migration": { + "message": "Migration", + "description": "The label for category Migration in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Generating Entities": { + "message": "Generating Entities", + "description": "The label for category Generating Entities in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Basic CRUD": { + "message": "Basic CRUD", + "description": "The label for category Basic CRUD in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Relations": { + "message": "Relations", + "description": "The label for category Relations in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Writing Tests": { + "message": "Writing Tests", + "description": "The label for category Writing Tests in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Advanced Queries": { + "message": "Advanced Queries", + "description": "The label for category Advanced Queries in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Schema Statement": { + "message": "Schema Statement", + "description": "The label for category Schema Statement in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.GraphQL Support": { + "message": "GraphQL Support", + "description": "The label for category GraphQL Support in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Admin Panel": { + "message": "Admin Panel", + "description": "The label for category Admin Panel in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.Internal Design": { + "message": "Internal Design", + "description": "The label for category Internal Design in sidebar tutorialSidebar" + }, + "sidebar.tutorialSidebar.category.What's Next": { + "message": "What's Next", + "description": "The label for category What's Next in sidebar tutorialSidebar" + } +} diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-basic-schema.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-basic-schema.md new file mode 100644 index 00000000000..607b8472a8c --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-basic-schema.md @@ -0,0 +1,9 @@ +# Basic Schema + +We will be using this [basic schema](https://github.com/SeaQL/sea-orm/tree/master/src/tests_cfg) for demonstration: + ++ `cake` one-to-many `fruit` ++ `cake` many-to-many `filling` ++ `cake_filling` is the junction table between `cake` and `filling` + +![Basic Schema](https://raw.githubusercontent.com/SeaQL/sea-orm/master/src/tests_cfg/basic_schema.svg) \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-create-table.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-create-table.md new file mode 100644 index 00000000000..6e1bcedcf8e --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-create-table.md @@ -0,0 +1,125 @@ +# Create Table + +:::tip Rustacean Sticker Pack 🦀 +[Our stickers](https://www.sea-ql.org/sticker-pack/) are made with a premium water-resistant vinyl with a unique matte finish. +Stick them on your laptop, notebook, or any gadget to show off your love for Rust! +::: + +To create tables in database instead of writing [`TableCreateStatement`](https://docs.rs/sea-query/*/sea_query/table/struct.TableCreateStatement.html) manually, you can derive it from `Entity` using [`Schema::create_table_from_entity`](https://docs.rs/sea-orm/*/sea_orm/schema/struct.Schema.html#method.create_table_from_entity). This method will help you create a database table including all the columns and foreign key constraints defined in `Entity`. + +Below we use [`CakeFillingPrice`](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/cake_filling_price.rs) entity to demo its generated SQL statement. You can construct the same statement with [`TableCreateStatement`](https://docs.rs/sea-query/*/sea_query/table/struct.TableCreateStatement.html). + +Note that since version `0.7.0`, [`Schema::create_table_from_entity`](https://docs.rs/sea-orm/*/sea_orm/schema/struct.Schema.html#method.create_table_from_entity) no longer create indexes. If you need to create indexes in database please check [here](09-schema-statement/03-create-index.md) for details. + +```rust +use sea_orm::{sea_query::*, tests_cfg::*, EntityName, Schema}; + +let builder = db.get_database_backend(); +let schema = Schema::new(builder); + +assert_eq!( + builder.build(&schema.create_table_from_entity(CakeFillingPrice)), + builder.build( + &Table::create() + .table(CakeFillingPrice.table_ref()) + .col(integer(cake_filling_price::Column::CakeId)) + .col(integer(cake_filling_price::Column::FillingId)) + .col(decimal(cake_filling_price::Column::Price)) + .primary_key( + Index::create() + .name("pk-cake_filling_price") + .col(cake_filling_price::Column::CakeId) + .col(cake_filling_price::Column::FillingId) + .primary(), + ) + .foreign_key( + ForeignKeyCreateStatement::new() + .name("fk-cake_filling_price-cake_id-filling_id") + .from_tbl(CakeFillingPrice) + .from_col(cake_filling_price::Column::CakeId) + .from_col(cake_filling_price::Column::FillingId) + .to_tbl(CakeFilling) + .to_col(cake_filling::Column::CakeId) + .to_col(cake_filling::Column::FillingId), + ) + .to_owned() + ) +); +``` + +To further illustrate, we will show the SQL statements as string below. + +- PostgreSQL + ```rust + use sea_orm::{tests_cfg::*, DbBackend, Schema, Statement}; + + let db_postgres = DbBackend::Postgres; + let schema = Schema::new(db_postgres); + + assert_eq!( + db_postgres.build(&schema.create_table_from_entity(CakeFillingPrice)), + Statement::from_string( + db_postgres, + [ + r#"CREATE TABLE "public"."cake_filling_price" ("#, + r#""cake_id" integer NOT NULL,"#, + r#""filling_id" integer NOT NULL,"#, + r#""price" decimal NOT NULL,"#, + r#"CONSTRAINT "pk-cake_filling_price" PRIMARY KEY ("cake_id", "filling_id"),"#, + r#"CONSTRAINT "fk-cake_filling_price-cake_id-filling_id" FOREIGN KEY ("cake_id", "filling_id") REFERENCES "cake_filling" ("cake_id", "filling_id")"#, + r#")"#, + ] + .join(" ") + ) + ); + ``` + +- MySQL + ```rust + use sea_orm::{tests_cfg::*, DbBackend, Schema, Statement}; + + let db_mysql = DbBackend::MySql; + let schema = Schema::new(db_mysql); + + assert_eq!( + db_mysql.build(&schema.create_table_from_entity(CakeFillingPrice)), + Statement::from_string( + db_mysql, + [ + "CREATE TABLE `cake_filling_price` (", + "`cake_id` int NOT NULL,", + "`filling_id` int NOT NULL,", + "`price` decimal NOT NULL,", + "PRIMARY KEY `pk-cake_filling_price` (`cake_id`, `filling_id`),", + "CONSTRAINT `fk-cake_filling_price-cake_id-filling_id` FOREIGN KEY (`cake_id`, `filling_id`) REFERENCES `cake_filling` (`cake_id`, `filling_id`)", + ")", + ] + .join(" ") + ) + ); + ``` + +- SQLite + ```rust + use sea_orm::{tests_cfg::*, DbBackend, Schema, Statement}; + + let db_sqlite = DbBackend::Sqlite; + let schema = Schema::new(db_sqlite); + + assert_eq!( + db_sqlite.build(&schema.create_table_from_entity(CakeFillingPrice)), + Statement::from_string( + db_sqlite, + [ + "CREATE TABLE `cake_filling_price` (", + "`cake_id` integer NOT NULL,", + "`filling_id` integer NOT NULL,", + "`price` real NOT NULL,", + "CONSTRAINT `pk-cake_filling_price`PRIMARY KEY (`cake_id`, `filling_id`),", + "FOREIGN KEY (`cake_id`, `filling_id`) REFERENCES `cake_filling` (`cake_id`, `filling_id`)", + ")", + ] + .join(" ") + ) + ); + ``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-custom-select.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-custom-select.md new file mode 100644 index 00000000000..22e0c8e290a --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-custom-select.md @@ -0,0 +1,232 @@ +# Custom Select + +:::caution We need your support! ⭐ +Thank you for using SeaORM. Please star our [GitHub repo](https://github.com/SeaQL/sea-orm)! Your support is vital to the continued development and maintenance of SeaORM. +::: + +By default, SeaORM will select all columns defined in the `Column` enum. You can override the defaults if you wish to select certain columns only. + +```rust +// Selecting all columns +assert_eq!( + cake::Entity::find() + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."id", "cake"."name" FROM "cake""# +); +``` + +## Select Partial Attributes + +Clear the default selection by calling the `select_only` method. Then, you can select some of the attributes or custom expressions afterwards. + +```rust +// Selecting the name column only +assert_eq!( + cake::Entity::find() + .select_only() + .column(cake::Column::Name) + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."name" FROM "cake""# +); +``` + +If you want to select multiple attributes at once, you can supply an array. + +```rust +assert_eq!( + cake::Entity::find() + .select_only() + .columns([cake::Column::Id, cake::Column::Name]) + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."id", "cake"."name" FROM "cake""# +); +``` + +Advanced example: conditionally select all columns except a specific column. + +```rust +assert_eq!( + cake::Entity::find() + .select_only() + .columns(cake::Column::iter().filter(|col| match col { + cake::Column::Id => false, + _ => true, + })) + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."name" FROM "cake""# +); +``` + +### Optional fields + +Since 0.12, SeaORM supports for partial select of `Option` model field. A `None` value will be filled when the select result does not contain the `Option` field without throwing an error. + +```rust +customer::ActiveModel { + name: Set("Alice".to_owned()), + notes: Set(Some("Want to communicate with Bob".to_owned())), + ..Default::default() +} +.save(db) +.await?; + +// The `notes` field was intentionally left out +let customer = Customer::find() + .select_only() + .column(customer::Column::Id) + .column(customer::Column::Name) + .one(db) + .await + .unwrap(); + +// The select result does not contain `notes` field. +// Since it's of type `Option`, it'll be `None` and no error will be thrown. +assert_eq!(customers.notes, None); +``` + +## Select Custom Expressions + +Select any custom expression with `column_as` / `expr_as` method, it takes any [`sea_query::SimpleExpr`](https://docs.rs/sea-query/*/sea_query/expr/enum.SimpleExpr.html) and an alias. Use [`sea_query::Expr`](https://docs.rs/sea-query/*/sea_query/expr/struct.Expr.html) helper to build `SimpleExpr`. + +```rust +use sea_query::{Alias, Expr, Func}; + +assert_eq!( + cake::Entity::find() + .column_as(Expr::col(cake::Column::Id).max().sub(Expr::col(cake::Column::Id)), "id_diff") + .column_as(Expr::cust("CURRENT_TIMESTAMP"), "current_time") + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."id", "cake"."name", MAX("id") - "id" AS "id_diff", CURRENT_TIMESTAMP AS "current_time" FROM "cake""# +); + +assert_eq!( + cake::Entity::find() + .expr_as(Func::upper(Expr::col((cake::Entity, cake::Column::Name))), "name_upper") + .build(DbBackend::MySql) + .to_string(), + "SELECT `cake`.`id`, `cake`.`name`, UPPER(`cake`.`name`) AS `name_upper` FROM `cake`" +); +``` + +## Handling Select Results + +### Custom Struct + +You can use a custom `struct` derived from the `FromQueryResult` trait to handle the result of a complex query. It is especially useful when dealing with custom columns or multiple joins which cannot directly be converted into models. It may be used to receive the result of any query, even raw SQL. + +```rust +use sea_orm::{FromQueryResult, JoinType, RelationTrait}; +use sea_query::Expr; + +#[derive(FromQueryResult)] +struct CakeAndFillingCount { + id: i32, + name: String, + count: i32, +} + +let cake_counts: Vec = cake::Entity::find() + .column_as(filling::Column::Id.count(), "count") + .join_rev( + // construct `RelationDef` on the fly + JoinType::InnerJoin, + cake_filling::Entity::belongs_to(cake::Entity) + .from(cake_filling::Column::CakeId) + .to(cake::Column::Id) + .into() + ) + // reuse a `Relation` from existing Entity + .join(JoinType::InnerJoin, cake_filling::Relation::Filling.def()) + .group_by(cake::Column::Id) + .into_model::() + .all(db) + .await?; +``` + +### Unstructured Tuple + +You can select a tuple (or single value) with the `into_tuple` method. + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter}; + +let res: Vec<(String, i64)> = cake::Entity::find() + .select_only() + .column(cake::Column::Name) + .column(cake::Column::Id.count()) + .group_by(cake::Column::Name) + .into_tuple() + .all(&db) + .await?; +``` + +## Select Partial Model + +In `0.12`, we introduced a new trait `PartialModelTrait` and pairing macro `DerivePartialModel` for improving the ergonomic of custom selects. + +Instead of: + +```rust +use user::Entity as User; + +#[derive(FromQueryResult)] +struct PartialUser { + pub id: i32, + pub avatar: String, + pub unique_id: Uuid, +} + +let query = User::find() + .select_only() + .column(Column::Id) + .column(Column::Avatar) + .column(Column::UniqueId) + .into_model::(); +``` + +You can define a partial model, and the corresponding columns will be automatically selected: + +```rust +#[derive(DerivePartialModel)] +#[sea_orm(entity = "User")] +struct PartialUser { + pub id: i32, + pub avatar: String, + pub unique_id: Uuid, +} + +let query = User::find().into_partial_model::(); +``` + +:::tip Since `1.0.0` +`DerivePartialModel` macro attribute `entity` supports complex types +```rust +#[sea_orm(entity = "::Entity")] +struct PartialUser { + .. +} +``` +::: + +Advanced usages include column remap and custom expression: + +```rust +#[derive(DerivePartialModel)] +#[sea_orm(entity = "User")] +struct PartialRow { + #[sea_orm(from_col = "id")] + user_id: i32, + #[sea_orm(from_expr = "Expr::col(user::Column::Id).add(1)")] + next_id: i32, +} + +// The above is equivalent to: +User::find() + .column_as(user::Column::Id, "user_id") + .column_as(Expr::col(user::Column::Id).add(1), "next_id") +``` \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-database-and-async-runtime.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-database-and-async-runtime.md new file mode 100644 index 00000000000..67cb9c76513 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-database-and-async-runtime.md @@ -0,0 +1,63 @@ +# Database & Async Runtime + +:::caution We need your support! ⭐ +Thank you for using SeaORM. Please star our [GitHub repo](https://github.com/SeaQL/sea-orm)! Your support is vital to the continued development and maintenance of SeaORM. +::: + +First, add `sea-orm` to the `[dependencies]` section of `Cargo.toml`. + +```toml title="Cargo.toml" +sea-orm = { version = "1.1.0", features = [ , , "macros" ] } +``` + +You must choose a `DATABASE_DRIVER` and an `ASYNC_RUNTIME`. `macros` is needed if you use SeaORM's generated entities (most likely). + +## DATABASE_DRIVER + +You can choose one or more from: + ++ `sqlx-mysql` - SQLx MySQL and MariaDB ++ `sqlx-postgres` - SQLx PostgreSQL ++ `sqlx-sqlite` - SQLx SQLite + +See also: [SQLx docs](https://docs.rs/crate/sqlx/latest/features). + +:::tip SQL Server (MSSQL) backend + +The installation and configuration of MSSQL driver can be found [here](https://www.sea-ql.org/SeaORM-X/docs/install-and-config/database-and-async-runtime/). + +::: + +## ASYNC_RUNTIME + +You have to choose one from: + +`runtime-async-std-native-tls`, `runtime-tokio-native-tls`, `runtime-async-std-rustls`, `runtime-tokio-rustls` + +Basically, they are in the form of `runtime-ASYNC_RUNTIME-TLS_LIB`: + ++ `ASYNC_RUNTIME` can be [`async-std`](https://crates.io/crates/async-std) or [`tokio`](https://crates.io/crates/tokio) ++ `TLS_LIB` can either be [`native-tls`](https://crates.io/crates/native-tls) or [`rustls`](https://crates.io/crates/rustls) + +1. Choose the ASYNC_RUNTIME corresponding to your Rust web framework: + +| ASYNC_RUNTIME | Web Framework | +| :-----------: | :------------: | +| `async-std` | [`Tide`](https://docs.rs/tide) | +| `tokio` | [`Axum`](https://docs.rs/axum), [`Actix`](https://actix.rs/), [`Poem`](https://docs.rs/poem), [`Rocket`](https://rocket.rs/) | + +2. `native-tls` uses the platform's native security facilities, while `rustls` is an (almost) pure Rust implementation. + +## Extra features + ++ `debug-print` - print every SQL statement to logger ++ `mock` - mock interface for unit testing ++ `macros` - procedural macros for your convenience ++ `with-chrono` - support [`chrono`](https://crates.io/crates/chrono) types ++ `with-time` - support [`time`](https://crates.io/crates/time) types ++ `with-json` - support [`serde-json`](https://crates.io/crates/serde-json) types ++ `with-rust_decimal` - support [`rust_decimal`](https://crates.io/crates/rust_decimal) types ++ `with-bigdecimal` - support [`bigdecimal`](https://crates.io/crates/bigdecimal) types ++ `with-uuid` - support [`uuid`](https://crates.io/crates/uuid) types ++ `postgres-array` - support array types in Postgres (automatically enabled when `sqlx-postgres` feature is turned on) ++ `sea-orm-internal` - opt-in unstable internal APIs (for accessing re-export SQLx types) diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-index.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-index.md new file mode 100644 index 00000000000..140e819ff59 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-index.md @@ -0,0 +1,149 @@ +# Index + +## Introduction + +1. Introduction + + 1.1. [What is an ORM](01-introduction/01-orm.md) + + 1.2. [Async Programming in Rust](01-introduction/02-async.md) + + 1.3. [SeaORM Concepts](01-introduction/03-sea-orm.md) + + 1.4. [Tutorial & Examples](01-introduction/04-tutorial.md) + +## Basics + +2. Installation & Configuration + + 2.1 [Choosing a Database & Async Runtime](02-install-and-config/01-database-and-async-runtime.md) + + 2.2 [Connection Pool](02-install-and-config/02-connection.md) + + 2.3 [Debug Log](02-install-and-config/03-debug-log.md) + +3. Migration + + 3.1 [Setting Up Migration](03-migration/01-setting-up-migration.md) + + 3.2 [Writing Migration](03-migration/02-writing-migration.md) + + 3.3 [Running Migration](03-migration/03-running-migration.md) + + 3.4 [Seeding Data](03-migration/04-seeding-data.md) + +4. Generating Entities + + 4.1 [Using `sea-orm-cli`](04-generate-entity/01-sea-orm-cli.md) + + 4.2 [Entity Structure](04-generate-entity/02-entity-format.md) + + 4.3 [Column Types](04-generate-entity/03-column-types.md) + + 4.4 [Enumeration](04-generate-entity/04-enumeration.md) + + 4.5 [New Type](04-generate-entity/05-newtype.md) + +5. Basic CRUD + + 5.1 [Basic Schema](05-basic-crud/01-basic-schema.md) + + 5.2 [SELECT: find, filter, sort, paging](05-basic-crud/02-select.md) + + 5.3 [ActiveModel & ActiveValue](05-basic-crud/03-active-model.md) + + 5.4 [INSERT: insert one & insert many](05-basic-crud/04-insert.md) + + 5.5 [UPDATE: find & save, update many](05-basic-crud/05-update.md) + + 5.6 [SAVE: insert or update](05-basic-crud/06-save.md) + + 5.7 [DELETE: delete one & delete many](05-basic-crud/07-delete.md) + + 5.8 [JSON](05-basic-crud/08-json.md) + + 5.9 [Raw SQL query](05-basic-crud/09-raw-sql.md) + +## Advanced Topics + +6. Relations + + 6.1 [One to One](06-relation/01-one-to-one.md) + + 6.2 [One to Many](06-relation/02-one-to-many.md) + + 6.3 [Many to Many](06-relation/03-many-to-many.md) + + 6.4 [Complex Relations](06-relation/04-complex-relations.md) + + 6.5 [Custom Join Condition](06-relation/06-custom-join-condition.md) + + 6.6 [Data Loader](06-relation/07-data-loader.md) + + 6.7 [Bakery Schema](06-relation/08-bakery-schema.md) + + 6.8 [Nested Selects](06-relation/09-nested-selects.md) + +7. Writing Tests + + 7.1 [Robust & Correct](07-write-test/01-testing.md) + + 7.2 [Mock Interface](07-write-test/02-mock.md) + + 7.3 [Using SQLite](07-write-test/03-sqlite.md) + +8. Advanced Queries + + 8.1 [Custom select](08-advanced-query/01-custom-select.md) + + 8.2 [Conditional expressions](08-advanced-query/02-conditional-expression.md) + + 8.3 [Aggregate functions](08-advanced-query/03-aggregate-function.md) + + 8.4 [Advanced Joins](08-advanced-query/04-advanced-joins.md) + + 8.5 [Sub Query](08-advanced-query/05-subquery.md) + + 8.6 [Transaction](08-advanced-query/06-transaction.md) + + 8.7 [Streaming](08-advanced-query/07-streaming.md) + + 8.8 [Custom Active Model](08-advanced-query/08-custom-active-model.md) + + 8.9 [Error Handling](08-advanced-query/09-error-handling.md) + +9. Schema Statement + + 9.1 [Create Table](09-schema-statement/01-create-table.md) + + 9.2 [Create Enum](09-schema-statement/02-create-enum.md) + + 9.3 [Create Index](09-schema-statement/03-create-index.md) + +10. GraphQL Support + + 10.1 [🧭 Seaography](10-graph-ql/01-seaography-intro.md) + + 10.2 [Getting Started](10-graph-ql/02-getting-started.md) + +11. Admin Panel + + 11.1 [🖥️ SeaORM Pro](11-sea-orm-pro/01-sea-orm-pro-intro.md) + + 11.2 [Getting Started](11-sea-orm-pro/02-getting-started.md) + +12. Internal Design + + 12.1 [Traits and Types](12-internal-design/01-trait-and-type.md) + + 12.2 [Derive Macros](12-internal-design/02-derive-macro.md) + + 12.3 [Compare with Diesel](12-internal-design/03-diesel.md) + + 12.4 [Architecture](12-internal-design/04-architecture.md) + + 12.5 [Architecture](12-internal-design/05-expanded-entity-format.md) + +13. What's Next + + 13.1 [What's Next](13-whats-next/01-whats-next.md) \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/01-orm.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/01-orm.md new file mode 100644 index 00000000000..de887d2a9e3 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/01-orm.md @@ -0,0 +1,13 @@ +# What is an ORM + +An Object Relational Mapper (ORM) is a programming library to help you interact with a relational database from an Object-Oriented Programming (OOP) language. + +Tables and columns in a database are mapped to objects and attributes, while additional methods allow you to load and store data from and to the database. + +Services built in Rust are lightweight (small binary size, low memory usage), safe (with compile-time guarantee), correct (if unit tests are well-designed), and fast (compile-time optimizations minimize runtime overhead). + +Due to Rust being a static, strongly typed, compiled, thread-safe, non-garbage-collected, and unconventional object-oriented language, working with an ORM in Rust is a bit different from other scripting languages you are already familiar with. + +SeaORM tries to help you in reaping the above benefits while avoiding the hiccups when programming in Rust. + +Let's get started. \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/02-async.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/02-async.md new file mode 100644 index 00000000000..4f8822d77b0 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/02-async.md @@ -0,0 +1,11 @@ +# Async Programming + +Async programming in Rust is a recent development, only having been stabilized in Rust [`1.39`](https://github.com/rust-lang/rust/releases/tag/1.39.0). The async ecosystem is rapidly evolving, and SeaORM is one of the first crates built from the ground up with async support in mind. + +The first thing to learn is the [`Future`](https://rust-lang.github.io/async-book/02_execution/02_future.html) trait. It's a placeholder for a function that will compute and return some value in the future. It's lazy, meaning `.await` must be called for any actual work to be done. `Future` allows us to achieve concurrency with little programming effort, e.g. [`future::join_all`](https://docs.rs/futures/latest/futures/future/fn.join_all.html) to execute multiple queries in parallel. + +Second, `async` in Rust is [multi-threaded programming](https://rust-lang.github.io/async-book/03_async_await/01_chapter.html) with syntactic sugar. A `Future` may move between threads, so any variables used in async bodies must be able to travel between threads, i.e. [`Send`](https://doc.rust-lang.org/nomicon/send-and-sync.html). + +Third, there are multiple async runtimes in Rust. [`async-std`](https://crates.io/crates/async-std) and [`tokio`](https://crates.io/crates/tokio) are the two most widely used. SeaORM's underlying driver, [`SQLx`](https://crates.io/crates/sqlx), supports both. + +Knowing these concepts is essential to get you up and running in async Rust. \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/03-sea-orm.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/03-sea-orm.md new file mode 100644 index 00000000000..bcaa346850b --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/03-sea-orm.md @@ -0,0 +1,15 @@ +# SeaORM Concepts + +In SeaORM, a database with a collection of tables is called a `Schema`. + +Each table corresponds to an [`Entity`](04-generate-entity/02-entity-format.md#entity) in SeaORM, which helps you perform `CRUD` (Create, Read, Update, and Delete) operations on relevant tables. + +The `Entity` trait provides an API for you to inspect its properties ([`Column`](04-generate-entity/02-entity-format.md#column), [`Relation`](04-generate-entity/02-entity-format.md#relation) and [`PrimaryKey`](04-generate-entity/02-entity-format.md#primary-key)) at runtime. + +Each table has multiple columns, which are referred to as `attribute`. + +These attributes and their values are grouped in a Rust struct (a [`Model`](12-internal-design/05-expanded-entity-format.md#model)) so that you can manipulate them. + +However, `Model` is for read operations only. To perform insert, update, or delete, you need to use [`ActiveModel`](12-internal-design/05-expanded-entity-format.md#active-model) which attaches meta-data on each attribute. + +Finally, there is no singleton (global context) in SeaORM. Application code is responsible for managing the ownership of the `DatabaseConnection`. We do provide integration examples for web frameworks including [Rocket](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example), [Actix](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example), [axum](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) and [poem](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) to help you get started quickly. \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/04-tutorial.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/04-tutorial.md new file mode 100644 index 00000000000..7252f11d59b --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/04-tutorial.md @@ -0,0 +1,19 @@ +# Tutorial & Examples + +If you prefer a step-by-step tutorial on how to use SeaORM, you can check out our official [SeaORM Tutorial](https://www.sea-ql.org/sea-orm-tutorial/). There are also some good tutorials written by [the community](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#learning-resources)! + +You can also check out [SeaORM Cookbook](https://www.sea-ql.org/sea-orm-cookbook/) for some frequently asked questions and recommended practices of using SeaORM. + +If you are so eager and want something grab-and-go, SeaQL maintains a set of official examples contributed by the community (we welcome more!): + ++ [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) ++ [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) ++ [GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example) ++ [jsonrpsee Example](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example) ++ [Loco Example](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_example) ++ [Loco REST Starter Example](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_starter) ++ [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) ++ [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) ++ [Salvo Example](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example) ++ [Tonic Example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example) ++ [Seaography Example](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/_category_.json b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/_category_.json new file mode 100644 index 00000000000..16a97d6e429 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Introduction", + "collapsed": false +} diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-one-to-one.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-one-to-one.md new file mode 100644 index 00000000000..c91a45894f8 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-one-to-one.md @@ -0,0 +1,118 @@ +# One to One + +:::tip Rustacean Sticker Pack 🦀 +[Our stickers](https://www.sea-ql.org/sticker-pack/) are made with a premium water-resistant vinyl with a unique matte finish. +Stick them on your laptop, notebook, or any gadget to show off your love for Rust! +::: + +A one-to-one relation is the most basic type of database relation. Let say a `Cake` entity has at most one `Fruit` topping. + +## Defining the Relation + +On the `Cake` entity, to define the relation: +1. Add a new variant `Fruit` to the `Relation` enum. +1. Define it with `has_one`. +1. Implement the `Related` trait. + +```rust title="entity/cake.rs" +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_one = "super::fruit::Entity")] + Fruit, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} +``` + +
+ It's expanded to: + +```rust {3,9,16} title="entity/cake.rs" +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_one(super::fruit::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} +``` +
+ +Alternatively, the definition can be shortened by the `DeriveRelation` macro, +where the following eliminates the need for the `RelationTrait` implementation above: + +## Defining the Inverse Relation + +On the `Fruit` entity, its `cake_id` attribute is referencing the primary key of `Cake` entity. + +:::tip + +The rule of thumb is, always define a `belongs_to` on the Entity with a foreign key `xxx_id`. + +::: + +To define the inverse relation: +1. Add a new enum variant `Relation::Cake` to the `Fruit` entity. +1. Write the definition of it with the `Entity::belongs_to()` method, we always define the inverse relation using this method. +1. Implement the `Related` trait. + +```rust title="entity/fruit.rs" +#[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() + } +} +``` + +
+ It's expanded to: + +```rust +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} +``` +
\ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-orm.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-orm.md new file mode 100644 index 00000000000..de887d2a9e3 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-orm.md @@ -0,0 +1,13 @@ +# What is an ORM + +An Object Relational Mapper (ORM) is a programming library to help you interact with a relational database from an Object-Oriented Programming (OOP) language. + +Tables and columns in a database are mapped to objects and attributes, while additional methods allow you to load and store data from and to the database. + +Services built in Rust are lightweight (small binary size, low memory usage), safe (with compile-time guarantee), correct (if unit tests are well-designed), and fast (compile-time optimizations minimize runtime overhead). + +Due to Rust being a static, strongly typed, compiled, thread-safe, non-garbage-collected, and unconventional object-oriented language, working with an ORM in Rust is a bit different from other scripting languages you are already familiar with. + +SeaORM tries to help you in reaping the above benefits while avoiding the hiccups when programming in Rust. + +Let's get started. \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-sea-orm-cli.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-sea-orm-cli.md new file mode 100644 index 00000000000..5825aa1c6ef --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-sea-orm-cli.md @@ -0,0 +1,73 @@ +# Using `sea-orm-cli` + +First, install `sea-orm-cli` with `cargo`. + +```shell +cargo install sea-orm-cli@1.1.0 +``` + +:::tip SQL Server (MSSQL) backend + +The installation and the usage of `sea-orm-cli` with MSSQL support can be found [here](https://www.sea-ql.org/SeaORM-X/docs/generate-entity/sea-orm-cli/). + +::: + +## Configure Environment + +Set `DATABASE_URL` in your environment, or create a `.env` file in your project root. Specify your database connection. + +```env title=".env" +DATABASE_URL=protocol://username:password@localhost/database +``` + +## Getting Help + +Use `-h` flag on any CLI command or subcommand for help. + +```shell +# List all available commands +sea-orm-cli -h + +# List all subcommands available in `generate` command +sea-orm-cli generate -h + +# Show how to use `generate entity` subcommand +sea-orm-cli generate entity -h +``` + +## Generating Entity Files + +Discover all tables in a database and generate a corresponding SeaORM entity file for each table. + +Supported databases: +- MySQL +- PostgreSQL +- SQLite + +Command line options: +- `-u` / `--database-url`: database URL (default: DATABASE_URL specified in ENV) +- `-s` / `--database-schema`: database schema (default: DATABASE_SCHEMA specified in ENV) + - for MySQL & SQLite, this argument is ignored + - for PostgreSQL, this argument is optional with default value 'public' +- `-o` / `--output-dir`: entity file output directory (default: current directory) +- `-v` / `--verbose`: print debug messages +- `-l` / `--lib`: generate index file as `lib.rs` instead of `mod.rs` +- `--include-hidden-tables`: generate entity files from hidden tables (tables with names starting with an underscore are hidden and ignored by default) +- `--ignore-tables`: skip generating entity file for specified tables (default: `seaql_migrations`) +- `--compact-format`: generate entity file of [compact format](04-generate-entity/02-entity-format.md) (default: true) +- `--expanded-format`: generate entity file of [expanded format](12-internal-design/05-expanded-entity-format.md) +- `--with-serde`: automatically derive serde Serialize / Deserialize traits for the entity (`none`, `serialize`, `deserialize`, `both`) (default: `none`) + - `--serde-skip-deserializing-primary-key`: generate entity model with primary key field labeled as `#[serde(skip_deserializing)]` + - `--serde-skip-hidden-column`: generate entity model with hidden column (column name starts with `_`) field labeled as `#[serde(skip)]` +- `--date-time-crate`: the datetime crate to use for generating entities (`chrono`, `time`) (default: `chrono`) +- `--max-connections`: maximum number of database connections to be initialized in the connection pool (default: `1`) +- `--model-extra-derives`: append extra derive macros to the generated model struct +- `--model-extra-attributes`: append extra attributes to generated model struct +- `--enum-extra-derives`: append extra derive macros to generated enums +- `--enum-extra-attributes`: append extra attributes to generated enums +- `--seaography`: generate addition structs in entities for seaography integration + +```shell +# Generate entity files of database `bakery` to `entity/src` +sea-orm-cli generate entity -u protocol://username:password@localhost/bakery -o entity/src +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-sea-orm-pro-intro.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-sea-orm-pro-intro.md new file mode 100644 index 00000000000..67e11a728ba --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-sea-orm-pro-intro.md @@ -0,0 +1,13 @@ +# 🖥️ SeaORM Pro + +![](https://www.sea-ql.org/blog/img/sea-orm-pro-light.png) + +SeaORM Pro is an admin panel solution allowing you to quickly and easily launch an admin panel for your application - frontend development skills not required (but certainly nice to have). + +An admin panel is essential for operating backend applications. But it often is an after-thought, or no dedicated resources (aka developer time) is put into developing them. + +Frontend engineers are usually focused on developing consumer-facing applications, which often led to insufficient effort being directed towards GUI of internal-facing applications. + +SeaORM Pro is designed to bridge this gap, providing a solution that is both quick to implement and reliable for long-term use. + +Learn more about [SeaORM Pro here](https://www.sea-ql.org/sea-orm-pro/docs/introduction/sea-orm-pro/). diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-seaography-intro.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-seaography-intro.md new file mode 100644 index 00000000000..075265282fc --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-seaography-intro.md @@ -0,0 +1,9 @@ +# 🧭 Seaography + +If you are building a full-stack application with a web GUI these days, it's likely you'd use GraphQL as the communication interface between frontend and backend. However, building GraphQL resolvers is no easy task for backend developers. + +[Seaography](https://github.com/SeaQL/seaography) is a GraphQL framework built on top of SeaORM and [async-graphql](https://github.com/async-graphql/async-graphql). Given a set of SeaORM entities, you can instantly launch a fully-featured GraphQL server / resolver with relational query, filters, pagination and mutations. + +SeaORM is dynamic by design with first-class GraphQL support. `async-graphql` `v5.0` introduced [dynamic schema](https://docs.rs/async-graphql/latest/async_graphql/dynamic/index.html) and is a perfect match with SeaORM. + +Seaography started out as a [Summer of Code 2022 project](https://github.com/SeaQL/summer-of-code/blob/main/2022/README.md#1-a-graphql-framework-on-top-of-seaorm). \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-setting-up-migration.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-setting-up-migration.md new file mode 100644 index 00000000000..3a98e9e550e --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-setting-up-migration.md @@ -0,0 +1,180 @@ +# Setting Up Migration + +:::tip Rustacean Sticker Pack 🦀 +[Our stickers](https://www.sea-ql.org/sticker-pack/) are made with a premium water-resistant vinyl with a unique matte finish. +Stick them on your laptop, notebook, or any gadget to show off your love for Rust! +::: + +If you are starting from a fresh database, it's better to version control your database schema. SeaORM ships with a migration tool, allowing you to write migrations in SeaQuery or SQL. + +If you already have a database with tables and data, you can skip this chapter and move on to [generating SeaORM entities](04-generate-entity/01-sea-orm-cli.md). + +## Migration Table + +A table will be created in your database to keep track of the applied migrations. It will be created automatically when you run the migration. + +
+ By default, it will be named `seaql_migrations`. You can also use a custom name for your migration table. + +```rust +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + // Override the name of migration table + fn migration_table_name() -> sea_orm::DynIden { + "override_migration_table_name".into_iden() + } + .. +} +``` +
+ +## Creating Migration Directory + +First, install `sea-orm-cli` with `cargo`. + +```shell +cargo install sea-orm-cli@1.1.0 +``` + +:::tip SQL Server (MSSQL) backend + +The installation of `sea-orm-cli` with MSSQL support can be found [here](https://www.sea-ql.org/SeaORM-X/docs/migration/setting-up-migration/). + +::: + +Then, setup the migration directory by executing `sea-orm-cli migrate init`. + +```shell +# Setup the migration directory in `./migration` +$ sea-orm-cli migrate init +Initializing migration directory... +Creating file `./migration/src/lib.rs` +Creating file `./migration/src/m20220101_000001_create_table.rs` +Creating file `./migration/src/main.rs` +Creating file `./migration/Cargo.toml` +Creating file `./migration/README.md` +Done! + +# If you want to setup the migration directory in else where +$ sea-orm-cli migrate init -d ./other/migration/dir +``` + +You should have a migration directory with a structure like below. + +``` +migration +├── Cargo.toml +├── README.md +└── src + ├── lib.rs # Migrator API, for integration + ├── m20220101_000001_create_table.rs # A sample migration file + └── main.rs # Migrator CLI, for running manually +``` + +Note that if you setup the migration directory directly within a Git repository, a `.gitignore` file will also be created. + +## Workspace Structure + +It is recommended to structure your cargo workspace as follows to share SeaORM entities between the app crate and the migration crate. Checkout the [integration examples](https://github.com/SeaQL/sea-orm/tree/master/examples) for demonstration. + +### Migration Crate + +Import the [`sea-orm-migration`](https://crates.io/crates/sea-orm-migration) and [`async-std`](https://crates.io/crates/async-std) crate. + +```toml title="migration/Cargo.toml" +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } + +[dependencies.sea-orm-migration] +version = "1.1.0" +features = [ + # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. + # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. + # e.g. + # "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature + # "sqlx-postgres", # `DATABASE_DRIVER` feature +] +``` + +Let's write a migration. Detailed instructions in the next section. + +```rust title="migration/src/m20220120_000001_create_post_table.rs" +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + } +} +``` + +### Entity Crate + +Create an entity crate in your root workspace. + +
+ You don't have SeaORM entities defined? + +You can create an entity crate without any entity files. Then, write the migration and run it to create tables in the database. Finally, [generate SeaORM entities](04-generate-entity/01-sea-orm-cli.md) with `sea-orm-cli` and output the entity files to `entity/src/entities` folder. + +After generating the entity files, you can re-export the generated entities by adding following lines in `entity/src/lib.rs`: + +```rust +mod entities; +pub use entities::*; +``` +
+ +``` +entity +├── Cargo.toml # Include SeaORM dependency +└── src + ├── lib.rs # Re-export SeaORM and entities + └── post.rs # Define the `post` entity +``` + +Specify SeaORM dependency. + +```toml title="entity/Cargo.toml" +[dependencies] +sea-orm = { version = "1.1.0" } +``` + +### App Crate + +This is where the application logic goes. + +Create a workspace that contains app, entity and migration crates and import the entity crate into the app crate. + +If we want to bundle the migration utility as part of your app, you'd also want to import the migration crate. + +```toml title="./Cargo.toml" +[workspace] +members = [".", "entity", "migration"] + +[dependencies] +entity = { path = "entity" } +migration = { path = "migration" } # depends on your needs + +[dependencies] +sea-orm = { version = "1.1.0", features = [..] } +``` + +In your app, you can then run the migrator on startup. + +```rust title="src/main.rs" +use migration::{Migrator, MigratorTrait}; + +let connection = sea_orm::Database::connect(&database_url).await?; +Migrator::up(&connection, None).await?; +``` \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-testing.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-testing.md new file mode 100644 index 00000000000..760aa346980 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-testing.md @@ -0,0 +1,64 @@ +# Robust & Correct + +Testing is an integral part of programming in Rust. You see, [`cargo test`](https://doc.rust-lang.org/cargo/commands/cargo-test.html) is built-in. + +If you don't use `unsafe` and your code compiles, then your Rust program is *safe*. However, it does not automatically become *robust*. Your program can still panic unexpectedly if you are not careful on error handling. + +Even if your program does not panic, it does not mean it is *correct*. It can still misbehave and create data chaos. + +You can improve the correctness of your program by writing adequate tests. + +## Types of Errors + +First, let's classify different causes of errors in a data-driven application: + +### 1. Type Errors + +1. misspelled or non-existent symbol (table or column) name +1. using incompatible functions or operators on data (e.g. add two strings) +1. invalid SQL query + - e.g. ambiguous symbol in a `JOIN` query + - e.g. forget to insert data on non-nullable columns + - e.g. forget to aggregate every column in a `GROUP BY` query + +### 2. Transaction Errors + +1. failed to maintain entity relationships +1. failed to maintain data consistency and constraints + +### 3. Behavioural Errors + +1. joining or filtering on wrong conditions +1. incomplete or incorrect query results +1. insert, update or delete operations with unawared side effects +1. any other behaviour not as intended + +> A note on 'unawared side effects': do not use `CASCADE` unless the relation is strictly parent-child + +## Mitigations + +Now, let's see how we can mitigate these errors: + +### 1. Type Errors + +Using Rust automatically saves you from misspelling symbols. + +Using a *completely static* query builder can eliminate this entire class of errors. However, it requires that every parameter be defined statically and available compile-time. This is a *harsh* requirement, as there is always something you could not know until your program starts (environment variables) and runs (runtime configuration change). This is especially awkward if you come from a scripting language background where the type system has always been dynamic. + +As such, SeaORM does not attempt to check things at compile-time. We intend to (still in development) provide runtime linting on the dynamically generated queries against the mentioned problems that you can enable in unit tests but disable in production. + +### 2. Transaction Errors + +These problems cannot be eliminated. It usually indicates your code has some logic bugs. When they happen, it is already too late, and your only choice is to abort. Instead, they have to be actively prevented: check beforehand the constraints before attempting data operations. + +You should write a bunch of unit tests that can reject bad data and prevent it from entering your database. Your unit tests should also verify that each *transaction* (in your application domain, not necessarily the database transaction) is sound. + +SeaORM helps you write these unit tests using the `Mock` database interface. + +### 3. Behavioural Errors + +This is basically testing your entire program on a domain level, requiring you to provide seed data and simulate the common user operations. Usually, you will do it in CI against a real database. However, SeaORM encourages you to scale down these tests so that the most important data-flow can be tested by Cargo's [integration tests](https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html). + +Since SeaORM is abstract over MySQL, PostgreSQL, and SQLite, you can use SQLite as a backend to test your program's behaviours. It is lightweight enough to run it frequently, locally, and on CI. The catch is, SQLite lacks some advanced features of MySQL or PostgreSQL, so depending on your use of database-specific features, not all logic can be tested inside SQLite. + +We are looking for SQLite alternatives that can simulate the more advanced features of MySQL and PostgreSQL. diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-trait-and-type.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-trait-and-type.md new file mode 100644 index 00000000000..8d94febc603 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-trait-and-type.md @@ -0,0 +1,66 @@ +# Traits and Types + +## Entity + +A unit struct that implements [`EntityTrait`](#) representing a table in the database. + +This trait contains the properties of an entity including +- Table Name (implemented [`EntityName`](#)) +- Column (implemented [`ColumnTrait`](#)) +- Relation (implemented [`RelationTrait`](#)) +- Primary Key (implemented [`PrimaryKeyTrait`](#) and [`PrimaryKeyToColumn`](#)) + +This trait also provides an API for CRUD actions +- Select: `find`, `find_*` +- Insert: `insert`, `insert_*` +- Update: `update`, `update_*` +- Delete: `delete`, `delete_*` + +## Column + +An enum that implements [`ColumnTrait`](#) representing all columns of the table and the column types and attributes. + +It also implements +- [`IdenStatic`](#) provides mapping to column identifier with static lifetime +- [`Iterable`](#) allows SeaORM core to iterate over all column variants + +## Primary Key + +An enum that implements [`PrimaryKeyTrait`](#) representing the primary key. Each primary key variant must have a corresponding column variant. + +It also implements +- [`IdenStatic`](#) provides mapping to primary key identifier with static lifetime +- [`Iterable`](#) allows SeaORM core to iterate over all primary key variants + +## Model + +A struct that implements [`ModelTrait`](#) storing the query result in memory. This is intended for readonly purposes, and it is stateless. + +It also implements +- [`FromQueryResult`](#) converts raw query result into corresponding model + +## Active Model + +A struct that implements [`ActiveModelTrait`](#) representing insert/update actions. This is intended to be edited and saved into database. + +It also implements +- [`ActiveModelBehavior`](#) defines handlers for different actions on an active model + +## Active Enum + +A enum that implements [`ActiveEnum`](#) representing value stored in database as a Rust enum variant. + +## Relation + +An enum that implements [`RelationTrait`](#) defined relations with other entities. + +It also implements +- [`Iterable`](#) allows SeaORM core to iterate over all relation variants + +## Related + +A generic trait, [`Related`](#), defines join paths to help you query related entities together, especially helpful in many-to-many relations. + +## Linked + +A trait, [`Linked`](#), defines complex join paths including chained relation, self referencing relation and multiple relations between two entities. diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-whats-next.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-whats-next.md new file mode 100644 index 00000000000..1ee5fd059be --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-whats-next.md @@ -0,0 +1,7 @@ +# What's Next for SeaORM? + +We still remember the time when we first [introduced SeaORM](https://www.sea-ql.org/blog/2021-09-20-introducing-sea-orm/) to the Rust community two years ago. + +Today, many open-source projects, many startups and companies are using SeaORM. If your company is using SeaORM and would like to be featured, please [submit a testimonial](https://forms.office.com/r/YbeqfTAgkJ). + +Open-source project is a never-ending work, and we are actively looking for ways to sustain the project. SeaQL.org is an independent open-source organization run by passionate developers. You can support us by starring and sharing our repositories and making a donation via [GitHub Sponsor](https://github.com/sponsors/SeaQL). diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-async.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-async.md new file mode 100644 index 00000000000..4f8822d77b0 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-async.md @@ -0,0 +1,11 @@ +# Async Programming + +Async programming in Rust is a recent development, only having been stabilized in Rust [`1.39`](https://github.com/rust-lang/rust/releases/tag/1.39.0). The async ecosystem is rapidly evolving, and SeaORM is one of the first crates built from the ground up with async support in mind. + +The first thing to learn is the [`Future`](https://rust-lang.github.io/async-book/02_execution/02_future.html) trait. It's a placeholder for a function that will compute and return some value in the future. It's lazy, meaning `.await` must be called for any actual work to be done. `Future` allows us to achieve concurrency with little programming effort, e.g. [`future::join_all`](https://docs.rs/futures/latest/futures/future/fn.join_all.html) to execute multiple queries in parallel. + +Second, `async` in Rust is [multi-threaded programming](https://rust-lang.github.io/async-book/03_async_await/01_chapter.html) with syntactic sugar. A `Future` may move between threads, so any variables used in async bodies must be able to travel between threads, i.e. [`Send`](https://doc.rust-lang.org/nomicon/send-and-sync.html). + +Third, there are multiple async runtimes in Rust. [`async-std`](https://crates.io/crates/async-std) and [`tokio`](https://crates.io/crates/tokio) are the two most widely used. SeaORM's underlying driver, [`SQLx`](https://crates.io/crates/sqlx), supports both. + +Knowing these concepts is essential to get you up and running in async Rust. \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-conditional-expression.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-conditional-expression.md new file mode 100644 index 00000000000..7a56c49fa09 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-conditional-expression.md @@ -0,0 +1,95 @@ +# Conditional Expressions + +You can add conditions to SeaORM find with the `filter` method. You can also restrict the aggregated result with `having` method. Both of them take [`sea_query::Condition`](https://docs.rs/sea-query/*/sea_query/query/struct.Condition.html) as a parameter. + +## AND Condition + +Construct the AND conditional expression with `Condition::all` method, and append any condition represented in [`sea_query::SimpleExpr`](https://docs.rs/sea-query/*/sea_query/expr/enum.SimpleExpr.html) with the `add` method. + +```rust +assert_eq!( + cake::Entity::find() + .filter( + Condition::all() + .add(cake::Column::Id.gte(1)) + .add(cake::Column::Name.like("%Cheese%")) + ) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `cake`.`id`, `cake`.`name` FROM `cake`", + "WHERE `cake`.`id` >= 1 AND `cake`.`name` LIKE '%Cheese%'", + ].join(" ") +); +``` + +## OR Condition + +Construct the OR conditional expression with `Condition::any` method, and append any condition represented in [`sea_query::SimpleExpr`](https://docs.rs/sea-query/*/sea_query/expr/enum.SimpleExpr.html) with the `add` method. + +```rust +assert_eq!( + cake::Entity::find() + .filter( + Condition::any() + .add(cake::Column::Id.eq(4)) + .add(cake::Column::Id.eq(5)) + ) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `cake`.`id`, `cake`.`name` FROM `cake`", + "WHERE `cake`.`id` = 4 OR `cake`.`id` = 5", + ].join(" ") +); +``` + +## Nested Condition + +The `add` method can also take another conditional expression. By doing this, we can construct complex nested conditions flexibly. + +```rust +assert_eq!( + cake::Entity::find() + .filter( + Condition::any() + .add( + Condition::all() + .add(cake::Column::Id.lte(30)) + .add(cake::Column::Name.like("%Chocolate%")) + ) + .add( + Condition::all() + .add(cake::Column::Id.gte(1)) + .add(cake::Column::Name.like("%Cheese%")) + ) + ) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `cake`.`id`, `cake`.`name` FROM `cake`", + "WHERE (`cake`.`id` <= 30 AND `cake`.`name` LIKE '%Chocolate%') OR", + "(`cake`.`id` >= 1 AND `cake`.`name` LIKE '%Cheese%')", + ].join(" ") +); +``` + +## Fluent conditional query + +Apply an operation on the QueryStatement if the given `Option` is `Some(_)`. It keeps your query expression fluent! + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + +assert_eq!( + cake::Entity::find() + .apply_if(Some(3), |mut query, v| { + query.filter(cake::Column::Id.eq(v)) + }) + .apply_if(Some(100), QuerySelect::limit) + .apply_if(None, QuerySelect::offset::>) // no-op + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = 3 LIMIT 100"# +); +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-connection.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-connection.md new file mode 100644 index 00000000000..2fe57d4c028 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-connection.md @@ -0,0 +1,103 @@ +# Database Connection + +To obtain a database connection, use the [`Database`](https://docs.rs/sea-orm/*/sea_orm/struct.Database.html) interface: + +```rust +let db: DatabaseConnection = Database::connect("protocol://username:password@host/database").await?; +``` + +`protocol` can be `mysql:`, `postgres:` or `sqlite:`. + +`host` is usually `localhost`, a domain name or an IP address. + +:::tip + +If you can't get `localhost` to work, try putting in an IP address and port number, e.g. `127.0.0.1:3306` or even `192.168.x.x`. + +::: + +Under the hood, a [`sqlx::Pool`](https://docs.rs/sqlx/0.5/sqlx/struct.Pool.html) is created and owned by [`DatabaseConnection`](https://docs.rs/sea-orm/*/sea_orm/enum.DatabaseConnection.html). + +Each time you call `execute` or `query_one/all` on it, a connection will be acquired and released from the pool. + +Multiple queries will execute in parallel as you `await` on them. + +## Connection String + +Here are some tips for database specific options: + +### MySQL + +``` +mysql://username:password@host/database +``` + +### Postgres + +#### Specify a schema + +``` +postgres://username:password@host/database?currentSchema=my_schema +``` + +### SQLite + +#### In memory + +``` +sqlite::memory: +``` + +#### Create file if not exists + +``` +sqlite://path/to/db.sqlite?mode=rwc +``` + +#### Read only + +``` +sqlite://path/to/db.sqlite?mode=ro +``` + +## Connect Options + +To configure the connection, use the [`ConnectOptions`](https://docs.rs/sea-orm/*/sea_orm/struct.ConnectOptions.html) interface: + +```rust +let mut opt = ConnectOptions::new("protocol://username:password@host/database"); +opt.max_connections(100) + .min_connections(5) + .connect_timeout(Duration::from_secs(8)) + .acquire_timeout(Duration::from_secs(8)) + .idle_timeout(Duration::from_secs(8)) + .max_lifetime(Duration::from_secs(8)) + .sqlx_logging(true) + .sqlx_logging_level(log::LevelFilter::Info) + .set_schema_search_path("my_schema"); // Setting default PostgreSQL schema + +let db = Database::connect(opt).await?; +``` + +## Checking Connection is Valid + +Checks if a connection to the database is still valid. + +```rust +|db: DatabaseConnection| { + assert!(db.ping().await.is_ok()); + db.clone().close().await; + assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire))); +} +``` + +## Closing Connection + +The connection will be automatically closed on drop. To close the connection explicitly, call the `close` method. + +```rust +let db = Database::connect(url).await?; + +// Closing connection here +db.close().await?; +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-create-enum.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-create-enum.md new file mode 100644 index 00000000000..1814d3b7289 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-create-enum.md @@ -0,0 +1,221 @@ +# Create Enum + +You can generate SQL statement to create database tables with enum columns via the [`Schema`](https://docs.rs/sea-orm/*/sea_orm/schema/struct.Schema.html) helper struct. + +## String & Integer Enum + +This is just an ordinary string / integer column that maps to a Rust enum. Example entity definition: + +```rust title="active_enum.rs" +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(schema_name = "public", table_name = "active_enum")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub category: Option, + pub color: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(1))")] +pub enum Category { + #[sea_orm(string_value = "B")] + Big, + #[sea_orm(string_value = "S")] + Small, +} + +#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "i32", db_type = "Integer")] +pub enum Color { + #[sea_orm(num_value = 0)] + Black, + #[sea_orm(num_value = 1)] + White, +} +``` + +As an illustration, the enums are just ordinary database columns. + +```rust +use sea_orm::{sea_query, Schema}; + +let builder = db.get_database_backend(); +let schema = Schema::new(builder); + +assert_eq!( + builder.build(&schema.create_table_from_entity(active_enum::Entity)), + builder.build( + &sea_query::Table::create() + .table(active_enum::Entity.table_ref()) + .col(pk_auto(active_enum::Column::Id)) + .col(string_len(active_enum::Column::Category, 1)) + .col(integer(active_enum::Column::Color)) + .to_owned() + ) +); +``` + +
+ Note that non-UAX#31 compliant characters would be converted as illustrated below. + +```rust +#[derive(Clone, Debug, PartialEq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")] +pub enum StringValue { + #[sea_orm(string_value = "")] + Member1, + #[sea_orm(string_value = "$")] + Member2, + #[sea_orm(string_value = "$$")] + Member3, + #[sea_orm(string_value = "AB")] + Member4, + #[sea_orm(string_value = "A_B")] + Member5, + #[sea_orm(string_value = "A$B")] + Member6, + #[sea_orm(string_value = "0 123")] + Member7, +} + +// The following will be generated +pub enum StringValueVariant { + __Empty, + _0x24, + _0x240x24, + Ab, + A0x5Fb, + A0x24B, + _0x300x20123, +} +``` +
+ +## Native Database Enum + +Enum support is different across databases. Let's go through them one-by-one. + +Consider the following entity: + +```rust title="active_enum.rs" +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(schema_name = "public", table_name = "active_enum")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub tea: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")] +pub enum Tea { + #[sea_orm(string_value = "EverydayTea")] + EverydayTea, + #[sea_orm(string_value = "BreakfastTea")] + BreakfastTea, +} +``` + +Note the `db_type` and extra `enum_name` attributes. + +### PostgreSQL + +Enums in PostgreSQL are defined by [`TypeCreateStatement`](https://docs.rs/sea-query/*/sea_query/extension/postgres/struct.TypeCreateStatement.html), which can be created from an `Entity` with the [`Schema::create_enum_from_entity`](https://docs.rs/sea-orm/*/sea_orm/schema/struct.Schema.html#method.create_enum_from_entity) method. + +You can also create it from `ActiveEnum` with the [`Schema::create_enum_from_active_enum`](https://docs.rs/sea-orm/*/sea_orm/schema/struct.Schema.html#method.create_enum_from_active_enum) method. + +```rust +use sea_orm::{Schema, Statement}; + +let db_postgres = DbBackend::Postgres; +let schema = Schema::new(db_postgres); + +assert_eq!( + schema + .create_enum_from_entity(active_enum::Entity) + .iter() + .map(|stmt| db_postgres.build(stmt)) + .collect::>(), + [Statement::from_string( + db_postgres, + r#"CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea')"# + ),] +); + +assert_eq!( + db_postgres.build(&schema.create_enum_from_active_enum::()), + Statement::from_string( + db_postgres, + r#"CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea')"# + ) +); + +assert_eq!( + db_postgres.build(&schema.create_table_from_entity(active_enum::Entity)), + Statement::from_string( + db_postgres, + [ + r#"CREATE TABLE "public"."active_enum" ("#, + r#""id" serial NOT NULL PRIMARY KEY,"#, + r#""tea" tea"#, + r#")"#, + ] + .join(" ") + ), +); +``` + +### MySQL + +In MySQL, enum is defined on table creation so you only need to call [`Schema::create_table_from_entity`](https://docs.rs/sea-orm/*/sea_orm/schema/struct.Schema.html#method.create_table_from_entity) once. + +```rust +use sea_orm::{Schema, Statement}; + +let db_mysql = DbBackend::MySql; +let schema = Schema::new(db_mysql); + +assert_eq!( + db_mysql.build(&schema.create_table_from_entity(active_enum::Entity)), + Statement::from_string( + db_mysql, + [ + "CREATE TABLE `active_enum` (", + "`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,", + "`tea` ENUM('EverydayTea', 'BreakfastTea')", + ")", + ] + .join(" ") + ), +); +``` + +### SQLite + +Enum is not supported in SQLite so it will be stored as `TEXT`. + +```rust +use sea_orm::{Schema, Statement}; + +let db_sqlite = DbBackend::Sqlite; +let schema = Schema::new(db_sqlite); + +assert_eq!( + db_sqlite.build(&schema.create_enum_from_entity(active_enum::Entity)), + Statement::from_string( + db_sqlite, + [ + "CREATE TABLE `active_enum` (", + "`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT,", + "`tea` text", + ")", + ] + .join(" ") + ), +); +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-derive-macro.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-derive-macro.md new file mode 100644 index 00000000000..62ca609fe6a --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-derive-macro.md @@ -0,0 +1,53 @@ +# Derive Macros + +## EntityModel + +The [`EntityModel`](#) derive macro is the 'almighty' macro which automatically generates `Entity`, `Column`, and `PrimaryKey` from a given `Model`. + +## Entity + +The [`DeriveEntity`](#) derive macro will implement [`EntityTrait`](#) for `Entity` and it assumes `Model`, `Column`, `PrimaryKey` and `Relation` exist in the current scope. It also provides implementation of [`Iden`](#) and [`IdenStatic`](#) for `Entity`. + +## Column + +The [`DeriveColumn`](#) derive macro will implement [`ColumnTrait`](#) for `Columns`. It defines the identifier of each column by implementing [`Iden`](#) and [`IdenStatic`](#). The [`EnumIter`](#) is also derived, allowing iteration over all enum variants. + +## Primary Key + +The [`DerivePrimaryKey`](#) derive macro will implement [`PrimaryKeyToColumn`](#) for `PrimaryKey` which defines tedious mappings between primary keys and columns. The [`EnumIter`](#) is also derived, allowing iteration over all enum variants. + +## Model + +The [`DeriveModel`](#) derive macro will implement [`ModelTrait`](#) for `Model`, which provides setters and getters for all attributes in the model. It also implements [`FromQueryResult`](#) to convert a query result into the corresponding `Model`. + +## Active Model + +The [`DeriveActiveModel`](#) derive macro will implement [`ActiveModelTrait`](#) for `ActiveModel` which provides setters and getters for all active values in the active model. + +## Partial Model + +The [`DerivePartialModel`](#) derive macro will implement [`PartialModelTrait`](#) for `Model`. + +## Active Enum + +The [`DeriveActiveEnum`](#) derive macro will implement [`ActiveEnum`](#) for any enums. + +## Relation + +The [`DeriveRelation`](#) derive macro will implement [`RelationTrait`](#) for `Relation`. + +## RelatedEntity + +The [`DeriveRelatedEntity`](#) derive macro will implement [`seaography::RelationBuilder`](#) for `RelatedEntity` enumeration when the `seaography` feature is enabled + +## Iterable + +The [`EnumIter`](#) derive macro will implement [`Iterable`](#) to allow iteration over all enum variants. + +## Value Type + +The [`DeriveValueType`](#) derive macro will implement `From for Value`, `sea_orm::TryGetable for T` and `sea_query::ValueType for T` for `T`. + +## Display + +The [`DeriveDisplay`](#) derive macro will implement `std::fmt::Display` for enums. \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-entity-format.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-entity-format.md new file mode 100644 index 00000000000..4a44a9da183 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-entity-format.md @@ -0,0 +1,262 @@ +# Entity Format + +Let's look at a simple [Cake](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/cake.rs) entity. + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, 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 {} +``` + +:::info + +Do not delete the `Relation` enum or `ActiveModelBehavior` impl block even if they are empty. +::: + +## Entity + +The `DeriveEntityModel` macro does all the heavy lifting of defining an `Entity` with associating `Model`, `Column` and `PrimaryKey`. + +### Table Name + +The `table_name` attribute specifies the corresponding table in the database. +Optionally, you can also specify the database schema or database name by `schema_name`. + +### Column Names + +By default, all column names are assumed to be in snake_case. You can override this behaviour for all columns in a model by specifying the `rename_all` attribute. + +```rust +#[sea_orm(rename_all = "camelCase")] +pub struct Model { ... } +``` + +
+ You can find a list of valid values for the `rename_all` attribute here + +- camelCase +- kebab-case +- mixed_case +- SCREAMING_SNAKE_CASE +- snake_case +- title_case +- UPPERCASE +- lowercase +- SCREAMING-KEBAB-CASE +- PascalCase + +
+ +## Column + +### Column Name + +You can override the column name by specifying the `column_name` attribute. + +```rust +#[derive(DeriveEntityModel)] +#[sea_orm(table_name = "user", rename_all = "camelCase")] +pub struct Model { + #[sea_orm(primary_key)] + id: i32, + first_name: String, // firstName + #[sea_orm(column_name = "lAsTnAmE")] + last_name: String, // lAsTnAmE +} +``` + +### Column Type + +The `column_type` attribute defines the database type backing the attribute. Usually you don't have to specify this, as it will inferred from the rust type. For example, `i32` maps to `integer` and `String` maps to `varchar` by default. You can read more about type mappings in the next chapter. + +```rust +pub quantity: i32, // integer by default +#[sea_orm(column_type = "Decimal(Some((16, 4)))")] +pub price: Decimal, // have to specify numeric precision +``` + +Because Postgres does not natively support unsigned integer types, using unsigned types (such as `u64`) is not recommended if you want to maintain compatibility. + +### Additional Properties + +You can add additional properties `default_value`, `unique`, `indexed` and `nullable` to a column. + +If you specified a custom `column_type` for an optional attribute, you must also specify `nullable`. + +```rust +#[sea_orm(column_type = "Text", default_value = "Sam", unique, indexed, nullable)] +pub name: Option +``` + +You can define unique keys that span multiple columns, the following will result in a unique index on `(order_id, cake_id)`. + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "lineitem")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(unique_key = "item")] + pub order_id: i32, + #[sea_orm(unique_key = "item")] + pub cake_id: i32, +} +``` + +These properties are used in [create_table_from_entity](https://docs.rs/sea-orm/latest/sea_orm/schema/struct.Schema.html#method.create_table_from_entity) to generate the table for the entity. + +### Cast Column Type on Select and Save + +If you need to select a column as one type but save it into the database as another, you can specify the `select_as` and the `save_as` attributes to perform the casting. A typical use case is selecting a column of type `citext` (case-insensitive text) as `String` in Rust and saving it into the database as `citext`. One should define the model field as below: + +```rust +#[sea_orm(select_as = "text", save_as = "citext")] +pub case_insensitive_text: String +``` + +### Ignore Attribute + +If you want to ignore a particular model attribute such that it maps to no database column, you can use the `ignore` macro attribute. + +```rust +#[sea_orm(ignore)] +pub ignore_me: String +``` + +## Primary Key + +Use the `primary_key` attribute to mark a column as the primary key. + +```rust +#[sea_orm(primary_key)] +pub id: i32 +``` + +### Auto Increment + +By default, `auto_increment` is implied for `primary_key` column. Override it by specifying `false`. + +```rust +#[sea_orm(primary_key, auto_increment = false)] +pub id: i32 +``` + +### Composite Key + +This is usually the case in junction tables, where a two-column tuple is used as the primary key. Simply annotate multiple columns to define a composite primary key. `auto_increment` is `false` for composite key. + +The max arity of a primary key is 12. + +```rust +pub struct Model { + #[sea_orm(primary_key)] + pub cake_id: i32, + #[sea_orm(primary_key)] + pub fruit_id: i32, +} +``` + +## Relation + +`DeriveRelation` is a macro to help you implement the [`RelationTrait`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.RelationTrait.html). + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, +} +``` + +If there are no relations, simply write: + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} +``` + +The [Related](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Related.html) trait connects entities together, such that you can build queries selecting both entities. + +Learn more about relations in the [Relation](06-relation/01-one-to-one.md) chapter. + +## Active Model Behavior + +Hooks for different actions on an `ActiveModel`. For example, you can perform custom validation logic or trigger side effects. Inside a transaction, you can even abort an action after it is done, preventing it from saving into the database. + +```rust +#[async_trait] +impl ActiveModelBehavior for ActiveModel { + /// Create a new ActiveModel with default values. Also used by `Default::default()`. + fn new() -> Self { + Self { + uuid: Set(Uuid::new_v4()), + ..ActiveModelTrait::default() + } + } + + /// Will be triggered before insert / update + async fn before_save(self, db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { + if self.price.as_ref() <= &0.0 { + Err(DbErr::Custom(format!( + "[before_save] Invalid Price, insert: {}", + insert + ))) + } else { + Ok(self) + } + } + + /// Will be triggered after insert / update + async fn after_save(model: Model, db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { + Ok(model) + } + + /// Will be triggered before delete + async fn before_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { + Ok(self) + } + + /// Will be triggered after delete + async fn after_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { + Ok(self) + } +} +``` + +If no customization is needed, simply write: + +```rust +impl ActiveModelBehavior for ActiveModel {} +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-getting-started.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-getting-started.md new file mode 100644 index 00000000000..8d291698762 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-getting-started.md @@ -0,0 +1,183 @@ +# Getting Started + +This example can be found on [SeaORM + Seaography Example](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example). + +![](https://raw.githubusercontent.com/SeaQL/sea-orm/master/examples/seaography_example/Seaography%20example.png) + +To get started, all you need is a live SQL database with schema. You can code everything in Rust by writing SeaORM migrations, or design the schema with a GUI tool (e.g. [DataGrip](https://www.jetbrains.com/datagrip/)). + +## Install Seaography CLI + +```sh +cargo install seaography-cli@^1.1.0 +``` + +## Generate Seaography Entities + +```sh +sea-orm-cli generate entity --output-dir graphql/src/entities --seaography +``` + +Generate entities with `sea-orm-cli` like you normally do, but with an additional `--seaography` flag. The entities are basically good-old SeaORM entities, but with an additional `RelatedEntity` enum. + +```rust title="examples/seaography_example/graphql/src/entities/cake.rs" +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "cake")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub name: String, + #[sea_orm(column_type = "Decimal(Some((16, 4)))")] + pub price: Decimal, + pub bakery_id: i32, + pub gluten_free: i8, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::bakery::Entity", + from = "Column::BakeryId", + to = "super::bakery::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Bakery, + #[sea_orm(has_many = "super::cake_baker::Entity")] + CakeBaker, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Bakery.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::CakeBaker.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_baker::Relation::Baker.def() + } + fn via() -> Option { + Some(super::cake_baker::Relation::Cake.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} + +// Additional schema meta exposed to Seaography ++ #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] ++ pub enum RelatedEntity { ++ #[sea_orm(entity = "super::bakery::Entity")] ++ Bakery, ++ #[sea_orm(entity = "super::cake_baker::Entity")] ++ CakeBaker, ++ #[sea_orm(entity = "super::baker::Entity")] ++ Baker, ++ } +``` + +## Generate GraphQL Project + +Generating a fresh project is the easiest way to launch a GraphQL server. +However, Seaography can easily be integrated to an existing web server built with any web framework. + +Seaography supports Poem, Actix, and Axum out of the box. + +Run the following command: + +```sh +seaography-cli graphql graphql/src/entities $DATABASE_URL sea-orm-seaography-example +``` + +Full help: + +```sh +🧭 A dynamic GraphQL framework for SeaORM + +Usage: seaography-cli [OPTIONS] + +Arguments: + Project destination folder + SeaORM entities folder + Database URL to write in .env + Crate name for generated project + +Options: + -f, --framework + Which web framework to use [default: poem] [possible values: actix, poem, axum] + --depth-limit + GraphQL depth limit + --complexity-limit + GraphQL complexity limit + -h, --help + Print help + -V, --version + Print version +``` + +## Start the server + +```sh +cd graphql +cargo run +``` + +You are of course free to modify the project to suit your needs. +The interesting bit starts at the `seaography::register_entities!` macro in `query_root.rs`. +You can add custom entities, queries and mutations to the GraphQL schema. + +## Run some queries + +```sh +Visit GraphQL Playground at http://localhost:8000 +``` + +Navigate to the GraphQL Playground, and then start running some queries! + +### Bakery -> Cake -> Baker + +```graphql +{ + bakery(pagination: { page: { limit: 10, page: 0 } }, orderBy: { name: ASC }) { + nodes { + name + cake { + nodes { + name + price + baker { + nodes { + name + } + } + } + } + } + } +} +``` + +### List gluten-free cakes and know where to buy them + +```graphql +{ + cake(filters: { glutenFree: { eq: 1 } }) { + nodes { + name + price + glutenFree + bakery { + name + } + } + } +} +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/01-database-and-async-runtime.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/01-database-and-async-runtime.md new file mode 100644 index 00000000000..67cb9c76513 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/01-database-and-async-runtime.md @@ -0,0 +1,63 @@ +# Database & Async Runtime + +:::caution We need your support! ⭐ +Thank you for using SeaORM. Please star our [GitHub repo](https://github.com/SeaQL/sea-orm)! Your support is vital to the continued development and maintenance of SeaORM. +::: + +First, add `sea-orm` to the `[dependencies]` section of `Cargo.toml`. + +```toml title="Cargo.toml" +sea-orm = { version = "1.1.0", features = [ , , "macros" ] } +``` + +You must choose a `DATABASE_DRIVER` and an `ASYNC_RUNTIME`. `macros` is needed if you use SeaORM's generated entities (most likely). + +## DATABASE_DRIVER + +You can choose one or more from: + ++ `sqlx-mysql` - SQLx MySQL and MariaDB ++ `sqlx-postgres` - SQLx PostgreSQL ++ `sqlx-sqlite` - SQLx SQLite + +See also: [SQLx docs](https://docs.rs/crate/sqlx/latest/features). + +:::tip SQL Server (MSSQL) backend + +The installation and configuration of MSSQL driver can be found [here](https://www.sea-ql.org/SeaORM-X/docs/install-and-config/database-and-async-runtime/). + +::: + +## ASYNC_RUNTIME + +You have to choose one from: + +`runtime-async-std-native-tls`, `runtime-tokio-native-tls`, `runtime-async-std-rustls`, `runtime-tokio-rustls` + +Basically, they are in the form of `runtime-ASYNC_RUNTIME-TLS_LIB`: + ++ `ASYNC_RUNTIME` can be [`async-std`](https://crates.io/crates/async-std) or [`tokio`](https://crates.io/crates/tokio) ++ `TLS_LIB` can either be [`native-tls`](https://crates.io/crates/native-tls) or [`rustls`](https://crates.io/crates/rustls) + +1. Choose the ASYNC_RUNTIME corresponding to your Rust web framework: + +| ASYNC_RUNTIME | Web Framework | +| :-----------: | :------------: | +| `async-std` | [`Tide`](https://docs.rs/tide) | +| `tokio` | [`Axum`](https://docs.rs/axum), [`Actix`](https://actix.rs/), [`Poem`](https://docs.rs/poem), [`Rocket`](https://rocket.rs/) | + +2. `native-tls` uses the platform's native security facilities, while `rustls` is an (almost) pure Rust implementation. + +## Extra features + ++ `debug-print` - print every SQL statement to logger ++ `mock` - mock interface for unit testing ++ `macros` - procedural macros for your convenience ++ `with-chrono` - support [`chrono`](https://crates.io/crates/chrono) types ++ `with-time` - support [`time`](https://crates.io/crates/time) types ++ `with-json` - support [`serde-json`](https://crates.io/crates/serde-json) types ++ `with-rust_decimal` - support [`rust_decimal`](https://crates.io/crates/rust_decimal) types ++ `with-bigdecimal` - support [`bigdecimal`](https://crates.io/crates/bigdecimal) types ++ `with-uuid` - support [`uuid`](https://crates.io/crates/uuid) types ++ `postgres-array` - support array types in Postgres (automatically enabled when `sqlx-postgres` feature is turned on) ++ `sea-orm-internal` - opt-in unstable internal APIs (for accessing re-export SQLx types) diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/02-connection.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/02-connection.md new file mode 100644 index 00000000000..2fe57d4c028 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/02-connection.md @@ -0,0 +1,103 @@ +# Database Connection + +To obtain a database connection, use the [`Database`](https://docs.rs/sea-orm/*/sea_orm/struct.Database.html) interface: + +```rust +let db: DatabaseConnection = Database::connect("protocol://username:password@host/database").await?; +``` + +`protocol` can be `mysql:`, `postgres:` or `sqlite:`. + +`host` is usually `localhost`, a domain name or an IP address. + +:::tip + +If you can't get `localhost` to work, try putting in an IP address and port number, e.g. `127.0.0.1:3306` or even `192.168.x.x`. + +::: + +Under the hood, a [`sqlx::Pool`](https://docs.rs/sqlx/0.5/sqlx/struct.Pool.html) is created and owned by [`DatabaseConnection`](https://docs.rs/sea-orm/*/sea_orm/enum.DatabaseConnection.html). + +Each time you call `execute` or `query_one/all` on it, a connection will be acquired and released from the pool. + +Multiple queries will execute in parallel as you `await` on them. + +## Connection String + +Here are some tips for database specific options: + +### MySQL + +``` +mysql://username:password@host/database +``` + +### Postgres + +#### Specify a schema + +``` +postgres://username:password@host/database?currentSchema=my_schema +``` + +### SQLite + +#### In memory + +``` +sqlite::memory: +``` + +#### Create file if not exists + +``` +sqlite://path/to/db.sqlite?mode=rwc +``` + +#### Read only + +``` +sqlite://path/to/db.sqlite?mode=ro +``` + +## Connect Options + +To configure the connection, use the [`ConnectOptions`](https://docs.rs/sea-orm/*/sea_orm/struct.ConnectOptions.html) interface: + +```rust +let mut opt = ConnectOptions::new("protocol://username:password@host/database"); +opt.max_connections(100) + .min_connections(5) + .connect_timeout(Duration::from_secs(8)) + .acquire_timeout(Duration::from_secs(8)) + .idle_timeout(Duration::from_secs(8)) + .max_lifetime(Duration::from_secs(8)) + .sqlx_logging(true) + .sqlx_logging_level(log::LevelFilter::Info) + .set_schema_search_path("my_schema"); // Setting default PostgreSQL schema + +let db = Database::connect(opt).await?; +``` + +## Checking Connection is Valid + +Checks if a connection to the database is still valid. + +```rust +|db: DatabaseConnection| { + assert!(db.ping().await.is_ok()); + db.clone().close().await; + assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire))); +} +``` + +## Closing Connection + +The connection will be automatically closed on drop. To close the connection explicitly, call the `close` method. + +```rust +let db = Database::connect(url).await?; + +// Closing connection here +db.close().await?; +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/03-debug-log.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/03-debug-log.md new file mode 100644 index 00000000000..12be66e39cb --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/03-debug-log.md @@ -0,0 +1,39 @@ +# Debug Log + +SeaORM logs debug messages via the [`tracing`](https://crates.io/crates/tracing) crate. + +You can enable SeaORM's logging with the `debug-print` feature flag: + +```toml +[dependencies.sea-orm] +version = "1.1.0" +features = ["debug-print"] +``` + +You need to setup [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) to capture and view the debug log. See the snippet below and a complete example [here](https://github.com/SeaQL/sea-orm/blob/master/examples/actix_example/src/main.rs). + +```rust +pub async fn main() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .with_test_writer() + .init(); + + // ... +} +``` + +SeaORM's debug print injects parameters into the SQL string, which makes it easier to read. Instead of seeing `SELECT "chef"."name" FROM "chef" WHERE "chef"."id" = $1`, you will see `SELECT "chef"."name" FROM "chef" WHERE "chef"."id" = 100`. + +## SQLx Logging + +SQLx also logs by default. If you turned on SeaORM's `debug-print`, you can disable SQLx's log by passing [`ConnectOptions`](https://docs.rs/sea-orm/*/sea_orm/struct.ConnectOptions.html) to `connect()`. + +```rust +let mut opt = ConnectOptions::new("protocol://username:password@host/database".to_owned()); +opt + .sqlx_logging(false) // Disable SQLx log + .sqlx_logging_level(log::LevelFilter::Info); // Or set SQLx log level + +let db = Database::connect(opt).await?; +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/_category_.json b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/_category_.json new file mode 100644 index 00000000000..45d86975adf --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Installation & Configuration", + "collapsed": false +} diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-mock.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-mock.md new file mode 100644 index 00000000000..e718c53907f --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-mock.md @@ -0,0 +1,218 @@ +# Mock Interface + +You can unit test your application logic using the mock database interface. + +:::info +You need to enable the `mock` feature flag in Cargo.toml. +::: + +The mock database has no data in it, so you have to define the expected data to be returned when CRUD operations are performed. +- The query result should be provided to support select operations +- The exec result should be provided to support insert, update, and delete operations + +To ensure the correctness of your application logic, you can also validate the transaction log in the mock database. + +Check out how we write unit tests using mock connection [here](https://github.com/SeaQL/sea-orm/blob/master/src/executor/paginator.rs#L250). + +## Mocking Query Result + +We create a mock database for PostgreSQL with `MockDatabase::new(DatabaseBackend::Postgres)`. Then, query results are prepared using the `append_query_results` method. Note that we pass a vector of vectors to it, representing multiple query results, each with more than one model. Finally, we convert it into a connection and use it to perform CRUD operations just like a normal live connection. + +One special thing about `MockDatabase` is that you can check the transaction log of it. Any SQL query run on the mock database will be recorded; you can validate each of the log to ensure the correctness of your application logic. + +```rust +#[cfg(test)] +mod tests { + use sea_orm::{ + entity::prelude::*, entity::*, tests_cfg::*, + DatabaseBackend, MockDatabase, Transaction, + }; + + #[async_std::test] + async fn test_find_cake() -> Result<(), DbErr> { + // Create MockDatabase with mock query results + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results([ + // First query result + vec![cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + }], + // Second query result + vec![ + cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + }, + cake::Model { + id: 2, + name: "Chocolate Forest".to_owned(), + }, + ], + ]) + .append_query_results([ + // Third query result + [( + cake::Model { + id: 1, + name: "Apple Cake".to_owned(), + }, + fruit::Model { + id: 2, + name: "Apple".to_owned(), + cake_id: Some(1), + }, + )], + ]) + .into_connection(); + + // Find a cake from MockDatabase + // Return the first query result + assert_eq!( + cake::Entity::find().one(&db).await?, + Some(cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + }) + ); + + // Find all cakes from MockDatabase + // Return the second query result + assert_eq!( + cake::Entity::find().all(&db).await?, + [ + cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + }, + cake::Model { + id: 2, + name: "Chocolate Forest".to_owned(), + }, + ] + ); + + // Find all cakes with its related fruits + assert_eq!( + cake::Entity::find() + .find_also_related(fruit::Entity) + .all(&db) + .await?, + [( + cake::Model { + id: 1, + name: "Apple Cake".to_owned(), + }, + Some(fruit::Model { + id: 2, + name: "Apple".to_owned(), + cake_id: Some(1), + }) + )] + ); + + // Checking transaction log + assert_eq!( + db.into_transaction_log(), + [ + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, + [1u64.into()] + ), + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, + [] + ), + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name", "fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id" FROM "cake" LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#, + [] + ), + ] + ); + + Ok(()) + } +} +``` + +## Mocking Execution Result + +This is very similar to mocking query result, the differences are that we use the `append_exec_results` method here and we perform insert, update, and delete operations here in the unit test. The `append_exec_results` method takes a vector of `MockExecResult`, each representing the exec result of the corresponding operation. + +```rust +#[cfg(test)] +mod tests { + use sea_orm::{ + entity::prelude::*, entity::*, tests_cfg::*, + DatabaseBackend, MockDatabase, MockExecResult, Transaction, + }; + + #[async_std::test] + async fn test_insert_cake() -> Result<(), DbErr> { + // Create MockDatabase with mock execution result + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results([ + [cake::Model { + id: 15, + name: "Apple Pie".to_owned(), + }], + [cake::Model { + id: 16, + name: "Apple Pie".to_owned(), + }], + ]) + .append_exec_results([ + MockExecResult { + last_insert_id: 15, + rows_affected: 1, + }, + MockExecResult { + last_insert_id: 16, + rows_affected: 1, + }, + ]) + .into_connection(); + + // Prepare the ActiveModel + let apple = cake::ActiveModel { + name: Set("Apple Pie".to_owned()), + ..Default::default() + }; + + // Insert the ActiveModel into MockDatabase + assert_eq!( + apple.clone().insert(&db).await?, + cake::Model { + id: 15, + name: "Apple Pie".to_owned() + } + ); + + // If you want to check the last insert id + let insert_result = cake::Entity::insert(apple).exec(&db).await?; + assert_eq!(insert_result.last_insert_id, 16); + + // Checking transaction log + assert_eq!( + db.into_transaction_log(), + [ + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id", "name""#, + ["Apple Pie".into()] + ), + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#, + ["Apple Pie".into()] + ), + ] + ); + + Ok(()) + } +} +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-one-to-many.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-one-to-many.md new file mode 100644 index 00000000000..7bdf5ec1e1a --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-one-to-many.md @@ -0,0 +1,96 @@ +# One to Many + +A one-to-many relation is similar to a one-to-one relation. In the previous section, we gave the example of "a `Cake` entity has at most one `Fruit` topping". To make it a one-to-many relation, we remove the "at most one" constraint. So, we have a `Cake` entity that might have many `Fruit` toppings. + +## Defining the Relation + +This is almost identical to defining a one-to-one relation; the only difference is that we use `Entity::has_many()` method here. + +```rust title="entity/cake.rs" +#[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() + } +} +``` + +
+ It's expanded to: + +```rust {3,9,16} +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} +``` +
+ +## Defining the Inverse Relation + +It is the same as defining the one-to-one inverse relation. + +```rust title="entity/fruit.rs" +#[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() + } +} +``` + +
+ It's expanded to: + +```rust +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} +``` +
\ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-select.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-select.md new file mode 100644 index 00000000000..5a1e8861b25 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-select.md @@ -0,0 +1,164 @@ +# Select + +Once you have defined the entity, you are ready to retrieve data from the database. Each row of data in the database corresponds to a `Model`. + +By default, SeaORM will select all columns defined in the `Column` enum. + +## Find by Primary Key + +Find a model by its primary key, it can be a single key or composite key. We start by calling [`find_by_id`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html#method.find_by_id) on [`Entity`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html) which helps you construct the select query and condition automatically. Then, fetch a single model from the database with the `one` method. + +```rust +use super::cake::Entity as Cake; +use super::cake_filling::Entity as CakeFilling; + +// Find by primary key +let cheese: Option = Cake::find_by_id(1).one(db).await?; + +// Find by composite primary keys +let vanilla: Option = CakeFilling::find_by_id((6, 8)).one(db).await?; +``` + +## Find with Conditions and Orders + +In addition to retrieving a model by primary key, you can also retrieve one or more models matching specific conditions in a certain order. The [`find`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html#method.find) method gives you access to the query builder in SeaORM. It supports the construction of all common select expressions like `where` and `order by`. They can be constructed using [`filter`](https://docs.rs/sea-orm/*/sea_orm/query/trait.QueryFilter.html#method.filter) and [`order_by_*`](https://docs.rs/sea-orm/*/sea_orm/query/trait.QueryOrder.html#method.order_by) methods respectively. + +> Read more about [conditional expression](08-advanced-query/02-conditional-expression.md). + +```rust +let chocolate: Vec = Cake::find() + .filter(cake::Column::Name.contains("chocolate")) + .order_by_asc(cake::Column::Name) + .all(db) + .await?; +``` + +## Find Related Models + +> Read more on the [Relation](06-relation/01-one-to-one.md) chapter. + +### Lazy Loading + +Use the [`find_related`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.ModelTrait.html#method.find_related) method. + +Related models are loaded on demand when you ask for them, preferable if you want to load related models based on some application logic. Note that lazy loading will increase database round trips compared to eager loading. + +```rust +// Find a cake model first +let cheese: Option = Cake::find_by_id(1).one(db).await?; +let cheese: cake::Model = cheese.unwrap(); + +// Then, find all related fruits of this cake +let fruits: Vec = cheese.find_related(Fruit).all(db).await?; +``` + +### Eager Loading + +All related models are loaded at once. This provides minimum database round trips compared to lazy loading. + +#### One to One + +Use the [`find_also_related`](https://docs.rs/sea-orm/*/sea_orm/query/struct.Select.html#method.find_also_related) method. + +```rust +let fruits_and_cakes: Vec<(fruit::Model, Option)> = Fruit::find().find_also_related(Cake).all(db).await?; +``` + +#### One to Many / Many to Many + +Using the [`find_with_related`](https://docs.rs/sea-orm/*/sea_orm/query/struct.Select.html#method.find_with_related) method, the related models will be grouped by the parent models. This method handles both 1-N and M-N cases, and will perform 2 joins when there is a junction table involved. + +```rust +let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() + .find_with_related(Fruit) + .all(db) + .await?; +``` + +### Batch Loading + +Since 0.11, we introduced a [LoaderTrait](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html) to load related entities in batches. + +Compared to eager loading, it saves bandwidth (consider the one to many case, the one side rows may duplicate) at the cost of one (or two, in the case of many to many) more database roundtrip. + +#### One to One + +Use the [load_one](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_one) method. + +```rust +let fruits: Vec = Fruit::find().all(db).await?; +let cakes: Vec> = fruits.load_one(Cake, db).await?; +``` + +#### One to Many + +Use the [load_many](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_many) method. + +```rust +let cakes: Vec = Cake::find().all(db).await?; +let fruits: Vec> = cakes.load_many(Fruit, db).await?; +``` + +#### Many to Many + +Use the [load_many_to_many](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_many_to_many) method. You have to provide the junction Entity. + +```rust +let cakes: Vec = Cake::find().all(db).await?; +let fillings: Vec> = cakes.load_many_to_many(Filling, CakeFilling, db).await?; +``` + +## Paginate Result + +Convert any SeaORM select into a [paginator](https://docs.rs/sea-orm/*/sea_orm/struct.Paginator.html) with custom page size. + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake}; +let mut cake_pages = cake::Entity::find() + .order_by_asc(cake::Column::Id) + .paginate(db, 50); + +while let Some(cakes) = cake_pages.fetch_and_next().await? { + // Do something on cakes: Vec +} +``` + +## Cursor Pagination + +Use cursor pagination If you want to paginate rows based on column(s) such as the primary key. + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake}; +// Create a cursor that order by `cake`.`id` +let mut cursor = cake::Entity::find().cursor_by(cake::Column::Id); + +// Filter paginated result by `cake`.`id` > 1 AND `cake`.`id` < 100 +cursor.after(1).before(100); + +// Get first 10 rows (order by `cake`.`id` ASC) +for cake in cursor.first(10).all(db).await? { + // Do something on cake: cake::Model +} + +// Get last 10 rows (order by `cake`.`id` DESC but rows are returned in ascending order) +for cake in cursor.last(10).all(db).await? { + // Do something on cake: cake::Model +} +``` + +Paginate rows based on a composite primary key is also available. + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake_filling}; +let rows = cake_filling::Entity::find() + .cursor_by((cake_filling::Column::CakeId, cake_filling::Column::FillingId)) + .after((0, 1)) + .before((10, 11)) + .first(3) + .all(&db) + .await?; +``` + +## Select custom + +If you want to select custom columns and expressions, read the [custom select](08-advanced-query/01-custom-select.md) section. diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-writing-migration.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-writing-migration.md new file mode 100644 index 00000000000..2b919c880dc --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-writing-migration.md @@ -0,0 +1,382 @@ +# Writing Migration + +Each migration contains two methods: `up` and `down`. The `up` method is used to alter the database schema, such as adding new tables, columns or indexes, while the `down` method revert the actions performed in the `up` method. + +The SeaORM migration system has the following advantages: + +1. Write DDL statements with SeaQuery or SQL +2. Execute multiple DDL (with conditions) +3. Seed data using the SeaORM API + +## Creating Migrations + +Generate a new migration file by executing `sea-orm-cli migrate generate` command. + +If you name the file with spaces, it will be converted according to the convention automatically. + +```shell +sea-orm-cli migrate generate NAME_OF_MIGRATION [--local-time] + +# E.g. to generate `migration/src/m20220101_000001_create_table.rs` shown below +sea-orm-cli migrate generate create_table + +# This create the same migration file as above command +sea-orm-cli migrate generate "create table" +``` + +Or you can create a migration file using the template below. Name the file according to the naming convention `mYYYYMMDD_HHMMSS_migration_name.rs`. + +```rust title="migration/src/m20220101_000001_create_table.rs" +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( ... ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table( ... ) + .await + } +} +``` + +Additionally, you have to include the new migration in the [`MigratorTrait::migrations`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/migrator/trait.MigratorTrait.html#tymethod.migrations) method. Note that the migrations should be sorted in chronological order. + +```rust title="migration/src/lib.rs" +pub use sea_orm_migration::*; + +mod m20220101_000001_create_table; + +pub struct Migrator; + +#[async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20220101_000001_create_table::Migration), + ] + } +} +``` + +## Defining Migration + +See [`SchemaManager`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/manager/struct.SchemaManager.html) for API reference. + +### Using SeaQuery + +Click [here](https://github.com/SeaQL/sea-query#table-create) to take a quick tour of SeaQuery's DDL statements. + +You can use the [`DeriveIden`](https://docs.rs/sea-orm/*/sea_orm/derive.DeriveIden.html) macro to define identifiers that will be used in your migration. + +```rust +#[derive(DeriveIden)] +enum Post { + Table, // this is a special case; will be mapped to `post` + Id, + Title, + #[sea_orm(iden = "full_text")] // Renaming the identifier + Text, +} + +assert_eq!(Post::Table.to_string(), "post"); +assert_eq!(Post::Id.to_string(), "id"); +assert_eq!(Post::Title.to_string(), "title"); +assert_eq!(Post::Text.to_string(), "full_text"); +``` + +Here are some common DDL snippets you may find useful. + +#### Schema Creation Methods +- Create Table + ```rust + use sea_orm::{EnumIter, Iterable}; + + #[derive(DeriveIden)] + enum Post { + Table, + Id, + Title, + #[sea_orm(iden = "text")] // Renaming the identifier + Text, + Category, + } + + #[derive(Iden, EnumIter)] + pub enum Category { + #[iden = "Feed"] + Feed, + #[iden = "Story"] + Story, + } + + // Remember to import `sea_orm_migration::schema::*` schema helpers into scope + use sea_orm_migration::{prelude::*, schema::*}; + + // Defining the table schema + manager + .create_table( + Table::create() + .table(Post::Table) + .if_not_exists() + .col(pk_auto(Post::Id)) + .col(string(Post::Title)) + .col(string(Post::Text)) + .col(enumeration_null(Post::Category, "category", Category::iter())) + ) + .await + + // above is equivalent to: + manager + .create_table( + Table::create() + .table(Post::Table) + .if_not_exists() + .col(ColumnDef::new(Post::Id) + .integer() + .not_null() + .auto_increment() + .primary_key() + ) + .col(ColumnDef::new(Post::Title).string().not_null()) + .col(ColumnDef::new(Post::Text).string().not_null()) + .col(ColumnDef::new(Post::Category) + .enumeration("category", Category::iter())) + ) + .await + ``` +- Create Index + ```rust + manager.create_index(sea_query::Index::create()..) + ``` +- Create Foreign Key + ```rust + manager.create_foreign_key(sea_query::ForeignKey::create()..) + ``` +- Create Data Type (PostgreSQL only) + ```rust + use sea_orm::{EnumIter, Iterable}; + use sea_orm_migration::prelude::extension::postgres::Type; + + #[derive(DeriveIden)] + struct CategoryEnum; + + #[derive(DeriveIden, EnumIter)] + enum CategoryVariants { + Feed, + #[sea_orm(iden = "story")] + Story, + } + + manager + .create_type( + Type::create() + .as_enum(CategoryEnum) + .values(CategoryVariants::iter()) + .to_owned() + ) + .await?; + ``` + +#### Schema Mutation Methods +- Drop Table + ```rust + use entity::post; + + manager.drop_table(sea_query::Table::drop()..) + ``` +- Alter Table + ```rust + manager.alter_table(sea_query::Table::alter()..) + ``` +- Rename Table + ```rust + manager.rename_table(sea_query::Table::rename()..) + ``` +- Truncate Table + ```rust + manager.truncate_table(sea_query::Table::truncate()..) + ``` +- Drop Index + ```rust + manager.drop_index(sea_query::Index::drop()..) + ``` +- Drop Foreign Key + ```rust + manager.drop_foreign_key(sea_query::ForeignKey::drop()..) + ``` +- Alter Data Type (PostgreSQL only) + ```rust + manager.alter_type(sea_query::Type::alter()..) + ``` +- Drop Data Type (PostgreSQL only) + ```rust + manager.drop_type(sea_query::extension::postgres::Type()..) + ``` + +#### Schema Inspection Methods + +- Has Table + ```rust + manager.has_table("table_name") + ``` +- Has Column + ```rust + manager.has_column("table_name", "column_name") + ``` +- Has Index + ```rust + manager.has_index("table_name", "index_name") + ``` + +### Using raw SQL + +You can write migration files in raw SQL, but then you lost the multi-backend compatibility SeaQuery offers. + +```rust title="migration/src/m20220101_000001_create_table.rs" +use sea_orm::Statement; +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + // Use `execute_unprepared` if the SQL statement doesn't have value bindings + db.execute_unprepared( + "CREATE TABLE `cake` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` varchar(255) NOT NULL + )" + ) + .await?; + + // Construct a `Statement` if the SQL contains value bindings + let stmt = Statement::from_sql_and_values( + manager.get_database_backend(), + r#"INSERT INTO `cake` (`name`) VALUES (?)"#, + ["Cheese Cake".into()] + ); + db.execute(stmt).await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .get_connection() + .execute_unprepared("DROP TABLE `cake`") + .await?; + + Ok(()) + } +} +``` + +## Tip 1: combining multiple schema changes in one migration + +You can combine multiple changes within both up and down migration functions. Here is a complete example: + +```rust +// Remember to import `sea_orm_migration::schema::*` schema helpers into scope +use sea_orm_migration::{prelude::*, schema::*}; + +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + + manager + .create_table( + sea_query::Table::create() + .table(Post::Table) + .if_not_exists() + .col(pk_auto(Post::Id)) + .col(string(Post::Title)) + .col(string(Post::Text)) + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx-post_title") + .table(Post::Table) + .col(Post::Title) + ) + .await?; + + Ok(()) // All good! +} +``` + +and here we have the matching down function: + +```rust +async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + + manager.drop_index(Index::drop().name("idx-post-title")) + .await?; + + manager.drop_table(Table::drop().table(Post::Table)) + .await?; + + Ok(()) // All good! +} +``` + +## Tip 2: `ADD COLUMN IF NOT EXISTS` + +Since this syntax is not available on MySQL, you can: + +```rust +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + if !manager.has_column("my_table", "col_to_add").await? { + // ALTER TABLE `my_table` ADD COLUMN `col_to_add` .. + } + + Ok(()) +} +``` + +## Tip 3: Seed data with Entity + +```rust +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + cake::ActiveModel { + name: Set("Cheesecake".to_owned()), + ..Default::default() + } + .insert(db) + .await?; + + Ok(()) +} +``` + +[Full example](https://github.com/SeaQL/sea-orm/blob/master/examples/seaography_example/migration/src/m20230102_000001_seed_bakery_data.rs). + +## Atomic Migration + +Migration will be executed in Postgres atomically that means migration scripts will be executed inside a transaction. Changes done to the database will be rolled back if the migration failed. However, atomic migration is not supported in MySQL and SQLite. + +You can start a transaction inside each migration to perform operations like [seeding sample data](03-migration/04-seeding-data.md#seeding-data-transactionally) for a newly created table. + +## Schema first or Entity first? + +In the grand scheme of things, we recommend a schema first approach: you write migrations first and then generate entities from a live database. + +At times, you might want to use the [`create_*_from_entity`](09-schema-statement/01-create-table.md) methods to bootstrap your database with several hand written entity files. + +That's perfectly fine if you intend to never change the entity schema. Or, you can keep the original entity and embed a copy in the migration file. diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-active-model.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-active-model.md new file mode 100644 index 00000000000..895eb52221d --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-active-model.md @@ -0,0 +1,127 @@ +# ActiveModel + +Before diving into insert and update operations we have to introduce `ActiveValue` and `ActiveModel`. + +## ActiveValue + +The state of a field in an `ActiveModel`. + +There are three possible states represented by three enum variants: + +- `Set` - A `Value` that's explicitly set by the application and sent to the database. + Use this to insert or set a specific value. + + When editing an existing value, you can use [`set_if_not_equal`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ActiveValue.html#method.set_if_not_equals) + to preserve the `Unchanged` state when the new value is the same as the old one. + Then you can meaningfully use methods like [`ActiveModelTrait::is_changed`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.ActiveModelTrait.html#method.is_changed). +- `Unchanged` - An existing, unchanged `Value` from the database. + + You get these when you query an existing `Model` + from the database and convert it into an `ActiveModel`. +- `NotSet` - An undefined `Value`. Nothing is sent to the database. + + When you create a new `ActiveModel`, its fields are `NotSet` by default. + + This can be useful when: + + - You insert a new record and want the database to generate a default value (e.g., an id). + - In an `UPDATE` statement, you don't want to update some fields. + +The difference between these states is useful +when constructing `INSERT` and `UPDATE` SQL statements (see examples below). +It's also useful for knowing which fields have changed in a record. + +### Examples + +```rust +use sea_orm::tests_cfg::{cake, fruit}; +use sea_orm::{DbBackend, entity::*, query::*}; + +// Here, we use `NotSet` to let the database automatically generate an `id`. +// This is different from `Set(None)` that explicitly sets `cake_id` to `NULL`. +assert_eq!( + Insert::one(fruit::ActiveModel { + id: ActiveValue::NotSet, + name: ActiveValue::Set("Orange".to_owned()), + cake_id: ActiveValue::Set(None), + }) + .build(DbBackend::Postgres) + .to_string(), + r#"INSERT INTO "fruit" ("name", "cake_id") VALUES ('Orange', NULL)"# +); + +// Here, we update the record, set `cake_id` to the new value +// and use `NotSet` to avoid updating the `name` field. +// `id` is the primary key, so it's used in the condition and not updated. +assert_eq!( + Update::one(fruit::ActiveModel { + id: ActiveValue::Unchanged(1), + name: ActiveValue::NotSet, + cake_id: ActiveValue::Set(Some(2)), + }) + .build(DbBackend::Postgres) + .to_string(), + r#"UPDATE "fruit" SET "cake_id" = 2 WHERE "fruit"."id" = 1"# +); +``` + +## ActiveModel + +An `ActiveModel` has all the attributes of `Model` wrapped in `ActiveValue`. + +You can use `ActiveModel` to insert a row with a subset of columns set. + +```rust +let cheese: Option = Cake::find_by_id(1).one(db).await?; + +// Get Model +let model: cake::Model = cheese.unwrap(); +assert_eq!(model.name, "Cheese Cake".to_owned()); + +// Into ActiveModel +let active_model: cake::ActiveModel = model.into(); +assert_eq!(active_model.name, ActiveValue::unchanged("Cheese Cake".to_owned())); +``` + +### Checking if an ActiveModel is changed + +You can check whether any field in an `ActiveModel` is `Set` with the [`is_changed`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ActiveModelTrait.html#method.is_changed) method. + +```rust +let mut fruit: fruit::ActiveModel = Default::default(); +assert!(!fruit.is_changed()); + +fruit.set(fruit::Column::Name, "apple".into()); +assert!(fruit.is_changed()); +``` + +### Convert ActiveModel back to Model + +Using [`try_into_model`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.TryIntoModel.html#tymethod.try_into_model) method you can convert ActiveModel back to Model. + +```rust +assert_eq!( + ActiveModel { + id: Set(2), + name: Set("Apple".to_owned()), + cake_id: Set(Some(1)), + } + .try_into_model() + .unwrap(), + Model { + id: 2, + name: "Apple".to_owned(), + cake_id: Some(1), + } +); + +assert_eq!( + ActiveModel { + id: Set(1), + name: NotSet, + cake_id: Set(None), + } + .try_into_model(), + Err(DbErr::AttrNotSet(String::from("name"))) +); +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-aggregate-function.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-aggregate-function.md new file mode 100644 index 00000000000..4633bd01861 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-aggregate-function.md @@ -0,0 +1,63 @@ +# Aggregate Functions + +You can group results selected from SeaORM find with the `group_by` method. If you wish to further restrict the grouped result set, the `having` method can help you achieve that. + +## Group By + +The `group_by` method can take a column of the entity or a complex [`sea_query::SimpleExpr`](https://docs.rs/sea-query/*/sea_query/expr/enum.SimpleExpr.html). + +```rust +assert_eq!( + cake::Entity::find() + .select_only() + .column(cake::Column::Name) + .group_by(cake::Column::Name) + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name""# +); + +assert_eq!( + cake::Entity::find() + .select_only() + .column_as(cake::Column::Id.count(), "count") + .column_as(cake::Column::Id.sum(), "sum_of_id") + .group_by(cake::Column::Name) + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT COUNT("cake"."id") AS "count", SUM("cake"."id") AS "sum_of_id" FROM "cake" GROUP BY "cake"."name""# +); +``` + +## Having + +The `having` method can take any conditional expressions introduced in the previous section. + +```rust +assert_eq!( + cake::Entity::find() + .having(cake::Column::Id.eq(4)) + .having(cake::Column::Id.eq(5)) + .build(DbBackend::MySql) + .to_string(), + "SELECT `cake`.`id`, `cake`.`name` FROM `cake` HAVING `cake`.`id` = 4 AND `cake`.`id` = 5" +); + +assert_eq!( + cake::Entity::find() + .select_only() + .column_as(cake::Column::Id.count(), "count") + .column_as(cake::Column::Id.sum(), "sum_of_id") + .group_by(cake::Column::Name) + .having(Expr::col("count").gt(6)) + .build(DbBackend::MySql) + .to_string(), + "SELECT COUNT(`cake`.`id`) AS `count`, SUM(`cake`.`id`) AS `sum_of_id` FROM `cake` GROUP BY `cake`.`name` HAVING `count` > 6" +); +``` + +:::info + +Aggregation functions such as `max`, `min`, `sum`, `count` are available in [`ColumnTrait`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ColumnTrait.html). + +::: diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-column-types.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-column-types.md new file mode 100644 index 00000000000..d5ac06c5305 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-column-types.md @@ -0,0 +1,150 @@ +# Column Types + +## Type mappings + +The column type will be derived automatically with the following mapping. + +:::tip SQL Server (MSSQL) backend + +The type mappings of MSSQL can be found [here](https://www.sea-ql.org/SeaORM-X/docs/generate-entity/entity-structure/). + +::: + +For the mappings of Rust primitive data types: + +| Rust type | Database Type
([`ColumnType`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ColumnType.html)) | SQLite
datatype | MySQL
datatype | PostgreSQL
datatype | +| --------- | --------- | --------- | --------- | --------- | +| `String` | Char | char | char | char | +| `String` | String | varchar | varchar | varchar | +| `i8` | TinyInteger | tinyint | tinyint | char | +| `u8` | TinyUnsigned | tinyint | tinyint unsigned | N/A | +| `i16` | SmallInteger | smallint | smallint | smallint | +| `u16` | SmallUnsigned | smallint | smallint unsigned | N/A | +| `i32` | Integer | integer | int | integer | +| `u32` | Unsigned | integer | int unsigned | N/A | +| `i64` | BigInteger | bigint | bigint | bigint | +| `u64` | BigUnsigned | bigint | bigint unsigned | N/A | +| `f32` | Float | float | float | real | +| `f64` | Double | double | double | double precision | +| `bool` | Boolean | boolean | bool | bool | +| `Vec` | Binary | blob | blob | bytea | + +For the mappings of Rust non-primitive data types. You can check [`entity/prelude.rs`](https://github.com/SeaQL/sea-orm/blob/master/src/entity/prelude.rs) for all of the reexported types. + +| Rust type | Database Type
([`ColumnType`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ColumnType.html)) | SQLite
datatype | MySQL
datatype | PostgreSQL
datatype | +| --------- | --------- | --------- | --------- | --------- | +| `Date`: chrono::NaiveDate
`TimeDate`: time::Date | Date | date_text | date | date | +| `Time`: chrono::NaiveTime
`TimeTime`: time::Time | Time | time_text | time | time | +| `DateTime`: chrono::NaiveDateTime
`TimeDateTime`: time::PrimitiveDateTime | DateTime | datetime_text | datetime | timestamp | +| `DateTimeLocal`: chrono::DateTime<Local>
`DateTimeUtc`: chrono::DateTime<Utc> | Timestamp | timestamp_text | timestamp | N/A | +| `DateTimeWithTimeZone`: chrono::DateTime<FixedOffset>
`TimeDateTimeWithTimeZone`: time::OffsetDateTime | TimestampWithTimeZone | timestamp_with_timezone_text | timestamp | timestamp with time zone | +| `Uuid`: uuid::Uuid, uuid::fmt::Braced, uuid::fmt::Hyphenated, uuid::fmt::Simple, uuid::fmt::Urn | Uuid | uuid_text | binary(16) | uuid | +| `Json`: serde_json::Value | Json | json_text | json | json | +| `Decimal`: rust_decimal::Decimal | Decimal | real | decimal | decimal | +| `PgVector`: pgvector::Vector | Vector | N/A | N/A | vector | +| `IpNetwork`: ipnetwork::IpNetwork | Inet | N/A | N/A | inet | + +You can override the default mappings between a Rust type and `ColumnType` with the `column_type` attribute. + +```rust +#[sea_orm(column_type = "Text")] +pub name: String +``` + +## JSON column + +If you need your JSON field to be deserialized into a struct. You would need to derive `FromJsonQueryResult` for it. + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "json_struct")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + // JSON column as `serde_json::Value` + pub json: Json, + // JSON column as custom struct + pub json_value: KeyValue, + // nullable JSON column as custom struct, backed by jsonb (Postgres only) + #[sea_orm(column_type = "JsonBinary")] + pub json_value_opt: Option, + // JSON column storing a vector of objects + pub json_value_vec: Vec, +} + +// The custom struct must derive `FromJsonQueryResult`, `Serialize` and `Deserialize` +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] +pub struct KeyValue { + pub id: i32, + pub name: String, + pub price: f32, + pub notes: Option, +} +``` + +If you want a cross-database way of implementing array column, you can wrap it with a wrapper type. + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "json_string_vec")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + // nullable JSON column storing a vector of string + pub str_vec: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] +pub struct StringVec(pub Vec); +``` + +More details and examples in the next chapter. + +## Postgres Array + +Array datatype is a Postgres-only feature. You can define a vector of primitive types that is already supported by SeaORM. + +```rust +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "collection")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub integers: Vec, + pub integers_opt: Option>, + pub floats: Vec, + pub doubles: Vec, + pub strings: Vec, +} +``` + +## Postgres Vector + +Since `1.1.6`, PgVector support is added. Requires `postgres-vector` feature flag. + +```rust +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "image_model")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: i32, + pub embedding: PgVector, +} +``` + +For a complete example, see [embedding_tests](https://github.com/SeaQL/sea-orm/blob/1.1.x/tests/embedding_tests.rs). + +## IpNetwork (Postgres) + +Since `1.1.8`, IpNetwork support is added. Requires `with-ipnetwork` feature flag. + +```rust +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "host_network")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub ipaddress: IpNetwork, + #[sea_orm(column_type = "Cidr")] + pub network: IpNetwork, +} diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-create-index.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-create-index.md new file mode 100644 index 00000000000..61c69049ca9 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-create-index.md @@ -0,0 +1,52 @@ +# Create Index + +You can create indices from entities using [`Schema::create_index_from_entity`](https://docs.rs/sea-orm/*/sea_orm/schema/struct.Schema.html#method.create_index_from_entity), or construct [`IndexCreateStatement`](https://docs.rs/sea-query/*/sea_query/index/struct.IndexCreateStatement.html) manually. + +Example [`Indexes`](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/indexes.rs) entity: + +```rust title="indexes.rs" +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(indexed)] + pub index1_attr: i32, + #[sea_orm(unique, indexed)] + pub index2_attr: i32, + #[sea_orm(unique_key = "my_unique")] + pub unique_key_a: String, + #[sea_orm(unique_key = "my_unique")] + pub unique_key_b: String, +} +``` + +```rust +use sea_orm::{sea_query, tests_cfg::*, Schema}; + +let builder = db.get_database_backend(); + +let stmts = Schema::new(builder).create_index_from_entity(indexes::Entity); + +let idx: IndexCreateStatement = Index::create() + .name("idx-indexes-index1_attr") + .table(indexes::Entity) + .col(indexes::Column::Index1Attr) + .to_owned(); +assert_eq!(builder.build(&stmts[0]), builder.build(&idx)); + +let idx: IndexCreateStatement = Index::create() + .name("idx-indexes-index2_attr") + .table(indexes::Entity) + .col(indexes::Column::Index2Attr) + .unique() + .take(); +assert_eq!(builder.build(&stmts[1]), builder.build(&idx)); + +let idx: IndexCreateStatement = Index::create() + .name("idx-indexes-my_unique") + .table(indexes::Entity) + .col(indexes::Column::UniqueKeyA) + .col(indexes::Column::UniqueKeyB) + .unique() + .take(); +assert_eq!(builder.build(&stmts[2]), builder.build(&idx)); +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-debug-log.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-debug-log.md new file mode 100644 index 00000000000..12be66e39cb --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-debug-log.md @@ -0,0 +1,39 @@ +# Debug Log + +SeaORM logs debug messages via the [`tracing`](https://crates.io/crates/tracing) crate. + +You can enable SeaORM's logging with the `debug-print` feature flag: + +```toml +[dependencies.sea-orm] +version = "1.1.0" +features = ["debug-print"] +``` + +You need to setup [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) to capture and view the debug log. See the snippet below and a complete example [here](https://github.com/SeaQL/sea-orm/blob/master/examples/actix_example/src/main.rs). + +```rust +pub async fn main() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .with_test_writer() + .init(); + + // ... +} +``` + +SeaORM's debug print injects parameters into the SQL string, which makes it easier to read. Instead of seeing `SELECT "chef"."name" FROM "chef" WHERE "chef"."id" = $1`, you will see `SELECT "chef"."name" FROM "chef" WHERE "chef"."id" = 100`. + +## SQLx Logging + +SQLx also logs by default. If you turned on SeaORM's `debug-print`, you can disable SQLx's log by passing [`ConnectOptions`](https://docs.rs/sea-orm/*/sea_orm/struct.ConnectOptions.html) to `connect()`. + +```rust +let mut opt = ConnectOptions::new("protocol://username:password@host/database".to_owned()); +opt + .sqlx_logging(false) // Disable SQLx log + .sqlx_logging_level(log::LevelFilter::Info); // Or set SQLx log level + +let db = Database::connect(opt).await?; +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-diesel.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-diesel.md new file mode 100644 index 00000000000..6189892c69f --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-diesel.md @@ -0,0 +1,41 @@ +# Compare with Diesel + +[SeaORM](https://github.com/SeaQL/sea-orm) and [Diesel](https://github.com/diesel-rs/diesel) share the same goal: to offer you a complete solution in interfacing with databases. + +Both SeaORM and Diesel work with MySQL, PostgreSQL, and SQLite, so you aren't constrained going with either. While Diesel allows third parties to implement custom backends, SeaORM does not. + +There are also other things we chose to do differently. + +## Architecture + +First off, perhaps the number one requested feature, async Rust support. While using async may not offer you better performance today, programming in async is an architectural decision you have to make early on. By choosing SeaORM, we together look forward to Rust's async ecosystem maturing. + +Under the hood, SeaORM together with [SQLx](https://crates.io/crates/sqlx) offers you a pure Rust technology stack. Diesel uses native drivers by default and it may take some effort for you to replace it with a pure Rust driver. Each side has their pros and cons, so it's up to your preference. + +SeaORM has a modular design. If you don't like the idea of an ORM, you'll definitely still want to use [SeaQuery](https://crates.io/crates/sea-query), the underlying query builder. It is lightweight and can be easily integrated into any project. The SeaQuery API is also available to you when using SeaORM, so you receive the benefits of high-level abstraction while still having the power of a flexible query builder when you need it. + +## Programming paradigm + +In addition to the sync vs async foundation, the biggest distinction between Diesel and SeaORM is static vs dynamic. + +Diesel provides an everything-compile-time API where types can be checked entirely statically. You can also do dynamic queries with Diesel, but you'd lose some of the benefits of compile-time type-checking. + +SeaORM is dynamic, in which things are established at runtime. It offers more flexibility. While you lose some compile-time checkings, SeaORM helps you to prove correctness by testing instead. + +Both libraries make heavy use of traits and generics, but SeaORM generates less types: each column in Diesel is a struct (each struct impl several traits), while each column in SeaORM is an enum variant (all columns of an entity together form one enum, which impl some traits). + +There are also no deeply nested generics in SeaORM (e.g.`A>>>`). + +## Schema Builder + +While in the Diesel ecosystem there are awesome libraries like [barrel](https://git.irde.st/spacekookie/barrel), SeaQL maintains the tools for schema building (SeaQuery) and management ([SeaSchema](https://github.com/SeaQL/sea-schema)). That means a familiar API and a unified experience. + +## Similarities + +Both Diesel and SeaORM are schema first, meaning it all starts from your existing database schema, instead of starting from your OOP classes. + +Both Diesel and SeaORM are relational, meaning you can do complex joins with defined relations. + +## Final words + +Diesel is a well-established library in the Rust ecosystem. SeaORM is very new. Neither can replace the other. We hope that the Rust community will grow stronger together. diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-many-to-many.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-many-to-many.md new file mode 100644 index 00000000000..bd3a5c8a592 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-many-to-many.md @@ -0,0 +1,136 @@ +# Many to Many + +A many-to-many relation is formed by three tables, where two tables are related via a junction table. As an example, a `Cake` has many `Filling` and `Filling` are shared by many `Cake` via an intermediate entity `CakeFilling`. + +## Defining the Relation + +On the `Cake` entity, implement the `Related` trait. + +`Relation` in SeaORM is an arrow: it has `from` and `to`. `cake_filling::Relation::Cake` defines `CakeFilling -> Cake`. Calling [`rev`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.RelationDef.html#method.rev) reverses it into `Cake -> CakeFilling`. + +Chaining this with `cake_filling::Relation::Filling` which defines `CakeFilling -> Filling` resulting in `Cake -> CakeFilling -> Filling`. + +```rust {4,10} title="entity/cake.rs" +impl Related for Entity { + // The final relation is Cake -> CakeFilling -> Filling + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + + fn via() -> Option { + // The original relation is CakeFilling -> Cake, + // after `rev` it becomes Cake -> CakeFilling + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} +``` + +Similarly, on the `Filling` entity, implement the `Related` trait. First, join with intermediate table `via` the inverse of `cake_filling::Relation::Filling` relation, then join `to` `Cake` entity with `cake_filling::Relation::Cake` relation. + +```rust {3,7} title="entity/filling.rs" +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Cake.def() + } + + fn via() -> Option { + Some(super::cake_filling::Relation::Filling.def().rev()) + } +} +``` + +## Defining the Inverse Relation + +On the `CakeFilling` entity, its `cake_id` attribute is referencing the primary key of `Cake` entity, and its `filling_id` attribute is referencing the primary key of `Filling` entity. + +To define the inverse relation: +1. Add two new variants `Cake` and `Filling` to the `Relation` enum. +1. Define both relations with `Entity::belongs_to()`. + +```rust title="entity/cake_filling.rs" +#[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, + #[sea_orm( + belongs_to = "super::filling::Entity", + from = "Column::FillingId", + to = "super::filling::Column::Id" + )] + Filling, +} +``` + +
+ It's expanded to: + +```rust +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Filling, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Filling => Entity::belongs_to(super::filling::Entity) + .from(Column::FillingId) + .to(super::filling::Column::Id) + .into(), + } + } +} +``` +
+ +## Limitation of Codegen + +Note that the implementation of `Related` with `via` and `to` methods will not be generated if there exists multiple paths via an intermediate table. + +For example, in the schema defined below, there are two paths: ++ Path 1. `users <-> users_votes <-> bills` ++ Path 2. `users <-> users_saved_bills <-> bills` + +Therefore, the implementation of `Related` will not be generated. + +```sql +CREATE TABLE users +( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + email TEXT UNIQUE NOT NULL, + ... +); +``` +```sql +CREATE TABLE bills +( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + ... +); +``` +```sql +CREATE TABLE users_votes +( + user_id uuid REFERENCES users (id) ON UPDATE CASCADE ON DELETE CASCADE, + bill_id uuid REFERENCES bills (id) ON UPDATE CASCADE ON DELETE CASCADE, + vote boolean NOT NULL, + CONSTRAINT users_bills_pkey PRIMARY KEY (user_id, bill_id) +); +``` +```sql +CREATE TABLE users_saved_bills +( + user_id uuid REFERENCES users (id) ON UPDATE CASCADE ON DELETE CASCADE, + bill_id uuid REFERENCES bills (id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT users_saved_bills_pkey PRIMARY KEY (user_id, bill_id) +); +``` \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/01-setting-up-migration.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/01-setting-up-migration.md new file mode 100644 index 00000000000..3a98e9e550e --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/01-setting-up-migration.md @@ -0,0 +1,180 @@ +# Setting Up Migration + +:::tip Rustacean Sticker Pack 🦀 +[Our stickers](https://www.sea-ql.org/sticker-pack/) are made with a premium water-resistant vinyl with a unique matte finish. +Stick them on your laptop, notebook, or any gadget to show off your love for Rust! +::: + +If you are starting from a fresh database, it's better to version control your database schema. SeaORM ships with a migration tool, allowing you to write migrations in SeaQuery or SQL. + +If you already have a database with tables and data, you can skip this chapter and move on to [generating SeaORM entities](04-generate-entity/01-sea-orm-cli.md). + +## Migration Table + +A table will be created in your database to keep track of the applied migrations. It will be created automatically when you run the migration. + +
+ By default, it will be named `seaql_migrations`. You can also use a custom name for your migration table. + +```rust +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + // Override the name of migration table + fn migration_table_name() -> sea_orm::DynIden { + "override_migration_table_name".into_iden() + } + .. +} +``` +
+ +## Creating Migration Directory + +First, install `sea-orm-cli` with `cargo`. + +```shell +cargo install sea-orm-cli@1.1.0 +``` + +:::tip SQL Server (MSSQL) backend + +The installation of `sea-orm-cli` with MSSQL support can be found [here](https://www.sea-ql.org/SeaORM-X/docs/migration/setting-up-migration/). + +::: + +Then, setup the migration directory by executing `sea-orm-cli migrate init`. + +```shell +# Setup the migration directory in `./migration` +$ sea-orm-cli migrate init +Initializing migration directory... +Creating file `./migration/src/lib.rs` +Creating file `./migration/src/m20220101_000001_create_table.rs` +Creating file `./migration/src/main.rs` +Creating file `./migration/Cargo.toml` +Creating file `./migration/README.md` +Done! + +# If you want to setup the migration directory in else where +$ sea-orm-cli migrate init -d ./other/migration/dir +``` + +You should have a migration directory with a structure like below. + +``` +migration +├── Cargo.toml +├── README.md +└── src + ├── lib.rs # Migrator API, for integration + ├── m20220101_000001_create_table.rs # A sample migration file + └── main.rs # Migrator CLI, for running manually +``` + +Note that if you setup the migration directory directly within a Git repository, a `.gitignore` file will also be created. + +## Workspace Structure + +It is recommended to structure your cargo workspace as follows to share SeaORM entities between the app crate and the migration crate. Checkout the [integration examples](https://github.com/SeaQL/sea-orm/tree/master/examples) for demonstration. + +### Migration Crate + +Import the [`sea-orm-migration`](https://crates.io/crates/sea-orm-migration) and [`async-std`](https://crates.io/crates/async-std) crate. + +```toml title="migration/Cargo.toml" +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } + +[dependencies.sea-orm-migration] +version = "1.1.0" +features = [ + # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. + # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. + # e.g. + # "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature + # "sqlx-postgres", # `DATABASE_DRIVER` feature +] +``` + +Let's write a migration. Detailed instructions in the next section. + +```rust title="migration/src/m20220120_000001_create_post_table.rs" +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + } +} +``` + +### Entity Crate + +Create an entity crate in your root workspace. + +
+ You don't have SeaORM entities defined? + +You can create an entity crate without any entity files. Then, write the migration and run it to create tables in the database. Finally, [generate SeaORM entities](04-generate-entity/01-sea-orm-cli.md) with `sea-orm-cli` and output the entity files to `entity/src/entities` folder. + +After generating the entity files, you can re-export the generated entities by adding following lines in `entity/src/lib.rs`: + +```rust +mod entities; +pub use entities::*; +``` +
+ +``` +entity +├── Cargo.toml # Include SeaORM dependency +└── src + ├── lib.rs # Re-export SeaORM and entities + └── post.rs # Define the `post` entity +``` + +Specify SeaORM dependency. + +```toml title="entity/Cargo.toml" +[dependencies] +sea-orm = { version = "1.1.0" } +``` + +### App Crate + +This is where the application logic goes. + +Create a workspace that contains app, entity and migration crates and import the entity crate into the app crate. + +If we want to bundle the migration utility as part of your app, you'd also want to import the migration crate. + +```toml title="./Cargo.toml" +[workspace] +members = [".", "entity", "migration"] + +[dependencies] +entity = { path = "entity" } +migration = { path = "migration" } # depends on your needs + +[dependencies] +sea-orm = { version = "1.1.0", features = [..] } +``` + +In your app, you can then run the migrator on startup. + +```rust title="src/main.rs" +use migration::{Migrator, MigratorTrait}; + +let connection = sea_orm::Database::connect(&database_url).await?; +Migrator::up(&connection, None).await?; +``` \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/02-writing-migration.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/02-writing-migration.md new file mode 100644 index 00000000000..2b919c880dc --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/02-writing-migration.md @@ -0,0 +1,382 @@ +# Writing Migration + +Each migration contains two methods: `up` and `down`. The `up` method is used to alter the database schema, such as adding new tables, columns or indexes, while the `down` method revert the actions performed in the `up` method. + +The SeaORM migration system has the following advantages: + +1. Write DDL statements with SeaQuery or SQL +2. Execute multiple DDL (with conditions) +3. Seed data using the SeaORM API + +## Creating Migrations + +Generate a new migration file by executing `sea-orm-cli migrate generate` command. + +If you name the file with spaces, it will be converted according to the convention automatically. + +```shell +sea-orm-cli migrate generate NAME_OF_MIGRATION [--local-time] + +# E.g. to generate `migration/src/m20220101_000001_create_table.rs` shown below +sea-orm-cli migrate generate create_table + +# This create the same migration file as above command +sea-orm-cli migrate generate "create table" +``` + +Or you can create a migration file using the template below. Name the file according to the naming convention `mYYYYMMDD_HHMMSS_migration_name.rs`. + +```rust title="migration/src/m20220101_000001_create_table.rs" +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( ... ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table( ... ) + .await + } +} +``` + +Additionally, you have to include the new migration in the [`MigratorTrait::migrations`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/migrator/trait.MigratorTrait.html#tymethod.migrations) method. Note that the migrations should be sorted in chronological order. + +```rust title="migration/src/lib.rs" +pub use sea_orm_migration::*; + +mod m20220101_000001_create_table; + +pub struct Migrator; + +#[async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20220101_000001_create_table::Migration), + ] + } +} +``` + +## Defining Migration + +See [`SchemaManager`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/manager/struct.SchemaManager.html) for API reference. + +### Using SeaQuery + +Click [here](https://github.com/SeaQL/sea-query#table-create) to take a quick tour of SeaQuery's DDL statements. + +You can use the [`DeriveIden`](https://docs.rs/sea-orm/*/sea_orm/derive.DeriveIden.html) macro to define identifiers that will be used in your migration. + +```rust +#[derive(DeriveIden)] +enum Post { + Table, // this is a special case; will be mapped to `post` + Id, + Title, + #[sea_orm(iden = "full_text")] // Renaming the identifier + Text, +} + +assert_eq!(Post::Table.to_string(), "post"); +assert_eq!(Post::Id.to_string(), "id"); +assert_eq!(Post::Title.to_string(), "title"); +assert_eq!(Post::Text.to_string(), "full_text"); +``` + +Here are some common DDL snippets you may find useful. + +#### Schema Creation Methods +- Create Table + ```rust + use sea_orm::{EnumIter, Iterable}; + + #[derive(DeriveIden)] + enum Post { + Table, + Id, + Title, + #[sea_orm(iden = "text")] // Renaming the identifier + Text, + Category, + } + + #[derive(Iden, EnumIter)] + pub enum Category { + #[iden = "Feed"] + Feed, + #[iden = "Story"] + Story, + } + + // Remember to import `sea_orm_migration::schema::*` schema helpers into scope + use sea_orm_migration::{prelude::*, schema::*}; + + // Defining the table schema + manager + .create_table( + Table::create() + .table(Post::Table) + .if_not_exists() + .col(pk_auto(Post::Id)) + .col(string(Post::Title)) + .col(string(Post::Text)) + .col(enumeration_null(Post::Category, "category", Category::iter())) + ) + .await + + // above is equivalent to: + manager + .create_table( + Table::create() + .table(Post::Table) + .if_not_exists() + .col(ColumnDef::new(Post::Id) + .integer() + .not_null() + .auto_increment() + .primary_key() + ) + .col(ColumnDef::new(Post::Title).string().not_null()) + .col(ColumnDef::new(Post::Text).string().not_null()) + .col(ColumnDef::new(Post::Category) + .enumeration("category", Category::iter())) + ) + .await + ``` +- Create Index + ```rust + manager.create_index(sea_query::Index::create()..) + ``` +- Create Foreign Key + ```rust + manager.create_foreign_key(sea_query::ForeignKey::create()..) + ``` +- Create Data Type (PostgreSQL only) + ```rust + use sea_orm::{EnumIter, Iterable}; + use sea_orm_migration::prelude::extension::postgres::Type; + + #[derive(DeriveIden)] + struct CategoryEnum; + + #[derive(DeriveIden, EnumIter)] + enum CategoryVariants { + Feed, + #[sea_orm(iden = "story")] + Story, + } + + manager + .create_type( + Type::create() + .as_enum(CategoryEnum) + .values(CategoryVariants::iter()) + .to_owned() + ) + .await?; + ``` + +#### Schema Mutation Methods +- Drop Table + ```rust + use entity::post; + + manager.drop_table(sea_query::Table::drop()..) + ``` +- Alter Table + ```rust + manager.alter_table(sea_query::Table::alter()..) + ``` +- Rename Table + ```rust + manager.rename_table(sea_query::Table::rename()..) + ``` +- Truncate Table + ```rust + manager.truncate_table(sea_query::Table::truncate()..) + ``` +- Drop Index + ```rust + manager.drop_index(sea_query::Index::drop()..) + ``` +- Drop Foreign Key + ```rust + manager.drop_foreign_key(sea_query::ForeignKey::drop()..) + ``` +- Alter Data Type (PostgreSQL only) + ```rust + manager.alter_type(sea_query::Type::alter()..) + ``` +- Drop Data Type (PostgreSQL only) + ```rust + manager.drop_type(sea_query::extension::postgres::Type()..) + ``` + +#### Schema Inspection Methods + +- Has Table + ```rust + manager.has_table("table_name") + ``` +- Has Column + ```rust + manager.has_column("table_name", "column_name") + ``` +- Has Index + ```rust + manager.has_index("table_name", "index_name") + ``` + +### Using raw SQL + +You can write migration files in raw SQL, but then you lost the multi-backend compatibility SeaQuery offers. + +```rust title="migration/src/m20220101_000001_create_table.rs" +use sea_orm::Statement; +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + // Use `execute_unprepared` if the SQL statement doesn't have value bindings + db.execute_unprepared( + "CREATE TABLE `cake` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` varchar(255) NOT NULL + )" + ) + .await?; + + // Construct a `Statement` if the SQL contains value bindings + let stmt = Statement::from_sql_and_values( + manager.get_database_backend(), + r#"INSERT INTO `cake` (`name`) VALUES (?)"#, + ["Cheese Cake".into()] + ); + db.execute(stmt).await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .get_connection() + .execute_unprepared("DROP TABLE `cake`") + .await?; + + Ok(()) + } +} +``` + +## Tip 1: combining multiple schema changes in one migration + +You can combine multiple changes within both up and down migration functions. Here is a complete example: + +```rust +// Remember to import `sea_orm_migration::schema::*` schema helpers into scope +use sea_orm_migration::{prelude::*, schema::*}; + +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + + manager + .create_table( + sea_query::Table::create() + .table(Post::Table) + .if_not_exists() + .col(pk_auto(Post::Id)) + .col(string(Post::Title)) + .col(string(Post::Text)) + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx-post_title") + .table(Post::Table) + .col(Post::Title) + ) + .await?; + + Ok(()) // All good! +} +``` + +and here we have the matching down function: + +```rust +async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + + manager.drop_index(Index::drop().name("idx-post-title")) + .await?; + + manager.drop_table(Table::drop().table(Post::Table)) + .await?; + + Ok(()) // All good! +} +``` + +## Tip 2: `ADD COLUMN IF NOT EXISTS` + +Since this syntax is not available on MySQL, you can: + +```rust +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + if !manager.has_column("my_table", "col_to_add").await? { + // ALTER TABLE `my_table` ADD COLUMN `col_to_add` .. + } + + Ok(()) +} +``` + +## Tip 3: Seed data with Entity + +```rust +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + cake::ActiveModel { + name: Set("Cheesecake".to_owned()), + ..Default::default() + } + .insert(db) + .await?; + + Ok(()) +} +``` + +[Full example](https://github.com/SeaQL/sea-orm/blob/master/examples/seaography_example/migration/src/m20230102_000001_seed_bakery_data.rs). + +## Atomic Migration + +Migration will be executed in Postgres atomically that means migration scripts will be executed inside a transaction. Changes done to the database will be rolled back if the migration failed. However, atomic migration is not supported in MySQL and SQLite. + +You can start a transaction inside each migration to perform operations like [seeding sample data](03-migration/04-seeding-data.md#seeding-data-transactionally) for a newly created table. + +## Schema first or Entity first? + +In the grand scheme of things, we recommend a schema first approach: you write migrations first and then generate entities from a live database. + +At times, you might want to use the [`create_*_from_entity`](09-schema-statement/01-create-table.md) methods to bootstrap your database with several hand written entity files. + +That's perfectly fine if you intend to never change the entity schema. Or, you can keep the original entity and embed a copy in the migration file. diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/03-running-migration.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/03-running-migration.md new file mode 100644 index 00000000000..bf6c958e209 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/03-running-migration.md @@ -0,0 +1,113 @@ +# Running Migration + +After you have defined the migrations, you can apply or revert migrations in the terminal or on application startup. + +## Command Line Interface (CLI) + +Migrations can be run manually in the terminal. `DATABASE_URL` must be set in your environment, follow the instructions [here](04-generate-entity/01-sea-orm-cli.md#configure-environment) to configure it. + +Supported commands: +- `init`: Initialize migration directory +- `generate`: Generate a new migration file +- `up`: Apply all pending migrations +- `up -n 10`: Apply 10 pending migrations +- `down`: Rollback last applied migration +- `down -n 10`: Rollback last 10 applied migrations +- `status`: Check the status of all migrations +- `fresh`: Drop all tables from the database, then reapply all migrations +- `refresh`: Rollback all applied migrations, then reapply all migrations +- `reset`: Rollback all applied migrations + +### Via `sea-orm-cli` + +The `sea-orm-cli` will execute `cargo run --manifest-path ./migration/Cargo.toml -- COMMAND` under the hood. + +```shell +$ sea-orm-cli migrate COMMAND +``` + +You can customize the manifest path. + +```shell +$ sea-orm-cli migrate COMMAND -d ./other/migration/dir +``` + +### Via SeaSchema Migrator CLI + +Run the migrator CLI defined in `migration/main.rs`. + +```shell +cd migration +cargo run -- COMMAND +``` + +## Migrating Programmatically + +You can perform migration on application startup with `Migrator`, which implements the [`MigratorTrait`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/migrator/trait.MigratorTrait.html). + +```rust title="src/main.rs" +use migration::{Migrator, MigratorTrait}; + +/// Apply all pending migrations +Migrator::up(db, None).await?; + +/// Apply 10 pending migrations +Migrator::up(db, Some(10)).await?; + +/// Rollback all applied migrations +Migrator::down(db, None).await?; + +/// Rollback last 10 applied migrations +Migrator::down(db, Some(10)).await?; + +/// Check the status of all migrations +Migrator::status(db).await?; + +/// Drop all tables from the database, then reapply all migrations +Migrator::fresh(db).await?; + +/// Rollback all applied migrations, then reapply all migrations +Migrator::refresh(db).await?; + +/// Rollback all applied migrations +Migrator::reset(db).await?; +``` + +## Running Migration on Any PostgreSQL Schema + +By default migration will be run on the `public` schema, you can now override it when running migration on the CLI or programmatically. + +For CLI, you can specify the target schema with `-s` / `--database_schema` option: +* via sea-orm-cli: `sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema` +* via SeaORM migrator: `cargo run -- -u postgres://root:root@localhost/database -s my_schema` + +You can also run the migration on the target schema programmatically: + +```rust +let connect_options = ConnectOptions::new("postgres://root:root@localhost/database") + .set_schema_search_path("my_schema") // Override the default schema + .to_owned(); + +let db = Database::connect(connect_options).await? + +migration::Migrator::up(&db, None).await?; +``` + +:::tip SQL Server (MSSQL) backend + +The configuration of running migration on any MSSQL schema can be found [here](https://www.sea-ql.org/SeaORM-X/docs/migration/running-migration/). + +::: + +## Checking Migration Status + +You can use `MigratorTrait::get_pending_migrations()` and `MigratorTrait::get_applied_migrations()` to retrieve the list of migrations. + +```rust +let migrations = Migrator::get_pending_migrations(db).await?; +assert_eq!(migrations.len(), 5); + +let migration = migrations[0]; +assert_eq!(migration.name(), "m20220118_000002_create_fruit_table"); +assert_eq!(migration.status(), MigrationStatus::Pending); +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/04-seeding-data.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/04-seeding-data.md new file mode 100644 index 00000000000..8d583ad2240 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/04-seeding-data.md @@ -0,0 +1,86 @@ +# Seeding Data + +You can retrieve a `DbConn` from `SchemaManager` and perform data operations as needed, for example, to seed data. + +```rust +use sea_orm_migration::sea_orm::{entity::*, query::*}; + +// ... + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + cake::ActiveModel { + name: Set("Cheesecake".to_owned()), + ..Default::default() + } + .insert(db) + .await?; + + Ok(()) + } +} +``` + +You can also execute any SeaQuery statement. + +```rust +use sea_orm_migration::sea_orm::{entity::*, query::*}; + +// ... + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let stmt = Query::insert() + .into_table(Cake::Table) + .columns([Cake::Name]) + .values_panic(["Tiramisu".into()]) + .to_owned(); + + manager.execute(stmt).await?; + + Ok(()) + } +} + +#[derive(DeriveIden)] +pub enum Cake { + Table, + Id, + Name, +} +``` + +## Seeding Data Transactionally + +Starts a transaction and execute SQL inside migration up and down. + +```rust +use sea_orm_migration::sea_orm::{entity::*, query::*}; + +// ... + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Get the connection and start a transaction + let db = manager.get_connection(); + let txn = db.begin().await?; + + cake::ActiveModel { + name: Set("Cheesecake".to_owned()), + ..Default::default() + } + .insert(&txn) + .await?; + + // Commit it + txn.commit().await?; + + Ok(()) + } +} +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/_category_.json b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/_category_.json new file mode 100644 index 00000000000..dd4e8384894 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migration", + "collapsed": false + } + \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-running-migration.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-running-migration.md new file mode 100644 index 00000000000..bf6c958e209 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-running-migration.md @@ -0,0 +1,113 @@ +# Running Migration + +After you have defined the migrations, you can apply or revert migrations in the terminal or on application startup. + +## Command Line Interface (CLI) + +Migrations can be run manually in the terminal. `DATABASE_URL` must be set in your environment, follow the instructions [here](04-generate-entity/01-sea-orm-cli.md#configure-environment) to configure it. + +Supported commands: +- `init`: Initialize migration directory +- `generate`: Generate a new migration file +- `up`: Apply all pending migrations +- `up -n 10`: Apply 10 pending migrations +- `down`: Rollback last applied migration +- `down -n 10`: Rollback last 10 applied migrations +- `status`: Check the status of all migrations +- `fresh`: Drop all tables from the database, then reapply all migrations +- `refresh`: Rollback all applied migrations, then reapply all migrations +- `reset`: Rollback all applied migrations + +### Via `sea-orm-cli` + +The `sea-orm-cli` will execute `cargo run --manifest-path ./migration/Cargo.toml -- COMMAND` under the hood. + +```shell +$ sea-orm-cli migrate COMMAND +``` + +You can customize the manifest path. + +```shell +$ sea-orm-cli migrate COMMAND -d ./other/migration/dir +``` + +### Via SeaSchema Migrator CLI + +Run the migrator CLI defined in `migration/main.rs`. + +```shell +cd migration +cargo run -- COMMAND +``` + +## Migrating Programmatically + +You can perform migration on application startup with `Migrator`, which implements the [`MigratorTrait`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/migrator/trait.MigratorTrait.html). + +```rust title="src/main.rs" +use migration::{Migrator, MigratorTrait}; + +/// Apply all pending migrations +Migrator::up(db, None).await?; + +/// Apply 10 pending migrations +Migrator::up(db, Some(10)).await?; + +/// Rollback all applied migrations +Migrator::down(db, None).await?; + +/// Rollback last 10 applied migrations +Migrator::down(db, Some(10)).await?; + +/// Check the status of all migrations +Migrator::status(db).await?; + +/// Drop all tables from the database, then reapply all migrations +Migrator::fresh(db).await?; + +/// Rollback all applied migrations, then reapply all migrations +Migrator::refresh(db).await?; + +/// Rollback all applied migrations +Migrator::reset(db).await?; +``` + +## Running Migration on Any PostgreSQL Schema + +By default migration will be run on the `public` schema, you can now override it when running migration on the CLI or programmatically. + +For CLI, you can specify the target schema with `-s` / `--database_schema` option: +* via sea-orm-cli: `sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema` +* via SeaORM migrator: `cargo run -- -u postgres://root:root@localhost/database -s my_schema` + +You can also run the migration on the target schema programmatically: + +```rust +let connect_options = ConnectOptions::new("postgres://root:root@localhost/database") + .set_schema_search_path("my_schema") // Override the default schema + .to_owned(); + +let db = Database::connect(connect_options).await? + +migration::Migrator::up(&db, None).await?; +``` + +:::tip SQL Server (MSSQL) backend + +The configuration of running migration on any MSSQL schema can be found [here](https://www.sea-ql.org/SeaORM-X/docs/migration/running-migration/). + +::: + +## Checking Migration Status + +You can use `MigratorTrait::get_pending_migrations()` and `MigratorTrait::get_applied_migrations()` to retrieve the list of migrations. + +```rust +let migrations = Migrator::get_pending_migrations(db).await?; +assert_eq!(migrations.len(), 5); + +let migration = migrations[0]; +assert_eq!(migration.name(), "m20220118_000002_create_fruit_table"); +assert_eq!(migration.status(), MigrationStatus::Pending); +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-sea-orm.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-sea-orm.md new file mode 100644 index 00000000000..bcaa346850b --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-sea-orm.md @@ -0,0 +1,15 @@ +# SeaORM Concepts + +In SeaORM, a database with a collection of tables is called a `Schema`. + +Each table corresponds to an [`Entity`](04-generate-entity/02-entity-format.md#entity) in SeaORM, which helps you perform `CRUD` (Create, Read, Update, and Delete) operations on relevant tables. + +The `Entity` trait provides an API for you to inspect its properties ([`Column`](04-generate-entity/02-entity-format.md#column), [`Relation`](04-generate-entity/02-entity-format.md#relation) and [`PrimaryKey`](04-generate-entity/02-entity-format.md#primary-key)) at runtime. + +Each table has multiple columns, which are referred to as `attribute`. + +These attributes and their values are grouped in a Rust struct (a [`Model`](12-internal-design/05-expanded-entity-format.md#model)) so that you can manipulate them. + +However, `Model` is for read operations only. To perform insert, update, or delete, you need to use [`ActiveModel`](12-internal-design/05-expanded-entity-format.md#active-model) which attaches meta-data on each attribute. + +Finally, there is no singleton (global context) in SeaORM. Application code is responsible for managing the ownership of the `DatabaseConnection`. We do provide integration examples for web frameworks including [Rocket](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example), [Actix](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example), [axum](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) and [poem](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) to help you get started quickly. \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-sqlite.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-sqlite.md new file mode 100644 index 00000000000..e9b85d11cb4 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-sqlite.md @@ -0,0 +1,83 @@ +# Using SQLite + +If you want to test application logic that does not require database-specific features, SQLite will be a good choice for you. + +Check out a simple example [here](https://github.com/SeaQL/sea-orm/blob/master/tests/basic.rs). + +## Integration Test + +It is recommended to execute more complex test cases in [integration tests](https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html). The following code snippet illustrates the steps of connecting to a database, setting up schema and performing tests. + +```rust +async fn main() -> Result<(), DbErr> { + // Connecting SQLite + let db = Database::connect("sqlite::memory:").await?; + + // Setup database schema + setup_schema(&db).await?; + + // Performing tests + testcase(&db).await?; + + Ok(()) +} +``` + +## Setup database schema + +To create tables in SQLite database for testing, instead of writing [`TableCreateStatement`](https://docs.rs/sea-query/*/sea_query/table/struct.TableCreateStatement.html) manually, you can derive it from `Entity` using [`Schema::create_table_from_entity`](https://docs.rs/sea-orm/*/sea_orm/schema/struct.Schema.html#method.create_table_from_entity). + +```rust +async fn setup_schema(db: &DbConn) { + + // Setup Schema helper + let schema = Schema::new(DbBackend::Sqlite); + + // Derive from Entity + let stmt: TableCreateStatement = schema.create_table_from_entity(MyEntity); + + // Or setup manually + assert_eq!( + stmt.build(SqliteQueryBuilder), + Table::create() + .table(MyEntity) + .col(integer(MyEntity::Column::Id)) + //... + .build(SqliteQueryBuilder) + ); + + // Execute create table statement + let result = db + .execute(db.get_database_backend().build(&stmt)) + .await; +} +``` + +## Performing tests + +Execute test cases and assert against the results. + +```rust +async fn testcase(db: &DbConn) -> Result<(), DbErr> { + + let baker_bob = baker::ActiveModel { + name: Set("Baker Bob".to_owned()), + contact_details: Set(serde_json::json!({ + "mobile": "+61424000000", + "home": "0395555555", + "address": "12 Test St, Testville, Vic, Australia" + })), + bakery_id: Set(2), + ..Default::default() + }; + + let baker_insert_res = Baker::insert(baker_bob) + .exec(db) + .await + .expect("could not insert baker"); + + assert_eq!(baker_insert_res.last_insert_id, 1); + + Ok(()) +} +``` diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-advanced-joins.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-advanced-joins.md new file mode 100644 index 00000000000..ae23aaf81cb --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-advanced-joins.md @@ -0,0 +1,360 @@ +# Advanced Joins + +An anatomy of a complex relational query with multiple joins and custom selects. + +## Schema + +Suppose we have a schema design of `BaseProduct` -> `ComplexProduct`, `BaseProduct` -> `ProductTypes`. + +#### `BaseProduct` + +```rust +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "base_product")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique)] + pub name: String, + pub type_id: i32, // linking to product_type +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_one = "super::complex_product::Entity")] + ComplexProduct, + #[sea_orm(has_many = "super::product_history::Entity")] + ProductHistory, + #[sea_orm( + belongs_to = "super::product_type::Entity", + from = "Column::TypeId", + to = "super::product_type::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + ProductType, +} +``` + +#### `ComplexProduct` + +```rust +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "complex_product")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub product_id: i64, // linking to base_product + #[sea_orm(column_type = "Decimal(Some((30, 15)))", nullable)] + pub price: Option, + #[sea_orm(column_type = "Decimal(Some((30, 15)))", nullable)] + pub lot_size: Option, + pub date_added: DateTime, + pub last_modified: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::base_product::Entity", + from = "Column::ProductId", + to = "super::base_product::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + BaseProduct, +} +``` + +#### `ProductType` + +Basically a 'enum table'. + +```rust +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "product_type")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(unique)] + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::base_product::Entity")] + BaseProduct, +} +``` + +## 1. Define result data structure + +```rust +#[derive(Clone, Debug, PartialEq, Eq, FromQueryResult, Serialize)] +pub struct ComplexProduct { + pub id: i64, + pub name: String, + pub r#type: String, + pub price: Decimal, + pub lot_size: Decimal, + pub date_added: DateTime, + pub last_modified: DateTime, + #[sea_orm(skip)] + pub history: Vec, +} +``` + +With `Serialize`, you can transform the select result into JSON directly. + +## 2. Define helper aliases + +```rust +#[derive(DeriveIden, Clone, Copy)] +pub struct Id; + +#[derive(DeriveIden, Clone, Copy)] +pub struct Name; + +#[derive(DeriveIden, Clone, Copy)] +pub struct Base; + +use complex_product::Entity as Prod; +pub type ProdCol = ::Column; +type ProdRel = ::Relation; +``` + +This would make our code much more concise and readable. + +Avoid using string literals because it's prone to typo. + +:::tip + +If you need to use many aliases, you can define a enum: + +```rust +#[derive(DeriveIden, Clone, Copy)] +pub enum Prod { + Base, + Frame, + Package, +} +``` + +::: + +## 3. Custom selects + +```rust +pub fn query() -> Select { + complex_product::Entity::find() + .select_only() + .tbl_col_as((Base, Id), "id") + .tbl_col_as((Base, Name), "name") + .column_as(product_type::Column::Name, "type") + .column_as(ProdCol::Price, "price") + .column_as(ProdCol::LotSize, "lot_size") + .column_as(ProdCol::DateAdded, "date_added") + .column_as(ProdCol::LastModified, "last_modified") + .join_as(JoinType::InnerJoin, ProdRel::BaseProduct.def(), Base) + .join(JoinType::InnerJoin, base_product::Relation::ProductType.def().from_alias(Base)) + .order_by_asc(Expr::col((Base, Id))) +} +``` + +Our query starts from `ComplexProduct`. We join back to `BaseProduct`, alias it as `Base`. We then join to `ProductType` via `Base`. + +``` +ComplexProduct -> BaseProduct as Base -> ProductType +``` + +:::tip + +It's possible to join in a diamond topology: + +``` +ComplexProduct -> BaseProduct -> Attribute + -> Material -> Attribute +``` + +```rust +.join_as(JoinType::LeftJoin, complex_product::Relation::BaseProduct.def(), Base) +.join_as(JoinType::LeftJoin, complex_product::Relation::Material.def(), Material) +.join(JoinType::InnerJoin, base_product::Relation::Attribute.def().from_alias(Base)) +.join(JoinType::InnerJoin, material::Relation::Attribute.def().from_alias(Material)) +``` + +::: + +### Custom join conditions + +You can use the `join` method to construct complex joins in select queries. It takes any `RelationDef`, and you can further customize the join conditions. Below is an illustration (albeit it's from the Bakery schema): + +```rust +use sea_orm::{JoinType, RelationTrait}; +use sea_query::Expr; + +assert_eq!( + cake::Entity::find() + .column_as(filling::Column::Id.count(), "count") + .column_as( + Expr::col(("fruit_alias", fruit::Column::Name)).into_simple_expr(), + "fruit_name" + ) + // construct `RelationDef` on the fly + .join_rev( + JoinType::InnerJoin, + cake_filling::Entity::belongs_to(cake::Entity) + .from(cake_filling::Column::CakeId) + .to(cake::Column::Id) + .into() + ) + // reuse a `Relation` from existing Entity + .join(JoinType::InnerJoin, cake_filling::Relation::Filling.def()) + // join with table alias and custom on condition + .join_as( + JoinType::LeftJoin, + cake::Relation::Fruit + .def() + .on_condition(|_left, right| { + Expr::col((right, fruit::Column::Name)) + .like("%tropical%") + .into_condition() + }), + "fruit_alias" + ) + .group_by(cake::Column::Id) + .having(filling::Column::Id.count().equals(Expr::value(2))) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `cake`.`id`, `cake`.`name`, COUNT(`filling`.`id`) AS `count`, `fruit_alias`.`name` AS `fruit_name` FROM `cake`", + "INNER JOIN `cake_filling` ON `cake_filling`.`cake_id` = `cake`.`id`", + "INNER JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`", + "LEFT JOIN `fruit` AS `fruit_alias` ON `cake`.`id` = `fruit_alias`.`cake_id` AND `fruit_alias`.`name` LIKE '%tropical%'", + "GROUP BY `cake`.`id`", + "HAVING COUNT(`filling`.`id`) = 2", + ] + .join(" ") +); +``` + +## 4. Filter Conditions + +Suppose we support the following query parameters on the API: + +```rust + +#[derive(Default, Deserialize)] +pub struct Query { + #[serde(default)] + pub id: Vec, + pub name: Option, +} +``` + +```rust +fn condition(query: Query) -> Condition { + Condition::all() + .add_option(if !query.id.is_empty() { + Some(Expr::col((Base, Id)).is_in(query.id)) + } else { None }) + .add_option(if let Some(name) = &query.name { + Some(Expr::col((Base, Name)).like(name)) + } else { None }) +} +``` + +Bonus tip: if you're only using Postgres you can replace `is_in` with `any`: + +```rust +use sea_orm::sea_query::extension::postgres::PgFunc; + +Expr::col((Base, Id)).eq(PgFunc::any(query.id)) // WHERE base.id = ANY($N) +``` + +```rust +let products = query() + .filter(condition(q)) + .into_model::() + .all(db) + .await?; +``` + +## 5. Extra: associated models + +Now, suppose we have a data structure associated with each `BaseProduct` recording its history: + +#### `ProductHistory` + +```rust +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "product_history")] +pub struct Model { + #[sea_orm(primary_key)] + #[serde(skip)] + pub id: i32, + pub product_id: i64, + pub from: DateTime, + pub until: DateTime, + pub name: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::base_product::Entity", + from = "Column::ProductId", + to = "super::base_product::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + BaseProduct, +} +``` + +Let's make a helper function to query the histories associated to a set of products: + +```rust +pub fn history_of(ids: Vec) -> Select { + product_history::Entity::find() + .filter(Expr::col(product_history::Column::ProductId).is_in(ids)) + .order_by_asc(product_history::Column::Id) +} + +let histories = history_of(products.iter().map(|s| s.id).collect::>()) + .all(db) + .await?; +``` + +The final step is to associate `product_history::Model` to `ComplexProduct`: + +```rust +pub fn associate( + mut parent: Vec, + children: Vec, +) -> Vec { + let len = parent.len(); + parent.dedup_by_key(|s| s.id); + if len != parent.len() { + warn!("parent is not unique."); + } + + let parent_id_map: HashMap = parent + .iter() + .enumerate() + .map(|(i, s)| (s.id, i)) + .collect(); + + // put children into associated parent + for item in children { + if let Some(index) = parent_id_map.get(&item.product_id) { + parent[*index].history.push(item); + } + } + + parent +} + +let products = associate(products, histories); +``` + +This is sometimes called "data loader" pattern, and can be generalized with generics to work with any model. diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-architecture.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-architecture.md new file mode 100644 index 00000000000..9def8735c7b --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-architecture.md @@ -0,0 +1,43 @@ +# Architecture + +> Let's dive under the Sea 🤿 + + + +To understand the architecture of SeaORM, let's discuss what is an ORM. ORM exists to provide abstractions over common operations you would do against a database and hide the implementation details like the actual SQL queries. + +With a good ORM, you shouldn't bother to look under the API surface. Until you do. I hear you say *'abstraction leaks'*, and yes, it does. + +The approach SeaORM takes is **'layered abstraction'**, where you'd dig one layer beneath if you want to. That's why we made SeaQuery into a standalone repository. It's useful on its own, and with a public API surface and a separate namespace, it's far more difficult to create confusing internal APIs than a monolithic approach. + +The central idea in SeaORM is nearly everything is runtime configurable. At compile time, it does not know what the underlying database is. + +What benefits does database-agnostic bring? For example, you can: + +1. Make your app work on any database, depending on runtime configuration +1. Use the same models and transfer them across different databases +1. Share entities across different projects by creating a 'data structure crate', where the database is chosen by downstream 'behaviour crates' + +The API of SeaORM is not a thin shell, but consist of layers, with each layer underneath being less abstract. + +There are different stages when the API is being utilized. + +So there are two dimensions to navigate the SeaORM code base, **'stage'** and **'abstractness'**. + +First is the declaration stage. Entities and relations among them are being defined with the `EntityTrait`, `ColumnTrait` & `RelationTrait` etc. + +Second is the query building stage. + +The top most layer is `Entity`'s `find*`, `insert`, `update` & `delete` methods, where you can intuitively perform basic CRUD operations. + +One layer down, is the `Select`, `Insert`, `Update` & `Delete` structs, where they each have their own API for more advanced operations. + +One layer down, is the SeaQuery `SelectStatement`, `InsertStatement`, `UpdateStatement` & `DeleteStatement`, where they have a rich API for you to fiddle with the SQL syntax tree. + +Third is the execution stage. A separate set of structs, `Selector`, `Inserter`, `Updater` & `Deleter`, are responsible for executing the statements against a database connection. + +Finally is the resolution stage, when query results are converted into Rust types and shoved into structs. Subsequently, if it is a relational query, the structs will be pieced together according to their relations. + +Because only the execution and resolution stages are database specific, we have the possibility to use a different driver by replacing those. + +I imagine some day, we will support a number of databases, with a matrix of different syntaxes, protocols and form-factors. diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-complex-relations.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-complex-relations.md new file mode 100644 index 00000000000..51bbbce1957 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-complex-relations.md @@ -0,0 +1,194 @@ +# Complex Relations + +## Linked + +The `Related` trait is a representation of the arrows (1-1, 1-N, M-N) we draw on Entity Relationship Diagrams. A [`Linked`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Linked.html) is composed of a chain of relations, and is useful when: + +1. there exist multiple join paths between a pair of entities, making it impossible to impl `Related` +1. joining across multiple entities in a relational query + +Implementing `Linked` trait is completely optional, as there are other ways of doing relational queries in SeaORM, which will be explained in later chapters. +With `Linked` implemented, several `find_*_linked` helper methods become available, and relationships can be defined in a single place. + +### Defining the Link + +Take [this](https://github.com/SeaQL/sea-orm/blob/1.1.x/src/tests_cfg/entity_linked.rs) as an example, where we join cake and filling via an intermediate `cake_filling` table. + +```rust title="entity/links.rs" +pub struct CakeToFilling; + +impl Linked for CakeToFilling { + type FromEntity = cake::Entity; + + type ToEntity = filling::Entity; + + fn link(&self) -> Vec { + vec![ + cake_filling::Relation::Cake.def().rev(), + cake_filling::Relation::Filling.def(), + ] + } +} +``` + +Alternatively, the `RelationDef` can be defined on the fly, where the following is equivalent to the above: + +```rust +pub struct CakeToFilling; + +impl Linked for CakeToFilling { + type FromEntity = cake::Entity; + + type ToEntity = filling::Entity; + + fn link(&self) -> Vec { + vec![ + cake_filling::Relation::Cake.def().rev(), + cake_filling::Entity::belongs_to(filling::Entity) + .from(cake_filling::Column::FillingId) + .to(filling::Column::Id) + .into(), + ] + } +} +``` + +### Lazy Loading + +Find fillings that can be filled into a cake with the [`find_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ModelTrait.html#method.find_linked) method. + +```rust +let cake_model = cake::Model { + id: 12, + name: "".to_owned(), +}; + +assert_eq!( + cake_model + .find_linked(cake::CakeToFilling) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `filling`.`id`, `filling`.`name`, `filling`.`vendor_id`", + "FROM `filling`", + "INNER JOIN `cake_filling` AS `r0` ON `r0`.`filling_id` = `filling`.`id`", + "INNER JOIN `cake` AS `r1` ON `r1`.`id` = `r0`.`cake_id`", + "WHERE `r1`.`id` = 12", + ] + .join(" ") +); +``` + +### Eager Loading + +[`find_also_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_also_linked) is a dual of `find_also_related`; [`find_with_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_with_linked) is a dual of `find_with_related`; : + +```rust +assert_eq!( + cake::Entity::find() + .find_also_linked(links::CakeToFilling) + .build(DbBackend::MySql) + .to_string(), + [ + r#"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,"#, + r#"`r1`.`id` AS `B_id`, `r1`.`name` AS `B_name`, `r1`.`vendor_id` AS `B_vendor_id`"#, + r#"FROM `cake`"#, + r#"LEFT JOIN `cake_filling` AS `r0` ON `cake`.`id` = `r0`.`cake_id`"#, + r#"LEFT JOIN `filling` AS `r1` ON `r0`.`filling_id` = `r1`.`id`"#, + ] + .join(" ") +); +``` + +## Self Referencing Relations + +The `Link` trait can also define self referencing relations. + +The following example defines an Entity that references itself. + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "self_join")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub uuid: Uuid, + pub uuid_ref: Option, + pub time: Option