diff --git a/SeaORM/docs/01-introduction/02-async.md b/SeaORM/docs/01-introduction/02-async.md index 4f8822d77b0..696666e2d7a 100644 --- a/SeaORM/docs/01-introduction/02-async.md +++ b/SeaORM/docs/01-introduction/02-async.md @@ -4,7 +4,7 @@ Async programming in Rust is a recent development, only having been stabilized i 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). +Second, in multi-threaded async executor, 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. 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-index.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-index.md new file mode 100644 index 00000000000..9befa891f3d --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-index.md @@ -0,0 +1,149 @@ +# 索引 + +## 介绍 + +1. 介绍 + + 1.1. [什么是 ORM](01-introduction/01-orm.md) + + 1.2. [Rust 中的异步编程](01-introduction/02-async.md) + + 1.3. [SeaORM 概念](01-introduction/03-sea-orm.md) + + 1.4. [教程与示例](01-introduction/04-tutorial.md) + +## 基础 + +2. 安装与配置 + + 2.1 [选择数据库与异步运行时](02-install-and-config/01-database-and-async-runtime.md) + + 2.2 [连接池](02-install-and-config/02-connection.md) + + 2.3 [调试日志](02-install-and-config/03-debug-log.md) + +3. 迁移 + + 3.1 [设置迁移](03-migration/01-setting-up-migration.md) + + 3.2 [编写迁移](03-migration/02-writing-migration.md) + + 3.3 [运行迁移](03-migration/03-running-migration.md) + + 3.4 [数据填充](03-migration/04-seeding-data.md) + +4. 生成实体 + + 4.1 [使用 `sea-orm-cli`](04-generate-entity/01-sea-orm-cli.md) + + 4.2 [实体结构](04-generate-entity/02-entity-format.md) + + 4.3 [列类型](04-generate-entity/03-column-types.md) + + 4.4 [枚举](04-generate-entity/04-enumeration.md) + + 4.5 [新类型](04-generate-entity/05-newtype.md) + +5. 基本 CRUD + + 5.1 [基本 Schema](05-basic-crud/01-basic-schema.md) + + 5.2 [SELECT: 查找、过滤、排序、分页](05-basic-crud/02-select.md) + + 5.3 [ActiveModel 与 ActiveValue](05-basic-crud/03-active-model.md) + + 5.4 [INSERT: 插入单个与插入多个](05-basic-crud/04-insert.md) + + 5.5 [UPDATE: 查找与保存,更新多个](05-basic-crud/05-update.md) + + 5.6 [SAVE: 插入或更新](05-basic-crud/06-save.md) + + 5.7 [DELETE: 删除单个与删除多个](05-basic-crud/07-delete.md) + + 5.8 [JSON](05-basic-crud/08-json.md) + + 5.9 [原始 SQL 查询](05-basic-crud/09-raw-sql.md) + +## 高级主题 + +6. 关系 + + 6.1 [一对一](06-relation/01-one-to-one.md) + + 6.2 [一对多](06-relation/02-one-to-many.md) + + 6.3 [多对多](06-relation/03-many-to-many.md) + + 6.4 [复杂关系](06-relation/04-complex-relations.md) + + 6.5 [自定义连接条件](06-relation/06-custom-join-condition.md) + + 6.6 [数据加载器](06-relation/07-data-loader.md) + + 6.7 [Bakery Schema](06-relation/08-bakery-schema.md) + + 6.8 [嵌套选择](06-relation/09-nested-selects.md) + +7. 编写测试 + + 7.1 [健壮与正确](07-write-test/01-testing.md) + + 7.2 [模拟接口](07-write-test/02-mock.md) + + 7.3 [使用 SQLite](07-write-test/03-sqlite.md) + +8. 高级查询 + + 8.1 [自定义选择](08-advanced-query/01-custom-select.md) + + 8.2 [条件表达式](08-advanced-query/02-conditional-expression.md) + + 8.3 [聚合函数](08-advanced-query/03-aggregate-function.md) + + 8.4 [高级连接](08-advanced-query/04-advanced-joins.md) + + 8.5 [子查询](08-advanced-query/05-subquery.md) + + 8.6 [事务](08-advanced-query/06-transaction.md) + + 8.7 [流式处理](08-advanced-query/07-streaming.md) + + 8.8 [自定义 Active Model](08-advanced-query/08-custom-active-model.md) + + 8.9 [错误处理](08-advanced-query/09-error-handling.md) + +9. Schema 语句 + + 9.1 [创建表](09-schema-statement/01-create-table.md) + + 9.2 [创建枚举](09-schema-statement/02-create-enum.md) + + 9.3 [创建索引](09-schema-statement/03-create-index.md) + +10. GraphQL 支持 + + 10.1 [🧭 Seaography](10-graph-ql/01-seaography-intro.md) + + 10.2 [入门](10-graph-ql/02-getting-started.md) + +11. 管理面板 + + 11.1 [🖥️ SeaORM Pro](11-sea-orm-pro/01-sea-orm-pro-intro.md) + + 11.2 [入门](11-sea-orm-pro/02-getting-started.md) + +12. 内部设计 + + 12.1 [特性与类型](12-internal-design/01-trait-and-type.md) + + 12.2 [派生宏](12-internal-design/02-derive-macro.md) + + 12.3 [与 Diesel 比较](12-internal-design/03-diesel.md) + + 12.4 [架构](12-internal-design/04-architecture.md) + + 12.5 [扩展实体格式](12-internal-design/05-expanded-entity-format.md) + +13. 下一步 + + 13.1 [下一步](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..ae362a90241 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/01-orm.md @@ -0,0 +1,13 @@ +# 什么是 ORM + +对象关系映射器(ORM)是一类编程库,用于帮助你在面向对象编程(OOP)语言中与关系数据库交互。 + +数据库中的表和列会被映射到对象与属性,同时提供额外的方法,让你可以从数据库加载数据或将数据存储到数据库中。。 + +使用 Rust 构建的服务具有以下特点:轻量(二进制文件小,内存使用率低)、安全(具有编译时保证)、正确(前提是单元测试设计良好)且快速(编译时优化能够最大限度地减少运行时开销)。 + +由于 Rust 是一种静态类型、强类型、编译型、线程安全、非垃圾回收,并且面向对象方式不常规的语言,因此在 Rust 中 ORM 的使用与你熟悉的其他脚本语言中有所不同。 + +SeaORM 旨在帮助你获得上述优势,同时规避在 Rust 中编程可能遇到的问题。 + +让我们开始吧。 \ 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..5e544422017 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/02-async.md @@ -0,0 +1,12 @@ +# 异步编程 + +Rust 的异步编程是最近才稳定的特性,直到 Rust [`1.39`](https://github.com/rust-lang/rust/releases/tag/1.39.0) 才正式引入。异步生态发展迅速,而 SeaORM 是最早从零开始就考虑异步支持的 crate 之一。 + + +首先需要了解的是 [`Future`](https://rust-lang.github.io/async-book/02_execution/02_future.html) trait。它是一个占位符,表示一个将来会计算并返回某个值的函数。它惰性求值,这意味着必须调用 `.await` 才会执行实际工作。`Future` 让我们能够以很少的编程成本实现并发,例如使用 [`future::join_all`](https://docs.rs/futures/latest/futures/future/fn.join_all.html) 来并行执行多个查询。 + +其次,在多线程异步执行器中。一个`Future` 可能会在线程之间移动,因此异步函数体中使用的变量都必须能够在线程之间传递,即实现 [`Send`](https://doc.rust-lang.org/nomicon/send-and-sync.html) trait。 + +第三,Rust 中有多个异步运行时。[`async-std`](https://crates.io/crates/async-std) 和 [`tokio`](https://crates.io/crates/tokio) 是其中最常用的两个。SeaORM 使用的底层驱动 [`SQLx`](https://crates.io/crates/sqlx) 同时支持这两者。 + +理解这些概念对于你快速上手 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..c55c0f95a8a --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/03-sea-orm.md @@ -0,0 +1,15 @@ +# SeaORM 概念 + +在 SeaORM 中,包含多个表的数据库称为 `Schema`。 + +每个表对应 SeaORM 中的一个 [`Entity`](04-generate-entity/02-entity-format.md#entity),它帮助你对相关表执行 `CRUD`(创建、读取、更新和删除)操作。 + +`Entity` trait 提供了一个 API,供你在运行时检查其属性(包括 [`Column`](04-generate-entity/02-entity-format.md#column)、[`Relation`](04-generate-entity/02-entity-format.md#relation) 和 [`PrimaryKey`](04-generate-entity/02-entity-format.md#primary-key))。 + +每个表都有多个列,这些列被称为 `attribute`。 + +这些属性及其对应的值会被组合到一个 Rust 结构体中(即 [`Model`](12-internal-design/05-expanded-entity-format.md#model)),方便你进行操作。 + +不过,`Model` 仅用于读取。要执行插入、更新或者删除,你需要使用 [`ActiveModel`](12-internal-design/05-expanded-entity-format.md#active-model),它为每个属性上附加元数据。 + +最后,SeaORM 中没有单例(全局上下文)的概念。应用程序代码需要负责管理 `DatabaseConnection` 的所有权。我们提供了一些与 Web 框架(包括 [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) 和 [poem](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example))的集成示例,帮助你快速上手。 \ 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..a3bb5a3bbbc --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/01-introduction/04-tutorial.md @@ -0,0 +1,19 @@ +# 教程与示例 + +如果你希望通过循序渐进的教程来学习 SeaORM程,可以查看我们官方的 [SeaORM 教程](https://www.sea-ql.org/sea-orm-tutorial/)。还有一些由社区编写的[优秀教程](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#learning-resources)! + +你还可以查看 [SeaORM Cookbook](https://www.sea-ql.org/sea-orm-cookbook/),这里收录了一些常见问题和使用 SeaORM 的推荐实践。 + +如果你急于上手,SeaQL 维护了一系列由社区贡献的官方示例(我们欢迎更多贡献!): + ++ [Actix 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) ++ [Axum 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) ++ [GraphQL 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example) ++ [jsonrpsee 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example) ++ [Loco 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_example) ++ [Loco REST Starter 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_starter) ++ [Poem 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) ++ [Rocket 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) ++ [Salvo 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example) ++ [Tonic 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example) ++ [Seaography 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) \ No newline at end of file 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/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..bf8d2ff81ce --- /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 @@ +# 数据库与异步运行时 + +:::caution 我们需要你的支持!⭐ +感谢你使用 SeaORM。请为我们的 [GitHub 仓库](https://github.com/SeaQL/sea-orm) 标星!你的支持对 SeaORM 的持续开发和维护至关重要。 +::: + +首先,将 `sea-orm` 添加到 `Cargo.toml` 的 `[dependencies]` 中。 + +```toml title="Cargo.toml" +sea-orm = { version = "1.1.0", features = [ , , "macros" ] } +``` + +你必须选择一个 `DATABASE_DRIVER` 和一个 `ASYNC_RUNTIME`。如果你使用 SeaORM 生成的实体,则需要 `macros`。 + +## DATABASE_DRIVER + +你可以从下列选项中选择一个或多个: + ++ `sqlx-mysql` - SQLx 的 MySQL 和 MariaDB 驱动 ++ `sqlx-postgres` - SQLx 的 PostgreSQL 驱动 ++ `sqlx-sqlite` - SQLx 的 SQLite 驱动 + +另请参阅:[SQLx 文档](https://docs.rs/crate/sqlx/latest/features)。 + +:::tip SQL Server (MSSQL) 后端 + +有关 MSSQL 驱动程序的安装和配置,请参考[这里](https://www.sea-ql.org/SeaORM-X/docs/install-and-config/database-and-async-runtime/) + +::: + +## ASYNC_RUNTIME + +你必须从以下选项中选择一个: + +`runtime-async-std-native-tls`、`runtime-tokio-native-tls`、`runtime-async-std-rustls`、`runtime-tokio-rustls` + +基本格式为 `runtime-ASYNC_RUNTIME-TLS_LIB`: + ++ `ASYNC_RUNTIME` 可以是 [`async-std`](https://crates.io/crates/async-std) 或 [`tokio`](https://crates.io/crates/tokio) ++ `TLS_LIB` 可以是 [`native-tls`](https://crates.io/crates/native-tls) 或 [`rustls`](https://crates.io/crates/rustls) + +1. 选择与你的 Rust Web 框架对应的 ASYNC_RUNTIME: + +| ASYNC_RUNTIME | Web 框架 | +| :-----------: | :------------: | +| `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` 使用平台的原生安全设施,而 `rustls` 是一个(几乎)纯 Rust 的实现。 + +## 额外功能 + ++ `debug-print` - 将每个 SQL 语句打印到日志 ++ `mock` - 用于单元测试的模拟接口 ++ `macros` - 方便的过程宏 ++ `with-chrono` - [`chrono`](https://crates.io/crates/chrono) 类型支持 ++ `with-time` - [`time`](https://crates.io/crates/time) 类型支持 ++ `with-json` - [`serde-json`](https://crates.io/crates/serde-json) 类型支持 ++ `with-rust_decimal` - [`rust_decimal`](https://crates.io/crates/rust_decimal) 类型支持 ++ `with-bigdecimal` - [`bigdecimal`](https://crates.io/crates/bigdecimal) 类型支持 ++ `with-uuid` - [`uuid`](https://crates.io/crates/uuid) 类型支持 ++ `postgres-array` - 支持 Postgres 中的数组类型(当 `sqlx-postgres` 功能开启时自动启用) ++ `sea-orm-internal` - 选择性启用不稳定的内部 API(用于访问重新导出的 SQLx 类型) \ No newline at end of file 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..3792eef2d4d --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/02-connection.md @@ -0,0 +1,103 @@ +# 数据库连接 + +要获取数据库连接,请使用 [`Database`](https://docs.rs/sea-orm/*/sea_orm/struct.Database.html) 接口: + +```rust +let db: DatabaseConnection = Database::connect("protocol://username:password@host/database").await?; +``` + +`protocol` 可以是 `mysql:`、`postgres:` 或 `sqlite:`。 + +`host` 通常是 `localhost`、域名或 IP 地址。 + +:::tip + +如果 `localhost` 无法工作,请尝试输入 IP 地址和端口号,例如 `127.0.0.1:3306` 甚至 `192.168.x.x`。 + +::: + +在底层,它会创建一个 [`sqlx::Pool`](https://docs.rs/sqlx/0.5/sqlx/struct.Pool.html) 并由 [`DatabaseConnection`](https://docs.rs/sea-orm/*/sea_orm/enum.DatabaseConnection.html) 持有。 + +每次调用 `execute` 或 `query_one/all` 时,都会从连接池中获取一个连接,并在完成后释放回连接池。 + +当你 `await` 多个查询时,它们将并行执行。 + +## 连接字符串 + +以下是一些数据库特定选项的示例: + +### MySQL + +``` +mysql://username:password@host/database +``` + +### Postgres + +#### 指定 Schema + +``` +postgres://username:password@host/database?currentSchema=my_schema +``` + +### SQLite + +#### 内存模式 + +``` +sqlite::memory: +``` + +#### 文件不存在时,创建文件 + +``` +sqlite://path/to/db.sqlite?mode=rwc +``` + +#### 只读模式 + +``` +sqlite://path/to/db.sqlite?mode=ro +``` + +## 连接选项 + +要配置连接,请使用 [`ConnectOptions`](https://docs.rs/sea-orm/*/sea_orm/struct.ConnectOptions.html) 接口: + +```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"); // 设置默认 PostgreSQL schema + +let db = Database::connect(opt).await?; +``` + +## 检查连接是否有效 + +检查与数据库的连接是否仍然有效的方法。 + +```rust +|db: DatabaseConnection| { + assert!(db.ping().await.is_ok()); + db.clone().close().await; + assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire))); +} +``` + +## 关闭连接 + +连接会在 drop 时自动关闭。要显式关闭连接,请调用 `close` 方法。 + +```rust +let db = Database::connect(url).await?; + +// 连接在此处关闭 +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..da8f170e618 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/02-install-and-config/03-debug-log.md @@ -0,0 +1,39 @@ +# 调试日志 + +SeaORM 通过 [`tracing`](https://crates.io/crates/tracing) crate 记录调试信息。 + +你可以通过 `debug-print` 功能标志(feature flag)启用 SeaORM 的日志记录功能: + +```toml +[dependencies.sea-orm] +version = "1.1.0" +features = ["debug-print"] +``` + +你需要设置 [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) 来捕获和查看调试日志。请参阅以下的代码片段和此处的[完整示例](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 的调试打印会将参数注入到 SQL 字符串中,使其更易于阅读。你会看到 `SELECT "chef"."name" FROM "chef" WHERE "chef"."id" = 100`,而不是 `SELECT "chef"."name" FROM "chef" WHERE "chef"."id" = $1`。 + +## SQLx 日志记录 + +SQLx 也默认记录日志。如果启用 SeaORM 的 `debug-print`,可以向 `connect()` 传递 [`ConnectOptions`](https://docs.rs/sea-orm/*/sea_orm/struct.ConnectOptions.html) 来禁用 SQLx 的日志。 + +```rust +let mut opt = ConnectOptions::new("protocol://username:password@host/database".to_owned()); +opt + .sqlx_logging(false) // 禁用 SQLx 日志 + .sqlx_logging_level(log::LevelFilter::Info); // 或者设置 SQLx 日志级别 + +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/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..99638d897e8 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/01-setting-up-migration.md @@ -0,0 +1,180 @@ +# 设置迁移 + +:::tip Rustacean 贴纸包 🦀 +[我们的贴纸](https://www.sea-ql.org/sticker-pack/)采用优质防水乙烯基材料制成,具有独特的哑光质感。 +把它们贴在你的笔记本、记事本或任何设备上,展示你对 Rust 的热爱! +::: + +如果你从一个全新的数据库开始,最好对数据库模式进行版本控制。SeaORM 附带了一个迁移工具,允许你使用 SeaQuery 或 SQL 编写迁移。 + +如果你已经有一个包含表和数据的数据库,可以跳过本章,直接前往[生成 SeaORM 实体](04-generate-entity/01-sea-orm-cli.md)。 + +## 迁移表 + +数据库中将创建一个表来跟踪已应用的迁移。这个表会在运行迁移时自动创建。 + +
+ 默认情况下,迁移表名称为 `seaql_migrations` 。你也可以自定义迁移表的名称。 + +```rust +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + // 覆盖迁移表的名称 + fn migration_table_name() -> sea_orm::DynIden { + "override_migration_table_name".into_iden() + } + .. +} +``` +
+ +## 创建迁移目录 + +首先,使用 `cargo` 安装 `sea-orm-cli`。 + +```shell +cargo install sea-orm-cli@1.1.0 +``` + +:::tip SQL Server (MSSQL) 后端 + +支持 MSSQL 的 `sea-orm-cli` 的安装说明在[这里](https://www.sea-ql.org/SeaORM-X/docs/migration/setting-up-migration/)。 + +::: + +然后,执行 `sea-orm-cli migrate init` 来设置迁移目录。 + +```shell +# 在 `./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! + +# 如果你想在其他地方设置迁移目录 +$ sea-orm-cli migrate init -d ./other/migration/dir +``` + +你应该会看到如下结构的迁移目录。 + +``` +migration +├── Cargo.toml +├── README.md +└── src + ├── lib.rs # Migrator API,用于集成迁移 + ├── m20220101_000001_create_table.rs # 示例迁移文件 + └── main.rs # Migrator CLI,用于手动运行迁移 +``` + +请注意,如果你直接在 Git 仓库中设置迁移目录,还会创建一个 `.gitignore` 文件。 + +## 工作区结构 + +建议你按如下方式组织 cargo 工作区,以便在应用程序 crate 和迁移 crate 之间共享 SeaORM 实体。请查看[集成示例](https://github.com/SeaQL/sea-orm/tree/master/examples)以获取演示。 + +### 迁移 crate + +导入 [`sea-orm-migration`](https://crates.io/crates/sea-orm-migration) 和 [`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 = [ + # 如果你想通过 CLI 运行迁移,请至少启用一个 `ASYNC_RUNTIME` 和 `DATABASE_DRIVER` 功能。 + # 支持的特性列表见 https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime + # 例如: + # "runtime-tokio-rustls", # `ASYNC_RUNTIME` 功能 + # "sqlx-postgres", # `DATABASE_DRIVER` 功能 +] +``` + +让我们编写一个迁移。详细说明在下一节。 + +```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> { + // 将下面的示例替换为你自己的迁移脚本 + todo!(); + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // 将下面的示例替换为你自己的迁移脚本 + todo!(); + } +} +``` + +### 实体 crate + +在你的根工作区中创建一个实体 crate。 + +
+ 还没有定义 SeaORM 实体? + +你可以创建一个不包含任何实体文件的实体 crate。然后,编写并运行迁移以在数据库中创建表。最后,使用 `sea-orm-cli` [生成 SeaORM 实体](04-generate-entity/01-sea-orm-cli.md)并将实体文件输出到 `entity/src/entities` 文件夹。 + +生成实体文件后,可以在 `entity/src/lib.rs` 中添加以下行来重新导出生成的实体: + +```rust +mod entities; +pub use entities::*; +``` +
+ +``` +entity +├── Cargo.toml # 包含 SeaORM 依赖 +└── src + ├── lib.rs # 重新导出 SeaORM 和实体 + └── post.rs # 定义 `post` 实体 +``` + +指定 SeaORM 依赖。 + +```toml title="entity/Cargo.toml" +[dependencies] +sea-orm = { version = "1.1.0" } +``` + +### 应用程序 crate + +这是应用程序逻辑所在的地方。 + +创建一个包含 app、entity 和 migration crate 的工作区,并在 app crate 中导入 entity crate。 + +如果你想捆绑迁移工具到应用的中,你也可以导入 migration crate。 + +```toml title="./Cargo.toml" +[workspace] +members = [".", "entity", "migration"] + +[dependencies] +entity = { path = "entity" } +migration = { path = "migration" } # 视你的需要而定 + +[dependencies] +sea-orm = { version = "1.1.0", features = [..] } +``` + +然后,在应用中,你可以在启动时运行迁移。 + +```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..4c04413d6c3 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/02-writing-migration.md @@ -0,0 +1,382 @@ +# 编写迁移 + +每个迁移包含两个方法:`up` 和 `down`。`up` 方法用于更改数据库模式,例如添加新表、列或索引,而 `down` 方法则用于回滚 `up` 方法中执行的操作。 + +SeaORM 迁移系统具有以下优点: + +1. 使用 SeaQuery 或 SQL 编写 DDL 语句 +2. 执行多个 DDL(带条件) +3. 使用 SeaORM API 填充(seed)数据 + +## 创建迁移 + +通过执行 `sea-orm-cli migrate generate` 命令来生成新的迁移文件。 + +如果你在文件名中使用空格,它会自动按约定进行转换。 + +```shell +sea-orm-cli migrate generate NAME_OF_MIGRATION [--local-time] + +# 例如,生成如下所示的 `migration/src/m20220101_000001_create_table.rs` +sea-orm-cli migrate generate create_table + +# 这将创建与上述命令相同的迁移文件 +sea-orm-cli migrate generate "create table" +``` + +或者你可以使用下面的模板创建迁移文件。按照命名约定 `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 + } +} +``` + +此外,你必须在 [`MigratorTrait::migrations`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/migrator/trait.MigratorTrait.html#tymethod.migrations) 方法中包含新的迁移。迁移应按时间顺序排序。 + +```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), + ] + } +} +``` + +## 定义迁移 + +请参阅 [`SchemaManager`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/manager/struct.SchemaManager.html) 获取 API 参考。 + +### 使用 SeaQuery + +点击[此处](https://github.com/SeaQL/sea-query#table-create)快速了解 SeaQuery 的 DDL 语句。 + +你可以使用 [`DeriveIden`](https://docs.rs/sea-orm/*/sea_orm/derive.DeriveIden.html) 宏来定义将在迁移中使用的标识符。 + +```rust +#[derive(DeriveIden)] +enum Post { + Table, // 这是一个特殊情况;将映射到 `post` + Id, + Title, + #[sea_orm(iden = "full_text")] // 重命名标识符 + 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"); +``` + +以下是一些你可能会觉得有用的常见 DDL 片段。 + +#### Schema 创建方法 +- 创建表 + ```rust + use sea_orm::{EnumIter, Iterable}; + + #[derive(DeriveIden)] + enum Post { + Table, + Id, + Title, + #[sea_orm(iden = "text")] // 重命名标识符 + Text, + Category, + } + + #[derive(Iden, EnumIter)] + pub enum Category { + #[iden = "Feed"] + Feed, + #[iden = "Story"] + Story, + } + + // 记得将 `sea_orm_migration::schema::*` schema 助手导入作用域 + use sea_orm_migration::{prelude::*, schema::*}; + + // 定义表 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 + + // 上述等同于: + 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 + ``` +- 创建索引 + ```rust + manager.create_index(sea_query::Index::create()..) + ``` +- 创建外键 + ```rust + manager.create_foreign_key(sea_query::ForeignKey::create()..) + ``` +- 创建数据类型 (仅限 PostgreSQL) + ```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 变更方法 +- 删除表 + ```rust + use entity::post; + + manager.drop_table(sea_query::Table::drop()..) + ``` +- 更改表 + ```rust + manager.alter_table(sea_query::Table::alter()..) + ``` +- 重命名表 + ```rust + manager.rename_table(sea_query::Table::rename()..) + ``` +- 清空表 + ```rust + manager.truncate_table(sea_query::Table::truncate()..) + ``` +- 删除索引 + ```rust + manager.drop_index(sea_query::Index::drop()..) + ``` +- 删除外键 + ```rust + manager.drop_foreign_key(sea_query::ForeignKey::drop()..) + ``` +- 更改数据类型 (仅限 PostgreSQL) + ```rust + manager.alter_type(sea_query::Type::alter()..) + ``` +- 删除数据类型 (仅限 PostgreSQL) + ```rust + manager.drop_type(sea_query::extension::postgres::Type()..) + ``` + +#### Schema 检查方法 + +- 检查表是否存在 + ```rust + manager.has_table("table_name") + ``` +- 检查列是否存在 + ```rust + manager.has_column("table_name", "column_name") + ``` +- 检查索引是否存在 + ```rust + manager.has_index("table_name", "index_name") + ``` + +### 使用原始 SQL + +你可以在迁移文件中使用原始 SQL,但这样会失去 SeaQuery 提供的多后端兼容性。 + +```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(); + + // 如果 SQL 语句没有值绑定,则应使用 `execute_unprepared` + db.execute_unprepared( + "CREATE TABLE `cake` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` varchar(255) NOT NULL + )" + ) + .await?; + + // 如果 SQL 包含值绑定,则应构造一个 `Statement` + 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(()) + } +} +``` + +## 技巧 1:在一个迁移中组合多个 schema 更改 + +你可以在 up 和 down 迁移函数中组合多个更改。这是一个完整的示例: + +```rust +// 记得将 `sea_orm_migration::schema::*` schema 助手导入作用域 +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(()) // 一切顺利! +} +``` + +这是匹配的 down 函数: + +```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(()) // 一切顺利! +} +``` + +## 技巧 2:`ADD COLUMN IF NOT EXISTS` + +由于 MySQL 不支持此语法,你可以这么做: + +```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(()) +} +``` + +## 技巧 3:使用实体填充数据 + +```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(()) +} +``` + +[完整示例](https://github.com/SeaQL/sea-orm/blob/master/examples/seaography_example/migration/src/m20230102_000001_seed_bakery_data.rs)。 + +## 原子迁移 + +在 Postgres 中,迁移会以原子方式执行,这意味着迁移脚本将在事务内执行。如果迁移失败,对数据库所做的更改将回滚。但是,MySQL 和 SQLite 不支持原子迁移。 + +你可以在迁移内启动事务,以执行诸如为新创建的表[填充示例数据](03-migration/04-seeding-data.md#seeding-data-transactionally)之类的操作。 + +## Schema 优先还是实体优先? + +总体而言,我们推荐 schema 优先的方法:先编写迁移,然后从运行中的数据库生成实体 + +但有时,你可能希望使用 [`create_*_from_entity`](09-schema-statement/01-create-table.md) 方法从手写的实体文件引导(bootstrap)数据库。 + +如果你打算永远不修改实体的 schema,这完全没问题。或者,你可以保留原始实体并将其副本嵌入到迁移文件中。 \ No newline at end of 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..b2a063c2613 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/03-running-migration.md @@ -0,0 +1,112 @@ +# 运行迁移 + +定义迁移后,你可以在终端或应用程序启动时应用或回滚迁移。 + +## 命令行界面 (CLI) + +迁移可以在终端中手动运行。`DATABASE_URL` 必须在你的环境中设置,请按照[此处](04-generate-entity/01-sea-orm-cli.md#configure-environment)的说明进行配置。 + +支持的命令: +- `init`:初始化迁移目录 +- `generate`:生成新的迁移文件 +- `up`:应用所有待处理的迁移 +- `up -n 10`:应用 10 个待处理的迁移 +- `down`:回滚上次应用的迁移 +- `down -n 10`:回滚上次应用的 10 个迁移 +- `status`:检查所有迁移的状态 +- `fresh`:从数据库中删除所有表,然后重新应用所有迁移 +- `refresh`:回滚所有已应用的迁移,然后重新应用所有迁移 +- `reset`:回滚所有已应用的迁移 + +### 通过 `sea-orm-cli` + +`sea-orm-cli` 将在底层执行 `cargo run --manifest-path ./migration/Cargo.toml -- COMMAND`。 + +```shell +$ sea-orm-cli migrate COMMAND +``` + +你可以自定义清单路径。 + +```shell +$ sea-orm-cli migrate COMMAND -d ./other/migration/dir +``` + +### 通过 SeaSchema Migrator CLI + +运行在 `migration/main.rs` 中定义的迁移器 CLI。 + +```shell +cd migration +cargo run -- COMMAND +``` + +## 以编程方式迁移 + +你可以在应用程序启动时使用 `Migrator` 执行迁移,`Migrator` 实现了 [`MigratorTrait`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/migrator/trait.MigratorTrait.html)。 + +```rust title="src/main.rs" +use migration::{Migrator, MigratorTrait}; + +/// 应用所有待处理的迁移 +Migrator::up(db, None).await?; + +/// 应用 10 个待处理的迁移 +Migrator::up(db, Some(10)).await?; + +/// 回滚所有已应用的迁移 +Migrator::down(db, None).await?; + +/// 回滚上次应用的 10 个迁移 +Migrator::down(db, Some(10)).await?; + +/// 检查所有迁移的状态 +Migrator::status(db).await?; + +/// 从数据库中删除所有表,然后重新应用所有迁移 +Migrator::fresh(db).await?; + +/// 回滚所有已应用的迁移,然后重新应用所有迁移 +Migrator::refresh(db).await?; + +/// 回滚所有已应用的迁移 +Migrator::reset(db).await?; +``` + +## 在任何 PostgreSQL Schema 上运行迁移 + +默认情况下,迁移将在 `public` schema 上运行,你现在可以在 CLI 或以编程方式运行迁移时覆盖它。 + +对于 CLI,你可以使用 `-s` / `--database_schema` 选项指定目标 schema: +* 通过 sea-orm-cli:`sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema` +* 通过 SeaORM 迁移器:`cargo run -- -u postgres://root:root@localhost/database -s my_schema` + +你还可以以编程方式在目标 schema 上运行迁移: + +```rust +let connect_options = ConnectOptions::new("postgres://root:root@localhost/database") + .set_schema_search_path("my_schema") // 覆盖默认 schema + .to_owned(); + +let db = Database::connect(connect_options).await? + +migration::Migrator::up(&db, None).await?; +``` + +:::tip SQL Server (MSSQL) 后端 + +在任何 MSSQL schema 上运行迁移的配置可以在[这里](https://www.sea-ql.org/SeaORM-X/docs/migration/running-migration/)找到。 + +::: + +## 检查迁移状态 + +你可以使用 `MigratorTrait::get_pending_migrations()` 和 `MigratorTrait::get_applied_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); \ No newline at end of file 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..fb166a7d5ec --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/03-migration/04-seeding-data.md @@ -0,0 +1,85 @@ +# 填充数据 + +你可以从 `SchemaManager` 中检索 `DbConn` 并根据需要执行数据操作,例如填充数据。 + +```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(()) + } +} +``` + +你还可以执行任何 SeaQuery 语句。 + +```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, +} +``` + +## 事务性填充数据 + +启动一个事务并在迁移的 up 和 down 中执行 SQL。 + +```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(); + let txn = db.begin().await?; + + cake::ActiveModel { + name: Set("Cheesecake".to_owned()), + ..Default::default() + } + .insert(&txn) + .await?; + + // 提交 + txn.commit().await?; + + Ok(()) + } +} \ No newline at end of file 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/04-generate-entity/01-sea-orm-cli.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/01-sea-orm-cli.md new file mode 100644 index 00000000000..25fa0d2ce29 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/01-sea-orm-cli.md @@ -0,0 +1,72 @@ +# 使用 `sea-orm-cli` + +首先,使用 `cargo` 安装 `sea-orm-cli`。 + +```shell +cargo install sea-orm-cli@1.1.0 +``` + +:::tip SQL Server (MSSQL) 后端 + +支持 MSSQL 的 `sea-orm-cli` 的安装和使用可以在[这里](https://www.sea-ql.org/SeaORM-X/docs/generate-entity/sea-orm-cli/)找到。 + +::: + +## 配置环境 + +在你的环境中设置 `DATABASE_URL`,或者在你的项目根目录中创建一个 `.env` 文件。指定你的数据库连接。 + +```env title=".env" +DATABASE_URL=protocol://username:password@localhost/database +``` + +## 获取帮助 + +在任何 CLI 命令或子命令上使用 `-h` 标志以获取帮助。 + +```shell +# 列出所有可用命令 +sea-orm-cli -h + +# 列出 `generate` 命令中所有可用的子命令 +sea-orm-cli generate -h + +# 显示如何使用 `generate entity` 子命令 +sea-orm-cli generate entity -h +``` + +## 生成实体文件 + +发现数据库中的所有表,并为每个表生成相应的 SeaORM 实体文件。 + +支持的数据库: +- MySQL +- PostgreSQL +- SQLite + +命令行选项: +- `-u` / `--database-url`:数据库 URL(默认:ENV 中指定的 DATABASE_URL) +- `-s` / `--database-schema`:数据库 schema(默认:ENV 中指定的 DATABASE_SCHEMA) + - 对于 MySQL 和 SQLite,此参数将被忽略 + - 对于 PostgreSQL,此参数是可选的,默认值为“public” +- `-o` / `--output-dir`:实体文件输出目录(默认:当前目录) +- `-v` / `--verbose`:打印调试消息 +- `-l` / `--lib`:将索引文件生成为 `lib.rs` 而不是 `mod.rs` +- `--include-hidden-tables`:从隐藏表(名称以下划线开头的表默认隐藏并忽略)生成实体文件 +- `--ignore-tables`:跳过为指定表生成实体文件(默认:`seaql_migrations`) +- `--compact-format`:生成[紧凑格式](04-generate-entity/02-entity-format.md)的实体文件(默认:true) +- `--expanded-format`:生成[扩展格式](12-internal-design/05-expanded-entity-format.md)的实体文件 +- `--with-serde`:自动为实体派生 serde Serialize / Deserialize trait(`none`、`serialize`、`deserialize`、`both`)(默认:`none`) + - `--serde-skip-deserializing-primary-key`:生成带有主键字段标记为 `#[serde(skip_deserializing)]` 的实体模型 + - `--serde-skip-hidden-column`:生成带有隐藏列(列名以下划线开头)字段标记为 `#[serde(skip)]` 的实体模型 +- `--date-time-crate`:用于生成实体的日期时间 crate(`chrono`、`time`)(默认:`chrono`) +- `--max-connections`:连接池中要初始化的最大数据库连接数(默认:`1`) +- `--model-extra-derives`:向生成的模型结构体追加额外的派生宏 +- `--model-extra-attributes`:向生成的模型结构体追加额外的属性 +- `--enum-extra-derives`:向生成的枚举追加额外的派生宏 +- `--enum-extra-attributes`:向生成的枚举追加额外的属性 +- `--seaography`:在实体中生成附加结构体以进行 seaography 集成 + +```shell +# 将数据库 `bakery` 的实体文件生成到 `entity/src` +sea-orm-cli generate entity -u protocol://username:password@localhost/bakery -o entity/src \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/02-entity-format.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/02-entity-format.md new file mode 100644 index 00000000000..cbf107fcf5e --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/02-entity-format.md @@ -0,0 +1,260 @@ +# 实体格式 + +让我们看一个简单的 [Cake](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/cake.rs) 实体。 + +```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 + +即使 `Relation` 枚举或 `ActiveModelBehavior` impl 块为空,也不要删除它们。 +::: + +## 实体 + +`DeriveEntityModel` 宏完成了定义 `Entity` 以及关联 `Model`、`Column` 和 `PrimaryKey` 的所有繁重工作。 + +### 表名 + +`table_name` 属性指定数据库中对应的表。 +可选地,你还可以通过 `schema_name` 指定数据库 schema 或数据库名称。 + +### 列名 + +默认情况下,所有列名都假定为 snake_case。你可以通过指定 `rename_all` 属性来覆盖模型中所有列的此行为。 + +```rust +#[sea_orm(rename_all = "camelCase")] +pub struct Model { ... } +``` + +
+ 你可以在此处找到 `rename_all` 属性的有效值列表 + +- camelCase +- kebab-case +- mixed_case +- SCREAMING_SNAKE_CASE +- snake_case +- title_case +- UPPERCASE +- lowercase +- SCREAMING-KEBAB-CASE +- PascalCase + +
+ +## 列 + +### 列名 + +你可以通过指定 `column_name` 属性来覆盖列名。 + +```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` 属性定义了支持该属性的数据库类型。通常你不必指定此项,因为它将从 rust 类型推断出来。例如,`i32` 默认映射到 `integer`,`String` 映射到 `varchar`。你可以在下一章中阅读有关类型映射的更多信息。 + +```rust +pub quantity: i32, // 默认整数 +#[sea_orm(column_type = "Decimal(Some((16, 4)))")] +pub price: Decimal, // 必须指定数字精度 +``` + +由于 Postgres 不原生支持无符号整数类型,因此如果你想保持兼容性,不建议使用无符号类型(例如 `u64`)。 + +### 附加属性 + +你可以为列添加附加属性 `default_value`、`unique`、`indexed` 和 `nullable`。 + +如果你为可选属性指定了自定义 `column_type`,则还必须指定 `nullable`。 + +```rust +#[sea_orm(column_type = "Text", default_value = "Sam", unique, indexed, nullable)] +pub name: Option +``` + +你可以定义跨多列的唯一键,以下将导致 `(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, +} +``` + +这些属性用于 [create_table_from_entity](https://docs.rs/sea-orm/latest/sea_orm/schema/struct.Schema.html#method.create_table_from_entity) 以生成实体的表。 + +### 在 Select 和 Save 上转换列类型 + +如果你需要将列选择为一种类型,但将其保存到数据库中为另一种类型,你可以指定 `select_as` 和 `save_as` 属性来执行转换。一个典型的用例是将 `citext`(不区分大小写的文本)类型的列在 Rust 中选择为 `String`,并将其保存到数据库中为 `citext`。应该如下定义模型字段: + +```rust +#[sea_orm(select_as = "text", save_as = "citext")] +pub case_insensitive_text: String +``` + +### 忽略属性 + +如果你想忽略某个特定的模型属性,使其不映射到任何数据库列,你可以使用 `ignore` 宏属性。 + +```rust +#[sea_orm(ignore)] +pub ignore_me: String +``` + +## 主键 + +使用 `primary_key` 属性将列标记为主键。 + +```rust +#[sea_orm(primary_key)] +pub id: i32 +``` + +### 自动递增 + +默认情况下,`primary_key` 列隐含 `auto_increment`。通过指定 `false` 来覆盖它。 + +```rust +#[sea_orm(primary_key, auto_increment = false)] +pub id: i32 +``` + +### 复合键 + +这通常发生在连接表中,其中两列元组用作主键。只需注释多列即可定义复合主键。复合键的 `auto_increment` 为 `false`。 + +主键的最大元数为 12。 + +```rust +pub struct Model { + #[sea_orm(primary_key)] + pub cake_id: i32, + #[sea_orm(primary_key)] + pub fruit_id: i32, +} +``` + +## 关系 + +`DeriveRelation` 是一个宏,可帮助你实现 [`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, +} +``` + +如果没有关系,只需编写: + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} +``` + +[Related](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Related.html) trait 将实体连接在一起,以便你可以构建选择两个实体的查询。 + +在[关系](06-relation/01-one-to-one.md)一章中了解更多关于关系的信息。 + +## Active Model 行为 + +`ActiveModel` 上不同操作的钩子。例如,你可以执行自定义验证逻辑或触发副作用。在事务内部,你甚至可以在操作完成后中止操作,防止其保存到数据库中。 + +```rust +#[async_trait] +impl ActiveModelBehavior for ActiveModel { + /// 使用默认值创建新的 ActiveModel。也由 `Default::default()` 使用。 + fn new() -> Self { + Self { + uuid: Set(Uuid::new_v4()), + ..ActiveModelTrait::default() + } + } + + /// 将在插入/更新之前触发 + 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) + } + } + + /// 将在插入/更新之后触发 + async fn after_save(model: Model, db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { + Ok(model) + } + + /// 将在删除之前触发 + async fn before_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { + Ok(self) + } + + /// 将在删除之后触发 + async fn after_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { + Ok(self) + } +} +``` + +如果不需要自定义,只需编写: + +```rust +impl ActiveModelBehavior for ActiveModel {} \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/03-column-types.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/03-column-types.md new file mode 100644 index 00000000000..834f6eec5b3 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/03-column-types.md @@ -0,0 +1,150 @@ +# 列类型 + +## 类型映射 + +列类型将通过以下映射自动派生。 + +:::tip SQL Server (MSSQL) 后端 + +MSSQL 的类型映射可以在[这里](https://www.sea-ql.org/SeaORM-X/docs/generate-entity/entity-structure/)找到。 + +::: + +Rust 原始数据类型的映射: + +| Rust 类型 | 数据库类型
([`ColumnType`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ColumnType.html)) | SQLite
数据类型 | MySQL
数据类型 | PostgreSQL
数据类型 | +| --------- | --------- | --------- | --------- | --------- | +| `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 | + +Rust 非原始数据类型的映射。你可以查看 [`entity/prelude.rs`](https://github.com/SeaQL/sea-orm/blob/master/src/entity/prelude.rs) 以获取所有重新导出的类型。 + +| Rust 类型 | 数据库类型
([`ColumnType`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ColumnType.html)) | SQLite
数据类型 | MySQL
数据类型 | PostgreSQL
数据类型 | +| --------- | --------- | --------- | --------- | --------- | +| `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
`DateTimeUtc`:chrono::DateTime | Timestamp | timestamp_text | timestamp | N/A | +| `DateTimeWithTimeZone`:chrono::DateTime
`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 | + +你可以使用 `column_type` 属性覆盖 Rust 类型和 `ColumnType` 之间的默认映射。 + +```rust +#[sea_orm(column_type = "Text")] +pub name: String +``` + +## JSON 列 + +如果你需要将 JSON 字段反序列化为结构体。你需要为其派生 `FromJsonQueryResult`。 + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "json_struct")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + // JSON 列作为 `serde_json::Value` + pub json: Json, + // JSON 列作为自定义结构体 + pub json_value: KeyValue, + // 可空 JSON 列作为自定义结构体,由 jsonb 支持(仅限 Postgres) + #[sea_orm(column_type = "JsonBinary")] + pub json_value_opt: Option, + // 存储对象向量的 JSON 列 + pub json_value_vec: Vec, +} + +// 自定义结构体必须派生 `FromJsonQueryResult`、`Serialize` 和 `Deserialize` +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] +pub struct KeyValue { + pub id: i32, + pub name: String, + pub price: f32, + pub notes: Option, +} +``` + +如果你想要一种跨数据库实现数组列的方法,你可以用包装类型将其包装起来。 + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "json_string_vec")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + // 可空 JSON 列存储字符串向量 + pub str_vec: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] +pub struct StringVec(pub Vec); +``` + +更多详细信息和示例在下一章。 + +## Postgres 数组 + +数组数据类型是 Postgres 独有的功能。你可以定义一个 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 向量 + +自 `1.1.6` 起,添加了 PgVector 支持。需要 `postgres-vector` 功能标志。 + +```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, +} +``` + +有关完整示例,请参阅 [embedding_tests](https://github.com/SeaQL/sea-orm/blob/1.1.x/tests/embedding_tests.rs)。 + +## IpNetwork (Postgres) + +自 `1.1.8` 起,添加了 IpNetwork 支持。需要 `with-ipnetwork` 功能标志。 + +```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, +} \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/04-enumeration.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/04-enumeration.md new file mode 100644 index 00000000000..5f20bc90529 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/04-enumeration.md @@ -0,0 +1,298 @@ +# ActiveEnum + +你可以在模型中使用 Rust 枚举,其中值映射到数据库字符串、整数或原生枚举。 + +## 字符串 + +对于字符串枚举,除了能够为每个变体指定字符串值之外,你还可以为枚举指定 `rename_all` 属性,如果所有值都应基于大小写转换具有字符串值。 + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "camelCase")] +pub enum Category { + BigTask, + SmallWork, +} +``` + +上述等同于: + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")] +pub enum Category { + #[sea_orm(string_value = "bigTask")] + BigTask, + #[sea_orm(string_value = "smallBreak")] + SmallWork, +} +``` + +它使用 `string_value` 手动指定字符串值。 + +
+ 你可以在此处找到 `rename_all` 属性的有效值列表: + +- camelCase +- kebab-case +- mixed_case +- SCREAMING_SNAKE_CASE +- snake_case +- title_case +- UPPERCASE +- lowercase +- SCREAMING-KEBAB-CASE +- PascalCase + +
+ +### 简单枚举字符串 + +`DeriveValueType` 添加了对枚举的支持。它为由字符串数据库类型支持的客户端枚举提供了 `DeriveActiveEnum` 的更简单替代方案。 +你必须提供自定义的 `from_str` 和 `to_str` 实现。 + +```rust +#[derive(DeriveValueType)] +#[sea_orm(value_type = "String")] +pub enum Category { + BigTask, + SmallWork, +} +``` + +阅读下一章了解更多详情。 + +## 整数 + +```rust +#[derive(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, +} +``` +或者,你可以编写: +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "i32", db_type = "Integer")] +pub enum Color { + Black = 0, + White = 1, +} +``` + +## 原生数据库枚举 + +```rust +#[derive(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, +} +``` + +## MySQL + +MySQL 枚举只是列定义的一部分,不能用于不同的表。 + +```rust +Table::create() + .table(Posts::TableName) + .col(ColumnDef::new(Posts::ColumnName) + .enumeration("tea", ["EverydayTea", "BreakfastTea"])) + +"CREATE TABLE `table_name` (`column_name` ENUM('EverydayTea', 'BreakfastTea'))", +``` + +## Postgres + +如果你使用 Postgres,枚举必须在迁移中以单独的 `Type` 语句创建,你可以使用以下方式创建它: + +### 1. `TYPE` 语句 + +[完整示例](https://github.com/SeaQL/sea-orm/blob/master/sea-orm-migration/tests/common/migration/m20220118_000004_create_tea_enum.rs)。 + +```rust +// 在迁移中运行: + +manager + .create_type( + // CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea') + Type::create() + .as_enum("tea") + .values(["EverydayTea", "BreakfastTea"]) + .to_owned(), + ) + .await?; +``` + +### 2. `create_enum_from_active_enum` +此方法将提供一个接口,用于将类型添加到数据库、将类型用于表列以及在填充数据时将此类型的值添加到行。 + +1. 定义一个 `ActiveEnum` + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea_type")] +pub enum TeaType { + #[sea_orm(string_value = "EverydayTea")] + EverydayTea, + #[sea_orm(string_value = "BreakfastTea")] + BreakfastTea, +} +``` + +2. 在数据库中创建类型 + +```rust +use sea_orm::{Schema, DbBackend}; + +// 在迁移中: +let schema = Schema::new(DbBackend::Postgres); + +manager + .create_type( + // CREATE TYPE "tea_type" AS ENUM ('EverydayTea', 'BreakfastTea') + schema.create_enum_from_active_enum::(), + ) + .await?; +``` + +3. 在创建表时将类型用作表列类型 + +```rust diff +// 在迁移中: + +manager::create() + .table(Tea::Table) + .if_not_exists() + .col(Column::new(Tea::Type).custom(TeaType::name())) // 将类型用于表列 + // ... 更多列 +``` +> 另请参阅 [Schema 创建方法 - 创建表](https://www.sea-ql.org/SeaORM/docs/migration/writing-migration/#schema-creation-methods) + +4. 在填充数据库时使用类型 + +```rust +// 在迁移中 + +let insert = Query::insert() + .into_table(Tea::Table) + .columns([Tea::TeaType]) + .values_panic([TeaType::EverydayTea.as_enum()]) // 调用 `as_enum` 将变体转换为 SimpleExpr + .to_owned(); + +manager.execute(insert).await?; +// ... +``` +> 另请参阅 [填充数据 - 使用 sea_query 语句](https://www.sea-ql.org/SeaORM/docs/migration/seeding-data/#:~:text=write%20SeaQuery%20statement%20to%20seed%20the%20table) + +## Trait 实现 + +[`DeriveActiveEnum`](https://docs.rs/sea-orm/*/sea_orm/derive.DeriveActiveEnum.html) 宏在底层实现了 [`ActiveEnum`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.ActiveEnum.html) trait。 + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm( + rs_type = "String", + db_type = "String(StringLen::N(1))", + enum_name = "category" +)] +pub enum Category { + #[sea_orm(string_value = "B")] + Big, + #[sea_orm(string_value = "S")] + Small, +} +``` + +
+ 为了说明目的,这大致是宏实现的内容: +
+ +```rust +use sea_orm::entity::prelude::*; + +#[derive(Debug, PartialEq, Eq, EnumIter)] +pub enum Category { + Big, + Small, +} + +// 手动实现 +impl ActiveEnum for Category { + // 宏属性 `rs_type` 在此处粘贴 + type Value = String; + + // 默认情况下,如果未明确提供 `enum_name`,则 Rust 枚举的名称为驼峰式 + fn name() -> String { + "category".to_owned() + } + + // 将 Rust 枚举变体映射到相应的 `num_value` 或 `string_value` + fn to_value(&self) -> Self::Value { + match self { + Self::Big => "B", + Self::Small => "S", + } + .to_owned() + } + + // 将 `num_value` 或 `string_value` 映射到相应的 Rust 枚举变体 + fn try_from_value(v: &Self::Value) -> Result { + match v.as_ref() { + "B" => Ok(Self::Big), + "S" => Ok(Self::Small), + _ => Err(DbErr::Type(format!( + "unexpected value for Category enum: {}", + v + ))), + } + } + + // 宏属性 `db_type` 在此处粘贴 + fn db_type() -> ColumnDef { + ColumnType::String(Some(1)).def() + } +} +``` +
+
+ +## 在模型上使用 ActiveEnum + +```rust +use sea_orm::entity::prelude::*; + +// 定义 `Category` 活动枚举 +#[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(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "active_enum")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + // 表示使用 `Category` 活动枚举的数据库列 + pub category: Category, + pub category_opt: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/05-newtype.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/05-newtype.md new file mode 100644 index 00000000000..952cec0f918 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/05-newtype.md @@ -0,0 +1,310 @@ +# 新类型 + +你可以定义一个新类型 (`T`) 并将其用作模型字段。必须实现以下 trait。 + +1. 为 [`sea_query::Value`](https://docs.rs/sea-query/*/sea_query/value/enum.Value.html) 实现 `From` +2. 为 `T` 实现 [`sea_orm::TryGetable`](https://docs.rs/sea-orm/*/sea_orm/trait.TryGetable.html) +3. 为 `T` 实现 [`sea_query::ValueType`](https://docs.rs/sea-query/*/sea_query/value/trait.ValueType.html) +4. 为 `T` 实现 [`sea_query::Nullable`](https://docs.rs/sea-query/*/sea_query/value/trait.Nullable.html) + +## 包装类型 + +你可以创建包装 SeaORM 支持的任何类型的新类型。 + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "custom_value_type")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub number: Integer, +} + +#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] +pub struct Integer(i32); +``` + +
+ 其中 `Integer` 展开为: + +```rust +#[automatically_derived] +impl std::convert::From for sea_orm::Value { + fn from(source: Integer) -> Self { + source.0.into() + } +} + +#[automatically_derived] +impl sea_orm::TryGetable for Integer { + fn try_get_by(res: &sea_orm::QueryResult, idx: I) + -> std::result::Result { + ::try_get_by(res, idx).map(|v| Integer(v)) + } +} + +#[automatically_derived] +impl sea_orm::sea_query::ValueType for Integer { + fn try_from(v: sea_orm::Value) -> std::result::Result { + ::try_from(v).map(|v| Integer(v)) + } + + fn type_name() -> std::string::String { + stringify!(Integer).to_owned() + } + + fn array_type() -> sea_orm::sea_query::ArrayType { + sea_orm::sea_query::ArrayType::Int + } + + fn column_type() -> sea_orm::sea_query::ColumnType { + sea_orm::sea_query::ColumnType::Integer + } +} + +#[automatically_derived] +impl sea_orm::sea_query::Nullable for Integer { + fn null() -> sea_orm::Value { + ::null() + } +} +``` +
+ +### 将包装类型用作主键 + +:::tip 自 `2.0.0` 起 +::: + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "custom_value_type")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: Integer, +} +``` + +仅适用于 `i8` / `i16` / `i32` / `i64` / `u8` / `u16` / `u32` / `u64`。 + +## 包装 `Vec` (仅限 Postgres) + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "custom_vec_type")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub str_vec: StringVec, +} + +#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] +pub struct StringVec(pub Vec); +``` + +
+ 其中 `StringVec` 展开为: + +```rust +#[automatically_derived] +impl std::convert::From for Value { + fn from(source: StringVec) -> Self { + source.0.into() + } +} + +#[automatically_derived] +impl sea_orm::TryGetable for StringVec { + fn try_get_by(res: &QueryResult, idx: I) -> Result { + as sea_orm::TryGetable>::try_get_by(res, idx).map(|v| StringVec(v)) + } +} + +#[automatically_derived] +impl sea_orm::sea_query::ValueType for StringVec { + fn try_from(v: Value) -> Result { + as sea_orm::sea_query::ValueType>::try_from(v).map(|v| StringVec(v)) + } + + fn type_name() -> String { + stringify!(StringVec).to_owned() + } + + fn array_type() -> sea_orm::sea_query::ArrayType { + sea_orm::sea_query::ArrayType::String + } + + fn column_type() -> sea_orm::sea_query::ColumnType { + sea_orm::sea_query::ColumnType::String(StringLen::None) + } +} + +#[automatically_derived] +impl sea_orm::sea_query::Nullable for Integer { + fn null() -> sea_orm::Value { + as sea_orm::sea_query::Nullable>::null() + } +} +``` +
+ +## 包装 `Vec` (后端通用) + +你还可以通过将对象序列化/反序列化为 JSON 来定义后端通用的 `Vec` 字段: + +```rust +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "json_vec_type")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub json_vec: ObjectVec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] +pub struct ObjectVec(pub Vec); + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct MyObject { + .. +} +``` + +
+ 其中 `ObjectVec` 展开为: + +```rust +impl sea_orm::TryGetableFromJson for ObjectVec {} + +impl std::convert::From for sea_orm::Value { + fn from(source: ObjectVec) -> Self { + sea_orm::Value::Json(serde_json::to_value(&source).ok().map(|s| std::boxed::Box::new(s))) + } +} + +impl sea_orm::sea_query::ValueType for ObjectVec { + fn try_from(v: sea_orm::Value) -> Result { + match v { + sea_orm::Value::Json(Some(json)) => Ok( + serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?, + ), + _ => Err(sea_orm::sea_query::ValueTypeErr), + } + } + + fn type_name() -> String { + stringify!(ObjectVec).to_owned() + } + + fn array_type() -> sea_orm::sea_query::ArrayType { + sea_orm::sea_query::ArrayType::Json + } + + fn column_type() -> sea_orm::sea_query::ColumnType { + sea_orm::sea_query::ColumnType::Json + } +} + +impl sea_orm::sea_query::Nullable for ObjectVec { + fn null() -> sea_orm::Value { + sea_orm::Value::Json(None) + } +} +``` +
+ +## 枚举字符串 + +自 `1.1.8` 起,`DeriveValueType` 也支持 `enum` 类型。它为由字符串数据库类型支持的客户端枚举提供了 `DeriveActiveEnum` 的更简单替代方案。 + +```rust +#[derive(DeriveValueType)] +#[sea_orm(value_type = "String")] +pub enum Tag { + Hard, + Soft, +} + +// `from_str` 默认为 `std::str::FromStr::from_str` +impl std::str::FromStr for Tag { + type Err = sea_orm::sea_query::ValueTypeErr; + fn from_str(s: &str) -> Result { .. } +} + +// `to_str` 默认为 `std::string::ToString::to_string`。 +impl std::fmt::Display for Tag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. } +} +``` + +
+ 其中 `Tag` 展开为: + +```rust +#[automatically_derived] +impl std::convert::From for sea_orm::Value { + fn from(source: Tag) -> Self { + std::string::ToString::to_string(&source).into() + } +} + +#[automatically_derived] +impl sea_orm::TryGetable for Tag { + fn try_get_by(res: &sea_orm::QueryResult, idx: I) + -> std::result::Result { + let string = String::try_get_by(res, idx)?; + std::str::FromStr::from_str(&string).map_err(|err| sea_orm::TryGetError::DbErr(sea_orm::DbErr::Type(format!("{err:?}")))) + } +} + +#[automatically_derived] +impl sea_orm::sea_query::ValueType for Tag { + fn try_from(v: sea_orm::Value) -> std::result::Result { + let string = ::try_from(v)?; + std::str::FromStr::from_str(&string).map_err(|_| sea_orm::sea_query::ValueTypeErr) + } + + fn type_name() -> std::string::String { + stringify!(Tag).to_owned() + } + + fn array_type() -> sea_orm::sea_query::ArrayType { + sea_orm::sea_query::ArrayType::String + } + + fn column_type() -> sea_orm::sea_query::ColumnType { + sea_orm::sea_query::ColumnType::String(sea_orm::sea_query::StringLen::None) + } +} + +#[automatically_derived] +impl sea_orm::sea_query::Nullable for Tag { + fn null() -> sea_orm::Value { + sea_orm::Value::String(None) + } +} +``` +
+ +你可以使用自定义函数覆盖 `from_str` 和 `to_str`,这在你使用 [`strum::Display`](https://docs.rs/strum/latest/strum/derive.Display.html) 和 [`strum::EnumString`](https://docs.rs/strum/latest/strum/derive.EnumString.html) 或手动实现方法时特别有用: + +```rust +#[derive(DeriveValueType)] +#[sea_orm(value_type = "String", from_str = "Tag::from_str", to_str = "Tag::to_str")] +pub enum Tag { + Color, + Grey, +} + +impl Tag { + fn from_str(s: &str) -> Result { .. } + + fn to_str(&self) -> &'static str { .. } +} \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/_category_.json b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/_category_.json new file mode 100644 index 00000000000..300b9eddb52 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/04-generate-entity/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Generating Entities", + "collapsed": false +} diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/01-basic-schema.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/01-basic-schema.md new file mode 100644 index 00000000000..0825d058fdf --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/01-basic-schema.md @@ -0,0 +1,9 @@ +# 基本 Schema + +我们将使用这个[基本 schema](https://github.com/SeaQL/sea-orm/tree/master/src/tests_cfg) 进行演示: + ++ `cake` 一对多 `fruit` ++ `cake` 多对多 `filling` ++ `cake_filling` 是 `cake` 和 `filling` 之间的连接表 + +![基本 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/05-basic-crud/02-select.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/02-select.md new file mode 100644 index 00000000000..b25799746c8 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/02-select.md @@ -0,0 +1,164 @@ +# 选择 + +定义实体后,你就可以从数据库中检索数据了。数据库中的每一行数据都对应一个 `Model`。 + +默认情况下,SeaORM 将选择 `Column` 枚举中定义的所有列。 + +## 按主键查找 + +通过主键查找模型,它可以是单个键或复合键。我们首先在 [`Entity`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html) 上调用 [`find_by_id`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html#method.find_by_id),它帮助你自动构建选择查询和条件。然后,使用 `one` 方法从数据库中获取单个模型。 + +```rust +use super::cake::Entity as Cake; +use super::cake_filling::Entity as CakeFilling; + +// 按主键查找 +let cheese: Option = Cake::find_by_id(1).one(db).await?; + +// 按复合主键查找 +let vanilla: Option = CakeFilling::find_by_id((6, 8)).one(db).await?; +``` + +## 带条件和排序的查找 + +除了通过主键检索模型之外,你还可以按特定顺序检索匹配特定条件的一个或多个模型。[`find`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html#method.find) 方法允许你访问 SeaORM 中的查询构建器。它支持构建所有常见的选择表达式,如 `where` 和 `order by`。它们可以分别使用 [`filter`](https://docs.rs/sea-orm/*/sea_orm/query/trait.QueryFilter.html#method.filter) 和 [`order_by_*`](https://docs.rs/sea-orm/*/sea_orm/query/trait.QueryOrder.html#method.order_by) 方法构建。 + +> 阅读更多关于[条件表达式](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?; +``` + +## 查找相关模型 + +> 阅读更多关于[关系](06-relation/01-one-to-one.md)一章的内容。 + +### 延迟加载 + +使用 [`find_related`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.ModelTrait.html#method.find_related) 方法。 + +相关模型在你请求时按需加载,如果你想根据某些应用程序逻辑加载相关模型,则更可取。请注意,与急切加载相比,延迟加载会增加数据库往返次数。 + +```rust +// 首先查找一个蛋糕模型 +let cheese: Option = Cake::find_by_id(1).one(db).await?; +let cheese: cake::Model = cheese.unwrap(); + +// 然后,查找此蛋糕的所有相关水果 +let fruits: Vec = cheese.find_related(Fruit).all(db).await?; +``` + +### 急切加载 + +所有相关模型一次性加载。与延迟加载相比,这提供了最少的数据库往返次数。 + +#### 一对一 + +使用 [`find_also_related`](https://docs.rs/sea-orm/*/sea_orm/query/struct.Select.html#method.find_also_related) 方法。 + +```rust +let fruits_and_cakes: Vec<(fruit::Model, Option)> = Fruit::find().find_also_related(Cake).all(db).await?; +``` + +#### 一对多 / 多对多 + +使用 [`find_with_related`](https://docs.rs/sea-orm/*/sea_orm/query/struct.Select.html#method.find_with_related) 方法,相关模型将按父模型分组。此方法处理 1-N 和 M-N 两种情况,并且在涉及连接表时将执行 2 次连接。 + +```rust +let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() + .find_with_related(Fruit) + .all(db) + .await?; +``` + +### 批量加载 + +自 0.11 起,我们引入了 [LoaderTrait](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html) 以批量加载相关实体。 + +与急切加载相比,它以一次(或两次,在多对多情况下)更多的数据库往返为代价节省了带宽(考虑一对多情况,一侧的行可能会重复)。 + +#### 一对一 + +使用 [load_one](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_one) 方法。 + +```rust +let fruits: Vec = Fruit::find().all(db).await?; +let cakes: Vec> = fruits.load_one(Cake, db).await?; +``` + +#### 一对多 + +使用 [load_many](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_many) 方法。 + +```rust +let cakes: Vec = Cake::find().all(db).await?; +let fruits: Vec> = cakes.load_many(Fruit, db).await?; +``` + +#### 多对多 + +使用 [load_many_to_many](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_many_to_many) 方法。你必须提供连接实体。 + +```rust +let cakes: Vec = Cake::find().all(db).await?; +let fillings: Vec> = cakes.load_many_to_many(Filling, CakeFilling, db).await?; +``` + +## 分页结果 + +使用自定义页面大小将任何 SeaORM 选择转换为[分页器](https://docs.rs/sea-orm/*/sea_orm/struct.Paginator.html)。 + +```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? { + // 对 cakes: Vec 执行操作 +} +``` + +## 光标分页 + +如果你想根据主键等列对行进行分页,请使用光标分页。 + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake}; +// 创建一个按 `cake`.`id` 排序的光标 +let mut cursor = cake::Entity::find().cursor_by(cake::Column::Id); + +// 按 `cake`.`id` > 1 AND `cake`.`id` < 100 过滤分页结果 +cursor.after(1).before(100); + +// 获取前 10 行(按 `cake`.`id` 升序排序) +for cake in cursor.first(10).all(db).await? { + // 对 cake: cake::Model 执行操作 +} + +// 获取后 10 行(按 `cake`.`id` 降序排序,但行按升序返回) +for cake in cursor.last(10).all(db).await? { + // 对 cake: cake::Model 执行操作 +} +``` + +基于复合主键的分页也可用。 + +```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?; +``` + +## 选择自定义 + +如果你想选择自定义列和表达式,请阅读[自定义选择](08-advanced-query/01-custom-select.md)部分。 \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/03-active-model.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/03-active-model.md new file mode 100644 index 00000000000..e905b400f66 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/03-active-model.md @@ -0,0 +1,124 @@ +# ActiveModel + +在深入了解插入和更新操作之前,我们必须介绍 `ActiveValue` 和 `ActiveModel`。 + +## ActiveValue + +`ActiveModel` 中字段的状态。 + +有三种可能的状态,由三个枚举变体表示: + +- `Set` - 由应用程序显式设置并发送到数据库的 `Value`。 + 用于插入或设置特定值。 + + 编辑现有值时,你可以使用 [`set_if_not_equal`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ActiveValue.html#method.set_if_not_equals) + 在新值与旧值相同时保留 `Unchanged` 状态。 + 然后你可以有意义地使用 [`ActiveModelTrait::is_changed`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.ActiveModelTrait.html#method.is_changed) 等方法。 +- `Unchanged` - 数据库中现有且未更改的 `Value`。 + + 当你从数据库中查询现有 `Model` 并将其转换为 `ActiveModel` 时,你会得到这些。 +- `NotSet` - 未定义的 `Value`。没有内容发送到数据库。 + + 当你创建新的 `ActiveModel` 时,其字段默认是 `NotSet`。 + + 这在以下情况下很有用: + + - 你插入新记录并希望数据库生成默认值(例如,id)。 + - 在 `UPDATE` 语句中,你不想更新某些字段。 + +这些状态之间的差异在构建 `INSERT` 和 `UPDATE` SQL 语句时很有用(请参阅下面的示例)。 +它对于了解记录中哪些字段已更改也很有用。 + +### 示例 + +```rust +use sea_orm::tests_cfg::{cake, fruit}; +use sea_orm::{DbBackend, entity::*, query::*}; + +// 在这里,我们使用 `NotSet` 让数据库自动生成 `id`。 +// 这与显式将 `cake_id` 设置为 `NULL` 的 `Set(None)` 不同。 +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)"# +); + +// 在这里,我们更新记录,将 `cake_id` 设置为新值 +// 并使用 `NotSet` 避免更新 `name` 字段。 +// `id` 是主键,因此它用于条件中,并且不更新。 +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 + +`ActiveModel` 具有 `Model` 的所有属性,并包装在 `ActiveValue` 中。 + +你可以使用 `ActiveModel` 插入一行,其中设置了列的子集。 + +```rust +let cheese: Option = Cake::find_by_id(1).one(db).await?; + +// 获取模型 +let model: cake::Model = cheese.unwrap(); +assert_eq!(model.name, "Cheese Cake".to_owned()); + +// 转换为 ActiveModel +let active_model: cake::ActiveModel = model.into(); +assert_eq!(active_model.name, ActiveValue::unchanged("Cheese Cake".to_owned())); +``` + +### 检查 ActiveModel 是否已更改 + +你可以使用 [`is_changed`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ActiveModelTrait.html#method.is_changed) 方法检查 `ActiveModel` 中的任何字段是否已 `Set`。 + +```rust +let mut fruit: fruit::ActiveModel = Default::default(); +assert!(!fruit.is_changed()); + +fruit.set(fruit::Column::Name, "apple".into()); +assert!(fruit.is_changed()); +``` + +### 将 ActiveModel 转换回 Model + +使用 [`try_into_model`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.TryIntoModel.html#tymethod.try_into_model) 方法可以将 ActiveModel 转换回 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"))) +); \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/04-insert.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/04-insert.md new file mode 100644 index 00000000000..88cb696cd37 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/04-insert.md @@ -0,0 +1,256 @@ +# 插入 + +## 插入一条 + +插入一个活动模型并返回一个新的 `Model`。其值从数据库中检索,因此任何自动生成的字段都将被填充。 + +```rust +let pear = fruit::ActiveModel { + name: Set("Pear".to_owned()), + ..Default::default() // 所有其他属性都是 `NotSet` +}; + +let pear: fruit::Model = pear.insert(db).await?; +``` + +插入一个活动模型并返回最后插入的 id。其类型与模型的主键类型匹配,因此如果模型具有复合主键,它可能是一个元组。 + +```rust +let pear = fruit::ActiveModel { + name: Set("Pear".to_owned()), + ..Default::default() // 所有其他属性都是 `NotSet` +}; + +let res: InsertResult = fruit::Entity::insert(pear).exec(db).await?; +assert_eq!(res.last_insert_id, 28) +``` + +:::tip SQL Server (MSSQL) 后端 + +MSSQL 的 `IDENTITY INSERT` [此处](https://www.sea-ql.org/SeaORM-X/docs/basic-crud/insert/)有文档说明。 + +::: + +## 插入多条 + +插入多个活动模型并返回最后插入的 id。 + +```rust +let apple = fruit::ActiveModel { + name: Set("Apple".to_owned()), + ..Default::default() +}; + +let orange = fruit::ActiveModel { + name: Set("Orange".to_owned()), + ..Default::default() +}; + +let res: InsertResult = Fruit::insert_many([apple, orange]).exec(db).await?; +assert_eq!(res.last_insert_id, 30) +``` + +向 `insert_many` 方法提供空集将导致错误。但是,你可以使用 `on_empty_do_nothing` 更改行为,它将 `InsertResult` 包装在 `TryInsertResult` 中。 + +```rust +let res = Bakery::insert_many(std::iter::empty()) + .on_empty_do_nothing() + .exec(db) + .await; + +assert!(matches!(res, Ok(TryInsertResult::Empty))); +``` + +## 冲突时 + +插入具有冲突行为的活动模型。 + +```rust +let orange = cake::ActiveModel { + id: ActiveValue::set(2), + name: ActiveValue::set("Orange".to_owned()), +}; + +assert_eq!( + cake::Entity::insert(orange.clone()) + .on_conflict( + // 冲突时不做任何操作 + sea_query::OnConflict::column(cake::Column::Name) + .do_nothing() + .to_owned() + ) + .build(DbBackend::Postgres) + .to_string(), + r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING"#, +); + +assert_eq!( + cake::Entity::insert(orange) + .on_conflict( + // 冲突时更新 + sea_query::OnConflict::column(cake::Column::Name) + .update_column(cake::Column::Name) + .to_owned() + ) + .build(DbBackend::Postgres) + .to_string(), + r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name""#, +); +``` + +执行 upsert 语句而不插入或更新任何行将导致 `DbErr::RecordNotInserted` 错误。 + +```rust +// 当 `id` 列有冲突值时,不做任何操作 +let on_conflict = OnConflict::column(Column::Id).do_nothing().to_owned(); + +// 将 `1`、`2`、`3` 插入到表中 +let res = Entity::insert_many([ + ActiveModel { id: Set(1) }, + ActiveModel { id: Set(2) }, + ActiveModel { id: Set(3) }, +]) +.on_conflict(on_conflict.clone()) +.exec(db) +.await; + +assert_eq!(res?.last_insert_id, 3); + +// 将 `4` 和之前的 3 行一起插入到表中 +let res = Entity::insert_many([ + ActiveModel { id: Set(1) }, + ActiveModel { id: Set(2) }, + ActiveModel { id: Set(3) }, + ActiveModel { id: Set(4) }, +]) +.on_conflict(on_conflict.clone()) +.exec(db) +.await; + +assert_eq!(res?.last_insert_id, 4); + +// 重复上次插入。由于所有 4 行都已存在,这实际上没有做任何操作。 +// 将抛出 `DbErr::RecordNotInserted` 错误。 +let res = Entity::insert_many([ + ActiveModel { id: Set(1) }, + ActiveModel { id: Set(2) }, + ActiveModel { id: Set(3) }, + ActiveModel { id: Set(4) }, +]) +.on_conflict(on_conflict) +.exec(db) +.await; + +assert_eq!(res.err(), Some(DbErr::RecordNotInserted)); +``` + +如果你希望 `RecordNotInserted` 是 `Ok` 而不是错误,请调用 `.do_nothing()`: + +```rust +let res = Entity::insert_many([..]) + .on_conflict(on_conflict) + .do_nothing() + .exec(db) + .await; + +assert!(matches!(res, Ok(TryInsertResult::Conflicted))); +``` + +### MySQL 支持 + +在主键 `ON CONFLICT` 上设置 `DO NOTHING`,但使用 MySQL 特定的 polyfill。 + +```rust +let orange = cake::ActiveModel { + id: ActiveValue::set(2), + name: ActiveValue::set("Orange".to_owned()), +}; + +assert_eq!( + cake::Entity::insert(orange.clone()) + .on_conflict_do_nothing() + .build(DbBackend::MySql) + .to_string(), + r#"INSERT INTO `cake` (`id`, `name`) VALUES (2, 'Orange') ON DUPLICATE KEY UPDATE `id` = `id`"#, +); +assert_eq!( + cake::Entity::insert(orange.clone()) + .on_conflict_do_nothing() + .build(DbBackend::Postgres) + .to_string(), + r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("id") DO NOTHING"#, +); +assert_eq!( + cake::Entity::insert(orange) + .on_conflict_do_nothing() + .build(DbBackend::Sqlite) + .to_string(), + r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("id") DO NOTHING"#, +); +``` + +## 返回插入的模型 + +Postgres 和 SQLite 支持,以下在插入后返回新插入的模型。 + +```rust +assert_eq!( + cake::Entity::insert(cake::ActiveModel { + id: NotSet, + name: Set("Apple Pie".to_owned()), + }) + .exec_with_returning(&db) + .await?, + cake::Model { + id: 1, + name: "Apple Pie".to_owned(), + } +); +``` + +```rust +assert_eq!( + cake::Entity::insert_many([ + cake::ActiveModel { + id: NotSet, + name: Set("Apple Pie".to_owned()), + }, + cake::ActiveModel { + id: NotSet, + name: Set("Choco Pie".to_owned()), + }, + ]) + .exec_with_returning(&db) + .await?, + [ + cake::Model { + id: 1, + name: "Apple Pie".to_owned(), + }, + cake::Model { + id: 2, + name: "Choco Pie".to_owned(), + } + ] +); +``` + +如果你只需要插入后的主键,还有一个 `exec_with_returning_keys`。 + +```rust +assert_eq!( + cakes_bakers::Entity::insert_many([ + cakes_bakers::ActiveModel { + cake_id: Set(1), + baker_id: Set(2), + }, + cakes_bakers::ActiveModel { + cake_id: Set(2), + baker_id: Set(1), + }, + ]) + .exec_with_returning_keys(db) + .await + .unwrap(), + [(1, 2), (2, 1)] +); \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/05-update.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/05-update.md new file mode 100644 index 00000000000..0bcbf858d87 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/05-update.md @@ -0,0 +1,75 @@ +# 更新 + +## 更新一条 + +你将从查找结果中获得一个 `Model`。如果你想将模型保存回数据库,你需要*首先*将其转换为 `ActiveModel`。生成的查询将只包含 `Set` 属性。 + +```rust +let pear: Option = Fruit::find_by_id(28).one(db).await?; + +// 转换为 ActiveModel +let mut pear: fruit::ActiveModel = pear.unwrap().into(); + +// 更新名称属性 +pear.name = Set("Sweet pear".to_owned()); + +// SQL: `UPDATE "fruit" SET "name" = 'Sweet pear' WHERE "id" = 28` +let pear: fruit::Model = pear.update(db).await?;``` + +要更新所有属性,你可以将 `Unchanged` 转换为 `Set`。 + +```rust +// 转换为 ActiveModel +let mut pear: fruit::ActiveModel = pear.into(); + +// 更新名称属性 +pear.name = Set("Sweet pear".to_owned()); + +// 将特定属性设置为“脏”(强制更新) +pear.reset(fruit::Column::CakeId); +// 或者,将所有属性设置为“脏”(强制更新) +pear.reset_all(); + +// SQL: `UPDATE "fruit" SET "name" = 'Sweet pear', "cake_id" = 10 WHERE "id" = 28` +let pear: fruit::Model = pear.update(db).await?; +``` + +## 更新多条 + +你还可以更新数据库中的多行,而无需使用 SeaORM select 查找每个 `Model`。 + +```rust +// 使用 ActiveModel 批量设置属性 +let update_result: UpdateResult = Fruit::update_many() + .set(pear) + .filter(fruit::Column::Id.eq(1)) + .exec(db) + .await?; + +// UPDATE `fruit` SET `cake_id` = 1 WHERE `fruit`.`name` LIKE '%Apple%' +Fruit::update_many() + .col_expr(fruit::Column::CakeId, Expr::value(1)) + .filter(fruit::Column::Name.contains("Apple")) + .exec(db) + .await?; +``` + +## 返回更新的模型 + +仅 Postgres 支持,SQLite 需要 `sqlite-use-returning-for-3_35` 功能标志。 + +```rust +let fruits: Vec = Fruit::update_many() + .col_expr(fruit::Column::CakeId, Expr::value(1)) + .filter(fruit::Column::Name.contains("Apple")) + .exec_with_returning(db) + .await?; + +assert_eq!( + fruits[0], + fruit::Model { + id: 2, + name: "Apple".to_owned(), + cake_id: Some(1), + } +); \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/06-save.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/06-save.md new file mode 100644 index 00000000000..d75c3b80b41 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/06-save.md @@ -0,0 +1,31 @@ +# 保存 + +这是一个帮助方法,用于将 `ActiveModel` 保存(插入/更新)到数据库中。 + +## 保存行为 + +保存 `ActiveModel` 时,它将根据主键属性执行插入或更新: + +- 如果主键是 `NotSet`,则插入 +- 如果主键是 `Set` 或 `Unchanged`,则更新 + +## 用法 + +调用 `save` 插入或更新 `ActiveModel`。 + +```rust +use sea_orm::ActiveValue::NotSet; + +let banana = fruit::ActiveModel { + id: NotSet, // 主键是 NotSet + name: Set("Banana".to_owned()), + ..Default::default() // 所有其他属性都是 `NotSet` +}; + +// 插入,因为主键 `id` 是 `NotSet` +let banana: fruit::ActiveModel = banana.save(db).await?; + +banana.name = Set("Banana Mongo".to_owned()); + +// 更新,因为主键 `id` 是 `Unchanged` +let banana: fruit::ActiveModel = banana.save(db).await?; \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/07-delete.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/07-delete.md new file mode 100644 index 00000000000..2ccc61c3977 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/07-delete.md @@ -0,0 +1,65 @@ +# 删除 + +## 删除一条 + +从数据库中查找 `Model`,然后从数据库中删除相应的行。 + +```rust +use sea_orm::entity::ModelTrait; + +let orange: Option = Fruit::find_by_id(30).one(db).await?; +let orange: fruit::Model = orange.unwrap(); + +let res: DeleteResult = orange.delete(db).await?; +assert_eq!(res.rows_affected, 1); +``` + +## 按主键删除 + +除了从数据库中选择 `Model` 然后删除它之外。你还可以直接通过主键从数据库中删除一行。 + +```rust +let res: DeleteResult = Fruit::delete_by_id(38).exec(db).await?; +assert_eq!(res.rows_affected, 1); +``` + +## 删除多条 + +你还可以从数据库中删除多行,而无需使用 SeaORM select 查找每个 `Model`。 + +```rust +// DELETE FROM `fruit` WHERE `fruit`.`name` LIKE '%Orange%' +let res: DeleteResult = fruit::Entity::delete_many() + .filter(fruit::Column::Name.contains("Orange")) + .exec(db) + .await?; + +assert_eq!(res.rows_affected, 2); +``` + +## 返回已删除的模型 + +仅 Postgres 支持,SQLite 需要 `sqlite-use-returning-for-3_35` 功能标志。 + +```rust +assert_eq!( + fruit::Entity::delete(ActiveModel { + id: Set(3), + ..Default::default() + }) + .exec_with_returning(db) + .await?, + Some(fruit::Model { + id: 3, + name: "Apple".to_owned(), + }) +); +``` + +```rust +let deleted_models: Vec = order::Entity::delete_many() + .filter(order::Column::CustomerId.eq(22)) + .exec_with_returning(db) + .await? + +assert_eq!(deleted_models.len(), 2); // 删除两项 \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/08-json.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/08-json.md new file mode 100644 index 00000000000..33dcd727aee --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/08-json.md @@ -0,0 +1,130 @@ +# JSON + +## 选择 JSON 结果 + +所有 SeaORM 选择都能够返回 `serde_json::Value`。 + +```rust +// 按 id 查找 +let cake: Option = Cake::find_by_id(1) + .into_json() + .one(db) + .await?; + +assert_eq!( + cake, + Some(serde_json::json!({ + "id": 1, + "name": "Cheese Cake" + })) +); + +// 带过滤器的查找 +let cakes: Vec = Cake::find() + .filter(cake::Column::Name.contains("chocolate")) + .order_by_asc(cake::Column::Name) + .into_json() + .all(db) + .await?; + +assert_eq!( + cakes, + [ + serde_json::json!({ + "id": 2, + "name": "Chocolate Forest" + }), + serde_json::json!({ + "id": 8, + "name": "Chocolate Cupcake" + }), + ] +); + +// 分页 JSON 结果 +let cake_pages: Paginator<_> = Cake::find() + .filter(cake::Column::Name.contains("chocolate")) + .order_by_asc(cake::Column::Name) + .into_json() + .paginate(db, 50); + +while let Some(cakes) = cake_pages.fetch_and_next().await? { + // 对 cakes: Vec 执行操作 +} +``` + +## 从原始 SQL 中选择 JSON + +```rust +let result: Vec = JsonValue::find_by_statement(Statement::from_sql_and_values( + DbBackend::Postgres, + r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name"#, + [], + )) + .all(&db) + .await?; +``` + +## 将 JSON 转换为 ActiveModel + +如果你想将用户输入保存到数据库中,你可以轻松地将 JSON 值转换为 `ActiveModel`。你可能希望[跳过反序列化](https://serde.rs/attr-skip-serializing.html)一些不需要的属性。 + +:::tip 自 `2.0.0` 起 + +模型的所有字段都不需要存在于 JSON 输入中,未定义的字段将简单地变为 `ActiveValue::NotSet`。 + +::: + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "fruit")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub name: String, + pub cake_id: Option, +} +``` + +使用 `set_from_json` 方法设置 `ActiveModel` 中的属性。 + +```rust +// 带有主键的 ActiveModel +let mut fruit = fruit::ActiveModel { + id: ActiveValue::Set(1), + name: ActiveValue::NotSet, + cake_id: ActiveValue::NotSet, +}; + +// 请注意,此方法不会更改 ActiveModel 中的主键值 +fruit.set_from_json(json!({ + "id": 8, + "name": "Apple", + "cake_id": 1, +}))?; + +assert_eq!( + fruit, + fruit::ActiveModel { + id: ActiveValue::Set(1), + name: ActiveValue::Set("Apple".to_owned()), + cake_id: ActiveValue::Set(Some(1)), + } +); +``` + +你还可以使用 `from_json` 方法从 JSON 值创建新的 `ActiveModel`。 + +```rust +let fruit = fruit::ActiveModel::from_json(json!({ + "name": "Apple", +}))?; + +assert_eq!( + fruit, + fruit::ActiveModel { + id: ActiveValue::NotSet, + name: ActiveValue::Set("Apple".to_owned()), + cake_id: ActiveValue::NotSet, + } +); \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/09-raw-sql.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/09-raw-sql.md new file mode 100644 index 00000000000..158dbcf1e29 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/09-raw-sql.md @@ -0,0 +1,137 @@ +# 原始 SQL + +:::tip 自 `2.0.0` 起 + +新增了 `raw_sql` 宏,它具有许多简洁的特性,使编写原始 SQL 查询更加符合人体工程学。 + +特别是,你可以使用 `({..ids})` 将数组扩展为 `(?, ?, ?)`。 + +在 [SeaQuery 刚刚让编写原始 SQL 变得更加愉快](https://www.sea-ql.org/blog/2025-08-15-sea-query-raw-sql/) 中了解更多。 + +::: + +## 通过原始 SQL 查找模型 + +```rust +let id = 1; + +let cake: Option = cake::Entity::find() + .from_raw_sql(raw_sql!( + Postgres, + r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = {id}"# + )) + .one(&db) + .await?; +``` + +## 通过原始 SQL 选择到自定义结构体 + +这里也演示了嵌套选择。 + +```rust +#[derive(FromQueryResult)] +struct Cake { + name: String, + #[sea_orm(nested)] + bakery: Option, +} + +#[derive(FromQueryResult)] +struct Bakery { + #[sea_orm(alias = "bakery_name")] + name: String, +} + +let cake_ids = [2, 3, 4]; + +let cake: Option = Cake::find_by_statement(raw_sql!( + Sqlite, + r#"SELECT "cake"."name", "bakery"."name" AS "bakery_name" + FROM "cake" + LEFT JOIN "bakery" ON "cake"."bakery_id" = "bakery"."id" + WHERE "cake"."id" IN ({..cake_ids})"# +)) +.one(db) +.await?; +``` + +## 分页原始 SQL 查询 + +你可以对 [`SelectorRaw`](https://docs.rs/sea-orm/*/sea_orm/struct.SelectorRaw.html) 进行分页并批量获取 `Model`。 + +```rust +let ids = vec![1, 2, 3, 4]; + +let mut cake_pages = cake::Entity::find() + .from_raw_sql(raw_sql!( + Postgres, + r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" IN ({..ids})"# + )) + .paginate(db, 10); + +while let Some(cakes) = cake_pages.fetch_and_next().await? { + // 对 cakes: Vec 进行操作 +} +``` + +## 从查询中检查原始 SQL + +在任何 CRUD 操作上使用 `build` 和 `to_string` 方法来获取特定于数据库的原始 SQL,用于调试目的。 + +```rust +use sea_orm::{DbBackend, QueryTrait}; + +assert_eq!( + cake_filling::Entity::find_by_id((6, 8)) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `cake_filling`.`cake_id`, `cake_filling`.`filling_id` FROM `cake_filling`", + "WHERE `cake_filling`.`cake_id` = 6 AND `cake_filling`.`filling_id` = 8", + ].join(" ") +); +``` + +## 使用原始查询和执行接口 + +你可以使用 `sea-query` 构建 SQL 语句,并直接在 SeaORM 内部的 `DatabaseConnection` 接口上查询/执行它。 + +### 使用 `query_one` 和 `query_all` 方法获取自定义结果 + +```rust +let query_res: Option = db + .query_one_raw(Statement::from_string( + DbBackend::MySql, + "SELECT * FROM `cake`;", + )) + .await?; +let query_res = query_res.unwrap(); +let id: i32 = query_res.try_get("", "id")?; + +let query_res_vec: Vec = db + .query_all_raw(Statement::from_string( + DbBackend::MySql, + "SELECT * FROM `cake`;", + )) + .await?; +``` + +### 使用 `execute` 方法执行查询 + +```rust +let exec_res: ExecResult = db + .execute(Statement::from_string( + DbBackend::MySql, + "DROP DATABASE IF EXISTS `sea`;", + )) + .await?; +assert_eq!(exec_res.rows_affected(), 1); +``` + +## 执行未预处理的 SQL 语句 + +你可以使用 [`ConnectionTrait::execute_unprepared`](https://docs.rs/sea-orm/*/sea_orm/trait.ConnectionTrait.html#tymethod.execute_unprepared) 执行未预处理的 SQL 语句。 + +```rust +let exec_res: ExecResult = + db.execute_unprepared("CREATE EXTENSION IF NOT EXISTS citext").await?; \ No newline at end of file diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/_category_.json b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/_category_.json new file mode 100644 index 00000000000..14ef5126e1c --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/05-basic-crud/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Basic CRUD", + "collapsed": false +} diff --git a/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/01-one-to-one.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/01-one-to-one.md new file mode 100644 index 00000000000..9222702816e --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/01-one-to-one.md @@ -0,0 +1,118 @@ +# 一对一关系 + +:::tip Rustacean 贴纸包 🦀 +[我们的贴纸](https://www.sea-ql.org/sticker-pack/) 采用优质防水乙烯基材料制成,具有独特的哑光表面。 +将它们贴在你的笔记本电脑、记事本或任何小工具上,以展示你对 Rust 的热爱! +::: + +一对一关系是最基本的数据库关系类型。假设一个 `Cake` 实体最多有一个 `Fruit` 配料。 + +## 定义关系 + +在 `Cake` 实体上,定义关系: +1. 向 `Relation` 枚举添加一个新的变体 `Fruit`。 +2. 使用 `has_one` 定义它。 +3. 实现 `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() + } +} +``` + +
+ 它展开为: + +```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() + } +} +``` +
+ +或者,可以使用 `DeriveRelation` 宏缩短定义, +其中以下内容消除了对上述 `RelationTrait` 实现的需求: + +## 定义反向关系 + +在 `Fruit` 实体上,其 `cake_id` 属性引用 `Cake` 实体的主键。 + +:::tip + +经验法则是,始终在外键 `xxx_id` 的实体上定义 `belongs_to`。 + +::: + +要定义反向关系: +1. 向 `Fruit` 实体添加一个新的枚举变体 `Relation::Cake`。 +2. 使用 `Entity::belongs_to()` 方法编写其定义,我们始终使用此方法定义反向关系。 +3. 实现 `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() + } +} +``` + +
+ 它展开为: + +```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/06-relation/02-one-to-many.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/02-one-to-many.md new file mode 100644 index 00000000000..b5469151d9a --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/02-one-to-many.md @@ -0,0 +1,96 @@ +# 一对多关系 + +一对多关系类似于一对一关系。在上一节中,我们举例说明了“一个 `Cake` 实体最多有一个 `Fruit` 配料”。为了使其成为一对多关系,我们删除了“最多一个”的约束。因此,我们有一个 `Cake` 实体,它可能有许多 `Fruit` 配料。 + +## 定义关系 + +这与定义一对一关系几乎相同;唯一的区别是我们在这里使用 `Entity::has_many()` 方法。 + +```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() + } +} +``` + +
+ 它展开为: + +```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() + } +} +``` +
+ +## 定义反向关系 + +它与定义一对一反向关系相同。 + +```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() + } +} +``` + +
+ 它展开为: + +```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/06-relation/03-many-to-many.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/03-many-to-many.md new file mode 100644 index 00000000000..2280e8f5a16 --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/03-many-to-many.md @@ -0,0 +1,132 @@ +# 多对多关系 + +多对多关系由三张表组成,其中两张表通过一张连接表关联。例如,一个 `Cake` 有许多 `Filling`,而 `Filling` 通过一个中间实体 `CakeFilling` 被许多 `Cake` 共享。 + +## 定义关系 + +在 `Cake` 实体上,实现 `Related` trait。 + +SeaORM 中的 `Relation` 是一个箭头:它有 `from` 和 `to`。`cake_filling::Relation::Cake` 定义了 `CakeFilling -> Cake`。调用 [`rev`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.RelationDef.html#method.rev) 将其反转为 `Cake -> CakeFilling`。 + +将其与定义 `CakeFilling -> Filling` 的 `cake_filling::Relation::Filling` 链接起来,结果是 `Cake -> CakeFilling -> Filling`。 + +```rust {4,10} title="entity/cake.rs" +impl Related for Entity { + // 最终关系是 Cake -> CakeFilling -> Filling + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + + fn via() -> Option { + // 原始关系是 CakeFilling -> Cake, + // 在 `rev` 之后变为 Cake -> CakeFilling + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} +``` + +类似地,在 `Filling` 实体上,实现 `Related` trait。首先,通过 `via` 中间表的 `cake_filling::Relation::Filling` 关系的反向连接,然后通过 `cake_filling::Relation::Cake` 关系连接到 `Cake` 实体。 + +```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()) + } +} +``` + +## 定义反向关系 + +在 `CakeFilling` 实体上,其 `cake_id` 属性引用 `Cake` 实体的主键,其 `filling_id` 属性引用 `Filling` 实体的主键。 + +要定义反向关系: +1. 向 `Relation` 枚举添加两个新的变体 `Cake` 和 `Filling`。 +2. 使用 `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, +} +``` + +
+ 它展开为: + +```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(), + } + } +} +``` +
+ +## 代码生成器的限制 + +请注意,如果存在通过中间表的多个路径,则不会生成带有 `via` 和 `to` 方法的 `Related` 实现。 + +例如,在下面定义的模式中,存在两条路径: ++ 路径 1. `users <-> users_votes <-> bills` ++ 路径 2. `users <-> users_saved_bills <-> bills` + +因此,不会生成 `Related` 的实现。 + +```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/06-relation/04-complex-relations.md b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/04-complex-relations.md new file mode 100644 index 00000000000..7af09a7384d --- /dev/null +++ b/SeaORM/i18n/zh-Hans/docusaurus-plugin-content-docs/current/06-relation/04-complex-relations.md @@ -0,0 +1,193 @@ +# 复杂关系 + +## 链接 + +`Related` trait 是我们在实体关系图上绘制的箭头(1-1、1-N、M-N)的表示。[`Linked`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Linked.html) 由关系链组成,在以下情况下很有用: + +1. 一对实体之间存在多个连接路径,导致无法实现 `Related` +2. 在关系查询中跨多个实体进行连接 + +实现 `Linked` trait 是完全可选的,因为 SeaORM 中还有其他进行关系查询的方法,这将在后面的章节中解释。 +实现 `Linked` 后,可以使用多个 `find_*_linked` 辅助方法,并且可以在一个地方定义关系。 + +### 定义链接 + +以 [此](https://github.com/SeaQL/sea-orm/blob/1.1.x/src/tests_cfg/entity_linked.rs) 为例,我们通过中间 `cake_filling` 表连接蛋糕和馅料。 + +```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(), + ] + } +} +``` + +或者,`RelationDef` 可以在运行时定义,以下内容与上述内容等效: + +```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(), + ] + } +} +``` + +### 延迟加载 + +使用 [`find_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ModelTrait.html#method.find_linked) 方法查找可以填充到蛋糕中的馅料。 + +```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(" ") +); +``` + +### 急切加载 + +[`find_also_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_also_linked) 是 `find_also_related` 的对偶;[`find_with_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_with_linked) 是 `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(" ") +); +``` + +## 自引用关系 + +`Link` trait 也可以定义自引用关系。 + +以下示例定义了一个引用自身的实体。 + +```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