From bf0b9f572e31b4881d9691a535553696fea88d2e Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Thu, 18 Jan 2024 09:48:52 +0800 Subject: [PATCH] New Crowdin updates (#718) * New translations write_plugins.md (Russian) * New translations advanced_query.md (Turkish) * New translations associations.md (Turkish) * New translations write_plugins.md (Turkish) * New translations advanced_query.md (Chinese Simplified) * New translations associations.md (Chinese Simplified) * New translations write_plugins.md (Chinese Simplified) * New translations advanced_query.md (Portuguese, Brazilian) * New translations associations.md (Portuguese, Brazilian) * New translations write_plugins.md (Portuguese, Brazilian) * New translations advanced_query.md (Indonesian) * New translations associations.md (Indonesian) * New translations write_plugins.md (Indonesian) * New translations advanced_query.md (Persian) * New translations associations.md (Persian) * New translations write_plugins.md (Persian) * New translations advanced_query.md (Azerbaijani) * New translations associations.md (Azerbaijani) * New translations write_plugins.md (Azerbaijani) * New translations advanced_query.md (Hindi) * New translations associations.md (Hindi) * New translations write_plugins.md (Hindi) * Update source file advanced_query.md * Update source file associations.md * Update source file write_driver.md * Update source file write_plugins.md * New translations associations.md (French) * New translations write_plugins.md (French) * New translations associations.md (Spanish) * New translations write_plugins.md (Spanish) * New translations associations.md (Arabic) * New translations write_plugins.md (Arabic) * New translations associations.md (German) * New translations write_plugins.md (German) * New translations associations.md (Italian) * New translations write_plugins.md (Italian) * New translations associations.md (Japanese) * New translations write_plugins.md (Japanese) * New translations associations.md (Korean) * New translations write_plugins.md (Korean) * New translations associations.md (Polish) * New translations write_plugins.md (Polish) * New translations associations.md (Russian) * New translations write_plugins.md (Russian) * New translations associations.md (Turkish) * New translations write_plugins.md (Turkish) * New translations associations.md (Chinese Simplified) * New translations write_plugins.md (Chinese Simplified) * New translations associations.md (Portuguese, Brazilian) * New translations write_plugins.md (Portuguese, Brazilian) * New translations associations.md (Indonesian) * New translations write_plugins.md (Indonesian) * New translations associations.md (Persian) * New translations write_plugins.md (Persian) * New translations associations.md (Azerbaijani) * New translations write_plugins.md (Azerbaijani) * New translations associations.md (Hindi) * New translations write_plugins.md (Hindi) * New translations write_driver.md (French) * New translations write_driver.md (Spanish) * New translations write_driver.md (Arabic) * New translations write_driver.md (German) * New translations write_driver.md (Italian) * New translations write_driver.md (Japanese) * New translations write_driver.md (Korean) * New translations write_driver.md (Polish) * New translations write_driver.md (Russian) * New translations write_driver.md (Turkish) * New translations write_driver.md (Chinese Simplified) * New translations write_driver.md (Portuguese, Brazilian) * New translations write_driver.md (Indonesian) * New translations write_driver.md (Persian) * New translations write_driver.md (Azerbaijani) * New translations write_driver.md (Hindi) * New translations write_plugins.md (French) * New translations write_plugins.md (Spanish) * New translations write_plugins.md (Arabic) * New translations write_plugins.md (German) * New translations write_plugins.md (Italian) * New translations write_plugins.md (Japanese) * New translations write_plugins.md (Korean) * New translations write_plugins.md (Polish) * New translations write_plugins.md (Russian) * New translations write_plugins.md (Turkish) * New translations write_plugins.md (Chinese Simplified) * New translations write_plugins.md (Portuguese, Brazilian) * New translations write_plugins.md (Indonesian) * New translations write_plugins.md (Persian) * New translations write_plugins.md (Azerbaijani) * New translations write_plugins.md (Hindi) * New translations context.md (French) * New translations error_handling.md (French) * New translations method_chaining.md (French) * New translations write_driver.md (French) * New translations context.md (Spanish) * New translations error_handling.md (Spanish) * New translations method_chaining.md (Spanish) * New translations write_driver.md (Spanish) * New translations context.md (Arabic) * New translations error_handling.md (Arabic) * New translations method_chaining.md (Arabic) * New translations write_driver.md (Arabic) * New translations context.md (German) * New translations error_handling.md (German) * New translations method_chaining.md (German) * New translations write_driver.md (German) * New translations context.md (Italian) * New translations error_handling.md (Italian) * New translations method_chaining.md (Italian) * New translations write_driver.md (Italian) * New translations context.md (Japanese) * New translations error_handling.md (Japanese) * New translations method_chaining.md (Japanese) * New translations write_driver.md (Japanese) * New translations context.md (Korean) * New translations error_handling.md (Korean) * New translations method_chaining.md (Korean) * New translations write_driver.md (Korean) * New translations context.md (Polish) * New translations error_handling.md (Polish) * New translations method_chaining.md (Polish) * New translations write_driver.md (Polish) * New translations context.md (Russian) * New translations error_handling.md (Russian) * New translations method_chaining.md (Russian) * New translations write_driver.md (Russian) * New translations context.md (Turkish) * New translations error_handling.md (Turkish) * New translations method_chaining.md (Turkish) * New translations write_driver.md (Turkish) * New translations context.md (Chinese Simplified) * New translations error_handling.md (Chinese Simplified) * New translations method_chaining.md (Chinese Simplified) * New translations write_driver.md (Chinese Simplified) * New translations context.md (Portuguese, Brazilian) * New translations error_handling.md (Portuguese, Brazilian) * New translations method_chaining.md (Portuguese, Brazilian) * New translations write_driver.md (Portuguese, Brazilian) * New translations context.md (Indonesian) * New translations error_handling.md (Indonesian) * New translations method_chaining.md (Indonesian) * New translations write_driver.md (Indonesian) * New translations context.md (Persian) * New translations error_handling.md (Persian) * New translations method_chaining.md (Persian) * New translations write_driver.md (Persian) * New translations context.md (Azerbaijani) * New translations error_handling.md (Azerbaijani) * New translations method_chaining.md (Azerbaijani) * New translations write_driver.md (Azerbaijani) * New translations context.md (Hindi) * New translations error_handling.md (Hindi) * New translations method_chaining.md (Hindi) * New translations write_driver.md (Hindi) * Update source file context.md * Update source file error_handling.md * Update source file method_chaining.md * Update source file write_driver.md * Update source file write_plugins.md * New translations models.md (French) * New translations models.md (Spanish) * New translations models.md (Arabic) * New translations models.md (German) * New translations models.md (Italian) * New translations models.md (Japanese) * New translations models.md (Korean) * New translations models.md (Polish) * New translations models.md (Russian) * New translations models.md (Turkish) * New translations models.md (Chinese Simplified) * New translations models.md (Portuguese, Brazilian) * New translations models.md (Indonesian) * New translations models.md (Persian) * New translations models.md (Azerbaijani) * New translations models.md (Hindi) * Update source file models.md * New translations dbresolver.md (French) * New translations dbresolver.md (Spanish) * New translations dbresolver.md (Arabic) * New translations dbresolver.md (German) * New translations dbresolver.md (Italian) * New translations dbresolver.md (Japanese) * New translations dbresolver.md (Korean) * New translations dbresolver.md (Polish) * New translations dbresolver.md (Russian) * New translations dbresolver.md (Turkish) * New translations dbresolver.md (Chinese Simplified) * New translations dbresolver.md (Portuguese, Brazilian) * New translations dbresolver.md (Indonesian) * New translations dbresolver.md (Persian) * New translations dbresolver.md (Azerbaijani) * New translations dbresolver.md (Hindi) * Update source file dbresolver.md * New translations connecting_to_the_database.md (Chinese Simplified) * New translations conventions.md (French) * New translations many_to_many.md (French) * New translations security.md (French) * New translations conventions.md (Spanish) * New translations many_to_many.md (Spanish) * New translations security.md (Spanish) * New translations conventions.md (Arabic) * New translations many_to_many.md (Arabic) * New translations security.md (Arabic) * New translations conventions.md (German) * New translations many_to_many.md (German) * New translations security.md (German) * New translations conventions.md (Italian) * New translations many_to_many.md (Italian) * New translations security.md (Italian) * New translations conventions.md (Japanese) * New translations many_to_many.md (Japanese) * New translations security.md (Japanese) * New translations conventions.md (Korean) * New translations many_to_many.md (Korean) * New translations security.md (Korean) * New translations conventions.md (Polish) * New translations many_to_many.md (Polish) * New translations security.md (Polish) * New translations conventions.md (Russian) * New translations many_to_many.md (Russian) * New translations security.md (Russian) * New translations conventions.md (Turkish) * New translations many_to_many.md (Turkish) * New translations security.md (Turkish) * New translations conventions.md (Chinese Simplified) * New translations many_to_many.md (Chinese Simplified) * New translations security.md (Chinese Simplified) * New translations conventions.md (Portuguese, Brazilian) * New translations many_to_many.md (Portuguese, Brazilian) * New translations security.md (Portuguese, Brazilian) * New translations conventions.md (Indonesian) * New translations many_to_many.md (Indonesian) * New translations security.md (Indonesian) * New translations conventions.md (Persian) * New translations many_to_many.md (Persian) * New translations security.md (Persian) * New translations conventions.md (Azerbaijani) * New translations many_to_many.md (Azerbaijani) * New translations security.md (Azerbaijani) * New translations conventions.md (Hindi) * New translations many_to_many.md (Hindi) * New translations security.md (Hindi) * Update source file conventions.md * Update source file many_to_many.md * Update source file security.md * New translations gorm_config.md (Korean) * New translations gorm_config.md (Korean) * New translations advanced_query.md (Turkish) * New translations community.md (Persian) * New translations advanced_query.md (Persian) --- pages/ar_SA/docs/advanced_query.md | 327 +++++++++++------ pages/ar_SA/docs/associations.md | 259 +++++++------ pages/ar_SA/docs/context.md | 54 ++- pages/ar_SA/docs/conventions.md | 2 +- pages/ar_SA/docs/create.md | 2 +- pages/ar_SA/docs/dbresolver.md | 6 +- pages/ar_SA/docs/error_handling.md | 90 ++++- pages/ar_SA/docs/many_to_many.md | 2 +- pages/ar_SA/docs/method_chaining.md | 139 ++++--- pages/ar_SA/docs/models.md | 57 ++- pages/ar_SA/docs/security.md | 4 +- pages/ar_SA/docs/transactions.md | 4 +- pages/ar_SA/docs/write_driver.md | 102 +++++- pages/ar_SA/docs/write_plugins.md | 90 +++-- pages/ar_SA/gen/associations.md | 16 +- pages/ar_SA/gen/query.md | 4 +- pages/ar_SA/gen/sql_annotation.md | 2 +- pages/az_AZ/docs/advanced_query.md | 327 +++++++++++------ pages/az_AZ/docs/associations.md | 259 +++++++------ pages/az_AZ/docs/context.md | 54 ++- pages/az_AZ/docs/conventions.md | 2 +- pages/az_AZ/docs/create.md | 2 +- pages/az_AZ/docs/dbresolver.md | 6 +- pages/az_AZ/docs/error_handling.md | 84 ++++- pages/az_AZ/docs/many_to_many.md | 2 +- pages/az_AZ/docs/method_chaining.md | 139 ++++--- pages/az_AZ/docs/models.md | 57 ++- pages/az_AZ/docs/security.md | 4 +- pages/az_AZ/docs/transactions.md | 4 +- pages/az_AZ/docs/write_driver.md | 102 +++++- pages/az_AZ/docs/write_plugins.md | 86 +++-- pages/az_AZ/gen/associations.md | 16 +- pages/az_AZ/gen/query.md | 4 +- pages/az_AZ/gen/sql_annotation.md | 2 +- pages/de_DE/docs/advanced_query.md | 329 +++++++++++------ pages/de_DE/docs/associations.md | 259 +++++++------ pages/de_DE/docs/context.md | 54 ++- pages/de_DE/docs/conventions.md | 2 +- pages/de_DE/docs/create.md | 2 +- pages/de_DE/docs/dbresolver.md | 6 +- pages/de_DE/docs/error_handling.md | 84 ++++- pages/de_DE/docs/many_to_many.md | 2 +- pages/de_DE/docs/method_chaining.md | 139 ++++--- pages/de_DE/docs/models.md | 57 ++- pages/de_DE/docs/security.md | 4 +- pages/de_DE/docs/transactions.md | 4 +- pages/de_DE/docs/write_driver.md | 102 +++++- pages/de_DE/docs/write_plugins.md | 86 +++-- pages/de_DE/gen/associations.md | 16 +- pages/de_DE/gen/query.md | 4 +- pages/de_DE/gen/sql_annotation.md | 2 +- pages/es_ES/docs/advanced_query.md | 327 +++++++++++------ pages/es_ES/docs/associations.md | 259 +++++++------ pages/es_ES/docs/context.md | 54 ++- pages/es_ES/docs/conventions.md | 26 +- pages/es_ES/docs/create.md | 2 +- pages/es_ES/docs/dbresolver.md | 6 +- pages/es_ES/docs/error_handling.md | 84 ++++- pages/es_ES/docs/many_to_many.md | 2 +- pages/es_ES/docs/method_chaining.md | 139 ++++--- pages/es_ES/docs/models.md | 57 ++- pages/es_ES/docs/security.md | 4 +- pages/es_ES/docs/transactions.md | 4 +- pages/es_ES/docs/write_driver.md | 102 +++++- pages/es_ES/docs/write_plugins.md | 86 +++-- pages/es_ES/gen/associations.md | 16 +- pages/es_ES/gen/query.md | 4 +- pages/es_ES/gen/sql_annotation.md | 2 +- pages/fa_IR/community.md | 2 +- pages/fa_IR/docs/advanced_query.md | 337 ++++++++++------- pages/fa_IR/docs/associations.md | 259 +++++++------ pages/fa_IR/docs/context.md | 54 ++- pages/fa_IR/docs/conventions.md | 2 +- pages/fa_IR/docs/create.md | 2 +- pages/fa_IR/docs/dbresolver.md | 6 +- pages/fa_IR/docs/error_handling.md | 84 ++++- pages/fa_IR/docs/many_to_many.md | 2 +- pages/fa_IR/docs/method_chaining.md | 139 ++++--- pages/fa_IR/docs/models.md | 57 ++- pages/fa_IR/docs/security.md | 4 +- pages/fa_IR/docs/transactions.md | 4 +- pages/fa_IR/docs/write_driver.md | 102 +++++- pages/fa_IR/docs/write_plugins.md | 86 +++-- pages/fa_IR/gen/associations.md | 16 +- pages/fa_IR/gen/query.md | 4 +- pages/fa_IR/gen/sql_annotation.md | 2 +- pages/fr_FR/docs/advanced_query.md | 327 +++++++++++------ pages/fr_FR/docs/associations.md | 259 +++++++------ pages/fr_FR/docs/context.md | 54 ++- pages/fr_FR/docs/conventions.md | 2 +- pages/fr_FR/docs/create.md | 2 +- pages/fr_FR/docs/dbresolver.md | 6 +- pages/fr_FR/docs/error_handling.md | 84 ++++- pages/fr_FR/docs/many_to_many.md | 2 +- pages/fr_FR/docs/method_chaining.md | 139 ++++--- pages/fr_FR/docs/models.md | 57 ++- pages/fr_FR/docs/security.md | 4 +- pages/fr_FR/docs/transactions.md | 4 +- pages/fr_FR/docs/write_driver.md | 102 +++++- pages/fr_FR/docs/write_plugins.md | 86 +++-- pages/fr_FR/gen/associations.md | 16 +- pages/fr_FR/gen/query.md | 4 +- pages/fr_FR/gen/sql_annotation.md | 2 +- pages/hi_IN/docs/advanced_query.md | 345 +++++++++++------- pages/hi_IN/docs/associations.md | 272 ++++++++------ pages/hi_IN/docs/context.md | 59 ++- pages/hi_IN/docs/conventions.md | 2 +- pages/hi_IN/docs/create.md | 2 +- pages/hi_IN/docs/dbresolver.md | 6 +- pages/hi_IN/docs/error_handling.md | 84 ++++- pages/hi_IN/docs/many_to_many.md | 2 +- pages/hi_IN/docs/method_chaining.md | 139 ++++--- pages/hi_IN/docs/models.md | 57 ++- pages/hi_IN/docs/security.md | 4 +- pages/hi_IN/docs/transactions.md | 4 +- pages/hi_IN/docs/write_driver.md | 102 +++++- pages/hi_IN/docs/write_plugins.md | 86 +++-- pages/hi_IN/gen/associations.md | 16 +- pages/hi_IN/gen/query.md | 4 +- pages/hi_IN/gen/sql_annotation.md | 2 +- pages/id_ID/docs/advanced_query.md | 329 +++++++++++------ pages/id_ID/docs/associations.md | 261 +++++++------ pages/id_ID/docs/context.md | 54 ++- pages/id_ID/docs/conventions.md | 2 +- pages/id_ID/docs/create.md | 2 +- pages/id_ID/docs/dbresolver.md | 6 +- pages/id_ID/docs/error_handling.md | 84 ++++- pages/id_ID/docs/many_to_many.md | 2 +- pages/id_ID/docs/method_chaining.md | 139 ++++--- pages/id_ID/docs/models.md | 62 +++- pages/id_ID/docs/security.md | 4 +- pages/id_ID/docs/transactions.md | 4 +- pages/id_ID/docs/write_driver.md | 102 +++++- pages/id_ID/docs/write_plugins.md | 86 +++-- pages/id_ID/gen/associations.md | 16 +- pages/id_ID/gen/query.md | 4 +- pages/id_ID/gen/sql_annotation.md | 2 +- pages/it_IT/contribute.md | 2 +- pages/it_IT/docs/advanced_query.md | 327 +++++++++++------ pages/it_IT/docs/associations.md | 259 +++++++------ pages/it_IT/docs/context.md | 54 ++- pages/it_IT/docs/conventions.md | 2 +- pages/it_IT/docs/create.md | 2 +- pages/it_IT/docs/dbresolver.md | 6 +- pages/it_IT/docs/error_handling.md | 84 ++++- pages/it_IT/docs/many_to_many.md | 2 +- pages/it_IT/docs/method_chaining.md | 139 ++++--- pages/it_IT/docs/models.md | 57 ++- pages/it_IT/docs/security.md | 4 +- pages/it_IT/docs/transactions.md | 4 +- pages/it_IT/docs/write_driver.md | 102 +++++- pages/it_IT/docs/write_plugins.md | 86 +++-- pages/it_IT/gen/associations.md | 16 +- pages/it_IT/gen/query.md | 4 +- pages/it_IT/gen/sql_annotation.md | 2 +- pages/it_IT/index.md | 14 +- pages/ja_JP/docs/advanced_query.md | 327 +++++++++++------ pages/ja_JP/docs/associations.md | 260 +++++++------ pages/ja_JP/docs/context.md | 54 ++- pages/ja_JP/docs/conventions.md | 2 +- pages/ja_JP/docs/create.md | 2 +- pages/ja_JP/docs/dbresolver.md | 6 +- pages/ja_JP/docs/error_handling.md | 84 ++++- pages/ja_JP/docs/many_to_many.md | 2 +- pages/ja_JP/docs/method_chaining.md | 139 ++++--- pages/ja_JP/docs/models.md | 57 ++- pages/ja_JP/docs/security.md | 4 +- pages/ja_JP/docs/transactions.md | 4 +- pages/ja_JP/docs/write_driver.md | 102 +++++- pages/ja_JP/docs/write_plugins.md | 86 +++-- pages/ja_JP/gen/associations.md | 16 +- pages/ja_JP/gen/query.md | 4 +- pages/ja_JP/gen/sql_annotation.md | 2 +- pages/ko_KR/docs/advanced_query.md | 327 +++++++++++------ pages/ko_KR/docs/associations.md | 262 +++++++------ pages/ko_KR/docs/context.md | 54 ++- pages/ko_KR/docs/conventions.md | 2 +- pages/ko_KR/docs/create.md | 2 +- pages/ko_KR/docs/dbresolver.md | 6 +- pages/ko_KR/docs/error_handling.md | 84 ++++- pages/ko_KR/docs/gorm_config.md | 12 +- pages/ko_KR/docs/many_to_many.md | 2 +- pages/ko_KR/docs/method_chaining.md | 139 ++++--- pages/ko_KR/docs/models.md | 57 ++- pages/ko_KR/docs/security.md | 4 +- pages/ko_KR/docs/transactions.md | 4 +- pages/ko_KR/docs/write_driver.md | 102 +++++- pages/ko_KR/docs/write_plugins.md | 86 +++-- pages/ko_KR/gen/associations.md | 16 +- pages/ko_KR/gen/query.md | 4 +- pages/ko_KR/gen/sql_annotation.md | 2 +- pages/pl_PL/docs/advanced_query.md | 329 +++++++++++------ pages/pl_PL/docs/associations.md | 259 +++++++------ pages/pl_PL/docs/context.md | 54 ++- pages/pl_PL/docs/conventions.md | 2 +- pages/pl_PL/docs/create.md | 2 +- pages/pl_PL/docs/dbresolver.md | 6 +- pages/pl_PL/docs/error_handling.md | 84 ++++- pages/pl_PL/docs/many_to_many.md | 2 +- pages/pl_PL/docs/method_chaining.md | 139 ++++--- pages/pl_PL/docs/models.md | 57 ++- pages/pl_PL/docs/security.md | 4 +- pages/pl_PL/docs/transactions.md | 4 +- pages/pl_PL/docs/write_driver.md | 102 +++++- pages/pl_PL/docs/write_plugins.md | 86 +++-- pages/pl_PL/gen/associations.md | 16 +- pages/pl_PL/gen/query.md | 4 +- pages/pl_PL/gen/sql_annotation.md | 2 +- pages/pt_BR/docs/advanced_query.md | 341 ++++++++++------- pages/pt_BR/docs/associations.md | 261 +++++++------ pages/pt_BR/docs/context.md | 54 ++- pages/pt_BR/docs/conventions.md | 2 +- pages/pt_BR/docs/create.md | 2 +- pages/pt_BR/docs/dbresolver.md | 6 +- pages/pt_BR/docs/error_handling.md | 85 ++++- pages/pt_BR/docs/many_to_many.md | 2 +- pages/pt_BR/docs/method_chaining.md | 139 ++++--- pages/pt_BR/docs/models.md | 57 ++- pages/pt_BR/docs/security.md | 4 +- pages/pt_BR/docs/transactions.md | 4 +- pages/pt_BR/docs/write_driver.md | 102 +++++- pages/pt_BR/docs/write_plugins.md | 86 +++-- pages/pt_BR/gen/associations.md | 16 +- pages/pt_BR/gen/query.md | 4 +- pages/pt_BR/gen/sql_annotation.md | 2 +- pages/ru_RU/docs/advanced_query.md | 329 +++++++++++------ pages/ru_RU/docs/associations.md | 275 ++++++++------ pages/ru_RU/docs/context.md | 54 ++- pages/ru_RU/docs/conventions.md | 2 +- pages/ru_RU/docs/create.md | 2 +- pages/ru_RU/docs/dbresolver.md | 6 +- pages/ru_RU/docs/error_handling.md | 84 ++++- pages/ru_RU/docs/many_to_many.md | 2 +- pages/ru_RU/docs/method_chaining.md | 139 ++++--- pages/ru_RU/docs/models.md | 57 ++- pages/ru_RU/docs/security.md | 4 +- pages/ru_RU/docs/transactions.md | 4 +- pages/ru_RU/docs/write_driver.md | 102 +++++- pages/ru_RU/docs/write_plugins.md | 86 +++-- pages/ru_RU/gen/associations.md | 16 +- pages/ru_RU/gen/query.md | 4 +- pages/ru_RU/gen/sql_annotation.md | 2 +- pages/tr_TR/docs/advanced_query.md | 329 +++++++++++------ pages/tr_TR/docs/associations.md | 261 +++++++------ pages/tr_TR/docs/context.md | 54 ++- pages/tr_TR/docs/conventions.md | 2 +- pages/tr_TR/docs/create.md | 2 +- pages/tr_TR/docs/dbresolver.md | 6 +- pages/tr_TR/docs/error_handling.md | 84 ++++- pages/tr_TR/docs/many_to_many.md | 2 +- pages/tr_TR/docs/method_chaining.md | 139 ++++--- pages/tr_TR/docs/models.md | 57 ++- pages/tr_TR/docs/security.md | 4 +- pages/tr_TR/docs/transactions.md | 4 +- pages/tr_TR/docs/write_driver.md | 102 +++++- pages/tr_TR/docs/write_plugins.md | 86 +++-- pages/tr_TR/gen/associations.md | 16 +- pages/tr_TR/gen/query.md | 4 +- pages/tr_TR/gen/sql_annotation.md | 2 +- pages/zh_CN/docs/advanced_query.md | 329 +++++++++++------ pages/zh_CN/docs/associations.md | 261 +++++++------ .../zh_CN/docs/connecting_to_the_database.md | 8 +- pages/zh_CN/docs/context.md | 54 ++- pages/zh_CN/docs/conventions.md | 2 +- pages/zh_CN/docs/create.md | 2 +- pages/zh_CN/docs/dbresolver.md | 12 +- pages/zh_CN/docs/error_handling.md | 86 ++++- pages/zh_CN/docs/many_to_many.md | 2 +- pages/zh_CN/docs/method_chaining.md | 139 ++++--- pages/zh_CN/docs/models.md | 57 ++- pages/zh_CN/docs/security.md | 8 +- pages/zh_CN/docs/transactions.md | 4 +- pages/zh_CN/docs/update.md | 34 +- pages/zh_CN/docs/write_driver.md | 102 +++++- pages/zh_CN/docs/write_plugins.md | 86 +++-- pages/zh_CN/gen/associations.md | 24 +- pages/zh_CN/gen/database_to_structs.md | 166 ++++----- pages/zh_CN/gen/delete.md | 38 +- pages/zh_CN/gen/index.md | 12 +- pages/zh_CN/gen/query.md | 4 +- pages/zh_CN/gen/sql_annotation.md | 48 +-- 281 files changed, 12181 insertions(+), 6713 deletions(-) diff --git a/pages/ar_SA/docs/advanced_query.md b/pages/ar_SA/docs/advanced_query.md index e24a2cf34f8..278b1955bda 100644 --- a/pages/ar_SA/docs/advanced_query.md +++ b/pages/ar_SA/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## Smart Select Fields -GORM allows selecting specific fields with [`Select`](query.html), if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Select `id`, `name` automatically when querying +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOTE** `QueryFields` mode will select by all fields' name for current model +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (FOR UPDATE) +## Locking GORM supports different types of locks, for example: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Refer [Raw SQL and SQL Builder](sql_builder.html) for more detail +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery -A subquery can be nested within a query, GORM can generate subquery when using a `*gorm.DB` object as param +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group Conditions -Easier to write complicated SQL query with Group Conditions +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN with multiple columns -Selecting IN with multiple columns +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Named Argument -GORM supports named arguments with [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, for example: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Check out [Raw SQL and SQL Builder](sql_builder.html#named_argument) for more detail +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM supports iterating through Rows +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` allows you to specify commonly-used queries which can be referenced as method calls +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Get matched records count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/ar_SA/docs/associations.md b/pages/ar_SA/docs/associations.md index 42e5a8e6131..ee30caf054f 100644 --- a/pages/ar_SA/docs/associations.md +++ b/pages/ar_SA/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Auto Create/Update -GORM will auto-save associations and its reference using [Upsert](create.html#upsert) when creating/updating a record. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -If you want to update associations's data, you should use the `FullSaveAssociations` mode: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Skip Auto Create/Update -To skip the auto save when creating/updating, you can use `Select` or `Omit`, for example: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE:** For many2many associations, GORM will upsert the associations before creating the join table references, if you want to skip the upserting of associations, you could skip it like: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -The following code will skip the creation of the association and its references +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Association fields +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Delete Associations + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -Association Mode contains some commonly used helper methods to handle relationships +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Find Associations +### Finding Associations -Find matched associations +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Find associations with conditions -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Append Associations +### Appending Associations -Append new associations for `many to many`, `has many`, replace current association for `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Replace Associations +### Replacing Associations -Replace current associations with new ones +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Delete Associations +### Deleting Associations -Remove the relationship between source & arguments if exists, only delete the reference, won't delete those objects from DB. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations +### Clearing Associations -Remove all reference between source & association, won't delete those associations +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Count Associations +### Counting Associations -Return the count of current associations +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Association Mode supports batch data, e.g: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/ar_SA/docs/context.md b/pages/ar_SA/docs/context.md index 62f9e2e3812..45314c04c57 100644 --- a/pages/ar_SA/docs/context.md +++ b/pages/ar_SA/docs/context.md @@ -3,11 +3,11 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go Find(&users) @@ -21,9 +21,9 @@ r. Get("/user", func(w http. Value("DB").(*gorm.DB) db. ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db. WithContext(ctx) @@ -31,9 +31,9 @@ tx. First(&user, 1) tx. Model(&user). Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -42,23 +42,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -69,32 +67,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/ar_SA/docs/conventions.md b/pages/ar_SA/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/ar_SA/docs/conventions.md +++ b/pages/ar_SA/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/ar_SA/docs/create.md b/pages/ar_SA/docs/create.md index fe1c6c09480..15f09941893 100644 --- a/pages/ar_SA/docs/create.md +++ b/pages/ar_SA/docs/create.md @@ -45,7 +45,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/ar_SA/docs/dbresolver.md b/pages/ar_SA/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/ar_SA/docs/dbresolver.md +++ b/pages/ar_SA/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/ar_SA/docs/error_handling.md b/pages/ar_SA/docs/error_handling.md index 892ecc79994..abac8cb83ba 100644 --- a/pages/ar_SA/docs/error_handling.md +++ b/pages/ar_SA/docs/error_handling.md @@ -3,49 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go -// Check if returns RecordNotFound error -err := db. First(&user, 100). Error -errors. +if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { + // Handle error... } ``` -Or +Or alternatively: ```go -// Check if returns RecordNotFound error -err := db. First(&user, 100). Error -errors. +if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors. Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/ar_SA/docs/many_to_many.md b/pages/ar_SA/docs/many_to_many.md index e12befa62ee..de443f27767 100644 --- a/pages/ar_SA/docs/many_to_many.md +++ b/pages/ar_SA/docs/many_to_many.md @@ -138,7 +138,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/ar_SA/docs/method_chaining.md b/pages/ar_SA/docs/method_chaining.md index b7ba48bbfd0..a9aae7d4fa4 100644 --- a/pages/ar_SA/docs/method_chaining.md +++ b/pages/ar_SA/docs/method_chaining.md @@ -3,7 +3,7 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go First(&user) @@ -15,117 +15,152 @@ for i := 0; i < 100; i++ { go tx. ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/ar_SA/docs/models.md b/pages/ar_SA/docs/models.md index d29f2f65d39..5908e752870 100644 --- a/pages/ar_SA/docs/models.md +++ b/pages/ar_SA/docs/models.md @@ -3,35 +3,52 @@ title: التصريح بالنماذج layout: صفحة --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## التصريح بالنماذج -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -مثلاً: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm. Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm. Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm. Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go Model @@ -47,7 +64,13 @@ type User struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/ar_SA/docs/security.md b/pages/ar_SA/docs/security.md index 3c76ba8284e..b5070d96da0 100644 --- a/pages/ar_SA/docs/security.md +++ b/pages/ar_SA/docs/security.md @@ -33,9 +33,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/ar_SA/docs/transactions.md b/pages/ar_SA/docs/transactions.md index 763669ef9b0..a517dcba992 100644 --- a/pages/ar_SA/docs/transactions.md +++ b/pages/ar_SA/docs/transactions.md @@ -62,8 +62,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/ar_SA/docs/write_driver.md b/pages/ar_SA/docs/write_driver.md index cfdb04a0ac0..a5599c5dd4c 100644 --- a/pages/ar_SA/docs/write_driver.md +++ b/pages/ar_SA/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema. Field) string - DefaultValueOf(*schema. Field) clause. Expression - BindVarTo(writer clause. Writer, stmt *Statement, v interface{}) - QuoteTo(clause. Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/ar_SA/docs/write_plugins.md b/pages/ar_SA/docs/write_plugins.md index ddaea3cf80e..293d6147fea 100644 --- a/pages/ar_SA/docs/write_plugins.md +++ b/pages/ar_SA/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go -db. After("gorm:create"). Query(). After("gorm:query"). Register("my_plugin:after_query", afterQuery) -db. Delete(). After("gorm:delete"). +// Remove the 'gorm:create' callback from Create operations +db.Callback().Create().Remove("gorm:create") ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go -db. Callback(). Create(). Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process +// Replace the 'gorm:create' callback with a new function +db.Callback().Create().Replace("gorm:create", newCreateFunction) ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db. Config. Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/ar_SA/gen/associations.md b/pages/ar_SA/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/ar_SA/gen/associations.md +++ b/pages/ar_SA/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/ar_SA/gen/query.md b/pages/ar_SA/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/ar_SA/gen/query.md +++ b/pages/ar_SA/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/ar_SA/gen/sql_annotation.md b/pages/ar_SA/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/ar_SA/gen/sql_annotation.md +++ b/pages/ar_SA/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/az_AZ/docs/advanced_query.md b/pages/az_AZ/docs/advanced_query.md index e24a2cf34f8..278b1955bda 100644 --- a/pages/az_AZ/docs/advanced_query.md +++ b/pages/az_AZ/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## Smart Select Fields -GORM allows selecting specific fields with [`Select`](query.html), if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Select `id`, `name` automatically when querying +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOTE** `QueryFields` mode will select by all fields' name for current model +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (FOR UPDATE) +## Locking GORM supports different types of locks, for example: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Refer [Raw SQL and SQL Builder](sql_builder.html) for more detail +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery -A subquery can be nested within a query, GORM can generate subquery when using a `*gorm.DB` object as param +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group Conditions -Easier to write complicated SQL query with Group Conditions +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN with multiple columns -Selecting IN with multiple columns +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Named Argument -GORM supports named arguments with [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, for example: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Check out [Raw SQL and SQL Builder](sql_builder.html#named_argument) for more detail +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM supports iterating through Rows +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` allows you to specify commonly-used queries which can be referenced as method calls +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Get matched records count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/az_AZ/docs/associations.md b/pages/az_AZ/docs/associations.md index 42e5a8e6131..ee30caf054f 100644 --- a/pages/az_AZ/docs/associations.md +++ b/pages/az_AZ/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Auto Create/Update -GORM will auto-save associations and its reference using [Upsert](create.html#upsert) when creating/updating a record. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -If you want to update associations's data, you should use the `FullSaveAssociations` mode: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Skip Auto Create/Update -To skip the auto save when creating/updating, you can use `Select` or `Omit`, for example: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE:** For many2many associations, GORM will upsert the associations before creating the join table references, if you want to skip the upserting of associations, you could skip it like: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -The following code will skip the creation of the association and its references +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Association fields +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Delete Associations + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -Association Mode contains some commonly used helper methods to handle relationships +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Find Associations +### Finding Associations -Find matched associations +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Find associations with conditions -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Append Associations +### Appending Associations -Append new associations for `many to many`, `has many`, replace current association for `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Replace Associations +### Replacing Associations -Replace current associations with new ones +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Delete Associations +### Deleting Associations -Remove the relationship between source & arguments if exists, only delete the reference, won't delete those objects from DB. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations +### Clearing Associations -Remove all reference between source & association, won't delete those associations +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Count Associations +### Counting Associations -Return the count of current associations +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Association Mode supports batch data, e.g: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/az_AZ/docs/context.md b/pages/az_AZ/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/az_AZ/docs/context.md +++ b/pages/az_AZ/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/az_AZ/docs/conventions.md b/pages/az_AZ/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/az_AZ/docs/conventions.md +++ b/pages/az_AZ/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/az_AZ/docs/create.md b/pages/az_AZ/docs/create.md index ce5544182c5..ae06885fad6 100644 --- a/pages/az_AZ/docs/create.md +++ b/pages/az_AZ/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/az_AZ/docs/dbresolver.md b/pages/az_AZ/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/az_AZ/docs/dbresolver.md +++ b/pages/az_AZ/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/az_AZ/docs/error_handling.md b/pages/az_AZ/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/az_AZ/docs/error_handling.md +++ b/pages/az_AZ/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/az_AZ/docs/many_to_many.md b/pages/az_AZ/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/az_AZ/docs/many_to_many.md +++ b/pages/az_AZ/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/az_AZ/docs/method_chaining.md b/pages/az_AZ/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/az_AZ/docs/method_chaining.md +++ b/pages/az_AZ/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/az_AZ/docs/models.md b/pages/az_AZ/docs/models.md index a82cc2e3279..9e901068759 100644 --- a/pages/az_AZ/docs/models.md +++ b/pages/az_AZ/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaring Models -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/az_AZ/docs/security.md b/pages/az_AZ/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/az_AZ/docs/security.md +++ b/pages/az_AZ/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/az_AZ/docs/transactions.md b/pages/az_AZ/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/az_AZ/docs/transactions.md +++ b/pages/az_AZ/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/az_AZ/docs/write_driver.md b/pages/az_AZ/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/az_AZ/docs/write_driver.md +++ b/pages/az_AZ/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/az_AZ/docs/write_plugins.md b/pages/az_AZ/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/az_AZ/docs/write_plugins.md +++ b/pages/az_AZ/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/az_AZ/gen/associations.md b/pages/az_AZ/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/az_AZ/gen/associations.md +++ b/pages/az_AZ/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/az_AZ/gen/query.md b/pages/az_AZ/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/az_AZ/gen/query.md +++ b/pages/az_AZ/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/az_AZ/gen/sql_annotation.md b/pages/az_AZ/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/az_AZ/gen/sql_annotation.md +++ b/pages/az_AZ/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/de_DE/docs/advanced_query.md b/pages/de_DE/docs/advanced_query.md index 17b0ad5c38b..9083a36ed8a 100644 --- a/pages/de_DE/docs/advanced_query.md +++ b/pages/de_DE/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## Intelligente Feldauswahl -GORM allows selecting specific fields with [`Select`](query.html), if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -13,7 +13,7 @@ type User struct { Name string Age int Gender string - // hunderte weitere Felder + // hundreds of fields } type APIUser struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Wähle `id`, `name` bei Abfragen automatisch aus +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**Beachte**, dass der `QueryFields` Modus wird alle Feldnamen des aktuellen Models berücksichtigen +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // mit dieser Option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Modus +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (für Updates) +## Locking GORM unterstützt verschiedene Locks. Beispielsweise: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Siehe [Raw SQL und SQL Builder](sql_builder.html) für weitere Informationen +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery -Eine Unterabfrage kann innerhalb einer Abfrage verschachtelt werden, GORM kann Unterabfrage erzeugen, wenn ein `*gorm.DB` Objekt als Parameter verwendet wird +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM ermöglicht die Nutzung von Unterabfragen in FROM Blöcken mit der `Table` Methode: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Gruppierungsbedingungen -Gruppierungsbedingungen erleichtern das Schreiben komplexer SQL Abfragen +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN mit mehreren Spalten -Auswahl von IN mit mehreren Spalten +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Benannte Argumente -GORM unterstützt benannte Argumente mit [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) oder `map[string]interface{}{}` +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Check out [Raw SQL and SQL Builder](sql_builder.html#named_argument) for more detail +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Ergebnisse in Maps speichern -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM supports iterating through Rows +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` allows you to specify commonly-used queries which can be referenced as method calls +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Get matched records count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/de_DE/docs/associations.md b/pages/de_DE/docs/associations.md index 59db3e2af20..8c59d1dee1d 100644 --- a/pages/de_DE/docs/associations.md +++ b/pages/de_DE/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Automatisch erstellen/aktualisieren -GORM will auto-save associations and its reference using [Upsert](create.html#upsert) when creating/updating a record. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -If you want to update associations's data, you should use the `FullSaveAssociations` mode: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Automatisches Erstellen/Aktualisieren überspringen -To skip the auto save when creating/updating, you can use `Select` or `Omit`, for example: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE:** For many2many associations, GORM will upsert the associations before creating the join table references, if you want to skip the upserting of associations, you could skip it like: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -The following code will skip the creation of the association and its references +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Association fields +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Delete Associations + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -Association Mode contains some commonly used helper methods to handle relationships +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Find Associations +### Finding Associations -Find matched associations +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Find associations with conditions -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Append Associations +### Appending Associations -Append new associations for `many to many`, `has many`, replace current association for `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Replace Associations +### Replacing Associations -Replace current associations with new ones +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Delete Associations +### Deleting Associations -Remove the relationship between source & arguments if exists, only delete the reference, won't delete those objects from DB. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations +### Clearing Associations -Remove all reference between source & association, won't delete those associations +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Count Associations +### Counting Associations -Return the count of current associations +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Association Mode supports batch data, e.g: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/de_DE/docs/context.md b/pages/de_DE/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/de_DE/docs/context.md +++ b/pages/de_DE/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/de_DE/docs/conventions.md b/pages/de_DE/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/de_DE/docs/conventions.md +++ b/pages/de_DE/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/de_DE/docs/create.md b/pages/de_DE/docs/create.md index f3b59406d10..146f61489d3 100644 --- a/pages/de_DE/docs/create.md +++ b/pages/de_DE/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/de_DE/docs/dbresolver.md b/pages/de_DE/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/de_DE/docs/dbresolver.md +++ b/pages/de_DE/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/de_DE/docs/error_handling.md b/pages/de_DE/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/de_DE/docs/error_handling.md +++ b/pages/de_DE/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/de_DE/docs/many_to_many.md b/pages/de_DE/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/de_DE/docs/many_to_many.md +++ b/pages/de_DE/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/de_DE/docs/method_chaining.md b/pages/de_DE/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/de_DE/docs/method_chaining.md +++ b/pages/de_DE/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/de_DE/docs/models.md b/pages/de_DE/docs/models.md index a82cc2e3279..9e901068759 100644 --- a/pages/de_DE/docs/models.md +++ b/pages/de_DE/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaring Models -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/de_DE/docs/security.md b/pages/de_DE/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/de_DE/docs/security.md +++ b/pages/de_DE/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/de_DE/docs/transactions.md b/pages/de_DE/docs/transactions.md index b5e5e2e29d4..9e62bb124ff 100644 --- a/pages/de_DE/docs/transactions.md +++ b/pages/de_DE/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/de_DE/docs/write_driver.md b/pages/de_DE/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/de_DE/docs/write_driver.md +++ b/pages/de_DE/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/de_DE/docs/write_plugins.md b/pages/de_DE/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/de_DE/docs/write_plugins.md +++ b/pages/de_DE/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/de_DE/gen/associations.md b/pages/de_DE/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/de_DE/gen/associations.md +++ b/pages/de_DE/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/de_DE/gen/query.md b/pages/de_DE/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/de_DE/gen/query.md +++ b/pages/de_DE/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/de_DE/gen/sql_annotation.md b/pages/de_DE/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/de_DE/gen/sql_annotation.md +++ b/pages/de_DE/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/es_ES/docs/advanced_query.md b/pages/es_ES/docs/advanced_query.md index 44244e03bf2..a61233b41dd 100644 --- a/pages/es_ES/docs/advanced_query.md +++ b/pages/es_ES/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: página ## Campos de selección inteligentes -GORM permite seleccionar campos específicos con [`Select`](query.html), si usas esto a menudo en tu aplicación, tal vez quieras definir una estructura más pequeña para el uso del API que pueda seleccionar automáticamente campos específicos, por ejemplo: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Select `id`, `name` automatically when querying +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOTA:** En el modo `QueryFields`, se seleccionarán automáticamente todos los campos del modelo actual por su nombre +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Bloqueo (Para Actualizaciones) +## Locking GORM soporta diferentes tipos de bloqueos, por ejemplo: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Consulte [Raw SQL y SQL Builder](sql_builder.html) para más detalles +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## Subconsultas -Una subconsulta puede ser anidada dentro de una consulta, GORM puede generar subconsulta al usar un objeto `*gorm.DB` como parámetro +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### Desde Subconsulta -GORM permite usar subconsultas en la cláusula FROM con el método `Table`, por ejemplo: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Condiciones de grupo -Fácil de escribir una consulta SQL complicada con las condiciones de grupo +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN con múltiples columnas -Seleccionando IN con múltiples columnas +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Argumentos nombrados -GORM soporta argumentos nombrados con [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) o `map[string]interface{}{}`, por ejemplo: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Consulta [Raw SQL and SQL Builder](sql_builder.html#named_argument) para más detalles +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Buscar en Mapa -GORM permite escanear resultados a `map[string]interface{}` o `[]map[string]interface{}`, no olvide especificar el `Modelo` o `Tabla`, por ejemplo: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Obtener el primer registro coincidente o inicializar una nueva instancia con determinadas condiciones (sólo funciona con la estructura o condiciones del mapa) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Inicializar estructura con más atributos si no se encuentra el registro, esos `Attrs` no se utilizarán para construir la consulta SQL +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` atributos al struct independientemente de que se encuentre o no, esos atributos no se utilizarán para construir una consulta SQL y los datos finales no se guardarán en la base de datos +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Obtenga el primer registro coincidente o cree uno nuevo con las condiciones dadas (solo funciona con struct, las condiciones del mapa), `RowsAfected` devuelve el conteo de registros creado/actualizado +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Inicializar struct con más atributos si no se encuentra el registro, esos `Attrs` no se utilizarán para construir la consulta SQL +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` atributos al registro independientemente de que se encuentre o no y guardarlos de vuelta a la base de datos. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimización/Index Hints -Los hints de optimización permiten controlar el optimizador de consultas para elegir un determinado plan de ejecución, GORM lo soporta con `gorm.io/hints`, ej: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Los hints de índices permiten pasar hints de índice a la base de datos en caso de que el planificador de consultas se confunda. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Consulte [Pistas/Index/Comentario optimizador](hints.html) para más detalles +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteración -GORM soporta iteración a través de las filas +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## Búsqueda por Lotes -Consultar y procesar registros en lote +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Consultas Hooks -GORM permite hooks `AfterFind` para una consulta, se llamará al consultar un registro, consulte [Hooks](hooks.html) para más detalles +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Consulta una sola columna de la base de datos y escanear en un slice, si desea consultar múltiples columnas, usa `Select` con [`Scan`](query.html#scan) en su lugar +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Ámbitos -`Scopes` le permite especificar consultas usadas comúnmente que pueden ser referenciadas como llamadas a métodos +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Consulta [Scopes](scopes.html) para más detalles +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Recuento -Obtener recuento de registros coincidentes +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/es_ES/docs/associations.md b/pages/es_ES/docs/associations.md index feec5cd4244..2b12bf6edba 100644 --- a/pages/es_ES/docs/associations.md +++ b/pages/es_ES/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Crear/ actualizar Automáticamente -GORM guardará automáticamente asociaciones y su referencia al usar [Upsert](create.html#upsert), cuando se crear/actualiza un registro. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -Si desea actualizar los datos de las asociaciones, debe usar el modo `FullSaveAssociations` mode: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Crear/ actualizar Automáticamente -Para omitir el guardado automático al crear/actualizar, puede utilizar `Select` o `Omit`, por ejemplo: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTA:** Para las asociaciones many2many, GORM actualizará las asociaciones antes de crear las referencias de la tabla de unión, si quieres omitir el upseting de las asociaciones, puedes saltarla: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -El siguiente código omitirá la creación de la asociación y sus referencias +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Seleccionar/Omitir campos de asociación +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Eliminar Asociaciones + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Modo de asociación -El modo de asociación contiene algunos métodos de ayuda comúnmente utilizados para manejar relaciones +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Buscar asociaciones +### Finding Associations -Buscar asociaciones coincidentes +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Buscar asociaciones con condiciones -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Añadir asociaciones +### Appending Associations -Añadir nuevas asociaciones para `muchas a muchas`, `tiene muchas`, reemplazar asociación actual para `tiene un`, `pertenece a` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Reemplazar asociaciones +### Replacing Associations -Reemplazar asociaciones actuales por nuevas +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Eliminar Asociaciones +### Deleting Associations -Eliminar la relación entre la fuente & argumentos si existe, solo eliminar la referencia, no eliminará esos objetos de la DB. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Limpiar asociaciones +### Clearing Associations -Eliminar toda referencia entre la fuente & asociación, no eliminará esas asociaciones +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Contar Asociaciones +### Counting Associations -Devolver el recuento de asociaciones actuales +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Datos de Lote +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -El modo de asociación soporta datos por lotes, por ejemplo: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Etiqueta | Descripción | -| ---------------- | -------------------------------------------------------------------------------------------------------------- | -| foreignKey | Especifica el nombre de la columna del modelo actual que se utiliza como clave foránea para la tabla de unión | -| references | Especifica el nombre de la columna de la tabla de referencia que se asigna a la clave foránea de la tabla join | -| polymorphic | Especifica el tipo polimórfico como el nombre del modelo | -| polymorphicValue | Especifica el valor polimórfico, nombre de tabla por defecto | -| many2many | Especifica el nombre de la tabla de unión | -| joinForeignKey | Especifica el nombre de columna de clave foránea de la tabla de unión que mapea a la tabla actual | -| joinForeignKey | Especifica el nombre de columna de clave foránea de la tabla de unión que mapea a la tabla de referencia | -| constraint | Restricción de relaciones, por ejemplo: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Etiqueta | Descripción | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinForeignKey` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/es_ES/docs/context.md b/pages/es_ES/docs/context.md index b39518d2ab0..b54b6b36fef 100644 --- a/pages/es_ES/docs/context.md +++ b/pages/es_ES/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM proporciona soporte para `Context`, se puede utilizar con el método `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Modo de sesión única +### Modo de sesión única -El modo de sesión única se utiliza generalmente cuando se desea realizar una sola operación. +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Modo de sesión continua +### Continuous Session Mode -El modo de sesión continua se utiliza generalmente cuando se desea realizar un grupo de operaciones, por ejemplo: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Tiempo de espera del Contexto +### Context Timeout -Puede pasar un contexto con un tiempo de espera a `db.WithContext` para establecer un tiempo de espera en las consultas de larga duración, por ejemplo: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Contexto en Hooks/Callbacks +### Contexto en Hooks/Callbacks -Puede acceder al objeto `Context` desde el `Statement` actual, por ejemplo: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Ejemplo de Middleware Chi +### Integration with Chi Middleware -El modo de sesión continua que puede ser útil al manejar solicitudes de API, por ejemplo, se puede configurar `*gorm.DB` con Contexto de Tiempo de espera en middlewares, y luego usar `*gorm.DB` en el procesamiento de todas las solicitudes. - -El siguiente es un ejemplo de middleware Chi: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTA** Establecer `Context` con `WithContext` es seguro para gorutinas, consulte [Session](session.html) para obtener detalles. -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger acepta `Contexto` también, puedes usarlo para el seguimiento de registros, consulta [Logger](logger.html) para más detalles +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/es_ES/docs/conventions.md b/pages/es_ES/docs/conventions.md index 664efad156c..8bd7b4e7374 100644 --- a/pages/es_ES/docs/conventions.md +++ b/pages/es_ES/docs/conventions.md @@ -3,32 +3,32 @@ title: Conventions layout: page --- -## `ID` as Primary Key +## `ID` como Clave Primaria -GORM uses the field with the name `ID` as the table's primary key by default. +GORM utiliza el campo con el nombre `ID` como clave principal de la tabla por defecto. ```go type User struct { -  ID   string // field named `ID` will be used as a primary field by default -  Name string +ID string // campo llamado `ID` será usado como un campo primario por defecto +Name string } ``` -You can set other fields as primary key with tag `primaryKey` +Puede establecer otros campos como clave primaria con la etiqueta `primaryKey` ```go -// Set field `UUID` as primary field +// Establecer el campo `UUID` como campo primario type Animal struct { -  ID     int64 -  UUID   string `gorm:"primaryKey"` -  Name   string -  Age    int64 +ID int64 +UUID string `gorm:"primaryKey"` +Name string +Age int64 } ``` -Also check out [Composite Primary Key](composite_primary_key.html) +También echa un vistazo a [Clave Primaria Compuesta](composite_primary_key.html) -## Pluralized Table Name +## Nombre de tabla pluralizada GORM pluralizes struct name to `snake_cases` as table name, for struct `User`, its table name is `users` by convention @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/es_ES/docs/create.md b/pages/es_ES/docs/create.md index c9722a76c2d..152c9e7ff8d 100644 --- a/pages/es_ES/docs/create.md +++ b/pages/es_ES/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/es_ES/docs/dbresolver.md b/pages/es_ES/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/es_ES/docs/dbresolver.md +++ b/pages/es_ES/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/es_ES/docs/error_handling.md b/pages/es_ES/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/es_ES/docs/error_handling.md +++ b/pages/es_ES/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/es_ES/docs/many_to_many.md b/pages/es_ES/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/es_ES/docs/many_to_many.md +++ b/pages/es_ES/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/es_ES/docs/method_chaining.md b/pages/es_ES/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/es_ES/docs/method_chaining.md +++ b/pages/es_ES/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/es_ES/docs/models.md b/pages/es_ES/docs/models.md index 939bab915c2..8a87b82f0a4 100644 --- a/pages/es_ES/docs/models.md +++ b/pages/es_ES/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declarando modelos -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -Por ejemplo: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Convenciones +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Convenciones -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM ha definido una estructura `gorm.Model`, que incluye campos `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -Puedes insertarlo en tu estructura para incluir esos campos, consulta [Estructura incrustada](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/es_ES/docs/security.md b/pages/es_ES/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/es_ES/docs/security.md +++ b/pages/es_ES/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/es_ES/docs/transactions.md b/pages/es_ES/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/es_ES/docs/transactions.md +++ b/pages/es_ES/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/es_ES/docs/write_driver.md b/pages/es_ES/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/es_ES/docs/write_driver.md +++ b/pages/es_ES/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/es_ES/docs/write_plugins.md b/pages/es_ES/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/es_ES/docs/write_plugins.md +++ b/pages/es_ES/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/es_ES/gen/associations.md b/pages/es_ES/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/es_ES/gen/associations.md +++ b/pages/es_ES/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/es_ES/gen/query.md b/pages/es_ES/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/es_ES/gen/query.md +++ b/pages/es_ES/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/es_ES/gen/sql_annotation.md b/pages/es_ES/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/es_ES/gen/sql_annotation.md +++ b/pages/es_ES/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/fa_IR/community.md b/pages/fa_IR/community.md index 5800a9a5f77..60c7f6928d1 100644 --- a/pages/fa_IR/community.md +++ b/pages/fa_IR/community.md @@ -5,7 +5,7 @@ layout: page ## Ask -**[How do I ask a good question?](https://stackoverflow.com/help/how-to-ask)** +**[چگونه یک سوال خوب بپرسم؟](https://stackoverflow.com/help/how-to-ask)** * Stackoverflow - [https://stackoverflow.com/questions/tagged/go-gorm](https://stackoverflow.com/questions/tagged/go-gorm) * Github Issues - [https://github.com/go-gorm/gorm/issues](https://github.com/go-gorm/gorm/issues) diff --git a/pages/fa_IR/docs/advanced_query.md b/pages/fa_IR/docs/advanced_query.md index 6cfb635ee1f..5dfdda170af 100644 --- a/pages/fa_IR/docs/advanced_query.md +++ b/pages/fa_IR/docs/advanced_query.md @@ -5,29 +5,27 @@ layout: page ## انتخاب هوشمند field ها -GORM allows selecting specific fields with [`Select`](query.html), if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. به طور خاص در هنگام برخورد با مدل های بزرگ که تنها به زیر مجموعه ای از فیلدها نیاز دارند، این ویژگی مفید است، مخصوصا در پاسخ های API. ```go -type User struct { +} type User struct ID uint Name string Age int Gender string - // hundreds of fields -} + // و صدها فیلد دیگر +{ -type APIUser struct { + } type APIUser struct ID uint Name string -} +{ -// Select `id`, `name` automatically when querying -db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// GORM به صورت خودکار فیلدهای id و name را ``` {% note warn %} -**NOTE** `QueryFields` mode will select by all fields' name for current model +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +33,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (FOR UPDATE) +## Locking GORM supports different types of locks, for example: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. + +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. + +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Refer [Raw SQL and SQL Builder](sql_builder.html) for more detail +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery -A subquery can be nested within a query, GORM can generate subquery when using a `*gorm.DB` object as param +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group Conditions -Easier to write complicated SQL query with Group Conditions +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN with multiple columns -Selecting IN with multiple columns +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Named Argument -GORM supports named arguments with [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, for example: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Check out [Raw SQL and SQL Builder](sql_builder.html#named_argument) for more detail +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM supports iterating through Rows +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +316,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch - - batch // Batch 1, 2, 3 + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` allows you to specify commonly-used queries which can be referenced as method calls +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` + +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Get matched records count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +494,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/fa_IR/docs/associations.md b/pages/fa_IR/docs/associations.md index 42e5a8e6131..ee30caf054f 100644 --- a/pages/fa_IR/docs/associations.md +++ b/pages/fa_IR/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Auto Create/Update -GORM will auto-save associations and its reference using [Upsert](create.html#upsert) when creating/updating a record. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -If you want to update associations's data, you should use the `FullSaveAssociations` mode: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Skip Auto Create/Update -To skip the auto save when creating/updating, you can use `Select` or `Omit`, for example: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE:** For many2many associations, GORM will upsert the associations before creating the join table references, if you want to skip the upserting of associations, you could skip it like: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -The following code will skip the creation of the association and its references +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Association fields +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Delete Associations + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -Association Mode contains some commonly used helper methods to handle relationships +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Find Associations +### Finding Associations -Find matched associations +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Find associations with conditions -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Append Associations +### Appending Associations -Append new associations for `many to many`, `has many`, replace current association for `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Replace Associations +### Replacing Associations -Replace current associations with new ones +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Delete Associations +### Deleting Associations -Remove the relationship between source & arguments if exists, only delete the reference, won't delete those objects from DB. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations +### Clearing Associations -Remove all reference between source & association, won't delete those associations +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Count Associations +### Counting Associations -Return the count of current associations +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Association Mode supports batch data, e.g: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/fa_IR/docs/context.md b/pages/fa_IR/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/fa_IR/docs/context.md +++ b/pages/fa_IR/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/fa_IR/docs/conventions.md b/pages/fa_IR/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/fa_IR/docs/conventions.md +++ b/pages/fa_IR/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/fa_IR/docs/create.md b/pages/fa_IR/docs/create.md index ce5544182c5..ae06885fad6 100644 --- a/pages/fa_IR/docs/create.md +++ b/pages/fa_IR/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/fa_IR/docs/dbresolver.md b/pages/fa_IR/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/fa_IR/docs/dbresolver.md +++ b/pages/fa_IR/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/fa_IR/docs/error_handling.md b/pages/fa_IR/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/fa_IR/docs/error_handling.md +++ b/pages/fa_IR/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/fa_IR/docs/many_to_many.md b/pages/fa_IR/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/fa_IR/docs/many_to_many.md +++ b/pages/fa_IR/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/fa_IR/docs/method_chaining.md b/pages/fa_IR/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/fa_IR/docs/method_chaining.md +++ b/pages/fa_IR/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/fa_IR/docs/models.md b/pages/fa_IR/docs/models.md index a82cc2e3279..9e901068759 100644 --- a/pages/fa_IR/docs/models.md +++ b/pages/fa_IR/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaring Models -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/fa_IR/docs/security.md b/pages/fa_IR/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/fa_IR/docs/security.md +++ b/pages/fa_IR/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/fa_IR/docs/transactions.md b/pages/fa_IR/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/fa_IR/docs/transactions.md +++ b/pages/fa_IR/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/fa_IR/docs/write_driver.md b/pages/fa_IR/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/fa_IR/docs/write_driver.md +++ b/pages/fa_IR/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/fa_IR/docs/write_plugins.md b/pages/fa_IR/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/fa_IR/docs/write_plugins.md +++ b/pages/fa_IR/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/fa_IR/gen/associations.md b/pages/fa_IR/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/fa_IR/gen/associations.md +++ b/pages/fa_IR/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/fa_IR/gen/query.md b/pages/fa_IR/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/fa_IR/gen/query.md +++ b/pages/fa_IR/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/fa_IR/gen/sql_annotation.md b/pages/fa_IR/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/fa_IR/gen/sql_annotation.md +++ b/pages/fa_IR/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/fr_FR/docs/advanced_query.md b/pages/fr_FR/docs/advanced_query.md index 313d6e58392..fe74e12de5a 100644 --- a/pages/fr_FR/docs/advanced_query.md +++ b/pages/fr_FR/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## Champs de sélection intelligente -GORM allows selecting specific fields with [`Select`](query.html), if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Select `id`, `name` automatique lors de la requête +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOTE** Le mode `QueryFields` sélectionnera par le nom de tous les champs pour le modèle actuel +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // avec cette option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Mode Session +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (FOR UPDATE) +## Locking GORM supports different types of locks, for example: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Reportez-vous à [Raw SQL et SQL Builder](sql_builder.html) pour plus de détails +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery -A subquery can be nested within a query, GORM can generate subquery when using a `*gorm.DB` object as param +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group Conditions -Easier to write complicated SQL query with Group Conditions +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN with multiple columns -Selecting IN with multiple columns +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Named Argument -GORM supports named arguments with [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, for example: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Check out [Raw SQL and SQL Builder](sql_builder.html#named_argument) for more detail +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM supports iterating through Rows +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` allows you to specify commonly-used queries which can be referenced as method calls +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Get matched records count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/fr_FR/docs/associations.md b/pages/fr_FR/docs/associations.md index f3044e30115..8394d18174e 100644 --- a/pages/fr_FR/docs/associations.md +++ b/pages/fr_FR/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Auto Create/Update -GORM enregistrera automatiquement les associations et ses références en utilisant [Upsert](create.html#upsert) lors de la création/mise à jour d'un enregistrement. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -Si vous voulez mettre à jour les données des associations, vous devez utiliser le mode `FullSaveAssociations`: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Skip Auto Create/Update -Pour ignorer la sauvegarde automatique lors de la création/mise à jour, vous pouvez utiliser ` Select ` ou `Omit`, par exemple : +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE :** Pour de nombreuses associations, GORM fera la mise en valeur des associations avant de créer les références de la table d'association, si vous voulez sauter l'insertion des associations, vous pouvez passer comme : +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -Le code suivant ignorera la création de l'association et de ses références +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Champs d'association +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Supprimer des associations + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Mode d'association -Le mode association contient des méthodes d'aide couramment utilisées pour gérer les relations +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Trouver des associations +### Finding Associations -Trouver les associations correspondantes +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Trouver des associations avec des conditions -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Ajouter des associations +### Appending Associations -Ajouter de nouvelles associations pour `many to many`, `has many`, remplacer l'association actuelle pour `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Remplacer des associations +### Replacing Associations -Remplacer les associations actuelles par de nouvelles associations +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Supprimer des associations +### Deleting Associations -Supprimer la relation entre les arguments source & s'il existe, supprimer seulement la référence, ne supprimera pas ces objets de la base de données. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations +### Clearing Associations -Supprimer toutes les références entre l'association source & ne supprimera pas ces associations +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Nombre d'associations +### Counting Associations -Renvoie le nombre d'associations actuelles +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Le mode association supporte les données par lot, par exemple: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Supprimer l'enregistrement de l'association -Par défaut `Replace`/`Delete`/`Clear` dans `gorm.Association` ne supprime que la référence, qui est, définit la clé étrangère des anciennes associations à null. - -Vous pouvez supprimer ces objets avec `Unscoped` (cela n'a rien à voir avec `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/fr_FR/docs/context.md b/pages/fr_FR/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/fr_FR/docs/context.md +++ b/pages/fr_FR/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/fr_FR/docs/conventions.md b/pages/fr_FR/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/fr_FR/docs/conventions.md +++ b/pages/fr_FR/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/fr_FR/docs/create.md b/pages/fr_FR/docs/create.md index 3cb92279b3b..82eb8d27fa4 100644 --- a/pages/fr_FR/docs/create.md +++ b/pages/fr_FR/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/fr_FR/docs/dbresolver.md b/pages/fr_FR/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/fr_FR/docs/dbresolver.md +++ b/pages/fr_FR/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/fr_FR/docs/error_handling.md b/pages/fr_FR/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/fr_FR/docs/error_handling.md +++ b/pages/fr_FR/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/fr_FR/docs/many_to_many.md b/pages/fr_FR/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/fr_FR/docs/many_to_many.md +++ b/pages/fr_FR/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/fr_FR/docs/method_chaining.md b/pages/fr_FR/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/fr_FR/docs/method_chaining.md +++ b/pages/fr_FR/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/fr_FR/docs/models.md b/pages/fr_FR/docs/models.md index a82cc2e3279..9e901068759 100644 --- a/pages/fr_FR/docs/models.md +++ b/pages/fr_FR/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaring Models -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/fr_FR/docs/security.md b/pages/fr_FR/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/fr_FR/docs/security.md +++ b/pages/fr_FR/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/fr_FR/docs/transactions.md b/pages/fr_FR/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/fr_FR/docs/transactions.md +++ b/pages/fr_FR/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/fr_FR/docs/write_driver.md b/pages/fr_FR/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/fr_FR/docs/write_driver.md +++ b/pages/fr_FR/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/fr_FR/docs/write_plugins.md b/pages/fr_FR/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/fr_FR/docs/write_plugins.md +++ b/pages/fr_FR/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/fr_FR/gen/associations.md b/pages/fr_FR/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/fr_FR/gen/associations.md +++ b/pages/fr_FR/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/fr_FR/gen/query.md b/pages/fr_FR/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/fr_FR/gen/query.md +++ b/pages/fr_FR/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/fr_FR/gen/sql_annotation.md b/pages/fr_FR/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/fr_FR/gen/sql_annotation.md +++ b/pages/fr_FR/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/hi_IN/docs/advanced_query.md b/pages/hi_IN/docs/advanced_query.md index 824254ddacf..756ee10d16d 100644 --- a/pages/hi_IN/docs/advanced_query.md +++ b/pages/hi_IN/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## स्मार्ट सेलेक्ट फील्ड्स -जीओआरएम(GORM) चयन के साथ विशिष्ट क्षेत्रों का चयन ( [`Select`](query.html)) करने की अनुमति देता है, यदि आप अक्सर इसे अपने आवेदन में उपयोग करते हैं, तो हो सकता है कि आप एपीआई उपयोग के लिए एक छोटी संरचना को परिभाषित करना चाहते हैं जो स्वचालित रूप से विशिष्ट क्षेत्रों का चयन कर सकता है, उदाहरण के लिए | +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -21,14 +21,13 @@ type APIUser struct { Name string } -// Select `id`, `name` automatically when querying -// / query करते समय `id`, `name` स्वचालित रूप से Select करता +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOTE** `QueryFields` mode वर्तमान के लिए सभी फ़ील्ड्स के नाम से Select करेगा +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -36,245 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -//db वह सभी fild ढूंढता है जो अंदर है(user) -// नीचे की तरह -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // इस विकल्प के साथ +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (FOR UPDATE) //लॉकिंग (UPDATE के लिए) +## Locking उदाहरण के लिए GORM विभिन्न प्रकार के locks का समर्थन करता है ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. + +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -अधिक विवरण के लिए [Raw SQL and SQL Builder](sql_builder.html) देखें +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery //सबक्वेरी -एक subquery को एक query के भीतर nested किया जा सकता है, param के रूप में `*gorm.DB` ऑब्जेक्ट का उपयोग करते समय GORM सबक्वेरी उत्पन्न कर सकता है +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); -// चुनें * "orders" से जहां amount > ("orders" से AVG(amount) चुनें); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery //सबक्वेरी से -GORM आपको `Table` विधि के साथ FROM खंड में subquery का उपयोग करने की अनुमति देता है, उदाहरण के लिए: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group Conditions //समूह की शर्तें -समूह शर्तों ( Group Conditions ) के साथ जटिल SQL query लिखना आसान +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN with multiple columns //IN कई कॉलम के साथ -एकाधिक कॉलम के साथ IN का चयन करना +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Named Argument // नामांकित तर्क -GORM नामित तर्कों का समर्थन करता है [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) या `map[ string]इंटरफ़ेस{}{}`, उदाहरण के लिए: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -अधिक विवरण के लिए [रॉ SQL और SQL बिल्डर](sql_builder.html#named_argument) देखें +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map //मानचित्र में खोजें -GORM परिणामों को `map[string]interface{}` या `[]map[string]interface{}` में स्कैन करने की अनुमति देता है, `Model` निर्दिष्ट करना न भूलें > या `तालिका`, उदाहरण के लिए: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -पहले मिलान किए गए रिकॉर्ड प्राप्त करें या दी गई शर्तों के साथ एक नया उदाहरण आरंभ करें (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -यदि रिकॉर्ड नहीं मिला तो अधिक विशेषताओं के साथ संरचना प्रारंभ करें, उन `Attrs` का उपयोग SQL query बनाने के लिए नहीं किया जाएगा +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` संरचना को विशेषताएँ मिले या न मिले, उन विशेषताओं का उपयोग SQL query बनाने के लिए नहीं किया जाएगा और अंतिम डेटा डेटाबेस में सहेजा नहीं जाएगा +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -पहले मिलान किए गए रिकॉर्ड प्राप्त करें या दी गई शर्तों के साथ एक नया बनाएं (only works with struct, map conditions), `RowsAffected` `` निर्मित/अपडेट किए गए रिकॉर्ड की संख्या लौटाता है

+`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. -
// User not found, create a new record with give conditions
+```go
+// Create a new record if not found
 result := db.FirstOrCreate(&user, User{Name: "non_existing"})
-// INSERT INTO "users" (name) VALUES ("non_existing");
+// SQL: INSERT INTO "users" (name) VALUES ("non_existing");
 // user -> User{ID: 112, Name: "non_existing"}
-// result.RowsAffected // => 1
+// result.RowsAffected // => 1 (record created)
 
-// Found user with `name` = `jinzhu`
-result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
-// user -> User{ID: 111, Name: "jinzhu", "Age": 18}
-// result.RowsAffected // => 0
-``
+// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) +``` -यदि रिकॉर्ड नहीं मिला तो अधिक विशेषताओं के साथ संरचना बनाएं, उन `Attrs` का उपयोग SQL query बनाने के लिए नहीं किया जाएगा +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -रिकॉर्ड के लिए विशेषताएँ `Assign` चाहे वह मिले या नहीं और उन्हें डेटाबेस में वापस सहेजें (save)। +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer संकेत query Optimizer को एक certain query execution योजना चुनने के लिए नियंत्रित करने की अनुमति देते हैं, GORM इसे `gorm.io/hints` के साथ समर्थन करता है, उदा: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Query planner के भ्रमित होने की स्थिति में इंडेक्स संकेत डेटाबेस को इंडेक्स संकेत पास करने की अनुमति देते हैं। +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -अधिक विवरण के लिए [Optimizer Hints/Index/Comment](hints.html) देखें

- - +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM Rows के माध्यम से पुनरावृति(iterating) का समर्थन करता है - +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -282,150 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` - - +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. ## FindInBatches -batch में Query और process रिकॉर्ड - +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // बैच प्रोसेसिंग में रिकॉर्ड मिले + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // इस batch में रिकॉर्ड की संख्या + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // रिटर्न err भविष्य के बैचों को रोक देगा + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // संसाधित रिकॉर्ड सभी बैचों(batch ) में गिने जाते हैं +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` - - +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. ## Query Hooks -GORM एक प्रश्न के लिए हुक`AfterFind` की अनुमति देता है, रिकॉर्ड की क्वेरी करते समय इसे कॉल किया जाएगा, विवरण के लिए [हुक](hooks.html) देखें - +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } -``` - - +// Usage of AfterFind hook happens automatically when a User is queried +``` ## Pluck -डेटाबेस से एकल कॉलम को क्वेरी करें और एक स्लाइस में स्कैन करें, यदि आप कई कॉलमों को query करना चाहते हैं, तो [`Scan`के साथ `Select` का उपयोग करें।](query.html#scan) इसके बजाय - +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` - - - ## Scopes -`Scopes` आपको आमतौर पर उपयोग की जाने वाली queries निर्दिष्ट करने की अनुमति देता है जिसे विधि कॉल(method calls) के रूप में संदर्भित(referenced) किया जा सकता है +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. +### Defining Scopes +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` + +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 -// सभी क्रेडिट कार्ड ऑर्डर और 1000 से अधिक राशि का पता लगाएं +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. -विवरण के लिए [Scopes](scopes.html) चेकआउट करें +## Count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. +### Getting the Count of Matched Records -## Count +You can use `Count` to determine the number of records that meet specific criteria in your queries. -मिलान किए गए रिकॉर्ड की संख्या प्राप्त करें +```go +var count int64 +// Counting users with specific names +db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition +db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' -```go +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` -// Count with Distinct +### Count with Distinct and Group + +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -434,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/hi_IN/docs/associations.md b/pages/hi_IN/docs/associations.md index 53cd4de1c2d..9b4d0f9297f 100644 --- a/pages/hi_IN/docs/associations.md +++ b/pages/hi_IN/docs/associations.md @@ -5,7 +5,11 @@ layout: page //पृष्ठ ## Auto Create/Update //ऑटो बनाएं/अपडेट करें -रिकॉर्ड बनाते/अपडेट(creating/updating) करते समय GORM [Upsert](create.html#upsert) का उपयोग करके एसोसिएशन(associations) और उसके संदर्भ को अपने आप सहेज लेगा। +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,64 +39,75 @@ db.Create(&user) db.Save(&user) ``` -अगर आप एसोसिएशन के डेटा को अपडेट करना चाहते हैं, तो आपको `FullSaveAssociations` मोड का इस्तेमाल करना चाहिए: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Skip Auto Create/Update // ऑटो क्रिएट/अपडेट छोड़ें -बनाते/अपडेट(creating/updating) करते समय ऑटो सेव को छोड़ने के लिए, आप `Select` या `Omit` का उपयोग कर सकते हैं, उदाहरण के लिए: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user -// उपयोगकर्ता बनाते समय बिलिंग एड्रेस बनाना छोड़ें +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user -// उपयोगकर्ता बनाते समय सभी संघों को छोड़ दें ``` {% note warn %} -**नोट:**कई से कई associations के लिए, GORM सम्मिलित तालिका संदर्भ बनाने से पहले associations को अपसेट करेगा, यदि आप associations के अप्सर्टिंग को छोड़ना चाहते हैं, तो आप इसे इस तरह छोड़ सकते हैं: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -निम्नलिखित कोड एसोसिएशन (association) और उसके संदर्भों(references) के निर्माण को छोड़ देगा +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Association fields // एसोसिएशन फ़ील्ड का चयन करें/छोड़ दें +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -99,54 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others -// उपयोगकर्ता और उसका बिलिंग पता, शिपिंग पता बनाएँ -// बिलिंग पता बनाते समय केवल इसके पते1, पते2 फ़ील्ड का उपयोग करें और अन्य को छोड़ दें +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Delete Associations // संघों को हटाएं + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -Association मोड में रिश्तों को संभालने के लिए आमतौर पर इस्तेमाल की जाने वाली कुछ सहायक विधियाँ(helper methods) होती हैं +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -// `उपयोगकर्ता` स्रोत मॉडल है, इसमें प्राथमिक कुंजी होनी चाहिए -// `Languages` एक रिश्ते का फील्ड नाम है -// यदि उपरोक्त दो आवश्यकताएं मेल खाती हैं, तो एसोसिएशनमोड को सफलतापूर्वक शुरू किया जाना चाहिए, या त्रुटि वापस आनी चाहिए -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Find Associations +### Finding Associations -(Find matched associations) मिलान किए गए संघों को खोजें +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Find associations with conditions // शर्तों के साथ जुड़ाव खोजें -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Append Associations //संघों को जोड़ें +### Appending Associations -`अनेक से अनेक(many to many)` के लिए नए संबंध जोड़ें, `has many`, `has one` के लिए वर्तमान संबंध बदलें, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -154,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Replace Associations // संघों को बदलें +### Replacing Associations -मौजूदा associations को नए के साथ बदलें +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Delete Associations // संघों को हटाएं +### Deleting Associations -स्रोत(source) और तर्क (मौजूद हैं, तो केवल reference हटाएं, उन objects को DB से नहीं हटाएंगे। +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations // स्पष्ट संघ +### Clearing Associations -Source और association के बीच सभी reference निकालें, उन associations को नहीं हटाएंगे +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Count Associations //संघों की गणना करें +### Counting Associations -वर्तमान associations की गिनती लौटाएँ +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -193,94 +245,78 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data // बैच डेटा +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -एसोसिएशन मोड बैच डेटा का समर्थन करता है, जैसे: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users //सभी उपयोगकर्ताओं के लिए सभी भूमिकाएँ खोजें db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team -//उपयोगकर्ता ए को सभी उपयोगकर्ता की टीम से हटाएं +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams -// सभी उपयोगकर्ताओं की टीमों की अलग-अलग गिनती प्राप्त करें +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error -// बैच डेटा के साथ `संलग्न`, `बदलें` के लिए, तर्कों की लंबाई डेटा की लंबाई के बराबर होनी चाहिए अन्यथा यह एक त्रुटि लौटाएगा +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team -// उदाहरण: हमारे पास 3 उपयोगकर्ता हैं, userA को user1 की टीम में जोड़ें, userB को user2 की टीम में जोड़ें, userA, userB और userC को user3 की टीम में जोड़ें + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC -// user1 की टीम को userA पर रीसेट करें, user2 की टीम को userB पर रीसेट करें, user3 की टीम को userA, userB और userC पर रीसेट करें + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| टैग | विवरण | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| foreignKey | वर्तमान मॉडल का column name Specifies करता है जिसका उपयोग तालिका में शामिल होने के लिए foreign key के रूप में किया जाता है | -| references | Reference की table के column नाम को Specifies करता है जिसे सम्मिलित table की foreign key से मैप किया जाता है | -| polymorphic // बहुरूपी | मॉडल नाम जैसे बहुरूपी (polymorphic type)प्रकार निर्दिष्ट(Specifies) करता है | -| polymorphicValue | (polymorphic value) बहुरूपी मान, डिफ़ॉल्ट तालिका(table) नाम निर्दिष्ट(Specifies) करता है | -| many2many | ज्वाइन टेबल नाम निर्दिष्ट(Specifies) करता है | -| joinForeignKey | ज्वाइन टेबल का foreign key कॉलम नाम निर्दिष्ट करता है जो वर्तमान टेबल में मैप करता है | -| joinReferences | ज्वाइन टेबल का foreign key कॉलम नाम निर्दिष्ट करता है जो reference की table में मैप करता है | -| constraint | संबंध बाधा, उदा: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| टैग | विवरण | +| ------------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic // +बहुरूपी` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/hi_IN/docs/context.md b/pages/hi_IN/docs/context.md index fd45e7bc76a..245e3ebbd13 100644 --- a/pages/hi_IN/docs/context.md +++ b/pages/hi_IN/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: पृष्ठ --- -GORM कॉन्टेक्स्ट सपोर्ट प्रदान करता है, आप इसे `WithContext` method के साथ उपयोग कर सकते हैं +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -सिंगल सेशन मोड आमतौर पर तब उपयोग किया जाता है जब आप एक ही ऑपरेशन करना चाहते हैं +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session मोड का उपयोग आमतौर पर तब किया जाता है जब आप operations का एक समूह perform करना चाहते हैं, उदाहरण के लिए: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -लंबे समय तक चलने वाले प्रश्नों(queries) के लिए टाइमआउट सेट करने के लिए आप `db.WithContext` के टाइमआउट के साथ संदर्भ(context) में पास कर सकते हैं, उदाहरण के लिए: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,25 +34,24 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -आप `Context` ऑब्जेक्ट को वर्तमान `स्टेटमेंट` से एक्सेस कर सकते हैं, उदाहरण के लिए: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session मोड जो API अनुरोधों(requests) को संभालने में सहायक हो सकता है, उदाहरण के लिए, आप मिडलवेयर में टाइमआउट संदर्भ के साथ `*gorm.DB` सेटअप कर सकते हैं और फिर `*gorm.DB सभी अनुरोधों को संसाधित करते समय

+GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. -

Following is a Chi middleware example:

- -
func SetDBMiddleware(next http.Handler) http.Handler {
+```go
+func SetDBMiddleware(next http.Handler) http.Handler {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     timeoutContext, _ := context.WithTimeout(context.Background(), time.Second)
     ctx := context.WithValue(r.Context(), "DB", db.WithContext(timeoutContext))
@@ -60,32 +59,26 @@ Continuous session मोड जो API अनुरोधों(requests) क
   })
 }
 
+// Router setup
 r := chi.NewRouter()
 r.Use(SetDBMiddleware)
 
+// Route handlers
 r.Get("/", func(w http.ResponseWriter, r *http.Request) {
-  db, ok := ctx.Value("DB").(*gorm.DB)
-
-  var users []User
-  db.Find(&users)
-
-  // lots of db operations
+  db, ok := r.Context().Value("DB").(*gorm.DB)
+  // ... db operations
 })
 
 r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
-  db, ok := ctx.Value("DB").(*gorm.DB)
-
-  var user User
-  db.First(&user)
-
-  // lots of db operations
+  db, ok := r.Context().Value("DB").(*gorm.DB)
+  // ... db operations
 })
-`
+``` + +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. -{% note %} -**ध्यान दें** `Context` को `WithContext` के साथ सेट करना गोरूटीन-सुरक्षित है, [Session देखें ](session.html) विवरण के लिए -{% endnote %} +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger `Context` भी स्वीकार करता है, आप इसे लॉग ट्रैकिंग के लिए उपयोग कर सकते हैं, विवरण के लिए [Logger](logger.html) देखें +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/hi_IN/docs/conventions.md b/pages/hi_IN/docs/conventions.md index 5ae42c3d985..b58cd96cafc 100644 --- a/pages/hi_IN/docs/conventions.md +++ b/pages/hi_IN/docs/conventions.md @@ -87,7 +87,7 @@ FROM clause में SubQuery का उपयोग कैसे करें, ### NamingStrategy -GORM users को डिफॉल्ट `NamingStrategy` को ओवरराइड करके डिफॉल्ट नेमिंग कन्वेंशन को बदलने की अनुमति देता है, जिसका उपयोग `TableName`, `ColumnName`, `JoinTableName` बनाने के लिए किया जाता है। code>, `RelationshipFKName`, `CheckerName`, `IndexName`, [GORM Config](gorm_config.html#naming_strategy) देखें जानकारी के लिए +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/hi_IN/docs/create.md b/pages/hi_IN/docs/create.md index f80a342c842..feb7d70a622 100644 --- a/pages/hi_IN/docs/create.md +++ b/pages/hi_IN/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/hi_IN/docs/dbresolver.md b/pages/hi_IN/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/hi_IN/docs/dbresolver.md +++ b/pages/hi_IN/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/hi_IN/docs/error_handling.md b/pages/hi_IN/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/hi_IN/docs/error_handling.md +++ b/pages/hi_IN/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/hi_IN/docs/many_to_many.md b/pages/hi_IN/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/hi_IN/docs/many_to_many.md +++ b/pages/hi_IN/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/hi_IN/docs/method_chaining.md b/pages/hi_IN/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/hi_IN/docs/method_chaining.md +++ b/pages/hi_IN/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/hi_IN/docs/models.md b/pages/hi_IN/docs/models.md index a82cc2e3279..9e901068759 100644 --- a/pages/hi_IN/docs/models.md +++ b/pages/hi_IN/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaring Models -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/hi_IN/docs/security.md b/pages/hi_IN/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/hi_IN/docs/security.md +++ b/pages/hi_IN/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/hi_IN/docs/transactions.md b/pages/hi_IN/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/hi_IN/docs/transactions.md +++ b/pages/hi_IN/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/hi_IN/docs/write_driver.md b/pages/hi_IN/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/hi_IN/docs/write_driver.md +++ b/pages/hi_IN/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/hi_IN/docs/write_plugins.md b/pages/hi_IN/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/hi_IN/docs/write_plugins.md +++ b/pages/hi_IN/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/hi_IN/gen/associations.md b/pages/hi_IN/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/hi_IN/gen/associations.md +++ b/pages/hi_IN/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/hi_IN/gen/query.md b/pages/hi_IN/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/hi_IN/gen/query.md +++ b/pages/hi_IN/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/hi_IN/gen/sql_annotation.md b/pages/hi_IN/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/hi_IN/gen/sql_annotation.md +++ b/pages/hi_IN/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/id_ID/docs/advanced_query.md b/pages/id_ID/docs/advanced_query.md index 70a4f5857b3..4250c6948f0 100644 --- a/pages/id_ID/docs/advanced_query.md +++ b/pages/id_ID/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## Bidang Pilih Pintar -GORM mengijinkan pengambilan field secara spesifik dengan [`Select`](query.html), jika kamu sering menggunakan ini didalam aplikasi kamu, mungkin kamu hendak mendefinisikan struct yang lebih kecil untuk API yang mana dapat mengambil field secara otomatis, sebagai contoh: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -13,7 +13,7 @@ type User struct { Name string Age int Gender string - // ratusan bidang + // hundreds of fields } type APIUser struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Pilih bidang `id`, `name` secara otomatis saat proses kueri +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**CATATAN** mode `QueryFields` akan memilih berdasarkan nama semua bidang untuk model saat ini +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,237 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... // Mode Sesi +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` + +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Penguncian (FOR UPDATE) +## Locking GORM mendukung berbagai jenis penguncian, misalnya: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. + +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Rujuk ke [SQL mentahan dan pembuat SQL](sql_builder.html) untuk detail lebih lanjut +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## Sub-Kueri -Sebuah sub-kueri dapat disarangkan dalam sebuah kueri, GORM dapat menghasilkan sub-kueri saat menggunakan objek `*gorm.DB` sebagai parameternya +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### Dari Sub-Kueri -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Pengelompokan Kondisi -Lebih mudah untuk menulis kueri SQL yang rumit dengan pengelompokan kondisi +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN dengan Beberapa Kolom -Memilih IN dengan beberapa kolom +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Argumen Bernama -GORM mendukung argumen bernama dengan [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) atau `map[string]interface{}{}`, misalnya: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Lihat ke [SQL mentahan dan pembuat SQL](sql_builder.html) untuk detail lebih lanjut +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Pencarian ke Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Dapatkan catatan pertama yang cocok atau inisialisasi instance baru dengan kondisi yang diberikan (hanya berfungsi dengan kondisi struct atau map) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// Pengguna tidak ditemukan, menginisiasi dengan kondisi yang diberikan +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Pengguna ditemukan dengan `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Pengguna ditemukan dengan `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// Pengguna tidak ditemukan, menginisiasi dengan kondisi yang diberikan +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Pengguna tidak ditemukan, menginisiasi dengan kondisi yang diberikan -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// Pengguna ditemukan dengan `name` = `jinzhu`, atribut akan diabaikan +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` atribut ke struct terlepas dari ditemukan atau tidak, atribut tersebut tidak akan digunakan untuk membuat kueri SQL dan data akhir tidak akan disimpan ke dalam database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// Pengguna tidak ditemukan, menginisiasi dengan kondisi yang diberikan dan menambahkan atribut +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Pengguna ditemukan dengan `name` = `jinzhu`, memperbaruinya dengan atribut yang diberikan +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Buat struct dengan lebih banyak atribut jika catatan tidak ditemukan, `Attrs` tersebut tidak akan digunakan untuk membuat kueri SQL +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// Pengguna tidak ditemukan, membuat catatan baru dengan kondisi dan atribut yang diberikan +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Pengguna ditemukan dengan `name` = `jinzhu`, atribut akan diabaikan +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` atribut ke catatan, terlepas itu ditemukan atau tidak dan menyimpannya kembali ke database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// Pengguna tidak ditemukan, membuat catatan baru dengan kondisi dan atribut yang diberikan +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Pengguna ditemukan dengan `name` = `jinzhu`, perbarui dengan atribut yang diberikan +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Indeks Hints -Optimizer hints memungkinkan untuk mengontrol pengoptimal kueri untuk memilih rencana eksekusi kueri tertentu, GORM mendukungnya dengan `gorm.io/hints`, misalnya: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Indeks hints memungkinkan melewatkan petunjuk indeks ke database jika perencana kueri bingung. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Rujuk ke [Optimizer Hints/Indeks/Komentar](hints.html) untuk detail lebih lanjut +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Pengulangan -GORM mendukung pengulangan melalui baris +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -273,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows merupakan metode dari `gorm.DB`, dapat digunakan untuk memindai suatu baris ke sebuah struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // lakukan sesuatu + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Meng-kueri dan memproses dalam suatu batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// ukuran batch 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // pemroses batch menemukan catatan + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // jumlah catatan di batch ini + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // mengembalikan eror dan memberhentikan batch berikutnya + // Returning an error will stop further batch processing return nil }) -result.Error // mengembalikan eror -result.RowsAffected // catatan yang diproses dihitung di semua batch +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Hook Kueri -GORM memungkinkan hook `AfterFind` untuk kueri, itu akan dipanggil saat meminta catatan, lihat [Hooks](hooks.html) untuk detailnya +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Kueri satu kolom dari database dan pindai menjadi irisan, jika Anda ingin membuat kueri beberapa kolom, gunakan `Select` dengan [`Scan`](query.html#scan) sebagai gantinya +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Pluck berbeda +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Meminta lebih dari satu kolom, gunakan `Scan` atau `Find` seperti ini: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` memungkinkan Anda menentukan kueri yang umum digunakan yang dapat dirujuk sebagai pemanggilan metode +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Cari semua kartu kredit dengan jumlah lebih dari 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Cari semua pesanan COD dengan jumlah lebih dari 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Cari semua pesanan dibayar dan dikirim dengan jumlah lebih dari 1000 ``` -Rujuk ke [Scopes](scopes.html) untuk detail lebih lanjut +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Dapatkan jumlah catatan yang sesuai +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Hitung dalam kelompok +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -406,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/id_ID/docs/associations.md b/pages/id_ID/docs/associations.md index f2d0191a432..5e93609b24a 100644 --- a/pages/id_ID/docs/associations.md +++ b/pages/id_ID/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Buat/Pembaruan Otomatis -GORM akan menyimpan asosiasi dan referensinya secara otomatis menggunakan [Upsert](create.html#upsert) saat membuat/memperbarui catatan. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -Jika Anda ingin memperbarui data asosiasi, Anda harus menggunakan mode `FullSaveAssociations`: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Lewati Buat/Pembaruan Otomatis -Untuk melewati simpan otomatis saat membuat/memperbarui, Anda dapat menggunakan `Select` atau `Omit`, misalnya: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Lewati pembuatan BillingAddress saat membuat pengguna baru +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Lewati semua asosiasi saat membuat pengguna ``` {% note warn %} -**CATATAN:** Untuk asosiasi bangak-ke-banyak GORM akan melakukan upsert pada asosiasi sebelum membuat referensi tabel join, jika anda ingin melewatkan upserting dari asosiasi, anda dapat melewatinya seperti: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -Kode berikut akan melewatkan pembuatan asosiasi dan referensinya +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Pilih/Abaikan bidang Asosiasi +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Buat pengguna dan BillingAddress-nya, ShippingAddress -// Saat membuat BillingAddress hanya gunakan field address1, address2 dan hilangkan yang lain +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Hapus Asosiasi + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Mode Asosiasi -Mode Asosiasi berisi beberapa metode pembantu yang umum digunakan untuk menangani hubungan +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Mulai mode asosiasi var user User db.Model(&user).Association("Languages") -// `user` adalah model sumber, harus berisi kunci utama -// `Languages` adalah nama bidang hubungan -// Jika dua persyaratan di atas cocok, Mode Asosiasi harus dimulai dengan sukses, atau itu akan mengembalikan kesalahan -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Cari Asosiasi +### Finding Associations -Cari asosiasi yang sesuai +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Cari asosiasi dengan kondisi -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Menambahkan Asosiasi +### Appending Associations -Tambahkan asosiasi baru untuk `many to many`, `has many`, ganti asosiasi saat ini untuk `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Ubah Asosiasi +### Replacing Associations -Ganti asosiasi saat ini dengan yang baru +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Hapus Asosiasi +### Deleting Associations -Hapus hubungan antara sumber & argumen jika ada, hanya hapus referensi, tidak akan menghapus objek tersebut dari DB. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Bersihkan Asosiasi +### Clearing Associations -Hapus semua referensi antara sumber & asosiasi, tidak akan menghapus asosiasi tersebut +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Hitung Asosiasi +### Counting Associations -Kembalikan jumlah asosiasi saat ini +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Data Batch +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Mode Asosiasi mendukung data batch, mis: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Cari semua peran untuk semua pengguna db.Model(&users).Association("Role").Find(&roles) +``` -// Hapus Pengguna A dari semua tim pengguna +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Dapatkan hitungan berbeda dari semua tim pengguna +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` + +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go +var users = []User{user1, user2, user3} -// Untuk `Append`, `Replace` dengan data batch, panjang argumen harus sama dengan panjang data atau akan mengembalikan kesalahan -var pengguna = []User{user1, user2, user3} -// misalnya: kami memiliki 3 pengguna, Tambahkan penggunaA ke tim pengguna1, tambahkan penggunaB ke tim pengguna2, tambahkan penggunaA, penggunaB, dan penggunaC ke tim pengguna3 +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Setel ulang tim pengguna1 ke penggunaA,setel ulang tim pengguna2 ke penggunaB, setel ulang tim pengguna3 ke penggunaA, penggunaB, dan penggunaC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Deskripsi | -| ---------------- | ---------------------------------------------------------------------------------------------- | -| foreignKey | Menentukan nama kolom dari model saat ini yang digunakan sebagai kunci asing ke tabel gabungan | -| references | Menentukan nama kolom tabel referensi yang dipetakan ke kunci asing tabel gabungan | -| polymorphic | Menentukan tipe polimorfik seperti nama model | -| polymorphicValue | Menentukan nilai polimorfik, nama tabel default | -| many2many | Menentukan nama tabel yang digabung | -| joinForeignKey | Menentukan nama kolom kunci asing dari tabel gabungan yang memetakan ke tabel saat ini | -| joinReferences | Menentukan nama kolom kunci asing dari tabel gabungan yang memetakan ke tabel referensi | -| constraint | Batasan relasi, mis: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Deskripsi | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/id_ID/docs/context.md b/pages/id_ID/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/id_ID/docs/context.md +++ b/pages/id_ID/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/id_ID/docs/conventions.md b/pages/id_ID/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/id_ID/docs/conventions.md +++ b/pages/id_ID/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/id_ID/docs/create.md b/pages/id_ID/docs/create.md index ce5544182c5..ae06885fad6 100644 --- a/pages/id_ID/docs/create.md +++ b/pages/id_ID/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/id_ID/docs/dbresolver.md b/pages/id_ID/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/id_ID/docs/dbresolver.md +++ b/pages/id_ID/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/id_ID/docs/error_handling.md b/pages/id_ID/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/id_ID/docs/error_handling.md +++ b/pages/id_ID/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/id_ID/docs/many_to_many.md b/pages/id_ID/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/id_ID/docs/many_to_many.md +++ b/pages/id_ID/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/id_ID/docs/method_chaining.md b/pages/id_ID/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/id_ID/docs/method_chaining.md +++ b/pages/id_ID/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/id_ID/docs/models.md b/pages/id_ID/docs/models.md index f9ef05894dc..4f85c2232b2 100644 --- a/pages/id_ID/docs/models.md +++ b/pages/id_ID/docs/models.md @@ -3,46 +3,70 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Deklarasi Model -Model adalah struct biasa dengan type basic Go, pointer/aliasnya type custom yang mengimplementasikan interface [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) dan [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -Contoh: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Konvensi +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Konvensi + +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -## gorm.Model +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. -GORM mendefinisikan struct `gorm.Model`, yang mencakup *field* `ID`, `CreatedAt`, `UpdatedAt`, ``DeletedAt< /kode>

+Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). -
// definisi gorm.Model
+### `gorm.Model`
+
+GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields:
+
+```go
+// definisi gorm.Model
 type Model struct {
   ID        uint           `gorm:"primaryKey"`
   CreatedAt time.Time
   UpdatedAt time.Time
   DeletedAt gorm.DeletedAt `gorm:"index"`
 }
-``
+``` + +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) -Anda dapat menyematkannya ke dalam struct Anda untuk menyertakan *field* tersebut, lihat [Embedded Struct](#embedded_struct) +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/id_ID/docs/security.md b/pages/id_ID/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/id_ID/docs/security.md +++ b/pages/id_ID/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/id_ID/docs/transactions.md b/pages/id_ID/docs/transactions.md index 6d473883a16..41fe7dafac0 100644 --- a/pages/id_ID/docs/transactions.md +++ b/pages/id_ID/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/id_ID/docs/write_driver.md b/pages/id_ID/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/id_ID/docs/write_driver.md +++ b/pages/id_ID/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/id_ID/docs/write_plugins.md b/pages/id_ID/docs/write_plugins.md index d0fa56610b2..90c0076ba70 100644 --- a/pages/id_ID/docs/write_plugins.md +++ b/pages/id_ID/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Panggilan Balik -GORM tersendiri mendukung `Callbacks`, memiliki panggilan balik untuk `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, Anda dapat sepenuhnya menyesuaikan GORM dengan mereka seperti yang Anda inginkan +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callback terdaftar ke `*gorm.DB` global, bukan tingkat sesi, jika Anda memerlukan `*gorm.DB` dengan callback yang berbeda, Anda perlu menginisialisasi `*gorm.DB` lain +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Mendaftarkan Panggilan Balik +### Registering a Callback -Daftarkan panggilan balik ke panggilan balik +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Menhapus Panggilan Balik +### Deleting a Callback -Hapus panggilan balik dari panggilan balik +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// hapus panggilan balik `gorm: create` dari Buat panggilan balik ``` -### Mengganti Panggilan Balik +### Replacing a Callback -Ganti panggilan balik yang memiliki nama yang sama dengan yang baru +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// ganti callback `gorm:create` dengan fungsi baru `newCreateFunction` untuk proses Create ``` -### Daftarkan Panggilan Balik dengan `Orders` +### Ordering Callbacks -Daftarkan Panggilan Balik dengan `Orders` +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// sebelum gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// setelah gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// setelah gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// setelah gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// sebelum gorm: perbarui +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// sebelum gorm:buat dan setelah gorm:sebelum_buat +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// sebelum panggilan balik lainnya +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// setelah panggilan balik lainnya +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Mendefinisikan Panggilan Balik +### Predefined Callbacks -GORM telah menetapkan [beberapa panggilan balik](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) untuk mengaktifkan fitur GORM saat ini, periksa sebelum memulai plugin Anda +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM menyediakan metode `Use` untuk mendaftarkan plugin, plugin perlu mengimplementasikan antarmuka `Plugin` +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -Metode `Initialize` akan dipanggil saat mendaftarkan plugin ke GORM pertama kali, dan GORM akan menyimpan plugin yang terdaftar, mengaksesnya seperti: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Lihat [Prometheus](prometheus.html) sebagai contoh +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/id_ID/gen/associations.md b/pages/id_ID/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/id_ID/gen/associations.md +++ b/pages/id_ID/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/id_ID/gen/query.md b/pages/id_ID/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/id_ID/gen/query.md +++ b/pages/id_ID/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/id_ID/gen/sql_annotation.md b/pages/id_ID/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/id_ID/gen/sql_annotation.md +++ b/pages/id_ID/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/it_IT/contribute.md b/pages/it_IT/contribute.md index 183b67e0ca4..13d9816a4c6 100644 --- a/pages/it_IT/contribute.md +++ b/pages/it_IT/contribute.md @@ -29,7 +29,7 @@ Per trafurre GORM nella tua lingua devi fare richiesta nelle [Issues su Github]( ## Donazioni -Your kindness and generosity is greatly appreciated, many thanks to all our sponsors! +La vostra gentilezza e la vostra generosità sono molto apprezzate, grazie mille a tutti i nostri sponsor! * Github Sponsors (Platinum, Gold Sponsors etc) diff --git a/pages/it_IT/docs/advanced_query.md b/pages/it_IT/docs/advanced_query.md index 26f83b434f0..e56e2be649d 100644 --- a/pages/it_IT/docs/advanced_query.md +++ b/pages/it_IT/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: pagina ## Facile Selezione dei Campi -GORM allows selecting specific fields with [`Select`](query.html), if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Select `id`, `name` automatically when querying +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOTE** `QueryFields` mode will select by all fields' name for current model +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (FOR UPDATE) +## Locking GORM supports different types of locks, for example: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Refer [Raw SQL and SQL Builder](sql_builder.html) for more detail +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery -A subquery can be nested within a query, GORM can generate subquery when using a `*gorm.DB` object as param +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group Conditions -Easier to write complicated SQL query with Group Conditions +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN with multiple columns -Selecting IN with multiple columns +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Named Argument -GORM supports named arguments with [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, for example: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Check out [Raw SQL and SQL Builder](sql_builder.html#named_argument) for more detail +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM supports iterating through Rows +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` allows you to specify commonly-used queries which can be referenced as method calls +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Get matched records count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/it_IT/docs/associations.md b/pages/it_IT/docs/associations.md index 42e5a8e6131..ee30caf054f 100644 --- a/pages/it_IT/docs/associations.md +++ b/pages/it_IT/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Auto Create/Update -GORM will auto-save associations and its reference using [Upsert](create.html#upsert) when creating/updating a record. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -If you want to update associations's data, you should use the `FullSaveAssociations` mode: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Skip Auto Create/Update -To skip the auto save when creating/updating, you can use `Select` or `Omit`, for example: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE:** For many2many associations, GORM will upsert the associations before creating the join table references, if you want to skip the upserting of associations, you could skip it like: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -The following code will skip the creation of the association and its references +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Association fields +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Delete Associations + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -Association Mode contains some commonly used helper methods to handle relationships +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Find Associations +### Finding Associations -Find matched associations +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Find associations with conditions -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Append Associations +### Appending Associations -Append new associations for `many to many`, `has many`, replace current association for `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Replace Associations +### Replacing Associations -Replace current associations with new ones +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Delete Associations +### Deleting Associations -Remove the relationship between source & arguments if exists, only delete the reference, won't delete those objects from DB. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations +### Clearing Associations -Remove all reference between source & association, won't delete those associations +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Count Associations +### Counting Associations -Return the count of current associations +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Association Mode supports batch data, e.g: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/it_IT/docs/context.md b/pages/it_IT/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/it_IT/docs/context.md +++ b/pages/it_IT/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/it_IT/docs/conventions.md b/pages/it_IT/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/it_IT/docs/conventions.md +++ b/pages/it_IT/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/it_IT/docs/create.md b/pages/it_IT/docs/create.md index ce5544182c5..ae06885fad6 100644 --- a/pages/it_IT/docs/create.md +++ b/pages/it_IT/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/it_IT/docs/dbresolver.md b/pages/it_IT/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/it_IT/docs/dbresolver.md +++ b/pages/it_IT/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/it_IT/docs/error_handling.md b/pages/it_IT/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/it_IT/docs/error_handling.md +++ b/pages/it_IT/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/it_IT/docs/many_to_many.md b/pages/it_IT/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/it_IT/docs/many_to_many.md +++ b/pages/it_IT/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/it_IT/docs/method_chaining.md b/pages/it_IT/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/it_IT/docs/method_chaining.md +++ b/pages/it_IT/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/it_IT/docs/models.md b/pages/it_IT/docs/models.md index a82cc2e3279..9e901068759 100644 --- a/pages/it_IT/docs/models.md +++ b/pages/it_IT/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaring Models -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/it_IT/docs/security.md b/pages/it_IT/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/it_IT/docs/security.md +++ b/pages/it_IT/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/it_IT/docs/transactions.md b/pages/it_IT/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/it_IT/docs/transactions.md +++ b/pages/it_IT/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/it_IT/docs/write_driver.md b/pages/it_IT/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/it_IT/docs/write_driver.md +++ b/pages/it_IT/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/it_IT/docs/write_plugins.md b/pages/it_IT/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/it_IT/docs/write_plugins.md +++ b/pages/it_IT/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/it_IT/gen/associations.md b/pages/it_IT/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/it_IT/gen/associations.md +++ b/pages/it_IT/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/it_IT/gen/query.md b/pages/it_IT/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/it_IT/gen/query.md +++ b/pages/it_IT/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/it_IT/gen/sql_annotation.md b/pages/it_IT/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/it_IT/gen/sql_annotation.md +++ b/pages/it_IT/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/it_IT/index.md b/pages/it_IT/index.md index 8fe0d9d5ddf..4d3635369cc 100644 --- a/pages/it_IT/index.md +++ b/pages/it_IT/index.md @@ -1,14 +1,14 @@ --- layout: index -description: The fantastic ORM library for Golang aims to be developer friendly. -subtitle: The fantastic ORM library for Golang +description: La fantastica libreria ORM per Golang che vuole amichevole per gli sviluppatori. +subtitle: La fantastica libreria ORM per Golang comments: false --- -* Full-Featured ORM -* Associations (has one, has many, belongs to, many to many, polymorphism, single-table inheritance) +* ORM completo +* Associazioni (has one, has many, belongs to, many to many, polymorphism, single-table inheritance) * Hooks (before/after create/save/update/delete/find) -* Eager loading with Preload, Joins +* Eager loading con Preload, Joins * Transactions, Nested Transactions, Save Point, RollbackTo to Saved Point * Context, Prepared Statement Mode, DryRun Mode * Batch Insert, FindInBatches, Find/Create with Map, CRUD with SQL Expr and Context Valuer @@ -16,6 +16,6 @@ comments: false * Composite Primary Key, Indexes, Constraints * Migrazioni Automatiche * Logger -* Extendable, flexible plugin API: Database Resolver (multiple databases, read/write splitting) / Prometheus... -* Every feature comes with tests +* Estendibile, plugin API flessibili: Database Resolver (database multipli, read/write splitting) / Prometheus... +* Ogni funzionalità è accompagnata da test * Developer Friendly diff --git a/pages/ja_JP/docs/advanced_query.md b/pages/ja_JP/docs/advanced_query.md index 76d6070e4eb..24ccd06e99e 100644 --- a/pages/ja_JP/docs/advanced_query.md +++ b/pages/ja_JP/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## 便利なフィールドの選択 -GORMでは [`Select`](query.html) で選択するフィールド指定することができます。アプリケーションでこれを頻繁に使用する場合は、特定のフィールドを自動的に選択できる、用途に適した構造体を定義するとよいでしょう。例: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Select `id`, `name` automatically when querying +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**注意** `QueryFields` モードを有効にすると、モデルのすべてのフィールド名を選択するようになります。 +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## ロック (FOR UPDATE) +## Locking GORMは数種類のロック処理をサポートしています。例: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -詳細については、[Raw SQL and SQL Builder](sql_builder.html)を参照してください。 +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## サブクエリ -クエリ内にサブクエリをネストすることができます。GORMは、パラメータとして `*gorm.DB` オブジェクトを使用するとサブクエリを生成できます。 +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From句でのサブクエリ -GORMでは、`Table`を用いることで、FROM句でサブクエリを使用することができます。例: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## 条件をグループ化する -グループ条件で複雑な SQL クエリを簡単に記述できます。 +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## 複数カラムでのIN -IN句で複数カラムを指定してレコードを取得することができます。 +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## 名前付き引数 -GORMは[`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg)や`map[string]interface{}{}`を使用した名前付き引数をサポートしています 。例: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -より詳細については、 [Raw SQL and SQL Builder](sql_builder.html#named_argument) も参照してみてください。 +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## 取得結果をマップに代入 -GORMでは取得結果を `map[string]interface{}`や `[]map[string]interface{}` に代入することができます。その際 `Model` や `Table` の指定を忘れないでください。例: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -条件に最初に一致するレコードを取得するか、指定された条件を使用して構造体のインスタンスを初期化します (構造体、map条件でのみ動作します)。 +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// ユーザを取得できないため、与えられた条件でユーザを初期化 +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// `name` = `jinzhu` の条件でユーザを取得できた +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// `name` = `jinzhu` の条件でユーザを取得できた +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -レコードが見つからない場合のみ、struct を指定した属性で初期化できます。これらの `Attrs(属性)` はSQLクエリの生成には使用されません。 +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// Userが見つからないため、取得条件とAttrsで指定された属性で構造体を初期化 +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// Userが見つからないため、取得条件とAttrsで指定された属性で構造体を初期化 -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// `name` = `jinzhu` のUserが見つかったため、Attrsで指定された属性は無視される +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` メソッドを使用すると、レコードが見つかったかどうかにかかわらず、指定した値を構造体に割り当てます。このメソッドで指定した属性はレコード取得時のSQLクエリには使用されません。また、生成されたデータのデータベースへの登録も行われません。 +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// Userが見つからないため、取得条件とAssignで指定された属性で構造体を初期化 +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// `name` = `jinzhu` のUserが見つかったため、取得レコードの値とAssignで指定された値で構造体を生成 +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -最初に一致するレコードを取得するか、または指定された条件で新しいレコードを作成します (構造体、マップ条件のみ使用可能です)。 `RowsAffected` は作成/更新されたレコード数を返します。 +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -`Attrs` メソッドを使用すると、レコードが見つからない場合に作成されるデータの属性をより多く指定することができます。このメソッドで指定した属性はレコード取得時のSQLクエリには使用されません。 +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// Userが見つからないため、取得条件とAttrsで指定された属性でレコードを作成 +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// `name` = `jinzhu`のユーザが見つかったため、Attrsで指定された属性は無視される +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` メソッドを使用すると、レコードが見つかったどうかにかかわらず、指定した値をデータベースに登録します。 +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// Userが見つからないため、取得条件とAssignで指定された属性でレコードを作成 +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// `name` = `jinzhu`のUserが見つかったため、Assignの値で取得したレコードを更新する +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -オプティマイザヒントを利用することで、特定のクエリ実行計画を選択するようオプティマイザを制御することができます。GORMでは、 `gorm.io/hints` でそれをサポートしています。例: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -クエリプランナーが最適なクエリを計画できていない場合、データベースにインデックスヒントを渡すことができます。 +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -詳細については、 [Optimizer Hints/Index/Comment](hints.html) を参照してください。 +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORMは行ごとのイテレーション処理をサポートしています。 +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -バッチ処理におけるクエリやレコード処理を行うことができます。 +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORMではレコード取得のフック処理に `AfterFind` を利用することができます。このメソッドはレコードを取得時に呼び出されます。 詳細は [Hooks](hooks.html) を参照してください。 +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -1つのカラムからのみ値を取得してsliceに代入することができます。複数のカラムから値を取得する場合は、 `Select` と [`Scan`](query.html#scan) を代わりに利用する必要があります。 +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// 2つ以上のカラムを指定する場合は、以下のように `Scan` もしくは `Find` を利用します +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -共通で使用されるクエリ処理は関数化することができます。`Scopes` を使用すると、関数化されたクエリ処理を指定することができます。 +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -詳細については [Scopes](scopes.html) を確認してください。 +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -条件に一致したレコード数を取得することができます。 +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/ja_JP/docs/associations.md b/pages/ja_JP/docs/associations.md index 9ab1a933306..77a380e7ed9 100644 --- a/pages/ja_JP/docs/associations.md +++ b/pages/ja_JP/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## アソシエーションの自動作成/更新 -GORMはレコードの作成・更新時に[Upsert](create.html#upsert)を使用して自動的に関連データとその参照を保存します。 +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -すでに存在するアソシエーションレコードを更新したい場合は、 `FullSaveAssociations` を使用してください。 +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## アソシエーションの自動作成/更新をスキップ -作成/更新時のアソシエーションレコードの自動保存をスキップするには、 `Select` または `Omit` を使用します。例: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. + +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// ユーザ作成時に BillingAddress の作成をスキップする +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// ユーザ作成時に全てのアソシエーションの保存をスキップする ``` {% note warn %} -**注意:** many2many(多対多)のアソシエーションの場合、中間テーブルのレコードを作成するより前に、アソシエーション先テーブルのレコードをupsertします。アソシエーション先へのupsertを省略したい場合は、以下のように記載します。 +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -次のコードはアソシエーション先のレコードの作成と中間テーブルでの参照の作成の両方をスキップします。 +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## アソシエーションフィールドの選択/省略 +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## 関連を削除する + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -Association Mode には、データの関連を処理するためによく使用されるヘルパーメソッドが含まれています。 +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` は大元のモデルであるため、主キーを含んでいる必要があります -// `Languages` は関連フィールドの名前です -// 上記2つの条件が揃うと、 AssociationMode は正常に開始されます。条件が揃っていない場合はエラーが返却されます。 -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### 関連の取得 +### Finding Associations -関連レコードを取得することができます。 +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` -条件を指定して関連を取得することも可能です。 - -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### 関連の追加 +### Appending Associations -`many to many` や `has many` の場合には関連を追加し、 `has one` や `belongs to` の場合には現在の関連を置き換えます。 +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,131 +201,121 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### 関連を置き換える +### Replacing Associations -現在の関連を新しいもので置き換えることができます。 +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### 関連を削除する +### Deleting Associations -引数で指定された値との関連がある場合、その値との関連を削除します。削除されるのは参照のみであり、参照先オブジェクトのレコードはDBから削除されません。 +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### 関連を全て削除する +### Clearing Associations -関連データとの参照を全て削除することができます。削除されるのは参照のみであり、参照先のレコードは削除されません。 +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### 関連の数を取得する +### Counting Associations -現在の関連の数を取得することができます。 +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() -// 条件付きで件数を数える +// Count with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### 一括データ処理 +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Association Mode はデータの一括処理もサポートしています。例: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// 全てのユーザの役割を全て取得する db.Model(&users).Association("Role").Find(&roles) +``` + +- **Deleting Associations**: Remove specific associations across multiple records. -// 全ユーザのチームからユーザAを削除する +```go db.Model(&users).Association("Team").Delete(&userA) +``` + +- **Counting Associations**: Get the count of associations for a batch of records. -// 重複を取り除いた全ユーザのチームの件数を取得する +```go db.Model(&users).Association("Team").Count() +``` + +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. -// 一括処理で `Append` や `Replace` を使用する場合は、それらの関数の引数とデータの数(以下でいう users の数)が一致している必要があります。 -// 一致していない場合はエラーが返却されます。 +```go var users = []User{user1, user2, user3} -// 例1: 3人のユーザABCを新たに追加するとした場合、以下のコードでは user1のチームにユーザA、user2のチームにユーザB、user3のチームにユーザABCを全員追加します +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// 例2: user1のチームをユーザAのみに、user2のチームをユーザBのみに、user3のチームをユーザABCのみにそれぞれリセットします +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## 関連レコードを削除する -デフォルトでは、`gorm.Association`の`Replace`/`Delete`/`Clear`は参照のみを削除します。つまり、古いアソシエーションの外部キーをNULLに設定します。 - -`Unscoped`を使用することで、オブジェクトを削除することができます。(`ManyToMany`の場合は挙動に変更はありません) - -削除方法は、 `gorm.DB` の内部で判定します。 +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +### Modifying Deletion Behavior with `Unscoped` -## Selectを使って削除する +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -レコード削除時に `Select` を使用することで、has one / has many / many2many 関係にある関連も同時に削除することができます。例: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// ユーザ削除時に ユーザのアカウントも削除します -db.Select("Account").Delete(&user) - -// ユーザ削除時に ユーザの注文とクレジットカードの関連レコードも削除します -db.Select("Orders", "CreditCards").Delete(&user) - -// ユーザ削除時に ユーザの全ての has one / has many / many2many の関連レコードも削除します -db.Select(clause.Associations).Delete(&user) - -// 複数ユーザ削除時に それぞれのユーザのアカウントも削除します -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**注意:** レコード削除時の主キーが非ゼロ値の場合のみ、関連レコードの削除が可能となります。GORMは指定の関連を削除するための条件として主キーを使用するためです。 +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// 名前が `jinzhu` である全てのユーザは削除されますが、ユーザのアカウントは削除されません - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// 名前が `jinzhu` で id が `1` のユーザが削除され、そのユーザのアカウントも削除されます - -db.Select("Account").Delete(&User{ID: 1}) -// id が `1` のユーザが削除され、そのユーザのアカウントも削除されます +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| タグ | 説明 | -| ---------------- | ------------------------------------------------- | -| foreignKey | テーブル結合時に、これを指定したモデルの外部キーとして使用するフィールド名を指定できます | -| references | テーブル結合時に、参照先テーブルの外部キーとして使用するフィールド名を指定できます | -| polymorphic | モデル名などのポリモーフィック関連の種別を指定できます | -| polymorphicValue | ポリモーフィック関連ので使用される値を指定できます。デフォルトはテーブル名です。 | -| many2many | 結合テーブル名を指摘できます | -| joinForeignKey | テーブル結合時に、これを指定したモデルの外部キーとして使用する結合テーブルのカラム名を指定できます | -| joinReferences | テーブル結合時に、参照先テーブルの外部キーとして使用する結合テーブルのカラム名を指定できます | -| constraint | 参照制約を指定できます。例:`OnUpdate`, `OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| タグ | 説明 | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/ja_JP/docs/context.md b/pages/ja_JP/docs/context.md index b765707f7c1..a97c2339812 100644 --- a/pages/ja_JP/docs/context.md +++ b/pages/ja_JP/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORMはContextをサポートしています。メソッド `WithContext`で使用できます。 +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## シングルセッション +### シングルセッション -シングルセッションは通常、単一の操作を実行するときに使用されます。 +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## 継続セッション +### Continuous Session Mode -継続セッションは、通常、一連の操作を実行するときに使用されます。例: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -timeoutを設定したcontextを `db.WithContext` に渡すことで、時間がかかるクエリのタイムアウト時間を設定する事ができます。例: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Hooks/Callbacks内でのcontextの使用 +### Hooks/Callbacks内でのcontextの使用 -`Statement` から `Context` にアクセスすることが可能です。例: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middlewareの例 +### Integration with Chi Middleware -継続セッションはAPIリクエストの処理に役立ちます。例えば、ミドルウェア内でのタイムアウト設定をしたContextを使って、`*gorm.DB`を設定できます。 そして、その `*gorm.DB` を使ってリクエストの処理を行います。 - -Chi ミドルウェアの例を以下に示します。 +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**注** `WithContext`で`Context`を設定するのはゴルーチンセーフの処理です。 詳細は[Session](session.html)を参照してください。 -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Loggerも `Context` もに対応しており、ログのトラッキングに使用することができます。詳細については [Logger](logger.html) を参照してください。 +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/ja_JP/docs/conventions.md b/pages/ja_JP/docs/conventions.md index 4785a1d8ed5..1fe665e7d6a 100644 --- a/pages/ja_JP/docs/conventions.md +++ b/pages/ja_JP/docs/conventions.md @@ -86,7 +86,7 @@ FROM句でサブクエリを使用する方法については、 [From SubQuery] ### NamingStrategy -`TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`の生成に使用されている`NamingStrategy`をオーバーライドすることで、デフォルトの命名規則を変更できます。詳細は[GORM Config](gorm_config.html#naming_strategy)を参照してください。 +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## カラム名 diff --git a/pages/ja_JP/docs/create.md b/pages/ja_JP/docs/create.md index 65903afff8f..06cc477ec0a 100644 --- a/pages/ja_JP/docs/create.md +++ b/pages/ja_JP/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## 一括作成 -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/ja_JP/docs/dbresolver.md b/pages/ja_JP/docs/dbresolver.md index 704a7ccd978..7bbc560f14e 100644 --- a/pages/ja_JP/docs/dbresolver.md +++ b/pages/ja_JP/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## 負荷分散 diff --git a/pages/ja_JP/docs/error_handling.md b/pages/ja_JP/docs/error_handling.md index 39e898d2c77..18efe8e6c50 100644 --- a/pages/ja_JP/docs/error_handling.md +++ b/pages/ja_JP/docs/error_handling.md @@ -3,47 +3,101 @@ title: エラーハンドリング layout: page --- -Goでは、エラー処理が重要です。 +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -[Finisher Methods](method_chaining.html#finisher_method) の後にエラーチェックを行うことをおすすめします。 +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -GORMでのエラーハンドリングは、メソッドチェーン可能なAPIのため、ふつうのGoコードとは異なります。 - -なんらかのエラーが発生した場合、GORMは `*gorm.DB`の `Error` フィールドに設定します。以下のようにチェックする必要があります: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // ここでエラーハンドリング + // Handle error... } ``` -または +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // ここでエラーハンドリング + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORMは、`First`, `Last`, `Take`でデータの検索に失敗した場合に`ErrRecordNotFound`を返します。もし複数のエラーが発生した場合は、`errors.Is`で`ErrRecordNotFound`エラーを確認することができます。たとえば以下のように使います +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// RecordNotFound エラーが返却されたかチェックする err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/ja_JP/docs/many_to_many.md b/pages/ja_JP/docs/many_to_many.md index afe4ebaf39e..d94ba70d46b 100644 --- a/pages/ja_JP/docs/many_to_many.md +++ b/pages/ja_JP/docs/many_to_many.md @@ -137,7 +137,7 @@ many2many リレーションを使った処理の詳細については [Associat type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/ja_JP/docs/method_chaining.md b/pages/ja_JP/docs/method_chaining.md index f08d1e93b82..21db56acd3f 100644 --- a/pages/ja_JP/docs/method_chaining.md +++ b/pages/ja_JP/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORMはメソッドチェーンが可能なため、次のようなコードを書くことができます。 +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -GORMには `Chain Method`, `Finisher Method`, `New Session Method` という3種類のメソッドがあります: +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/ja_JP/docs/models.md b/pages/ja_JP/docs/models.md index 9a6771d41f0..fd34e0abac6 100644 --- a/pages/ja_JP/docs/models.md +++ b/pages/ja_JP/docs/models.md @@ -3,35 +3,52 @@ title: モデルを宣言する layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## モデルを宣言する -モデルは Goの基本型、(基本型の)ポインタ/エイリアス、 [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) および [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) インターフェイスを実装するカスタム型からなる通常の構造体です。 +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -例: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## 規約 +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### 規約 -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORMは `gorm.Model` 構造体を定義しています。これには `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` のフィールドが含まれます。 +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Modelの定義 @@ -43,7 +60,13 @@ type Model struct { } ``` -この構造体を埋め込むことで、これらのフィールドを自身の構造体に含めることができます。 [Embedded Struct](#embedded_struct) も参照してください。 +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## 高度な機能 diff --git a/pages/ja_JP/docs/security.md b/pages/ja_JP/docs/security.md index e4eb20ed4a1..f6f90bfd183 100644 --- a/pages/ja_JP/docs/security.md +++ b/pages/ja_JP/docs/security.md @@ -38,9 +38,9 @@ db.First(&user, fmt.Sprintf("name = %v", userInput)) ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/ja_JP/docs/transactions.md b/pages/ja_JP/docs/transactions.md index 860f5cae055..cd8f3660410 100644 --- a/pages/ja_JP/docs/transactions.md +++ b/pages/ja_JP/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/ja_JP/docs/write_driver.md b/pages/ja_JP/docs/write_driver.md index 6743ccbb651..9ac1d3f8952 100644 --- a/pages/ja_JP/docs/write_driver.md +++ b/pages/ja_JP/docs/write_driver.md @@ -3,25 +3,101 @@ title: ドライバの作成 layout: page --- -## 新しいドライバを作成する +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORMは `sqlite`, `mysql`, `postgres`, `sqlserver` を公式にサポートしています。 +## Compatibility with MySQL or Postgres Dialects -いくつかのデータベースは `mysql` または `postgres` と互換性があります。 互換性がある場合は、それらのデータベース固有の文法を使用することができます。 +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -上記以外のデータベース使用する場合、新しいドライバを作成することができます。ドライバの作成には [Dialector](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector) インターフェイスを実装する必要があります。 +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -例として [MySQL Driver](https://github.com/go-gorm/mysql) を確認してみると良いでしょう。 +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/ja_JP/docs/write_plugins.md b/pages/ja_JP/docs/write_plugins.md index 8ee4240be15..cc92207d9df 100644 --- a/pages/ja_JP/docs/write_plugins.md +++ b/pages/ja_JP/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM内部では、 `Callbacks` の技術が活かされています。GORMには `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw` 処理のcallbackが用意されています。これらのcallbackを使うことでGORMを自由にカスタマイズすることができます。 +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks はグローバルな `*gorm.DB` に登録されます(セッションレベルではありません)。そのため、別のcallbackが登録された `*gorm.DB` が必要な場合は、新規の `*gorm.DB` を用意する必要があります。 +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Callbackを登録する +### Registering a Callback -独自のcallbackを登録できます +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Callbackを削除する +### Deleting a Callback -指定したcallbackを削除することができます +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Callbackを置き換える +### Replacing a Callback -同じ名称を指定することでcallbackを置き換えることができます +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### 実行順序を指定してCallbackを登録する +### Ordering Callbacks -実行順序を指定してCallbackを登録することができます。 +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### 定義済みのCallbacks +### Predefined Callbacks -GORMをより高機能にするために、 GORMにはすでに [定義されているCallbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) があります。プラグインを作成する前にそれらをチェックしてみるとよいでしょう。 +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## プラグイン +## Plugins -GORMにはプラグインを登録するための `Use` メソッドがあります。プラグインは `Plugin` インターフェイスを実装している必要があります。 +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -GORMにプラグインを登録すると `Initialize` メソッドが実行されます。 GORMは登録されたプラグインを保存しているため、以下のようにアクセスすることができます: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -プラグインの例として [Prometheus](prometheus.html) を参照するとよいでしょう。 +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/ja_JP/gen/associations.md b/pages/ja_JP/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/ja_JP/gen/associations.md +++ b/pages/ja_JP/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/ja_JP/gen/query.md b/pages/ja_JP/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/ja_JP/gen/query.md +++ b/pages/ja_JP/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/ja_JP/gen/sql_annotation.md b/pages/ja_JP/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/ja_JP/gen/sql_annotation.md +++ b/pages/ja_JP/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/ko_KR/docs/advanced_query.md b/pages/ko_KR/docs/advanced_query.md index cc442d000c8..c09205e20a2 100644 --- a/pages/ko_KR/docs/advanced_query.md +++ b/pages/ko_KR/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## Smart Select Fields -GORM allows selecting specific fields with [`Select`](query.html), if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Select `id`, `name` automatically when querying +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOTE** `QueryFields` 모드는 현재 모델에 대한 모든 필드 이름으로 선택됩니다. +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (FOR UPDATE) +## Locking GORM 은 다른 종류의 lock을 지원합니다. ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -자세한 내용은 [Raw SQL and SQL Builder](sql_builder.html) 을 참고하세요. +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery -서브쿼리는 쿼리 내에 중첩될 수 있으며, GORM은 `*gorm.DB` 개체를 매개 변수로 사용할 때 하위 쿼리를 생성할 수 있습니다. +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group Conditions (그룹화 조건) -그룹 조건을 사용하여 복잡한 SQL 쿼리를 더 쉽게 작성할 수 있습니다. +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN with multiple columns -Selecting IN with multiple columns +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Named Argument -GORM supports named arguments with [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, for example: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Check out [Raw SQL and SQL Builder](sql_builder.html#named_argument) for more detail +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM supports iterating through Rows +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` allows you to specify commonly-used queries which can be referenced as method calls +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Get matched records count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/ko_KR/docs/associations.md b/pages/ko_KR/docs/associations.md index 3045078295e..d5a5d1c55de 100644 --- a/pages/ko_KR/docs/associations.md +++ b/pages/ko_KR/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## 자동 생성/갱신 -GORM은 레코드를 생성/갱신할 때 [Upsert(업서트)](create.html#upsert)를 사용하여 관계와 참조를 자동으로 저장합니다. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -어소시에이션 데이터를 갱신하려면 `FullSaveAssociations` 모드를 사용해야 합니다. +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## 자동 생성/갱신 건너뛰기 -생성/갱신 시 자동 저장을 건너뛰려면 다음과 같이 `Select` 또는 `Omit`를 사용할 수 있습니다. +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE:** many2many 어소시에이션의 경우 GORM이 조인 테이블 레퍼런스를 생성하기 전에 어소시에이션을 업서트합니다. 어소시에이션 업서트를 건너뛰려면 다음과 같이 할 수 있습니다. +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -다음 코드는 어소시에이션과 레퍼런스 생성을 건너뜁니다. +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Association fields +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,52 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Delete Associations + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -어소시에이션 모드는 릴레이션십(relationship)을 처리하기 위해 일반적으로 사용되는 몇 가지 헬퍼 메소드가 있습니다. +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `user` 는 기초가 되는 모델이며, primary key 를 반드시 포함해야 한다. -// `Languages` is a relationship's field name -// `Languages` 는 name 필드로 릴레이션 되었다. -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -// 위의 두 요구 사항이 일치하는 경우 AssociationMode를 성공적으로 시작하거나 오류를 반환해야 합니다. -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### 연관된 결과(Find Association) +### Finding Associations -일치하는 연관된 결과 찾기 +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -조건을 통한 연관된 결과 찾기 -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Append Associations +### Appending Associations -`many to many`, `has many` 로 새로운 associations 를 추가 하고, `has one`, `belongs to` 로 현재 association을 교체 합니다. +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -150,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Replace Associations +### Replacing Associations -현재 associations 을 새로운 것으로 변경합니다. +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Delete Associations +### Deleting Associations -원본과 인수 간의 관계가 존재 한다면 제거하고, 참조만 삭제하며, DB에서 해당 개체를 삭제하지 않습니다. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations +### Clearing Associations -원본과 association 간의 모든 참조를 제거하고, association 은 제거 하지 않습니다. +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Count Associations +### Counting Associations -현재 연관된 수 를 반환 합니다. +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -189,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Association Mode 는 일괄 데이터를 제공 합니다. +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/ko_KR/docs/context.md b/pages/ko_KR/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/ko_KR/docs/context.md +++ b/pages/ko_KR/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/ko_KR/docs/conventions.md b/pages/ko_KR/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/ko_KR/docs/conventions.md +++ b/pages/ko_KR/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/ko_KR/docs/create.md b/pages/ko_KR/docs/create.md index 1a8d719b861..45cd2affcbe 100644 --- a/pages/ko_KR/docs/create.md +++ b/pages/ko_KR/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/ko_KR/docs/dbresolver.md b/pages/ko_KR/docs/dbresolver.md index ed5bfe828e9..8310c90e3d0 100644 --- a/pages/ko_KR/docs/dbresolver.md +++ b/pages/ko_KR/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/ko_KR/docs/error_handling.md b/pages/ko_KR/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/ko_KR/docs/error_handling.md +++ b/pages/ko_KR/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/ko_KR/docs/gorm_config.md b/pages/ko_KR/docs/gorm_config.md index c8f72238366..26fd9bcda5b 100644 --- a/pages/ko_KR/docs/gorm_config.md +++ b/pages/ko_KR/docs/gorm_config.md @@ -51,10 +51,10 @@ type Namer interface { ```go db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ NamingStrategy: schema.NamingStrategy{ - TablePrefix: "t_", // table name prefix, table for `User` would be `t_users` - SingularTable: true, // use singular table name, table for `User` would be `user` with this option enabled - NoLowerCase: true, // skip the snake_casing of names - NameReplacer: strings.NewReplacer("CID", "Cid"), // use name replacer to change struct/field name before convert it to db name + TablePrefix: "t_", // 테이블명의 접두사를 지정합니다. 예시로, User를 테이블명으로 변환할 때 t_users로 변환합니다. + SingularTable: true, // 단수형 테이블명을 사용합니다. 기본적으로 GORM은 복수형 테이블명 규칙이 적용되는데 true로 설정하면 구조체 이름 그대로 테이블명을 생성합니다. + NoLowerCase: true, // 소문자와 언더스코어를 사용한 스네이크 표기를 사용하지 않고, 구조체의 필드명을 그대로 사용합니다. + NameReplacer: strings.NewReplacer("CID", "Cid"), // 구조체의 필드 이름을 DB에 넣기 전에 변환하여 테이블/열 이름으로 매핑합니다. 예시로 문자열 CID를 Cid로 변경하여 넣는다는 예시입니다. }, }) ``` @@ -97,7 +97,7 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ ## DisableNestedTransaction -When using `Transaction` method inside a db transaction, GORM will use `SavePoint(savedPointName)`, `RollbackTo(savedPointName)` to give you the nested transaction support, you could disable it by using the `DisableNestedTransaction` option, refer [Session](session.html) for details +Db 트랜잭션 내부에서 또 다른 트랜잭션을 시작할 때, GORM은 중첩 트랜잭션을 지원하기 위해서 SavePoint와 RollbackTo 메서드를 사용합니다. DisableNestedTransaction 옵션을 이용하여 비활성화할 수 있으며, 자세한 내용은 Session을 참고하세요. ## AllowGlobalUpdate @@ -106,7 +106,7 @@ Enable global update/delete, refer [Session](session.html) for details ## DisableAutomaticPing -GORM automatically ping database after initialized to check database availability, disable it by setting it to `true` +GORM은 초기화 후에 데이터베이스와의 연결 상태를 확인하기 위해 기본적으로 Ping을 요청합니다. 그러나 자주 변경하지 않아 기본적인 Ping 요청을 제거하거나, 자원 소모나 부하를 줄이기 위해 이 기능을 비활성화할 수 있습니다. 또한 개발자가 직접 데이터베이스 연결 상태를 통제하고자 할 때, 필요한 시점에만 Ping을 보내도록 이 옵션을 활용할 수 있습니다. ```go db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ diff --git a/pages/ko_KR/docs/many_to_many.md b/pages/ko_KR/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/ko_KR/docs/many_to_many.md +++ b/pages/ko_KR/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/ko_KR/docs/method_chaining.md b/pages/ko_KR/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/ko_KR/docs/method_chaining.md +++ b/pages/ko_KR/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/ko_KR/docs/models.md b/pages/ko_KR/docs/models.md index 207fa41289e..f1683548aa0 100644 --- a/pages/ko_KR/docs/models.md +++ b/pages/ko_KR/docs/models.md @@ -3,35 +3,52 @@ title: 모델 선언 layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## 모델 선언 -모델은 기본 Go 유형, 포인터/별칭 또는 [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner)및 [Valuer](https://pkg.go.dev/database/sql/driver#Valuer)인터페이스를 구현하는 사용자 정의 유형이 있는 일반 구조체입니다. +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -예를 들면 다음과 같습니다: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## 규칙 +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### 규칙 -GORM은 구성보다 규칙을 선호합니다. 기본적으로 GORM은 `ID`를 기본 키로 사용하고, 복수형 `snake_cases`를 구조체 이름을 테이블 이름으로, `snake_case`를 열 이름으로, 생성/업데이트 시간을 추적하기 위해 `CreatedAt`, `UpdatedAt`을 사용합니다. +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -GORM에서 채택한 규칙을 따르는 경우 구성/코드를 거의 작성하지 않아도 됩니다. 규칙이 요구 사항과 일치하지 않는 경우 [GORM을 사용하여 규칙을 구성할 수 있습니다.](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM은 `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` 필드를 포함하는 `gorm.Model` 구조체를 정의합니다. +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -해당 필드를 포함하도록 구조체를 만들 할 수 있습니다. [Embedded Struct](#embedded_struct)를 참조하세요. +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## 고급 diff --git a/pages/ko_KR/docs/security.md b/pages/ko_KR/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/ko_KR/docs/security.md +++ b/pages/ko_KR/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/ko_KR/docs/transactions.md b/pages/ko_KR/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/ko_KR/docs/transactions.md +++ b/pages/ko_KR/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/ko_KR/docs/write_driver.md b/pages/ko_KR/docs/write_driver.md index 36f8de22e2b..a5599c5dd4c 100644 --- a/pages/ko_KR/docs/write_driver.md +++ b/pages/ko_KR/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## 새로운 드라이버 작성 +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM은 `sqlite`, `mysql`, `postgres`, `sqlserver`를 공식 지원합니다. +## Compatibility with MySQL or Postgres Dialects -어떤 database들은 `mysql` 또는 `postgres` 방언dialect을 그냥 사용하면 될 정도의 호환성을 가지고 있기도 합니다. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -그 이외에는, [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector)의 구현체를 만들어, 새로운 드라이버를 만들 수 있습니다. +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -[MySQL Driver](https://github.com/go-gorm/mysql) 예제를 참고하세요. +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/ko_KR/docs/write_plugins.md b/pages/ko_KR/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/ko_KR/docs/write_plugins.md +++ b/pages/ko_KR/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/ko_KR/gen/associations.md b/pages/ko_KR/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/ko_KR/gen/associations.md +++ b/pages/ko_KR/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/ko_KR/gen/query.md b/pages/ko_KR/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/ko_KR/gen/query.md +++ b/pages/ko_KR/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/ko_KR/gen/sql_annotation.md b/pages/ko_KR/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/ko_KR/gen/sql_annotation.md +++ b/pages/ko_KR/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/pl_PL/docs/advanced_query.md b/pages/pl_PL/docs/advanced_query.md index 73f736629c2..65fadfdda23 100644 --- a/pages/pl_PL/docs/advanced_query.md +++ b/pages/pl_PL/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: strona ## Inteligentne wyciąganie pól -GORM pozwala na wybranie określonych pól używając kwerendy [`Select`](query.html). Jeśli często używasz jej w swojej aplikacji, być może warto zdefiniować mniejszą strukturę dla API, co pozwala na automatyczne wybieranie niektórych pól, na przykład: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -13,7 +13,7 @@ type User struct { Name string Age int Gender string - // setki pól + // hundreds of fields } type APIUser struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Wybrać `id`, `name` awtomatycznie gdy pytamy +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**UWAGA** ` QueryFields ` wybierze według nazwy wszystkich pól dla bieżącego modelu +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // używając tej opcji +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Tryb sesji +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Blokady (FOR UPDATE) +## Locking GORM wspiera różne typy blokad, na przykład: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Należy odwołać się do [Raw SQL nad SQL Builder](sql_builder.html), aby pozyskać więcej informacji +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## Podzapytania -Podzapytanie może być zagnieżdzone w zapytaniu, GORM może generować pozapytania poczas używania obiektu `*gorm.DB` jako parametru +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group Conditions -Easier to write complicated SQL query with Group Conditions +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN z wieloma kolumnami -Używanie IN z wieloma kolumnami +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Nazwane argumenty -GORM wspiera nazwane argumenty używając [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) lub map[string]interface{}{}
, na przykład: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Należy odwołać się do [Raw SQL nad SQL Builder](sql_builder.html#named_argument), aby pozyskać więcej informacji +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteracja -GORM wspiera iteracje po wierszach +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Chcemy więcej niż jedną kolumnę, możemy użyć Scan oraz Find +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` pozwala na ustalanie często używanych kwerend, których możemy używać wykonując metody +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Znajdż wszystkie zamówienia opłacone za pomocą karty kredytowej o wartości większej niż 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Znajdż wszystkie zamówienia opłacone metodą za pobraniem o wartości większej niż 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Znajdź wszystkie zamówienia opłacone o wartości większej niż 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Zwraca ilość znalezionych rekordów +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/pl_PL/docs/associations.md b/pages/pl_PL/docs/associations.md index 42e5a8e6131..ee30caf054f 100644 --- a/pages/pl_PL/docs/associations.md +++ b/pages/pl_PL/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Auto Create/Update -GORM will auto-save associations and its reference using [Upsert](create.html#upsert) when creating/updating a record. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -If you want to update associations's data, you should use the `FullSaveAssociations` mode: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Skip Auto Create/Update -To skip the auto save when creating/updating, you can use `Select` or `Omit`, for example: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE:** For many2many associations, GORM will upsert the associations before creating the join table references, if you want to skip the upserting of associations, you could skip it like: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -The following code will skip the creation of the association and its references +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit Association fields +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Create user and his BillingAddress, ShippingAddress -// When creating the BillingAddress only use its address1, address2 fields and omit others +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Delete Associations + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Association Mode -Association Mode contains some commonly used helper methods to handle relationships +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Start Association Mode var user User db.Model(&user).Association("Languages") -// `user` is the source model, it must contains primary key -// `Languages` is a relationship's field name -// If the above two requirements matched, the AssociationMode should be started successfully, or it should return error -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Find Associations +### Finding Associations -Find matched associations +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Find associations with conditions -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Append Associations +### Appending Associations -Append new associations for `many to many`, `has many`, replace current association for `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,38 +201,43 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Replace Associations +### Replacing Associations -Replace current associations with new ones +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Delete Associations +### Deleting Associations -Remove the relationship between source & arguments if exists, only delete the reference, won't delete those objects from DB. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Clear Associations +### Clearing Associations -Remove all reference between source & association, won't delete those associations +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Count Associations +### Counting Associations -Return the count of current associations +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() // Count with conditions @@ -186,89 +245,77 @@ codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Batch Data +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Association Mode supports batch data, e.g: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/pl_PL/docs/context.md b/pages/pl_PL/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/pl_PL/docs/context.md +++ b/pages/pl_PL/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/pl_PL/docs/conventions.md b/pages/pl_PL/docs/conventions.md index 664efad156c..76e998ecdec 100644 --- a/pages/pl_PL/docs/conventions.md +++ b/pages/pl_PL/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/pl_PL/docs/create.md b/pages/pl_PL/docs/create.md index ce5544182c5..ae06885fad6 100644 --- a/pages/pl_PL/docs/create.md +++ b/pages/pl_PL/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/pl_PL/docs/dbresolver.md b/pages/pl_PL/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/pl_PL/docs/dbresolver.md +++ b/pages/pl_PL/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/pl_PL/docs/error_handling.md b/pages/pl_PL/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/pl_PL/docs/error_handling.md +++ b/pages/pl_PL/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/pl_PL/docs/many_to_many.md b/pages/pl_PL/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/pl_PL/docs/many_to_many.md +++ b/pages/pl_PL/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/pl_PL/docs/method_chaining.md b/pages/pl_PL/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/pl_PL/docs/method_chaining.md +++ b/pages/pl_PL/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/pl_PL/docs/models.md b/pages/pl_PL/docs/models.md index a82cc2e3279..9e901068759 100644 --- a/pages/pl_PL/docs/models.md +++ b/pages/pl_PL/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaring Models -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/pl_PL/docs/security.md b/pages/pl_PL/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/pl_PL/docs/security.md +++ b/pages/pl_PL/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/pl_PL/docs/transactions.md b/pages/pl_PL/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/pl_PL/docs/transactions.md +++ b/pages/pl_PL/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/pl_PL/docs/write_driver.md b/pages/pl_PL/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/pl_PL/docs/write_driver.md +++ b/pages/pl_PL/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/pl_PL/docs/write_plugins.md b/pages/pl_PL/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/pl_PL/docs/write_plugins.md +++ b/pages/pl_PL/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/pl_PL/gen/associations.md b/pages/pl_PL/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/pl_PL/gen/associations.md +++ b/pages/pl_PL/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/pl_PL/gen/query.md b/pages/pl_PL/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/pl_PL/gen/query.md +++ b/pages/pl_PL/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/pl_PL/gen/sql_annotation.md b/pages/pl_PL/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/pl_PL/gen/sql_annotation.md +++ b/pages/pl_PL/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/pt_BR/docs/advanced_query.md b/pages/pt_BR/docs/advanced_query.md index a08cffd0a8a..3ae5bf445bb 100644 --- a/pages/pt_BR/docs/advanced_query.md +++ b/pages/pt_BR/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## Seleção Inteligente de Campos -GORM permite selecionar campos específicos com [`Select`](query.html), se você frequentemente usa isso em seu aplicativo, talvez você queira definir uma estrutura menor para uso da API, que pode selecionar campos específicos automaticamente, por exemplo: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -13,7 +13,7 @@ type User struct { Name string Age int Gender string - // milhares de campos + // hundreds of fields } type APIUser struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Seleciona automaticamente o `id`, `name` +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOTA:** o modo `QueryFields` irá selecionar todos os campos do modelo +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // com esta opção +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Bloqueio (FOR UPDATE) +## Locking O GORM suporta diferente tipos de bloqueio, por exemplo: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Consulte [SQL Puro e Construtor de SQL](sql_builder.html) para mais detalhes +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## Subconsultas -Uma subconsulta pode ser aninhada dentro de outra consulta, o GORM pode gerar uma subconsulta quando se usa o objeto `*gorm.DB` como parâmetro +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### Consulta a partir de uma subconsulta -O GORM permite que você use uma subconsulta na cláusula FROM com o método `Table`, por exemplo: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Condições de agrupamento -Mais fácil de escrever uma complicada consulta SQL com condições de agrupamento +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN com múltiplas colunas -Consulta usando IN com múltimas colunas +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Argumento Nomeado -O GORM suporta argumentos nomeados com [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) ou `map[string]interface{}{}`, por exemplo: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Consulte [Raw SQL e SQL Builder](sql_builder.html#named_argument) para mais detalhes +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Mapear resultado de consulta -O GORM mapear o resultado de uma consulta para `map[string]interface{}` ou `[]map[string]interface{}`, não esqueça de definir o `Model` ou `Table`, por exemplo: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Obtenha o primeiro registro correspondente ou inicialize uma nova instância com determinadas condições (funciona apenas com struct ou com condições usando map) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// Usuário não encontrado, inicialize o objeto com os seguintes dados +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Pesquisa um usuário com `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Usuário encontrado com `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Inicializar struct com mais atributos se o registro não for encontrado, os `Attrs` não serão usados para criar uma consulta SQL +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// Usuário não encontrado, inicialize o objeto com os dados +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// Usuário não encontrado, inicialize o objeto com os dados -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Usuário encontrado com `name` = `jinzhu`, os atributos serão ignorados +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` atribue a struct independente se o registro foi localizado ou não, esses atributos não serão usados para criar uma consulta SQL e os dados finais não serão gravados no banco de dados +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// Usuário não encontrado, inicialize o objeto com os dados e preencha os atributos +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Usuário encontrado com `name` = `jinzhu`, altere o objeto preenchendo os atributos +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Seja o primeiro registro correspondente ou crie um novo com determinadas condições (só funciona com estrutura, condições de mapa), `RowsAffected` retornou contagem de registros criados/atualizados +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// Usuário não encontrado, crie um novo registro com os dados +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Usuário encontrado com `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Cria struct com mais atributos se o registro não for localizado, esses `Attrs` não serão usados para construir uma consulta SQL +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// Usuário não encontrado, crie um novo registro com os dados +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Usuário encontrado com `name` = `jinzhu`, os atributos serão ignorados +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` atribue ao registro independente se for encontrado ou não e grava de volta no banco de dados. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// Usuário não encontrado, inicialize o objeto com os dados e preencha os atributos +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Usuário encontrado com `name` = `jinzhu`, altere o objeto com os atributos +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Sugestões de Otimizer/Índice -Dicas de otimização permitem controlar o otimizador de consultas para escolher um determinado plano de execução de consultas, GORM suporta-o com `gorm.io/hints`, por exemplo: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Dicas de índice permitem a aprovação de dicas de índice para o banco de dados caso o planejador de consultas fique confuso. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Consulte [Dicas otimizador/Index/Comentário](hints.html) para obter mais detalhes +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteração -GORM suporta iteração através de Linhas +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows é um método do `gorm.DB`, ele pode ser usado para mapear o resultado para uma struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // faça alguma coisa + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Consultar e processar registros em lote +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// lote com tamanho 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // processando os registros encontrados em lotes + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // número de registros no lote + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Lote 1, 2, 3 - - // se retornar algum erro os próximos lotes são interrompidos + // Returning an error will stop further batch processing return nil }) -result.Error // erro retornado -result.RowsAffected // total de registros processados em todos lotes +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Hooks de consulta -GORM permite que hooks `AfterFind` para uma consulta, será chamado quando consultar um registro, consulte [Hooks](hooks.html) para obter detalhes +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Consultar uma coluna única do banco de dados e mapear em um slice, se você quiser consultar múltiplas colunas, use `Select` com [`Scan`](query.html#scan) em vez disso +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Precisa de mais de uma coluna, use `Scan` ou `Find` como abaixo: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Escopos -`Scopes` permite que você especifique consultas comumente usadas que podem ser referenciadas como chamadas de método +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } -func PaidWithCreditCard(db *gorm. B) *gorm.DB { +// Scope for orders paid with a credit card +func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func PaidWithCod(db *gorm. B) *gorm.DB { +// Scope for orders paid with cash on delivery (COD) +func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm. B) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { - return db. aqui("status IN (?)", status) +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("status IN (?)", status) } } +``` -db.Scopes(AmountGreaterThan1000, PaidWithCreditCard). ind(&orders -// Encontrar todas as ordens de cartão de crédito e valor maior que 1000 +### Applying Scopes in Queries -db.Scopes(AmountGreaterThan1000, PaidWithCod). ind(&orders) -// Encontrar todas as ordens de COD e quantidade superior a 1000 +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. -db. copes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Encontrar todas as ordens pagas e enviadas com valor superior a 1000 +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 +db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) + +// Applying scopes to find all COD orders with an amount greater than 1000 +db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) + +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 +db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) ``` -Confira [Scopes](scopes.html) para detalhes +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Obter quantidade de registros correspondentes +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count com Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/pt_BR/docs/associations.md b/pages/pt_BR/docs/associations.md index 6d20df088e1..5f7033c3d5c 100644 --- a/pages/pt_BR/docs/associations.md +++ b/pages/pt_BR/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Criar/Atualizar Automaticamente -GORM salvará associações automaticamente e sua referência usando [Upsert](create.html#upsert) quando criar ou atualizar um registro. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -Se você quiser atualizar os dados das associações, você deve usar o modo `FullSaveAssociations`: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Evitar Criação/Atualização Automática -Para evitar o salvamento automático ao criar/atualizar, você pode usar `Select` ou `Omit`, por exemplo: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Não cria BillingAddress quando cria um usuário +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Não cria todas associações quando cria um usuário ``` {% note warn %} -**OBSERVE:** Para relacionamentos muitos-para-muitos, GORM irá criar/atualizar automaticamente as associações antes de criar as referências da tabela de junção, se você quiser pular a criação/atualização automática das associações, poderia pular assim: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -O código a seguir pulará a criação da associação e suas referências +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Selecionar/Omitir campos de Associação +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Cria o usuário e seu BillingAddress, ShippingAddress -// Quando criar o BillingAddress usa apenas os campos address1, address2 e omite os outros +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## Excluir associações + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Modo de Associação -O modo de associação contém alguns métodos de ajuda comumente usados para lidar com relacionamento +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Inicia o modo de associação var user User db.Model(&user).Association("Languages") -// `user` é o model de origem, ele deve conter uma chave primária -// `Languages` é o nome do campo de um relacionamento -// Se os requisitos acima forem atendidos, o modo de associação deve ser iniciado, caso contrário um erro é retornado -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Consultar associações +### Finding Associations -Encontrar associações correspondentes +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Encontrar associações com as condições -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Acrescentar associações +### Appending Associations -Acrescentar novas associações para `many to many`, `has many`, substituir a associação atual para `has one`, `belongs to` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,128 +201,121 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Substituir associações +### Replacing Associations -Substituir associações atuais por novas +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Excluir associações +### Deleting Associations -Remova a relação entre a origem & argumentos se existir, exclua apenas a referência, não excluirá esses objetos do banco de dados. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Limpar associações +### Clearing Associations -Remover toda referência entre a origem & associação, não excluirá as associações +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Contar associações +### Counting Associations -Retornar o total de associações atuais +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() -// Contar as associações baseadas nas condições +// Count with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Lote de Dados +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -Modo de associação suporta dados em lote, por exemplo: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Consultar todas roles de todos usuários db.Model(&users).Association("Role").Find(&roles) +``` -// Excluir o usuário A de todos Teams +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Consulta o total de usuários únicos de todos os Team +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// Para fazesr `Append`, `Replace` com lote de dados, a quantidade de argumentos precisa ser igual a quantidade de dados ou será retornado um erro +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: nós temos 3 usuários acrescente o userA ao time user1, userB ao user2, userA, userB e userC ao user3 + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Redefina o time user1 com o userA,redefina o time user2 com o userB, o time user3 com o userA, userB e userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Apagar Registro de Associação -Por padrão, `Replace`/`Delete`/`Clear` em `gorm.Association` apenas deletam a referência, ou seja, definem a chave estrangeira das associações antigas como nula. - -Você pode excluir esses objetos com `Unscoped` (isso não tem nada a ver com `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -Como excluir é decidido por `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Exclusão reversível -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Apagar permanentemente -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Excluir com Select - -É permitido excluir relações selecionadas de has one/has many/many2many usando `Select` ao deletar registros, por exemplo: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// excluir a conta do usuário ao excluir o usuário -db.Select("Account").Delete(&user) - -// excluir as relações de Pedidos e CartoesDeCredito do usuário ao deletar o usuário -db.Select("Orders", "CreditCards").Delete(&user) - -// excluir as relações "has one/many/many2many" do usuário ao deletar o usuário -db.Select(clause.Associations).Delete(&user) - -// excluir a conta de cada usuário ao deletar os usuários -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**Nota:** As associações serão excluídas somente se a chave primária dos registros a serem excluídos não for zero. O GORM usará essas chaves primárias como condições para excluir as associações selecionadas +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// NÃO FUNCIONA -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// irá excluir todos os usuários com o nome `jinzhu`, mas as contas desses usuários não serão excluídas - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// irá excluir o usuário com o nome `jinzhu` e id = `1`, e a conta do usuário `1` será excluída - -db.Select("Account").Delete(&User{ID: 1}) -// irá excluir o usuário com id = `1`, e a conta do usuário `1` será excluída +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Associação de Tags -| Tag | Descrição | -| ---------------- | -------------------------------------------------------------------------------------------------------------- | -| foreignKey | Especifica o nome da coluna do model atual que é usado como uma chave estrangeira para a tabela de junção | -| references | Especifica o nome da coluna da tabela de referência que é mapeada para a chave estrangeira da tabela de junção | -| polymorphic | Especifica o tipo polimórfico como o nome do model | -| polymorphicValue | Especifica o valor polimórfico, nome da tabela padrão | -| many2many | Especifica nome da tabela de junção | -| joinForeignKey | Especifica o nome da coluna que é chave estrangeira na tabela de junção que será mapeada para a tabela atual | -| joinReferences | Especifica o nome da coluna estrangeira da tabela de junção que mapeia para a tabela de referência | -| constraint | Restrição do relacionamento, ex.: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Descrição | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/pt_BR/docs/context.md b/pages/pt_BR/docs/context.md index 3d6e68decf6..41ebb709568 100644 --- a/pages/pt_BR/docs/context.md +++ b/pages/pt_BR/docs/context.md @@ -3,19 +3,19 @@ title: Contexto layout: page --- -GORM fornece suporte a contextos, você pode usá-lo com o método `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Modo de sessão única +### Modo de sessão única -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/pt_BR/docs/conventions.md b/pages/pt_BR/docs/conventions.md index 6bab3bce263..8318f7b2e7b 100644 --- a/pages/pt_BR/docs/conventions.md +++ b/pages/pt_BR/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/pt_BR/docs/create.md b/pages/pt_BR/docs/create.md index 83597146b2f..e4f07e58b8c 100644 --- a/pages/pt_BR/docs/create.md +++ b/pages/pt_BR/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Inserção em Lote -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/pt_BR/docs/dbresolver.md b/pages/pt_BR/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/pt_BR/docs/dbresolver.md +++ b/pages/pt_BR/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/pt_BR/docs/error_handling.md b/pages/pt_BR/docs/error_handling.md index c94a0086ab0..6e5cc472348 100644 --- a/pages/pt_BR/docs/error_handling.md +++ b/pages/pt_BR/docs/error_handling.md @@ -3,52 +3,101 @@ title: Tratamento de Erro layout: page --- -O tratamento de erros em Go é importante. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -Você é encorajado a fazer uma verificação de erro após qualquer [Método Finalizador](method_chaining.html#finisher_method) +## Basic Error Handling -## Tratamento de Erro +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -O tratamento de erro no GORM é diferente do código Go mais idiomático, porque usa a API de encadeamento (chainable) de métodos. - -Se ocorrer algum erro, o GORM definirá o campo `Error` do `*gorm.DB`, que deve ser verificado da seguinte forma: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // tratamento de erros... + // Handle error... } ``` -} +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // erro ao lidar... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -O GORM retorna `ErrRecordNotFound` quando falha ao encontrar dados com os métodos `First`, `Last`, `Take`, se ocorrer vários erros, você pode verificar o erro `ErrRecordNotFound` com `erros. Is`, por exemplo: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Se ocorrer vários erros, `GetErrors` irá retornar `[]error` -errors := db.First(&user).Limit(10).Find(&users).GetErrors() +err := db.First(&user, 100).Error +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} +``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code -fmt.Println(len(errors)) +- **Example: Handling MySQL Error Codes** -for _, err := range errors { - fmt.Println(err) +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } } ``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/pt_BR/docs/many_to_many.md b/pages/pt_BR/docs/many_to_many.md index be104243db2..f38d60de587 100644 --- a/pages/pt_BR/docs/many_to_many.md +++ b/pages/pt_BR/docs/many_to_many.md @@ -139,7 +139,7 @@ Por favor cheque [Modo de associação](associations.html#Association-Mode) para type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/pt_BR/docs/method_chaining.md b/pages/pt_BR/docs/method_chaining.md index 8ada66a747a..7b69dc7dae8 100644 --- a/pages/pt_BR/docs/method_chaining.md +++ b/pages/pt_BR/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Encadeamento de Métodos layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/pt_BR/docs/models.md b/pages/pt_BR/docs/models.md index 29055ad9c8f..bda35c4e198 100644 --- a/pages/pt_BR/docs/models.md +++ b/pages/pt_BR/docs/models.md @@ -3,35 +3,52 @@ title: Declaração de Modelos layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaração de Modelos -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/pt_BR/docs/security.md b/pages/pt_BR/docs/security.md index 134e0e15cfe..f856934fefa 100644 --- a/pages/pt_BR/docs/security.md +++ b/pages/pt_BR/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/pt_BR/docs/transactions.md b/pages/pt_BR/docs/transactions.md index 6b1964aada2..f2888360634 100644 --- a/pages/pt_BR/docs/transactions.md +++ b/pages/pt_BR/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/pt_BR/docs/write_driver.md b/pages/pt_BR/docs/write_driver.md index 364b2b9d754..5981da86495 100644 --- a/pages/pt_BR/docs/write_driver.md +++ b/pages/pt_BR/docs/write_driver.md @@ -3,25 +3,101 @@ title: Escrever um Driver layout: page --- -## Escreva um novo driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -O GORM tem suporte oficial para `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Alguns bancos de dados podem ser compatíveis com o dialeto do `mysql` ou `postgres`, nesse caso seria possível usar o dialeto para essas bases de dados. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -Para outros, você pode escrever um novo driver, ele precisa implementar [a interface de dialeto](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Consulte o [Driver do MySQL](https://github.com/go-gorm/mysql) como exemplo +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/pt_BR/docs/write_plugins.md b/pages/pt_BR/docs/write_plugins.md index 9cc5ff6add9..1d7b3a99798 100644 --- a/pages/pt_BR/docs/write_plugins.md +++ b/pages/pt_BR/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/pt_BR/gen/associations.md b/pages/pt_BR/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/pt_BR/gen/associations.md +++ b/pages/pt_BR/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/pt_BR/gen/query.md b/pages/pt_BR/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/pt_BR/gen/query.md +++ b/pages/pt_BR/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/pt_BR/gen/sql_annotation.md b/pages/pt_BR/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/pt_BR/gen/sql_annotation.md +++ b/pages/pt_BR/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/ru_RU/docs/advanced_query.md b/pages/ru_RU/docs/advanced_query.md index 8e5b12dc9b7..3ce8ad2a7ff 100644 --- a/pages/ru_RU/docs/advanced_query.md +++ b/pages/ru_RU/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: страница ## Умный выбор полей -GORM позволяет выбирать определенные поля с помощью [`Select`](query.html), если вы часто используете их в своем приложении, вы можете использовать более короткий struct для выбора определенных полей автоматически: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -13,7 +13,7 @@ type User struct { Name string Age int Gender string - // следующие сотни полей + // hundreds of fields } type APIUser struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// Выбор`id`, `name` автоматически при запросе +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**Примечание** Режим `QueryFields` будет выбираться по имени всех полей для текущей модели +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // с помощью этой опции +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Режим сессии +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Блокировка (ДЛЯ ОБНОВЛЕНИЯ) +## Locking GORM поддерживает различные типы блокировок, например: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -Смотрите [Чистый SQL и Конструктор SQL](sql_builder.html) для получения более подробной информации +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## Подзапрос -Подзапрос может быть вложен в запрос, GORM сгенерирует подзапрос при использовании `*gorm.DB` объекта в качестве параметра +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### Из SubQuery (под запроса) -GORM позволяет вам использовать подзапрос в предложении FROM с помощью метода `Table`, например: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Группировка условий -Легче написать сложный SQL-запрос с группировкой условий +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN с несколькими столбцами -Выборка IN с несколькими столбцами +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Именованные аргументы -GORM поддерживает именованные аргументы при использовании [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) или `map[string]interface{}{}`, например: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Смотрите [Чистый SQL и Конструктор SQL](sql_builder.html#named_argument) для подробностей +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find с картами -GORM позволяет передавать результаты сканирования в `map[string]interface{}` или `[]map[string]interface{}`, не забудьте указать `Model` или `Table`, например: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Получить первую найденную запись, или инициализировать новую с заданными параметрами (работает только со структурой и картой) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// Пользователь не найден, создаем новую запись с данными значениями +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Найден пользователь с `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Найден пользователь с `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Инициализируйте структуру с большим количеством атрибутов, если запись не найдена, эти `Attrs` не будут использоваться для построения SQL-запроса +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// Пользователь не найден, инициализировать структуру с указанными параметрами и атрибутами Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// Пользователь не найден, инициализировать структуру с указанными параметрами и атрибутами Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Найден пользователь с параметрами `name` = `jinzhu`, атрибуты Attrs будут проигнорированы +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// пользователь -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Метод `Assign` назначает атрибуты в структуру, независимо от того, найдена запись или нет, эти атрибуты не будут участвовать в генерации запроса SQL и не будут сохранены в БД +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// Пользователь не найден, создать его с данными условиями и с атрибутами в Assign +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Найден пользователь с `name` = `jinzhu`, обновить запись с атрибутами в Assign +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Получите первую совпадающую запись или создайте новую с заданными условиями (работает только со структурой, условиями сопоставления), `rowsAffected` возвращает количество созданных/обновленных записей +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// Пользователь не найден, создать новую запись с заданными условиями +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Найден пользователь с `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Создание структуры с дополнительными атрибутами если запись не найдена. Эти `Attrs` атрибуты не будут использованы в построении SQL запроса +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User не найден, создать его с данными условиями и атрибутами в Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Найден user с `name` = `jinzhu`, атрибуты в Attrs будут проигнорированы +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -Метод `Assign` назначает атрибуты к записи, будет работать независимо от того, найдена запись или нет. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User не найден, создать его с данными условиями и атрибутами в Assign +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Найден user с `name` = `jinzhu`, обновить эту запись с атрибутами в Assign +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Оптимизатор/Индексы -Подсказки оптимизатора позволяют контролировать оптимизатор запросов для выбора определенного плана выполнения запроса, GORM поддерживает его с помощью `gorm.io/hints`, например: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Индексные подсказки позволяют передавать индексированные подсказки к базе данных, если планировщик запросов ошибается. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Смотрите [Подсказки оптимизатор/Индекс/Комментарий](hints.html) для получения более подробной информации +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Итерация -GORM поддерживает итерацию по строкам +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows метод `gorm.DB`, он может быть использован для сканирования строки в struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // делаем что-то + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Запрашивать и обрабатывать записи в пакете +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// размер пакета 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // пакетная обработка найденных записей + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // количество записей в этом пакете + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // возврат ошибки остановит обработку + // Returning an error will stop further batch processing return nil }) -result.Error // возвращаемая ошибка -result.RowsAffected // кол-во обработанных записей во всех пакетах +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Хуки запросов -GORM позволяет использовать хуки `AfterFind` для запроса, который будет вызываться при выполнении запроса. Смотрите [Хуки](hooks.html) для подробностей +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Запрос одного столбца из базы данных и запись его в слайс, если вы хотите получить несколько столбцов - используйте `Select` вместе с [`Scan`](query.html#scan) для этого +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Запрос больше одного столбца, используйте `Scan` или `Find` как в примере: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` позволяют указать часто используемые запросы, которые можно использовать позже как методы +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Найдите все заказы по кредитным картам на сумму более 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Найдите все заказы наложенным платежом на сумму более 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Найдите все оплаченные, отправленные заказы на сумму более 1000 ``` -Смотрите [Scopes](scopes.html) для получения дополнительной информации +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Получение количество найденных записей +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/ru_RU/docs/associations.md b/pages/ru_RU/docs/associations.md index ece4058f1da..6d5cebedea8 100644 --- a/pages/ru_RU/docs/associations.md +++ b/pages/ru_RU/docs/associations.md @@ -5,141 +5,195 @@ layout: страница ## Автоматические Create/Update -GORM будет автоматически сохранять связи и их ссылки с помощью [Upsert](create.html#upsert) при создании/обновлении записи. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ Name: "jinzhu", - BillingAddress: Address{Address1: "Платежный адрес - Адрес 1"}, - ShippingAddress: Address{Address1: "Адрес доставки - Адрес 1"}, + BillingAddress: Address{Address1: "Billing Address - Address 1"}, + ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, Emails: []Email{ {Email: "jinzhu@example.com"}, {Email: "jinzhu-2@example.com"}, }, Languages: []Language{ - {Name: "RU"}, + {Name: "ZH"}, {Name: "EN"}, }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; -// INSERT INTO "addresses" (address1) VALUES ("Платежный адрес - Адрес 1"), ("Адрес доставки - Адрес 1") ON DUPLICATE KEY DO NOTHING; +// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; // INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); // INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY DO NOTHING; -// INSERT INTO "languages" ("name") VALUES ('RU'), ('EN') ON DUPLICATE KEY DO NOTHING; +// INSERT INTO "languages" ("name") VALUES ('ZH'), ('EN') ON DUPLICATE KEY DO NOTHING; // INSERT INTO "user_languages" ("user_id","language_id") VALUES (111, 1), (111, 2) ON DUPLICATE KEY DO NOTHING; // COMMIT; db.Save(&user) ``` -Если понадобится обновить данные связей, то следует использовать режим `FullSaveAssociations`: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Пропуск автоматических Create/Update -Чтобы пропустить автоматическое сохранение при `create` / `update`, можно воспользоваться `Select` либо `Omit`, пример: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. + +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Пропустить создание BillingAddress при создании user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Пропуск всех связей при создании user ``` {% note warn %} -**Примечание:** Для связей `many2many` GORM будет вставлять связи перед созданием ссылок на `join` таблицу, если понадобится пропустить вставку связей, то сделать это можно следующим образом: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -Следующий код пропустит создание связи и ее ссылок +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Выбрать/пропускать поля ассоциации +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ - Name: "jenya", - BillingAddress: Address{Address1: "Платежный адрес - Адрес 1", Address2: "адрес2"}, - ShippingAddress: Address{Address1: "Адрес доставки - Адрес 1", Address2: "адрес2"}, + Name: "jinzhu", + BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"}, + ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// Создать пользователя его платежный адрес и адрес доставки -// При создании BillingAddress используйте только его поля address1, address2 и опускайте другие +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields ``` +## Удаление связей + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) +``` + +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## Типы ассоциаций -Режим связывания включает некоторые часто используемые вспомогательные методы для обработки отношений +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// Старт режима ассоциаций var user User db.Model(&user).Association("Languages") -// `пользователь` - это исходная модель, она должна содержать первичный ключ -// `Languages` - это имя связанного поля -// Если два вышеуказанных требования совпадают, AssociationMode должен быть запущен успешно, иначе он должен возвратить ошибку -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### Поиск связей +### Finding Associations -Поиск подходящей ассоциации +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Поиск ассоциаций с условиями -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### Добавление связей +### Appending Associations -Добавление новых связей для `многие-ко-многим` `много`, заменяет текущие связи `один`, `принадлежит` +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,130 +201,121 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### Замена связей +### Replacing Associations -Замена текущих связей новыми +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### Удаление связей +### Deleting Associations -Удаление связи между источником и связанными аргументами приводит к удалению ссылки, но сами объекты из БД не удаляются. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### Очистка связей +### Clearing Associations -Удаляет все ссылки между источником и связью, не удаляя связь +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### Подсчет связей +### Counting Associations -Возвращает количество существующих связей +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() -// Количество с учетом условий +// Count with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Пакетная обработка +### Batch Data Handling -Режим связывания поддерживает пакетную обработку, пример: +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. + +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Найти все роли для всех пользователей db.Model(&users).Association("Role").Find(&roles) +``` + +- **Deleting Associations**: Remove specific associations across multiple records. -// Удалить пользователя A из всех команд, в которых пользователь состоит +```go db.Model(&users).Association("Team").Delete(&userA) +``` + +- **Counting Associations**: Get the count of associations for a batch of records. -// Получить distinct количество всех команд пользователя +```go db.Model(&users).Association("Team").Count() +``` -// Для `Append`, `Replace` с пакетными данными длина аргументов должна быть равна длине данных, иначе будет возвращена ошибка +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// Например: у нас есть 3 пользователя, добавляем UserA в команду user1, UserB в команду user2, добавляем UserA, UserB и UserC в команду user3 +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Сбросить команду UserA у пользователя user1, сбросить команду UserB у пользователя user2, сбросить команды UserA, UserB и UserC у пользователя user3 +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Удаление связей -По умолчанию, `Replace`/`Delete`/`Clear` в `gorm.Association` удаляет только ссылку, то есть устанавливает для внешнего ключа значение null. - -Вы можете удалить эти объекты с помощью `Unscoped` (это не имеет ничего общего с `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -Способ удаления определяется в `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Мягкое удаление -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Удалить безвозвратно -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Удалить с помощью Select - -Вам разрешено удалять выбранные has one/has many/many2many отношения с помощью `Select` при удалении записей, например: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// удалить учетную запись пользователя при удалении пользователя -db.Select("Account").Delete(&user) - -// удалить заказы пользователя, связи с кредитными картами при удалении пользователя -db.Select("Orders", "CreditCards").Delete(&user) - -// удалить пользователя имеет отношение one/many/many2many при удалении пользователя -db.Select(clause.Associations).Delete(&user) - -// удалить учетную запись каждого пользователя при удалении пользователей -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**ПРИМЕЧАНИЕ:** Ассоциации будут удалены только в том случае, если первичный ключ удаляемых записей не равен нулю, GORM будет использовать эти первичные ключи в качестве условий для удаления выбранных связей +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// НЕ РАБОТАЕТ -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// удалит всех пользователей с именем `jinzhu`, но учетная запись этого пользователя удалена не будет - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// удалит пользователя с именем = `jinzhu` и id = `1`, учетная запись пользователя `1` будет удалена - -db.Select("Account").Delete(&User{ID: 1}) -// удалит пользователя с id = `1`, учетная запись пользователя `1` будет удалена +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Теги связей -| Тег | Описание | -| ---------------- | ------------------------------------------------------------------------------------------------------------ | -| foreignKey | Указывает имя столбца текущей модели, которая используется в качестве внешнего ключа для соединяемой таблицы | -| references | Задает имя столбца связанной таблицы, сопоставленной с внешним ключом объединенной таблицы | -| polymorphic | Задает полиморфный тип, например название модели | -| polymorphicValue | Указывает полиморфное значение, название таблицы по умолчанию | -| many2many | Указывает имя таблицы связи | -| joinForeignKey | Задает имя столбца внешнего ключа объединяемой таблицы, которое сопоставляется с текущей таблицей | -| joinReferences | Задает имя столбца внешнего ключа объединяемой таблицы, которое сопоставляется с связанной таблицей | -| constraint | Правила связей, например: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Тег | Описание | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/ru_RU/docs/context.md b/pages/ru_RU/docs/context.md index 2a28202ab69..5a3fcf1a889 100644 --- a/pages/ru_RU/docs/context.md +++ b/pages/ru_RU/docs/context.md @@ -3,19 +3,19 @@ title: Контекст layout: страница --- -GORM обеспечивает поддержку контекста, вы можете использовать его методом `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Режим одной сессии +### Режим одной сессии -Режим одной сессии, обычно используется, когда вы хотите выполнить одну операцию +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Режим непрерывной сессии +### Continuous Session Mode -Режим непрерывной сессии обычно используется, когда вы хотите выполнить группу операций, например: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Таймаут контекста +### Context Timeout -Вы можете установить контекст с тайм-аутом в `db.WithContext`, чтобы установить тайм-аут для длительно выполняющихся запросов, например: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Контекст в Hooks/Callbacks +### Контекст в Hooks/Callbacks -Вы можете получить доступ к объекту `Context` из текущего `Statement` объекта, например: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Пример Chi Middleware +### Integration with Chi Middleware -Режим непрерывного сеанса, который может быть полезен при обработке запросов API, например, вы можете настроить `*gorm.DB` с контекстом тайм-аута в middleware, а затем использовать `*gorm.DB` при обработке всех запросов - -Ниже приведен пример Chi middleware: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // много операций с базой данных + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // много операций с базой данных + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**ПРИМЕЧАНИЕ** Настройка `контекста` с помощью `WithContext` goroutine-safe, обратитесь к [Сессия](session.html) для получения подробной информации -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger также принимает ` контекст `, вы можете использовать его для отслеживания событий, обратитесь к [Logger](logger.html) для получения подробной информации +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/ru_RU/docs/conventions.md b/pages/ru_RU/docs/conventions.md index 6338ad18b3d..36999555bb6 100644 --- a/pages/ru_RU/docs/conventions.md +++ b/pages/ru_RU/docs/conventions.md @@ -86,7 +86,7 @@ db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{}) ### Стратегия именования -GORM позволяет пользователям изменять стратегию именования по умолчанию, переопределяя стандартную `NamingStrategy`, которая используется для сборки `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Смотрите [Настройки GORM](gorm_config.html#naming_strategy) для подробностей +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Название столбца diff --git a/pages/ru_RU/docs/create.md b/pages/ru_RU/docs/create.md index 7b63ed27401..a3cfe15bc78 100644 --- a/pages/ru_RU/docs/create.md +++ b/pages/ru_RU/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Пакетная вставка -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/ru_RU/docs/dbresolver.md b/pages/ru_RU/docs/dbresolver.md index b0e27fc5ad0..91e6fc31e63 100644 --- a/pages/ru_RU/docs/dbresolver.md +++ b/pages/ru_RU/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Балансировка Нагрузки diff --git a/pages/ru_RU/docs/error_handling.md b/pages/ru_RU/docs/error_handling.md index b02f01f1b95..70acf10e65a 100644 --- a/pages/ru_RU/docs/error_handling.md +++ b/pages/ru_RU/docs/error_handling.md @@ -3,47 +3,101 @@ title: Обработка ошибок layout: страница --- -В Go, очень важна обработка ошибок. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -Вам рекомендуется проверять ошибки после [Методов окончания](method_chaining.html#finisher_method) +## Basic Error Handling -## Обработка ошибок +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Обработка ошибок в GORM отличается от идиоматического Go кода из-за цепного API. - -Если возникнет ошибка, GORM заполнит поле `*gorm.DB` `Error`, его необходимо проверять следующим образом: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // обработка ошибок ... + // Handle error... } ``` -или +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // обработка ошибок ... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM возвращает `ErrRecordNotFound`, когда не удалось найти данные при помощи `First`, `Last`, `Take`, если произошло несколько ошибок, вы можете проверить `ErrRecordNotFound` при помощи `errors.Is`, например: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Проверка возвращается ли ошибка RecordNotFound err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -Если вы хотите иметь возможность использовать ошибки перевода диалекта (например, ErrDuplicatedKey), то включите флаг TranslateError при открытии подключения к базе данных. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Ошибки -[Список ошибок](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/ru_RU/docs/many_to_many.md b/pages/ru_RU/docs/many_to_many.md index a746dc6d1e9..5dadf94321e 100644 --- a/pages/ru_RU/docs/many_to_many.md +++ b/pages/ru_RU/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/ru_RU/docs/method_chaining.md b/pages/ru_RU/docs/method_chaining.md index fa57ba223b2..599ade9641a 100644 --- a/pages/ru_RU/docs/method_chaining.md +++ b/pages/ru_RU/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Цепочки методов layout: страница --- -GORM позволяет делать цепочки методов, так что вы можете написать код следующим образом: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Метод цепочки +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/ru_RU/docs/models.md b/pages/ru_RU/docs/models.md index bd3e32cdb7b..6aec954de0c 100644 --- a/pages/ru_RU/docs/models.md +++ b/pages/ru_RU/docs/models.md @@ -3,35 +3,52 @@ title: Объявление моделей layout: страница --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Объявление моделей -Модели являются обычными структурами с основными типами Go, указателями/псевдонимами или пользовательскими типами, реализующими интерфейсы [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) и [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -Например: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Преобразования +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Преобразования -GORM предпочитает преобразование конфигурации. По умолчанию GORM использует `ID` в качестве первичного ключа, преобразует имя структуры во множественное число в `snake_cases` в качестве имени таблицы, `snake_case` в качестве имени столбца и использует `СreatedAt`, `UpdatedAt` для отслеживания времени создания/обновления записи +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -Если вы будете следовать соглашениям, принятым GORM, вам нужно будет написать очень мало конфигурации / кода. Если соглашение не соответствует вашим требованиям, [GORM позволяет вам настраивать их](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM определяет структуру `gorm.Model`, которая включает в себя поля `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // объявление gorm.Model @@ -43,7 +60,13 @@ type Model struct { } ``` -Вы можете встроить gorm.Model в свою структуру. Чтобы включить эти поля, смотрите [Встроенные структуры](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Дополнительно diff --git a/pages/ru_RU/docs/security.md b/pages/ru_RU/docs/security.md index a95545c973a..bafab52afa0 100644 --- a/pages/ru_RU/docs/security.md +++ b/pages/ru_RU/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/ru_RU/docs/transactions.md b/pages/ru_RU/docs/transactions.md index b6c3073bbf2..28013179bf9 100644 --- a/pages/ru_RU/docs/transactions.md +++ b/pages/ru_RU/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/ru_RU/docs/write_driver.md b/pages/ru_RU/docs/write_driver.md index af5d5e2100d..fe6dd0c423b 100644 --- a/pages/ru_RU/docs/write_driver.md +++ b/pages/ru_RU/docs/write_driver.md @@ -3,25 +3,101 @@ title: Написание нового драйвера layout: страница --- -## Написание нового драйвера +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM предоставляет официальную поддержку `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Некоторые базы данных могут быть совместимы с `mysql` или `postgres` диалектами, в этом случае можно просто использовать диалект для этих баз данных. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -Для других вы можете создать новый драйвер, он должен реализовывать [интерфейс диалекта](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Смотрите [Драйвер MySQL](https://github.com/go-gorm/mysql) в качестве примера +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/ru_RU/docs/write_plugins.md b/pages/ru_RU/docs/write_plugins.md index 78346896231..ea769bd8a97 100644 --- a/pages/ru_RU/docs/write_plugins.md +++ b/pages/ru_RU/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: страница ## Callbacks -Сам GORM основан на методах `Callbacks`, у которого есть callback функции для `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, с помощью которых вы можете полностью настроить GORM по своему усмотрению +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Вызовы callback регистрируются в глобальной `*gorm.DB`, а не на сессионном уровне, если вам требуется `*gorm. B` с другими функциями callback, вам нужно инициализировать другой `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Регистрация функций callback +### Registering a Callback -Регистрация функции callback в Callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Удаление функций callback +### Deleting a Callback -Удаление функции callback из Callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// удалить callback `gorm:create` из обработчика Create ``` -### Замена функций callback +### Replacing a Callback -Заменить callback с идентичным именем на новый +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// заменить callback `gorm:create` новой функцией `newCreateFunction` для обработчика Create ``` -### Регистрация функций callback с порядком выполнения +### Ordering Callbacks -Регистрация функций callback с порядком выполнения +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Существующие функции callback +### Predefined Callbacks -GORM предоставляет [некоторые функции callback](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) для поддержки текущих функций GORM, ознакомьтесь с ними перед началом написания своих плагинов +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Плагин +## Plugins -GORM предоставляет метод `Use` для регистрации плагинов, плагин должен реализовывать интерфейс `Plugin` +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -Метод `Initialize` будет вызван при первом регистрации плагина в GORM, и GORM сохранит его в зарегистрированные плагины, обращайтесь к ним таким образом: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Смотрите [Prometheus](prometheus.html) в качестве примера +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/ru_RU/gen/associations.md b/pages/ru_RU/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/ru_RU/gen/associations.md +++ b/pages/ru_RU/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/ru_RU/gen/query.md b/pages/ru_RU/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/ru_RU/gen/query.md +++ b/pages/ru_RU/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/ru_RU/gen/sql_annotation.md b/pages/ru_RU/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/ru_RU/gen/sql_annotation.md +++ b/pages/ru_RU/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/tr_TR/docs/advanced_query.md b/pages/tr_TR/docs/advanced_query.md index d779f01ca1d..449bc8da3be 100644 --- a/pages/tr_TR/docs/advanced_query.md +++ b/pages/tr_TR/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## Akıllı Alan Seçimi -GORM, [`Select`](query.html) ile belirli alanların seçilmesine izin verir, bunu uygulamanızda sık sık kullanıyorsanız, API kullanımı için belirli alanları otomatik olarak seçebilen daha küçük bir yapı tanımlamak isteyebilirsiniz, örneğin: +GORM'da, [`Select`](query.html) yöntemini kullanarak belirli alanları verimli bir şekilde seçebilirsiniz. Bu, özellikle API yanıtlarında büyük modellerle uğraşırken ancak yalnızca bir alan alt kümesi gerektirdiğinde kullanışlıdır. ```go type User struct { @@ -13,7 +13,7 @@ type User struct { Name string Age int Gender string - // bir sürü alan olabilir + // hundreds of fields } type APIUser struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// `id` ve `name` alanları otomatik olarak sorgunuza eklenir +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**NOT** `Sorgu alanları` otomatik olarak belirttiğiniz modele eklenir +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Modu +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Kilitleme (Kaydın güncellenmesi için) +## Locking GORM bir kaç farklı türde kilitlemeyi destekler. örneğin: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -[Raw SQL and SQL Builder](sql_builder.html) daha fazla detay için referans +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## SubQuery -A subquery can be nested within a query, GORM can generate subquery when using a `*gorm.DB` object as param +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From SubQuery -GORM allows you using subquery in FROM clause with the method `Table`, for example: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Gruplama Koşulları -Gruplama şartlarını kullanarak daha komplike SQL sorguları yazabilirsiniz +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## IN with multiple columns -Selecting IN with multiple columns +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## Named Argument -GORM supports named arguments with [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, for example: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -Check out [Raw SQL and SQL Builder](sql_builder.html#named_argument) for more detail +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find To Map -GORM allows scanning results to `map[string]interface{}` or `[]map[string]interface{}`, don't forget to specify `Model` or `Table`, for example: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// User not found, initialize it with give conditions +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// Found user with `name` = `jinzhu` +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// Found user with `name` = `jinzhu` +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -Initialize struct with more attributes if record not found, those `Attrs` won't be used to build the SQL query +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// User not found, initialize it with give conditions and Attrs +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// User not found, initialize it with give conditions and Attrs -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// Found user with `name` = `jinzhu`, attributes will be ignored +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -`Assign` attributes to struct regardless it is found or not, those attributes won't be used to build SQL query and the final data won't be saved into database +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// Found user with `name` = `jinzhu`, update it with Assign attributes +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -Get first matched record or create a new one with given conditions (only works with struct, map conditions), `RowsAffected` returns created/updated record's count +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// User not found, create a new record with give conditions +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -Create struct with more attributes if record not found, those `Attrs` won't be used to build SQL query +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// User not found, create it with give conditions and Attrs +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, attributes will be ignored +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -`Assign` attributes to the record regardless it is found or not and save them back to the database. +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// User not found, initialize it with give conditions and Assign attributes +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// Found user with `name` = `jinzhu`, update it with Assign attributes +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## Optimizer/Index Hints -Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with `gorm.io/hints`, e.g: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -Index hints allow passing index hints to the database in case the query planner gets confused. +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -Refer [Optimizer Hints/Index/Comment](hints.html) for more details +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## Iteration -GORM supports iterating through Rows +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // do something + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -Query and process records in batch +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// batch size 100 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // batch processing found records + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // number of records in this batch + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // returns error will stop future batches + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // processed records count in all batches +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## Query Hooks -GORM allows hooks `AfterFind` for a query, it will be called when querying a record, refer [Hooks](hooks.html) for details +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Query single column from database and scan into a slice, if you want to query multiple columns, use `Select` with [`Scan`](query.html#scan) instead +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// Requesting more than one column, use `Scan` or `Find` like this: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scopes -`Scopes` allows you to specify commonly-used queries which can be referenced as method calls +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// Find all credit card orders and amount greater than 1000 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// Find all COD orders and amount greater than 1000 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// Find all paid, shipped orders that amount greater than 1000 ``` -Checkout [Scopes](scopes.html) for details +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Get matched records count +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/tr_TR/docs/associations.md b/pages/tr_TR/docs/associations.md index 8e1d34bcac0..311b1b6b9c6 100644 --- a/pages/tr_TR/docs/associations.md +++ b/pages/tr_TR/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## Otomatik Oluşturma/Güncelleme -GORM, referans objeyi ve bu referansın ilişkilerini [Upsert](create.html#upsert) metodu ile otomatik olarak kaydedebilir/güncelleyebilir. +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -Eğer ilişkili veriyi güncellemek istiyorsanız `FullSaveAssociations` modunu kullanmalısınız: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## Otomatik Oluşturma/Güncellemeyi Atlama -`Select` veya `Omit` kullanarak oluşturma/güncelleme işleminin etkilediği ilişkileri/entityleri değiştirebilirsiniz: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// BillingAddress oluşturmadan user oluştur +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// user oluştururken hiçbir ilişki oluşturma ``` {% note warn %} -**NOT:** Çoka çok ilişkiler için, GORM join tablosu referanslarından önce ilişkileri upsert(varsa güncelle, yoksa oluştur) edecektir, upsert adımını şu şekilde atlayabilirsiniz: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -The following code will skip the creation of the association and its references +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## İlişkisel Alanlar(Fields) ve Select/Omit +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// user ile user'a ait BillingAddress ve ShippingAddress'i oluştur -// BillingAddress'i oluştururken sadece address1, address2 alanlarını kullan ve diğerlerini yoksay +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## İlişkileri Sil + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## İlişki Modu -İlişki modu, ilişkileri idare etmek için sıklıkla kullanılan yardımcı metodları içerir +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// İlişki modunu başlat var user User db.Model(&user).Association("Languages") -// `user` kaynak model olarak primary key'e sahip olmalıdır -// `Languages` ilişkinin field adıdır -// Eğer yukarıdaki şartlar sağlanırsa, ilişki modu(AssociationMode) başlar, veya hata döndürür -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### İlişkileri Bul +### Finding Associations -Eşleşen ilişkileri bul +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -Belirli şartlara bağlı olarak ilişkileri bul -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### İlişki Ekle +### Appending Associations -`Çoka çok`, `bire çok`, ilişkilere yenisini eklemek; `bire bir`, `sahiplik` ilişkilerini değiştirmek için +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,128 +201,121 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### İlişkileri Değiştir +### Replacing Associations -Şu anki ilişkileri yenisiyle değiştirmek için +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### İlişkileri Sil +### Deleting Associations -Kaynak ve parametre arasında ilişki varsa siler, sadece referansı siler ve veritabanındaki objeleri silmez. +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### İlişkileri Temizle +### Clearing Associations -Kaynak & ilişki arasındaki tüm referansları siler, ilintili ilişkiyi silmez +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### İlişki Sayısı +### Counting Associations -Var olan ilişkilerin sayısını döndürür +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() -// Şartlara uyan ilişki sayısı +// Count with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### Toplu Veri +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -İlişki modu toplu veri işlemlerini destekler: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// Find all roles for all users db.Model(&users).Association("Role").Find(&roles) +``` -// Delete User A from all user's team +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// Get distinct count of all users' teams +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// For `Append`, `Replace` with batch data, the length of the arguments needs to be equal to the data's length or else it will return an error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## Delete Association Record -By default, `Replace`/`Delete`/`Clear` in `gorm.Association` only delete the reference, that is, set old associations's foreign key to null. - -You can delete those objects with `Unscoped` (it has nothing to do with `ManyToMany`). +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -How to delete is decided by `gorm.DB`. +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// Soft delete -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// Delete permanently -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## Delete with Select - -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// delete user's account when deleting user -db.Select("Account").Delete(&user) - -// delete user's Orders, CreditCards relations when deleting user -db.Select("Orders", "CreditCards").Delete(&user) - -// delete user's has one/many/many2many relations when deleting user -db.Select(clause.Associations).Delete(&user) - -// delete each user's account when deleting users -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**NOTE:** Associations will only be deleted if the deleting records's primary key is not zero, GORM will use those primary keys as conditions to delete selected associations +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// DOESN'T WORK -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// will delete all user with name `jinzhu`, but those user's account won't be deleted - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// will delete the user with name = `jinzhu` and id = `1`, and user `1`'s account will be deleted - -db.Select("Account").Delete(&User{ID: 1}) -// will delete the user with id = `1`, and user `1`'s account will be deleted +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## Association Tags -| Tag | Description | -| ---------------- | -------------------------------------------------------------------------------------------------- | -| foreignKey | Specifies column name of the current model that is used as a foreign key to the join table | -| references | Specifies column name of the reference's table that is mapped to the foreign key of the join table | -| polymorphic | Specifies polymorphic type such as model name | -| polymorphicValue | Specifies polymorphic value, default table name | -| many2many | Specifies join table name | -| joinForeignKey | Specifies foreign key column name of join table that maps to the current table | -| joinReferences | Specifies foreign key column name of join table that maps to the reference's table | -| constraint | Relations constraint, e.g: `OnUpdate`,`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| Tag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/tr_TR/docs/context.md b/pages/tr_TR/docs/context.md index d91deeb5630..b1fcbeb6e3e 100644 --- a/pages/tr_TR/docs/context.md +++ b/pages/tr_TR/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM provides Context support, you can use it with method `WithContext` +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## Single Session Mode +### Single Session Mode -Single session mode usually used when you want to perform a single operation +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## Continuous session mode +### Continuous Session Mode -Continuous session mode is usually used when you want to perform a group of operations, for example: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context timeout +### Context Timeout -You can pass in a context with a timeout to `db.WithContext` to set timeout for long running queries, for example: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Context in Hooks/Callbacks +### Context in Hooks/Callbacks -You can access the `Context` object from the current `Statement`, for example: +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi Middleware Example +### Integration with Chi Middleware -Continuous session mode which might be helpful when handling API requests, for example, you can set up `*gorm.DB` with Timeout Context in middlewares, and then use the `*gorm.DB` when processing all requests - -Following is a Chi middleware example: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // lots of db operations + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**NOTE** Setting `Context` with `WithContext` is goroutine-safe, refer [Session](session.html) for details -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger accepts `Context` too, you can use it for log tracking, refer [Logger](logger.html) for details +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/tr_TR/docs/conventions.md b/pages/tr_TR/docs/conventions.md index 59eb901b0ee..605621bdf9d 100644 --- a/pages/tr_TR/docs/conventions.md +++ b/pages/tr_TR/docs/conventions.md @@ -86,7 +86,7 @@ Check out [From SubQuery](advanced_query.html#from_subquery) for how to use SubQ ### NamingStrategy -GORM allows users change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## Column Name diff --git a/pages/tr_TR/docs/create.md b/pages/tr_TR/docs/create.md index d77603d7f75..35518c78e18 100644 --- a/pages/tr_TR/docs/create.md +++ b/pages/tr_TR/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## Batch Insert -To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be splited into multiple batches. +To efficiently insert large number of records, pass a slice to the `Create` method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/tr_TR/docs/dbresolver.md b/pages/tr_TR/docs/dbresolver.md index 2fe2aa9db18..50bc3a6e96a 100644 --- a/pages/tr_TR/docs/dbresolver.md +++ b/pages/tr_TR/docs/dbresolver.md @@ -96,13 +96,13 @@ But you can specifies which DB to use before starting a transaction, for example ```go // Start transaction based on default replicas db -tx := DB.Clauses(dbresolver.Read).Begin() +tx := db.Clauses(dbresolver.Read).Begin() // Start transaction based on default sources db -tx := DB.Clauses(dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Write).Begin() // Start transaction based on `secondary`'s sources -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## Load Balancing diff --git a/pages/tr_TR/docs/error_handling.md b/pages/tr_TR/docs/error_handling.md index cd4c06312db..abac8cb83ba 100644 --- a/pages/tr_TR/docs/error_handling.md +++ b/pages/tr_TR/docs/error_handling.md @@ -3,47 +3,101 @@ title: Error Handling layout: page --- -In Go, error handling is important. +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -You are encouraged to do error check after any [Finisher Methods](method_chaining.html#finisher_method) +## Basic Error Handling -## Error Handling +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -Error handling in GORM is different than idiomatic Go code because of its chainable API. - -If any error occurs, GORM will set `*gorm.DB`'s `Error` field, you need to check it like this: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // error handling... + // Handle error... } ``` -Or +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // error handling... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -GORM returns `ErrRecordNotFound` when failed to find data with `First`, `Last`, `Take`, if there are several errors happened, you can check the `ErrRecordNotFound` error with `errors.Is`, for example: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// Check if returns RecordNotFound error err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` + +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + ## Dialect Translated Errors -If you would like to be able to use the dialect translated errors(like ErrDuplicatedKey), then enable the TranslateError flag when opening a db connection. +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/tr_TR/docs/many_to_many.md b/pages/tr_TR/docs/many_to_many.md index 1a88af59c1e..d423d21742f 100644 --- a/pages/tr_TR/docs/many_to_many.md +++ b/pages/tr_TR/docs/many_to_many.md @@ -137,7 +137,7 @@ Please checkout [Association Mode](associations.html#Association-Mode) for worki type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/tr_TR/docs/method_chaining.md b/pages/tr_TR/docs/method_chaining.md index 8fdea93770b..21db56acd3f 100644 --- a/pages/tr_TR/docs/method_chaining.md +++ b/pages/tr_TR/docs/method_chaining.md @@ -3,123 +3,158 @@ title: Method Chaining layout: page --- -GORM allows method chaining, so you can write code like this: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -There are three kinds of methods in GORM: `Chain Method`, `Finisher Method`, `New Session Method`. +## Method Categories -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -In order to reuse a initialized `*gorm.DB` instance, you can use a `New Session Method` to create a shareable `*gorm.DB`, e.g: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## Chain Method +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -Chain methods are methods to modify or add `Clauses` to current `Statement`, like: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -Here is [the full lists](https://github.com/go-gorm/gorm/blob/master/chainable_api.go), also check out the [SQL Builder](sql_builder.html) for more details about `Clauses`. - -## Finisher Method - -Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -Check out [the full lists](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) here. - -## New Session Method - -GORM defined `Session`, `WithContext`, `Debug` methods as `New Session Method`, refer [Session](session.html) for more details. - -After a `Chain method`, `Finisher Method`, GORM returns an initialized `*gorm.DB` instance, which is NOT safe to reuse anymore, you should use a `New Session Method` to mark the `*gorm.DB` as shareable. - -Let's explain it with examples: - -Example 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(Bad) Example 2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -Example 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/tr_TR/docs/models.md b/pages/tr_TR/docs/models.md index a82cc2e3279..9e901068759 100644 --- a/pages/tr_TR/docs/models.md +++ b/pages/tr_TR/docs/models.md @@ -3,35 +3,52 @@ title: Declaring Models layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## Declaring Models -Models are normal structs with basic Go types, pointers/alias of them or custom types implementing [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -For Example: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## Conventions +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### Conventions -GORM prefers convention over configuration. By default, GORM uses `ID` as primary key, pluralizes struct name to `snake_cases` as table name, `snake_case` as column name, and uses `CreatedAt`, `UpdatedAt` to track creating/updating time +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -If you follow the conventions adopted by GORM, you'll need to write very little configuration/code. If convention doesn't match your requirements, [GORM allows you to configure them](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM defined a `gorm.Model` struct, which includes fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model definition @@ -43,7 +60,13 @@ type Model struct { } ``` -You can embed it into your struct to include those fields, refer [Embedded Struct](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## Advanced diff --git a/pages/tr_TR/docs/security.md b/pages/tr_TR/docs/security.md index d6e8133d867..5962f44b190 100644 --- a/pages/tr_TR/docs/security.md +++ b/pages/tr_TR/docs/security.md @@ -38,9 +38,9 @@ When retrieving objects with number primary key by user's input, you should chec ```go userInputID := "1=1;drop table users;" // safe, return error -id,err := strconv.Atoi(userInputID) +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) diff --git a/pages/tr_TR/docs/transactions.md b/pages/tr_TR/docs/transactions.md index 10c77b088e7..892aa1e86f0 100644 --- a/pages/tr_TR/docs/transactions.md +++ b/pages/tr_TR/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/tr_TR/docs/write_driver.md b/pages/tr_TR/docs/write_driver.md index ef7a234797d..a5599c5dd4c 100644 --- a/pages/tr_TR/docs/write_driver.md +++ b/pages/tr_TR/docs/write_driver.md @@ -3,25 +3,101 @@ title: Write Driver layout: page --- -## Write new driver +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM provides official support for `sqlite`, `mysql`, `postgres`, `sqlserver`. +## Compatibility with MySQL or Postgres Dialects -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -For others, you can create a new driver, it needs to implement [the dialect interface](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector). +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -Checkout the [MySQL Driver](https://github.com/go-gorm/mysql) as example +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/tr_TR/docs/write_plugins.md b/pages/tr_TR/docs/write_plugins.md index e2d3af0f679..293d6147fea 100644 --- a/pages/tr_TR/docs/write_plugins.md +++ b/pages/tr_TR/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM itself is powered by `Callbacks`, it has callbacks for `Create`, `Query`, `Update`, `Delete`, `Row`, `Raw`, you could fully customize GORM with them as you want +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -Callbacks are registered into the global `*gorm.DB`, not the session-level, if you require `*gorm.DB` with different callbacks, you need to initialize another `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### Register Callback +### Registering a Callback -Register a callback into callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### Delete Callback +### Deleting a Callback -Delete a callback from callbacks +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// delete callback `gorm:create` from Create callbacks ``` -### Replace Callback +### Replacing a Callback -Replace a callback having the same name with the new one +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// replace callback `gorm:create` with new function `newCreateFunction` for Create process ``` -### Register Callback with orders +### Ordering Callbacks -Register callbacks with orders +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// before gorm:create +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// after gorm:create +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// after gorm:query +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// after gorm:delete +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// before gorm:update +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// before gorm:create and after gorm:before_create +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// before any other callbacks +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// after any other callbacks +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### Defined Callbacks +### Predefined Callbacks -GORM has defined [some callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) to power current GORM features, check them out before starting your plugins +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## Plugin +## Plugins -GORM provides a `Use` method to register plugins, the plugin needs to implement the `Plugin` interface +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -The `Initialize` method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -Checkout [Prometheus](prometheus.html) as example +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/tr_TR/gen/associations.md b/pages/tr_TR/gen/associations.md index 1ab623fa965..7a70f89561b 100644 --- a/pages/tr_TR/gen/associations.md +++ b/pages/tr_TR/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### Relate to table in database -The association have to be speified by `gen.FieldRelate` +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ If associated model already exists, `gen.FieldRelateModel` can help you build as customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -Method `Field` will join a serious field name with ''.", for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### Find Associations @@ -305,14 +305,15 @@ users, err := u.WithContext(ctx).Preload(field.Associations).Find() users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -Specify selected columns with method `Select`. Foregin key must be selected. +Specify selected columns with method `Select`. Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/tr_TR/gen/query.md b/pages/tr_TR/gen/query.md index fd26cd256fa..8e7a57d03b2 100644 --- a/pages/tr_TR/gen/query.md +++ b/pages/tr_TR/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/tr_TR/gen/sql_annotation.md b/pages/tr_TR/gen/sql_annotation.md index 210153bc8cf..60b17273f6e 100644 --- a/pages/tr_TR/gen/sql_annotation.md +++ b/pages/tr_TR/gen/sql_annotation.md @@ -248,7 +248,7 @@ The `for` expression iterates over a slice to generate the SQL, let's explain by ```go // SELECT * FROM @@table // {{where}} -// {{for _,user:=range user}} +// {{for _,user:=range users}} // {{if user.Name !="" && user.Age >0}} // (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR // {{end}} diff --git a/pages/zh_CN/docs/advanced_query.md b/pages/zh_CN/docs/advanced_query.md index e5c9e2d9327..9173db1834c 100644 --- a/pages/zh_CN/docs/advanced_query.md +++ b/pages/zh_CN/docs/advanced_query.md @@ -5,7 +5,7 @@ layout: page ## 智能选择字段 -GORM 允许通过 [`Select`](query.html) 方法选择特定的字段,如果您在应用程序中经常使用此功能,你也可以定义一个较小的结构体,以实现调用 API 时自动选择特定的字段,例如: +In GORM, you can efficiently select specific fields using the [`Select`](query.html) method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses. ```go type User struct { @@ -13,7 +13,7 @@ type User struct { Name string Age int Gender string - // 假设后面还有几百个字段... + // hundreds of fields } type APIUser struct { @@ -21,13 +21,13 @@ type APIUser struct { Name string } -// 查询时会自动选择 `id`, `name` 字段 +// GORM will automatically select `id`, `name` fields when querying db.Model(&User{}).Limit(10).Find(&APIUser{}) -// SELECT `id`, `name` FROM `users` LIMIT 10 +// SQL: SELECT `id`, `name` FROM `users` LIMIT 10 ``` {% note warn %} -**注意** `QueryFields` 模式会根据当前 model 的所有字段名称进行 select。 +**NOTE** In `QueryFields` mode, all model fields are selected by their names. {% endnote %} ```go @@ -35,239 +35,282 @@ db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ QueryFields: true, }) +// Default behavior with QueryFields set to true db.Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // 带上这个选项 +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` -// Session Mode +// Using Session Mode with QueryFields db.Session(&gorm.Session{QueryFields: true}).Find(&user) -// SELECT `users`.`name`, `users`.`age`, ... FROM `users` +// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users` ``` -## Locking (FOR UPDATE) +## Locking GORM 支持多种类型的锁,例如: ```go +// Basic FOR UPDATE lock db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) -// SELECT * FROM `users` FOR UPDATE +// SQL: SELECT * FROM `users` FOR UPDATE +``` + +The above statement will lock the selected rows for the duration of the transaction. This can be used in scenarios where you are preparing to update the rows and want to prevent other transactions from modifying them until your transaction is complete. +The `Strength` can be also set to `SHARE` which locks the rows in a way that allows other transactions to read the locked rows but not to update or delete them. +```go db.Clauses(clause.Locking{ Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}, }).Find(&users) -// SELECT * FROM `users` FOR SHARE OF `users` +// SQL: SELECT * FROM `users` FOR SHARE OF `users` +``` + +The `Table` option can be used to specify the table to lock. This is useful when you are joining multiple tables and want to lock only one of them. + +Options can be provided like `NOWAIT` which tries to acquire a lock and fails immediately with an error if the lock is not available. It prevents the transaction from waiting for other transactions to release their locks. +```go db.Clauses(clause.Locking{ Strength: "UPDATE", Options: "NOWAIT", }).Find(&users) -// SELECT * FROM `users` FOR UPDATE NOWAIT +// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT ``` -查看 [原生 SQL 及构造器](sql_builder.html) 获取详情 +Another option can be `SKIP LOCKED` which skips over any rows that are already locked by other transactions. This is useful in high concurrency situations where you want to process rows that are not currently locked by other transactions. + +For more advanced locking strategies, refer to [Raw SQL and SQL Builder](sql_builder.html). ## 子查询 -子查询可以嵌套在查询中,GORM 允许在使用 `*gorm.DB` 对象作为参数时生成子查询 +Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter. ```go +// Simple subquery db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) -// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); +// Nested subquery subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) -// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ``` ### From 子查询 -GORM 允许您在 `Table` 方法中通过 FROM 子句使用子查询,例如: +GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization. ```go +// Using subquery in FROM clause db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) -// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 +// Combining multiple subqueries in FROM clause subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) -// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p ``` ## Group 条件 -使用 Group 条件可以更轻松的编写复杂 SQL +Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions. ```go +// Complex SQL query using Group Conditions db.Where( - db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), + db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( - db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), -).Find(&Pizza{}).Statement - -// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") + db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), +).Find(&Pizza{}) +// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge") ``` ## 带多个列的 In -带多个列的 In 查询 +GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query. ```go +// Using IN with multiple columns db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users) -// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); +// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user")); ``` ## 命名参数 -GORM 支持 [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) 和 `map[string]interface{}{}` 形式的命名参数,例如: +GORM enhances the readability and maintainability of SQL queries by supporting named arguments. This feature allows for clearer and more organized query construction, especially in complex queries with multiple parameters. Named arguments can be utilized using either [`sql.NamedArg`](https://tip.golang.org/pkg/database/sql/#NamedArg) or `map[string]interface{}{}`, providing flexibility in how you structure your queries. ```go +// Example using sql.NamedArg for named arguments db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" +// Example using a map for named arguments db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user) -// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 +// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1 ``` -查看 [原生 SQL 及构造器](sql_builder.html#named_argument) 获取详情 +For more examples and details, see [Raw SQL and SQL Builder](sql_builder.html#named_argument) ## Find 至 map -GORM 允许扫描结果至 `map[string]interface{}` 或 `[]map[string]interface{}`,此时别忘了指定 `Model` 或 `Table`,例如: +GORM provides flexibility in querying data by allowing results to be scanned into a `map[string]interface{}` or `[]map[string]interface{}`, which can be useful for dynamic data structures. + +When using `Find To Map`, it's crucial to include `Model` or `Table` in your query to explicitly specify the table name. This ensures that GORM understands which table to query against. ```go +// Scanning the first result into a map with Model result := map[string]interface{}{} db.Model(&User{}).First(&result, "id = ?", 1) +// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1 +// Scanning multiple results into a slice of maps with Table var results []map[string]interface{} db.Table("users").Find(&results) +// SQL: SELECT * FROM `users` ``` ## FirstOrInit -获取第一条匹配的记录,或者根据给定的条件初始化一个实例(仅支持 sturct 和 map 条件) +GORM's `FirstOrInit` method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method is compatible with both struct and map conditions and allows additional flexibility with the `Attrs` and `Assign` methods. ```go -// 未找到 user,则根据给定的条件初始化一条记录 +// If no User with the name "non_existing" is found, initialize a new User +var user User db.FirstOrInit(&user, User{Name: "non_existing"}) -// user -> User{Name: "non_existing"} +// user -> User{Name: "non_existing"} if not found -// 找到了 `name` = `jinzhu` 的 user +// Retrieving a user named "jinzhu" db.Where(User{Name: "jinzhu"}).FirstOrInit(&user) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found -// 找到了 `name` = `jinzhu` 的 user +// Using a map to specify the search condition db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -如果没有找到记录,可以使用包含更多的属性的结构体初始化 user,`Attrs` 不会被用于生成查询 SQL +### Using `Attrs` for Initialization + +When no record is found, you can use `Attrs` to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query. ```go -// 未找到 user,则根据给定的条件以及 Attrs 初始化 user +// If no User is found, initialize with given conditions and additional attributes db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; +// user -> User{Name: "non_existing", Age: 20} if not found -// 未找到 user,则根据给定的条件以及 Attrs 初始化 user -db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// user -> User{Name: "non_existing", Age: 20} - -// 找到了 `name` = `jinzhu` 的 user,则忽略 Attrs +// If a User named "Jinzhu" is found, `Attrs` are ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 18} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found ``` -不管是否找到记录,`Assign` 都会将属性赋值给 struct,但这些属性不会被用于生成查询 SQL,也不会被保存到数据库 +### Using `Assign` for Attributes + +The `Assign` method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won't be saved into the database. ```go -// 未找到 user,根据条件和 Assign 属性初始化 struct +// Initialize with given conditions and Assign attributes, regardless of record existence db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) -// user -> User{Name: "non_existing", Age: 20} +// user -> User{Name: "non_existing", Age: 20} if not found -// 找到 `name` = `jinzhu` 的记录,依然会更新 Assign 相关的属性 +// If a User named "Jinzhu" is found, update the struct with Assign attributes db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user) -// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1; -// user -> User{ID: 111, Name: "Jinzhu", Age: 20} +// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found ``` +`FirstOrInit`, along with `Attrs` and `Assign`, provides a powerful and flexible way to ensure a record exists and is initialized or updated with specific attributes in a single step. + ## FirstOrCreate -获取匹配的第一条记录或者根据给定条件创建一条新纪录(仅 struct, map 条件有效),`RowsAffected` 返回创建、更新的记录数 +`FirstOrCreate` in GORM is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The `RowsAffected` property is useful to determine the number of records created or updated. ```go -// 未找到 User,根据给定条件创建一条新纪录 +// Create a new record if not found result := db.FirstOrCreate(&user, User{Name: "non_existing"}) -// INSERT INTO "users" (name) VALUES ("non_existing"); +// SQL: INSERT INTO "users" (name) VALUES ("non_existing"); // user -> User{ID: 112, Name: "non_existing"} -// result.RowsAffected // => 1 +// result.RowsAffected // => 1 (record created) -// 找到 `name` = `jinzhu` 的 User -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 +// If the user is found, no new record is created +result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) +// user -> User{ID: 111, Name: "jinzhu", Age: 18} +// result.RowsAffected // => 0 (no record created) ``` -如果没有找到记录,可以使用包含更多的属性的结构体创建记录,`Attrs` 不会被用于生成查询 SQL 。 +### Using `Attrs` with FirstOrCreate + +`Attrs` can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query. ```go -// 未找到 user,根据条件和 Assign 属性创建记录 +// Create a new record with additional attributes if not found db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// 找到了 `name` = `jinzhu` 的 user,则忽略 Attrs +// If the user is found, `Attrs` are ignored db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; // user -> User{ID: 111, Name: "jinzhu", Age: 18} ``` -不管是否找到记录,`Assign` 都会将属性赋值给 struct,并将结果写回数据库 +### Using `Assign` with FirstOrCreate + +The `Assign` method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database. ```go -// 未找到 user,根据条件和 Assign 属性创建记录 +// Initialize and save new record with `Assign` attributes if not found db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1; -// INSERT INTO "users" (name, age) VALUES ("non_existing", 20); +// SQL: SELECT * FROM users WHERE name = 'non_existing'; +// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20); // user -> User{ID: 112, Name: "non_existing", Age: 20} -// 找到了 `name` = `jinzhu` 的 user,依然会根据 Assign 更新记录 +// Update found record with `Assign` attributes db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user) -// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; -// UPDATE users SET age=20 WHERE id = 111; -// user -> User{ID: 111, Name: "jinzhu", Age: 20} +// SQL: SELECT * FROM users WHERE name = 'jinzhu'; +// SQL: UPDATE users SET age=20 WHERE id = 111; +// user -> User{ID: 111, Name: "Jinzhu", Age: 20} ``` ## 优化器、索引提示 -优化器提示用于控制查询优化器选择某个查询执行计划,GORM 通过 `gorm.io/hints` 提供支持,例如: +GORM includes support for optimizer and index hints, allowing you to influence the query optimizer's execution plan. This can be particularly useful in optimizing query performance or when dealing with complex queries. + +Optimizer hints are directives that suggest how a database's query optimizer should execute a query. GORM facilitates the use of optimizer hints through the gorm.io/hints package. ```go import "gorm.io/hints" +// Using an optimizer hint to set a maximum execution time db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) -// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` +// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users` ``` -索引提示允许传递索引提示到数据库,以防查询计划器出现混乱。 +### Index Hints + +Index hints provide guidance to the database about which indexes to use. They can be beneficial if the query planner is not selecting the most efficient indexes for a query. ```go import "gorm.io/hints" +// Suggesting the use of a specific index db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) -// SELECT * FROM `users` USE INDEX (`idx_user_name`) +// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`) +// Forcing the use of certain indexes for a JOIN operation db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{}) -// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)" +// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`) ``` -参考 [优化器提示、索引、备注](hints.html) 获取详情 +These hints can significantly impact query performance and behavior, especially in large databases or complex data models. For more detailed information and additional examples, refer to [Optimizer Hints/Index/Comment](hints.html) in the GORM documentation. ## 迭代 -GORM 支持通过行进行迭代 +GORM supports the iteration over query results using the `Rows` method. This feature is particularly useful when you need to process large datasets or perform operations on each record individually. + +You can iterate through rows returned by a query, scanning each row into a struct. This method provides granular control over how each record is handled. ```go rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows() @@ -275,131 +318,176 @@ defer rows.Close() for rows.Next() { var user User - // ScanRows 方法用于将一行记录扫描至结构体 + // ScanRows scans a row into a struct db.ScanRows(rows, &user) - // 业务逻辑... + // Perform operations on each user } ``` +This approach is ideal for complex data processing that cannot be easily achieved with standard query methods. + ## FindInBatches -用于批量查询并处理记录 +`FindInBatches` allows querying and processing records in batches. This is especially useful for handling large datasets efficiently, reducing memory usage and improving performance. + +With `FindInBatches`, GORM processes records in specified batch sizes. Inside the batch processing function, you can apply operations to each batch of records. ```go -// 每次批量处理 100 条 +// Processing records in batches of 100 result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { for _, result := range results { - // 批量处理找到的记录 + // Operations on each record in the batch } + // Save changes to the records in the current batch tx.Save(&results) - tx.RowsAffected // 本次批量操作影响的记录数 + // tx.RowsAffected provides the count of records in the current batch + // The variable 'batch' indicates the current batch number - batch // Batch 1, 2, 3 - - // 如果返回错误会终止后续批量操作 + // Returning an error will stop further batch processing return nil }) -result.Error // returned error -result.RowsAffected // 整个批量操作影响的记录数 +// result.Error contains any errors encountered during batch processing +// result.RowsAffected provides the count of all processed records across batches ``` +`FindInBatches` is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance. + ## 查询钩子 -对于查询操作,GORM 支持 `AfterFind` 钩子,查询记录后会调用它,详情请参考 [钩子](hooks.html) +GORM offers the ability to use hooks, such as `AfterFind`, which are triggered during the lifecycle of a query. These hooks allow for custom logic to be executed at specific points, such as after a record has been retrieved from the databas. + +This hook is useful for post-query data manipulation or default value settings. For more detailed information and additional hook types, refer to [Hooks](hooks.html) in the GORM documentation. ```go func (u *User) AfterFind(tx *gorm.DB) (err error) { + // Custom logic after finding a user if u.Role == "" { - u.Role = "user" + u.Role = "user" // Set default role if not specified } return } + +// Usage of AfterFind hook happens automatically when a User is queried ``` ## Pluck -Pluck 用于从数据库查询单个列,并将结果扫描到切片。如果您想要查询多列,您应该使用 `Select` 和 [`Scan`](query.html#scan) +The `Pluck` method in GORM is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model. + +If you need to query more than one column, you can use `Select` with [Scan](query.html) or [Find](query.html) instead. ```go +// Retrieving ages of all users var ages []int64 -db.Model(&users).Pluck("age", &ages) +db.Model(&User{}).Pluck("age", &ages) +// Retrieving names of all users var names []string db.Model(&User{}).Pluck("name", &names) +// Retrieving names from a different table db.Table("deleted_users").Pluck("name", &names) -// Distinct Pluck +// Using Distinct with Pluck db.Model(&User{}).Distinct().Pluck("Name", &names) -// SELECT DISTINCT `name` FROM `users` +// SQL: SELECT DISTINCT `name` FROM `users` -// 超过一列的查询,应该使用 `Scan` 或者 `Find`,例如: +// Querying multiple columns db.Select("name", "age").Scan(&users) db.Select("name", "age").Find(&users) ``` ## Scope -`Scopes` 允许你指定常用的查询,可以在调用方法时引用这些查询 +`Scopes` in GORM are a powerful feature that allows you to define commonly-used query conditions as reusable methods. These scopes can be easily referenced in your queries, making your code more modular and readable. + +### Defining Scopes + +`Scopes` are defined as functions that modify and return a `gorm.DB` instance. You can define a variety of conditions as scopes based on your application's requirements. ```go +// Scope for filtering records where amount is greater than 1000 func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { return db.Where("amount > ?", 1000) } +// Scope for orders paid with a credit card func PaidWithCreditCard(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } +// Scope for orders paid with cash on delivery (COD) func PaidWithCod(db *gorm.DB) *gorm.DB { return db.Where("pay_mode_sign = ?", "C") } -func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB { - return func (db *gorm.DB) *gorm.DB { +// Scope for filtering orders by status +func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { return db.Where("status IN (?)", status) } } +``` +### Applying Scopes in Queries + +You can apply one or more scopes to a query by using the `Scopes` method. This allows you to chain multiple conditions dynamically. + +```go +// Applying scopes to find all credit card orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders) -// 查找所有金额大于 1000 的信用卡订单 +// Applying scopes to find all COD orders with an amount greater than 1000 db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders) -// 查找所有金额大于 1000 的货到付款订单 +// Applying scopes to find all orders with specific statuses and an amount greater than 1000 db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders) -// 查找所有金额大于 1000 且已付款或已发货的订单 ``` -查看 [Scopes](scopes.html) 获取详情 +`Scopes` are a clean and efficient way to encapsulate common query logic, enhancing the maintainability and readability of your code. For more detailed examples and usage, refer to [Scopes](scopes.html) in the GORM documentation. ## Count -Count 用于获取匹配的记录数 +The `Count` method in GORM is used to retrieve the number of records that match a given query. It's a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis. + +### Getting the Count of Matched Records + +You can use `Count` to determine the number of records that meet specific criteria in your queries. ```go var count int64 + +// Counting users with specific names db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2' +// Counting users with a single name condition db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) -// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) +// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' +// Counting records in a different table db.Table("deleted_users").Count(&count) -// SELECT count(1) FROM deleted_users; +// SQL: SELECT count(1) FROM deleted_users +``` + +### Count with Distinct and Group -// Count with Distinct +GORM also allows counting distinct values and grouping results. + +```go +// Counting distinct names db.Model(&User{}).Distinct("name").Count(&count) -// SELECT COUNT(DISTINCT(`name`)) FROM `users` +// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users` +// Counting distinct values with a custom select db.Table("deleted_users").Select("count(distinct(name))").Count(&count) -// SELECT count(distinct(name)) FROM deleted_users +// SQL: SELECT count(distinct(name)) FROM deleted_users -// Count with Group +// Counting grouped records users := []User{ {Name: "name1"}, {Name: "name2"}, @@ -408,5 +496,6 @@ users := []User{ } db.Model(&User{}).Group("name").Count(&count) -count // => 3 +// Count after grouping by name +// count => 3 ``` diff --git a/pages/zh_CN/docs/associations.md b/pages/zh_CN/docs/associations.md index d4092be4f0d..ff5f66e9e0b 100644 --- a/pages/zh_CN/docs/associations.md +++ b/pages/zh_CN/docs/associations.md @@ -5,7 +5,11 @@ layout: page ## 自动创建、更新 -在创建、更新记录时,GORM 会通过 [Upsert](create.html#upsert) 自动保存关联及其引用记录。 +GORM automates the saving of associations and their references when creating or updating records, using an upsert technique that primarily updates foreign key references for existing associations. + +### Auto-Saving Associations on Create + +When you create a new record, GORM will automatically save its associated data. This includes inserting data into related tables and managing foreign key references. ```go user := User{ @@ -22,6 +26,7 @@ user := User{ }, } +// Creating a user along with its associated addresses, emails, and languages db.Create(&user) // BEGIN TRANSACTION; // INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING; @@ -34,62 +39,75 @@ db.Create(&user) db.Save(&user) ``` -如果您想要更新关联的数据,您应该使用 ` FullSaveAssociations ` 模式: +### Updating Associations with `FullSaveAssociations` + +For scenarios where a full update of the associated data is required (not just the foreign key references), the `FullSaveAssociations` mode should be used. ```go +// Update a user and fully update all its associations db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) -// ... -// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1); -// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); -// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email); -// ... +// SQL: Fully updates addresses, users, emails tables, including existing associated records ``` +Using `FullSaveAssociations` ensures that the entire state of the model, including all its associations, is reflected in the database, maintaining data integrity and consistency throughout the application. + ## 跳过自动创建、更新 -若要在创建、更新时跳过自动保存,您可以使用 `Select` 或 `Omit`,例如: +GORM provides flexibility to skip automatic saving of associations during create or update operations. This can be achieved using the `Select` or `Omit` methods, which allow you to specify exactly which fields or associations should be included or excluded in the operation. + +### Using `Select` to Include Specific Fields + +The `Select` method lets you specify which fields of the model should be saved. This means that only the selected fields will be included in the SQL operation. ```go user := User{ - Name: "jinzhu", - BillingAddress: Address{Address1: "Billing Address - Address 1"}, - ShippingAddress: Address{Address1: "Shipping Address - Address 1"}, - Emails: []Email{ - {Email: "jinzhu@example.com"}, - {Email: "jinzhu-2@example.com"}, - }, - Languages: []Language{ - {Name: "ZH"}, - {Name: "EN"}, - }, + // User and associated data } +// Only include the 'Name' field when creating the user db.Select("Name").Create(&user) -// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2); +// SQL: INSERT INTO "users" (name) VALUES ("jinzhu"); +``` + +### Using `Omit` to Exclude Fields or Associations + +Conversely, `Omit` allows you to exclude certain fields or associations when saving a model. +```go +// Skip creating the 'BillingAddress' when creating the user db.Omit("BillingAddress").Create(&user) -// Skip create BillingAddress when creating a user +// Skip all associations when creating the user db.Omit(clause.Associations).Create(&user) -// Skip all associations when creating a user ``` {% note warn %} -**NOTE:** 对于 many2many 关联,GORM 在创建连接表引用之前,会先 upsert 关联。如果你想跳过关联的 upsert,你可以这样做: +**NOTE:** For many-to-many associations, GORM upserts the associations before creating join table references. To skip this upserting, use `Omit` with the association name followed by `.*`: ```go +// Skip upserting 'Languages' associations db.Omit("Languages.*").Create(&user) ``` -下面的代码将跳过创建关联及其引用 +To skip creating both the association and its references: ```go +// Skip creating 'Languages' associations and their references db.Omit("Languages").Create(&user) ``` {% endnote %} +Using `Select` and `Omit`, you can fine-tune how GORM handles the creation or updating of your models, giving you control over the auto-save behavior of associations. + ## Select/Omit 关联字段 +In GORM, when creating or updating records, you can use the `Select` and `Omit` methods to specifically include or exclude certain fields of an associated model. + +With `Select`, you can specify which fields of an associated model should be included when saving the primary model. This is particularly useful for selectively saving parts of an association. + +Conversely, `Omit` lets you exclude certain fields of an associated model from being saved. This can be useful when you want to prevent specific parts of an association from being persisted. + + ```go user := User{ Name: "jinzhu", @@ -97,49 +115,85 @@ user := User{ ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"}, } -// 创建 user 及其 BillingAddress、ShippingAddress -// 在创建 BillingAddress 时,仅使用其 address1、address2 字段,忽略其它字段 +// Create user and his BillingAddress, ShippingAddress, including only specified fields of BillingAddress db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user) +// SQL: Creates user and BillingAddress with only 'Address1' and 'Address2' fields +// Create user and his BillingAddress, ShippingAddress, excluding specific fields of BillingAddress db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user) +// SQL: Creates user and BillingAddress, omitting 'Address2' and 'CreatedAt' fields +``` + +## 删除关联 + +GORM allows for the deletion of specific associated relationships (has one, has many, many2many) using the `Select` method when deleting a primary record. This feature is particularly useful for maintaining database integrity and ensuring related data is appropriately managed upon deletion. + +You can specify which associations should be deleted along with the primary record by using `Select`. + +```go +// Delete a user's account when deleting the user +db.Select("Account").Delete(&user) + +// Delete a user's Orders and CreditCards associations when deleting the user +db.Select("Orders", "CreditCards").Delete(&user) + +// Delete all of a user's has one, has many, and many2many associations +db.Select(clause.Associations).Delete(&user) + +// Delete each user's account when deleting multiple users +db.Select("Account").Delete(&users) ``` +{% note warn %} +**NOTE:** It's important to note that associations will only be deleted if the primary key of the deleting record is not zero. GORM uses these primary keys as conditions to delete the selected associations. + +```go +// This will not work as intended +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) +// SQL: Deletes all users with name 'jinzhu', but their accounts won't be deleted + +// Correct way to delete a user and their account +db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) +// SQL: Deletes the user with name 'jinzhu' and ID '1', and the user's account + +// Deleting a user with a specific ID and their account +db.Select("Account").Delete(&User{ID: 1}) +// SQL: Deletes the user with ID '1', and the user's account +``` +{% endnote %} + ## 关联模式 -关联模式包含一些在处理关系时有用的方法 +Association Mode in GORM offers various helper methods to handle relationships between models, providing an efficient way to manage associated data. + +To start Association Mode, specify the source model and the relationship's field name. The source model must contain a primary key, and the relationship's field name should match an existing association. ```go -// 开始关联模式 var user User db.Model(&user).Association("Languages") -// `user` 是源模型,它的主键不能为空 -// 关系的字段名是 `Languages` -// 如果匹配了上面两个要求,会开始关联模式,否则会返回错误 -db.Model(&user).Association("Languages").Error +// Check for errors +error := db.Model(&user).Association("Languages").Error ``` -### 查找关联 +### Finding Associations -查找所有匹配的关联记录 +Retrieve associated records with or without additional conditions. ```go +// Simple find db.Model(&user).Association("Languages").Find(&languages) -``` - -查找带条件的关联 -```go +// Find with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages) - -db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages) ``` -### 添加关联 +### Appending Associations -为 `many to many`、`has many` 添加新的关联;为 `has one`, `belongs to` 替换当前的关联 +Add new associations for `many to many`, `has many`, or replace the current association for `has one`, `belongs to`. ```go +// Append new languages db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) @@ -147,128 +201,121 @@ db.Model(&user).Association("Languages").Append(&Language{Name: "DE"}) db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"}) ``` -### 替换关联 +### Replacing Associations -用一个新的关联替换当前的关联 +Replace current associations with new ones. ```go +// Replace existing languages db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN}) db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN) ``` -### 删除关联 +### Deleting Associations -如果存在,则删除源模型与参数之间的关系,只会删除引用,不会从数据库中删除这些对象。 +Remove the relationship between the source and arguments, only deleting the reference. ```go +// Delete specific languages db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) + db.Model(&user).Association("Languages").Delete(languageZH, languageEN) ``` -### 清空关联 +### Clearing Associations -删除源模型与关联之间的所有引用,但不会删除这些关联 +Remove all references between the source and association. ```go +// Clear all languages db.Model(&user).Association("Languages").Clear() ``` -### 关联计数 +### Counting Associations -返回当前关联的计数 +Get the count of current associations, with or without conditions. ```go +// Count all languages db.Model(&user).Association("Languages").Count() -// 条件计数 +// Count with conditions codes := []string{"zh-CN", "en-US", "ja-JP"} db.Model(&user).Where("code IN ?", codes).Association("Languages").Count() ``` -### 批量处理数据 +### Batch Data Handling + +Association Mode allows you to handle relationships for multiple records in a batch. This includes finding, appending, replacing, deleting, and counting operations for associated data. -关联模式也支持批量处理,例如: +- **Finding Associations**: Retrieve associated data for a collection of records. ```go -// 查询所有用户的所有角色 db.Model(&users).Association("Role").Find(&roles) +``` -// 从所有 team 中删除 user A +- **Deleting Associations**: Remove specific associations across multiple records. + +```go db.Model(&users).Association("Team").Delete(&userA) +``` -// 获取去重的用户所属 team 数量 +- **Counting Associations**: Get the count of associations for a batch of records. + +```go db.Model(&users).Association("Team").Count() +``` -// 对于批量数据的 `Append`、`Replace`,参数的长度必须与数据的长度相同,否则会返回 error +- **Appending/Replacing Associations**: Manage associations for multiple records. Note the need for matching argument lengths with the data. + +```go var users = []User{user1, user2, user3} -// 例如:现在有三个 user,Append userA 到 user1 的 team,Append userB 到 user2 的 team,Append userA、userB 和 userC 到 user3 的 team + +// Append different teams to different users in a batch +// Append userA to user1's team, userB to user2's team, and userA, userB, userC to user3's team db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC}) -// 重置 user1 team 为 userA,重置 user2 的 team 为 userB,重置 user3 的 team 为 userA、 userB 和 userC + +// Replace teams for multiple users in a batch +// Reset user1's team to userA, user2's team to userB, and user3's team to userA, userB, and userC db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC}) ``` ## 删除关联记录 -默认情况下, ` gorm.Association 中的 Replace `/` Delete `/` Clear ` 操作只会删除关联引用,也就是将旧的关联的外键设置为null。 - -您可以使用 `Unscoped` 来删除这些对象(与 `ManyToMany` 无关)。 +In GORM, the `Replace`, `Delete`, and `Clear` methods in Association Mode primarily affect the foreign key references, not the associated records themselves. Understanding and managing this behavior is crucial for data integrity. -删除操作由 `gorm.DB` 决定。 +- **Reference Update**: These methods update the association's foreign key to null, effectively removing the link between the source and associated models. +- **No Physical Record Deletion**: The actual associated records remain untouched in the database. -```go -// 软删除 -// UPDATE `languages` SET `deleted_at`= ... -db.Model(&user).Association("Languages").Unscoped().Clear() +### Modifying Deletion Behavior with `Unscoped` -// 永久删除 -// DELETE FROM `languages` WHERE ... -db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear() -``` +For scenarios requiring actual deletion of associated records, the `Unscoped` method alters this behavior. -## 使用选择删除 - -您可以在删除记录时通过 `Select` 来删除具有 has one、has many、many2many 关系的记录,例如: +- **Soft Delete**: Marks associated records as deleted (sets `deleted_at` field) without removing them from the database. ```go -// 删除 user 时,也删除 user 的 account -db.Select("Account").Delete(&user) - -// 删除 user 时,也删除 user 的 Orders、CreditCards 记录 -db.Select("Orders", "CreditCards").Delete(&user) - -// 删除 user 时,也删除用户所有 has one/many、many2many 记录 -db.Select(clause.Associations).Delete(&user) - -// 删除 users 时,也删除每一个 user 的 account -db.Select("Account").Delete(&users) +db.Model(&user).Association("Languages").Unscoped().Clear() ``` -{% note warn %} -**注意:**只有在待删除记录的主键不为零时,关联关系才会被删除。GORM会将这些主键作为条件来删除选定的关联关系。 +- **Permanent Delete**: Physically deletes the association records from the database. ```go -// 不会起作用 -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{}) -// 会删除所有 name=`jinzhu` 的 user,但这些 user 的 account 不会被删除 - -db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1}) -// 会删除 name = `jinzhu` 且 id = `1` 的 user,并且 user `1` 的 account 也会被删除 - -db.Select("Account").Delete(&User{ID: 1}) -// 会删除 id = `1` 的 user,并且 user `1` 的 account 也会被删除 +// db.Unscoped().Model(&user) +db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear() ``` -{% endnote %} ## 关联标签(Association Tags) -| 标签 | 描述 | -| ---------------- | ----------------------------- | -| foreignKey | 指定当前模型的列作为连接表的外键 | -| references | 指定引用表的列名,其将被映射为连接表外键 | -| polymorphic | 指定多态类型,比如模型名 | -| polymorphicValue | 指定多态值、默认表名 | -| many2many | 指定连接表表名 | -| joinForeignKey | 指定连接表的外键列名,其将被映射到当前表 | -| joinReferences | 指定连接表的外键列名,其将被映射到引用表 | -| constraint | 关系约束,例如:`OnUpdate`、`OnDelete` | +Association tags in GORM are used to specify how associations between models are handled. These tags define the relationship's details, such as foreign keys, references, and constraints. Understanding these tags is essential for setting up and managing relationships effectively. + +| 标签 | 描述 | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `foreignKey` | Specifies the column name of the current model used as a foreign key in the join table. | +| `references` | Indicates the column name in the reference table that the foreign key of the join table maps to. | +| `polymorphic` | Defines the polymorphic type, typically the model name. | +| `polymorphicValue` | Sets the polymorphic value, usually the table name, if not specified otherwise. | +| `many2many` | Names the join table used in a many-to-many relationship. | +| `joinForeignKey` | Identifies the foreign key column in the join table that maps back to the current model's table. | +| `joinReferences` | Points to the foreign key column in the join table that links to the reference model's table. | +| `constraint` | Specifies relational constraints like `OnUpdate`, `OnDelete` for the association. | diff --git a/pages/zh_CN/docs/connecting_to_the_database.md b/pages/zh_CN/docs/connecting_to_the_database.md index 45e231b7c49..b80d4059570 100644 --- a/pages/zh_CN/docs/connecting_to_the_database.md +++ b/pages/zh_CN/docs/connecting_to_the_database.md @@ -212,7 +212,7 @@ import ( ) func main() { - dsn := "tcp://localhost:9000?database=gorm&username=gorm&password=gorm&read_timeout=10&write_timeout=20" + dsn := "clickhouse://gorm:gorm@localhost:9942/gorm?dial_timeout=10s&read_timeout=20s" db, err := gorm.Open(clickhouse.Open(dsn), &gorm.Config{}) // 自动迁移 (这是GORM自动创建表的一种方式--译者注) @@ -252,8 +252,8 @@ sqlDB.SetConnMaxLifetime(time.Hour) 查看 [通用接口](generic_interface.html) 获取详情。 -## Unsupported Databases +## 还未支持的数据库 -Some databases may be compatible with the `mysql` or `postgres` dialect, in which case you could just use the dialect for those databases. +有些数据库可能兼容 `mysql`、`postgres` 的方言,在这种情况下,你可以直接使用这些数据库的方言。 -For others, [you are encouraged to make a driver, pull request welcome!](write_driver.html) +对于其他还未支持的数据库驱动,我们鼓励开发者积极的提交更多类型的数据库驱动! diff --git a/pages/zh_CN/docs/context.md b/pages/zh_CN/docs/context.md index eeea051144f..e849d33a376 100644 --- a/pages/zh_CN/docs/context.md +++ b/pages/zh_CN/docs/context.md @@ -3,19 +3,19 @@ title: Context layout: page --- -GORM 通过 `WithContext` 方法提供了 Context 支持 +GORM's context support, enabled by the `WithContext` method, is a powerful feature that enhances the flexibility and control of database operations in Go applications. It allows for context management across different operational modes, timeout settings, and even integration into hooks/callbacks and middlewares. Let's delve into these various aspects: -## 单会话模式 +### 单会话模式 -单会话模式通常被用于执行单次操作 +Single session mode is appropriate for executing individual operations. It ensures that the specific operation is executed within the context's scope, allowing for better control and monitoring. ```go db.WithContext(ctx).Find(&users) ``` -## 持续会话模式 +### Continuous Session Mode -持续会话模式通常被用于执行一系列操作,例如: +Continuous session mode is ideal for performing a series of related operations. It maintains the context across these operations, which is particularly useful in scenarios like transactions. ```go tx := db.WithContext(ctx) @@ -23,9 +23,9 @@ tx.First(&user, 1) tx.Model(&user).Update("role", "admin") ``` -## Context 超时 +### Context Timeout -对于长 Sql 查询,你可以传入一个带超时的 context 给 `db.WithContext` 来设置超时时间,例如: +Setting a timeout on the context passed to `db.WithContext` can control the duration of long-running queries. This is crucial for maintaining performance and avoiding resource lock-ups in database interactions. ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -34,23 +34,21 @@ defer cancel() db.WithContext(ctx).Find(&users) ``` -## Hooks/Callbacks 中的 Context +### Hooks/Callbacks 中的 Context -您可以从当前 `Statement`中访问 `Context` 对象,例如︰ +The context can also be accessed within GORM's hooks/callbacks. This enables contextual information to be used during these lifecycle events. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { ctx := tx.Statement.Context - // ... + // ... use context return } ``` -## Chi 中间件示例 +### Integration with Chi Middleware -在处理 API 请求时持续会话模式会比较有用。例如,您可以在中间件中为 `*gorm.DB` 设置超时 Context,然后使用 `*gorm.DB` 处理所有请求 - -下面是一个 Chi 中间件的示例: +GORM's context support extends to web server middlewares, such as those in the Chi router. This allows setting a context with a timeout for all database operations within the scope of a web request. ```go func SetDBMiddleware(next http.Handler) http.Handler { @@ -61,32 +59,26 @@ func SetDBMiddleware(next http.Handler) http.Handler { }) } +// Router setup r := chi.NewRouter() r.Use(SetDBMiddleware) +// Route handlers r.Get("/", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var users []User - db.Find(&users) - - // 你的其他 DB 操作... + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) r.Get("/user", func(w http.ResponseWriter, r *http.Request) { - db, ok := ctx.Value("DB").(*gorm.DB) - - var user User - db.First(&user) - - // 你的其他 DB 操作... + db, ok := r.Context().Value("DB").(*gorm.DB) + // ... db operations }) ``` -{% note %} -**注意** 通过 `WithContext` 设置的 `Context` 是线程安全的,参考[会话](session.html)获取详情 -{% endnote %} +**Note**: Setting the `Context` with `WithContext` is goroutine-safe. This ensures that database operations are safely managed across multiple goroutines. For more details, refer to the [Session documentation](session.html) in GORM. + +### Logger Integration -## Logger +GORM's logger also accepts `Context`, which can be used for log tracking and integrating with existing logging infrastructures. -Logger 也可以支持 `Context`,可用于日志追踪,查看 [Logger](logger.html) 获取详情 +Refer to [Logger documentation](logger.html) for more details. diff --git a/pages/zh_CN/docs/conventions.md b/pages/zh_CN/docs/conventions.md index 3ac23178d99..c3fd8f7752a 100644 --- a/pages/zh_CN/docs/conventions.md +++ b/pages/zh_CN/docs/conventions.md @@ -86,7 +86,7 @@ db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{}) ### 命名策略 -GORM 允许用户通过覆盖默认的`命名策略`更改默认的命名约定,命名策略被用于构建: `TableName`、`ColumnName`、`JoinTableName`、`RelationshipFKName`、`CheckerName`、`IndexName`。查看 [GORM 配置](gorm_config.html#naming_strategy) 获取详情 +GORM allows users to change the default naming conventions by overriding the default `NamingStrategy`, which is used to build `TableName`, `ColumnName`, `JoinTableName`, `RelationshipFKName`, `CheckerName`, `IndexName`, Check out [GORM Config](gorm_config.html#naming_strategy) for details ## 列名 diff --git a/pages/zh_CN/docs/create.md b/pages/zh_CN/docs/create.md index 7a5da1aa5ab..86928fe9286 100644 --- a/pages/zh_CN/docs/create.md +++ b/pages/zh_CN/docs/create.md @@ -50,7 +50,7 @@ db.Omit("Name", "Age", "CreatedAt").Create(&user) ## 批量插入 -要高效地插入大量记录,请将切片传递给`Create`方法。 GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too. 当记录可以分成多个批处理时,它将开始一个 **交易**。 +要高效地插入大量记录,请将切片传递给`Create`方法。 GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 `Hook` 方法 It will begin a **transaction** when records can be split into multiple batches. ```go var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} diff --git a/pages/zh_CN/docs/dbresolver.md b/pages/zh_CN/docs/dbresolver.md index 88e19142a83..177405a2237 100644 --- a/pages/zh_CN/docs/dbresolver.md +++ b/pages/zh_CN/docs/dbresolver.md @@ -95,14 +95,14 @@ db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).First(&user) 但您可以在事务开始之前指定使用哪个数据库,例如: ```go -// 通过默认 replicas db 开始事务 -tx := DB.Clauses(dbresolver.Read).Begin() +// Start transaction based on default replicas db +tx := db.Clauses(dbresolver.Read).Begin() -// 通过默认 sources db 开始事务 -tx := DB.Clauses(dbresolver.Write).Begin() +// Start transaction based on default sources db +tx := db.Clauses(dbresolver.Write).Begin() -// 通过 `secondary` 的 sources db 开始事务 -tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() +// Start transaction based on `secondary`'s sources +tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin() ``` ## 负载均衡 diff --git a/pages/zh_CN/docs/error_handling.md b/pages/zh_CN/docs/error_handling.md index a87180366d1..cc3660f2d76 100644 --- a/pages/zh_CN/docs/error_handling.md +++ b/pages/zh_CN/docs/error_handling.md @@ -3,47 +3,101 @@ title: 处理错误 layout: page --- -在 Go 中,处理错误是很重要的。 +Effective error handling is a cornerstone of robust application development in Go, particularly when interacting with databases using GORM. GORM's approach to error handling, influenced by its chainable API, requires a nuanced understanding. -我们鼓励您在调用任何 [Finisher 方法](method_chaining.html#finisher_method) 后,都进行错误检查 +## Basic Error Handling -## 处理错误 +GORM integrates error handling into its chainable method syntax. The `*gorm.DB` instance contains an `Error` field, which is set when an error occurs. The common practice is to check this field after executing database operations, especially after [Finisher Methods](method_chaining.html#finisher_method). -GORM 的错误处理与常见的 Go 代码不同,因为 GORM 提供的是链式 API。 - -如果遇到任何错误,GORM 会设置 `*gorm.DB` 的 `Error` 字段,您需要像这样检查它: +After a chain of methods, it's crucial to check the `Error` field: ```go if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { - // 处理错误... + // Handle error... } ``` -或者 +Or alternatively: ```go if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil { - // 处理错误... + // Handle error... } ``` -## ErrRecordNotFound +## `ErrRecordNotFound` -当 `First`、`Last`、`Take` 方法找不到记录时,GORM 会返回 `ErrRecordNotFound` 错误。如果发生了多个错误,你可以通过 `errors.Is` 判断错误是否为 `ErrRecordNotFound`,例如: +GORM returns `ErrRecordNotFound` when no record is found using methods like `First`, `Last`, `Take`. ```go -// 检查错误是否为 RecordNotFound err := db.First(&user, 100).Error -errors.Is(err, gorm.ErrRecordNotFound) +if errors.Is(err, gorm.ErrRecordNotFound) { + // Handle record not found error... +} ``` -## 翻译方言错误 -如果您希望将数据库的方言错误转换为gorm的错误类型(例如将MySQL中的“Duplicate entry”转换为ErrDuplicatedKey),则在打开数据库连接时启用TranslateError标志。 +## Handling Error Codes + +Many databases return errors with specific codes, which can be indicative of various issues like constraint violations, connection problems, or syntax errors. Handling these error codes in GORM requires parsing the error returned by the database and extracting the relevant code + +- **Example: Handling MySQL Error Codes** + +```go +import ( + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" +) + +// ... + +result := db.Create(&newRecord) +if result.Error != nil { + if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { + switch mysqlErr.Number { + case 1062: // MySQL code for duplicate entry + // Handle duplicate entry + // Add cases for other specific error codes + default: + // Handle other errors + } + } else { + // Handle non-MySQL errors or unknown errors + } +} +``` + +## Dialect Translated Errors + +GORM can return specific errors related to the database dialect being used, when `TranslateError` is enabled, GORM converts database-specific errors into its own generalized errors. ```go db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true}) ``` +- **ErrDuplicatedKey** + +This error occurs when an insert operation violates a unique constraint: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrDuplicatedKey) { + // Handle duplicated key error... +} +``` + +- **ErrForeignKeyViolated** + +This error is encountered when a foreign key constraint is violated: + +```go +result := db.Create(&newRecord) +if errors.Is(result.Error, gorm.ErrForeignKeyViolated) { + // Handle foreign key violation error... +} +``` + +By enabling `TranslateError`, GORM provides a more unified way of handling errors across different databases, translating database-specific errors into common GORM error types. + ## Errors -[Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) +For a complete list of errors that GORM can return, refer to the [Errors List](https://github.com/go-gorm/gorm/blob/master/errors.go) in GORM's documentation. diff --git a/pages/zh_CN/docs/many_to_many.md b/pages/zh_CN/docs/many_to_many.md index ff249c7402f..877f4ea11cf 100644 --- a/pages/zh_CN/docs/many_to_many.md +++ b/pages/zh_CN/docs/many_to_many.md @@ -137,7 +137,7 @@ GORM 可以通过 `Preload` 预加载 has many 关联的记录,查看 [预加 type Person struct { ID int Name string - Addresses []Address `gorm:"many2many:person_addressses;"` + Addresses []Address `gorm:"many2many:person_addresses;"` } type Address struct { diff --git a/pages/zh_CN/docs/method_chaining.md b/pages/zh_CN/docs/method_chaining.md index 3df7a146e3d..59c2e019344 100644 --- a/pages/zh_CN/docs/method_chaining.md +++ b/pages/zh_CN/docs/method_chaining.md @@ -3,123 +3,158 @@ title: 链式方法 layout: page --- -GORM 允许进行链式操作,所以您可以像这样写代码: +GORM's method chaining feature allows for a smooth and fluent style of coding. Here's an example: ```go db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user) ``` -GORM 中有三种类型的方法: `链式方法`、`终结方法`、`新建会话方法` +## Method Categories -在 `链式方法`, `终结方法`之后, GORM 返回一个初始化的 `*gorm.DB` 实例,实例不能安全地重复使用,并且新生成的 SQL 可能会被先前的条件污染,例如: +GORM organizes methods into three primary categories: `Chain Methods`, `Finisher Methods`, and `New Session Methods`. + +### Chain Methods + +Chain methods are used to modify or append `Clauses` to the current `Statement`. Some common chain methods include: + +- `Where` +- `Select` +- `Omit` +- `Joins` +- `Scopes` +- `Preload` +- `Raw` (Note: `Raw` cannot be used in conjunction with other chainable methods to build SQL) + +For a comprehensive list, visit [GORM Chainable API](https://github.com/go-gorm/gorm/blob/master/chainable_api.go). Also, the [SQL Builder](sql_builder.html) documentation offers more details about `Clauses`. + +### Finisher Methods + +Finisher methods are immediate, executing registered callbacks that generate and run SQL commands. This category includes methods: + +- `Create` +- `First` +- `Find` +- `Take` +- `Save` +- `Update` +- `Delete` +- `Scan` +- `Row` +- `Rows` + +For the full list, refer to [GORM Finisher API](https://github.com/go-gorm/gorm/blob/master/finisher_api.go). + +### New Session Methods + +GORM defines methods like `Session`, `WithContext`, and `Debug` as New Session Methods, which are essential for creating shareable and reusable `*gorm.DB` instances. For more details, see [Session](session.html) documentation. + +## Reusability and Safety + +A critical aspect of GORM is understanding when a `*gorm.DB` instance is safe to reuse. Following a `Chain Method` or `Finisher Method`, GORM returns an initialized `*gorm.DB` instance. This instance is not safe for reuse as it may carry over conditions from previous operations, potentially leading to contaminated SQL queries. For example: + +### Example of Unsafe Reuse ```go queryDB := DB.Where("name = ?", "jinzhu") +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query with unintended compounded condition queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20 ``` -为了重新使用初始化的 `*gorm.DB` 实例, 您可以使用 `新建会话方法` 创建一个可共享的 `*gorm.DB`, 例如: +### Example of Safe Reuse + +To safely reuse a `*gorm.DB` instance, use a New Session Method: ```go queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{}) +// First query queryDB.Where("age > ?", 10).First(&user) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 +// Second query, safely isolated queryDB.Where("age > ?", 20).First(&user2) -// SELECT * FROM users WHERE name = "jinzhu" AND age > 20 +// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 20 ``` -## 链式方法 +In this scenario, using `Session(&gorm.Session{})` ensures that each query starts with a fresh context, preventing the pollution of SQL queries with conditions from previous operations. This is crucial for maintaining the integrity and accuracy of your database interactions. -链式方法是将 `Clauses` 修改或添加到当前 `Statement` 的方法,例如: +## Examples for Clarity -`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw` (`Raw` can't be used with other chainable methods to build SQL)... +Let's clarify with a few examples: -这是 [完整方法列表](https://github.com/go-gorm/gorm/blob/master/chainable_api.go),也可以查看 [SQL 构建器](sql_builder.html) 获取更多关于 `Clauses` 的信息 - -## 终结方法 - -终结(方法) 是会立即执行注册回调的方法,然后生成并执行 SQL,比如这些方法: - -`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`... - -查看[完整方法列表](https://github.com/go-gorm/gorm/blob/master/finisher_api.go) - -## 新建会话方法 - -GORM 定义了 `Session`、`WithContext`、`Debug` 方法做为 `新建会话方法`,查看[会话](session.html) 获取详情. - -在 `链式方法`, `Finisher 方法`之后, GORM 返回一个初始化的 `*gorm.DB` 实例,不能安全地再使用。您应该使用 `新建会话方法` 来标记 `*gorm.DB` 为可共享。 - -让我们用实例来解释它: - -示例 1: +- **Example 1: Safe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized `*gorm.DB`, which is safe to reuse +// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users) -// `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement` -// `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL: +// The first `Where("name = ?", "jinzhu")` call is a chain method that initializes a `*gorm.DB` instance, or `*gorm.Statement`. +// The second `Where("age = ?", 18)` call adds a new condition to the existing `*gorm.Statement`. +// `Find(&users)` is a finisher method, executing registered Query Callbacks, generating and running: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18; db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users) -// `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement` -// `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it -// `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL: +// Here, `Where("name = ?", "jinzhu2")` starts a new chain, creating a fresh `*gorm.Statement`. +// `Where("age = ?", 20)` adds to this new statement. +// `Find(&users)` again finalizes the query, executing and generating: // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20; db.Find(&users) -// `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL: +// Directly calling `Find(&users)` without any `Where` starts a new chain and executes: // SELECT * FROM users; ``` -(错误的) 示例2: +In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries. + +- **(Bad) Example 2: Unsafe Instance Reuse** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe for initial reuse. tx := db.Where("name = ?", "jinzhu") -// `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse +// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) -// `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it -// `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL: +// Reuses 'tx' correctly for a single logical operation, executing: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// bad case +// Bad case tx.Where("age = ?", 28).Find(&users) -// `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it -// So the following generated SQL is polluted by the previous conditions: +// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query: // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28; ``` -示例 3: +In this bad example, reusing the `tx` variable leads to compounded conditions, which is generally not desirable. + +- **Example 3: Safe Reuse with New Session Methods** ```go db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) -// db is a new initialized *gorm.DB, which is safe to reuse +// 'db' is a newly initialized *gorm.DB, safe to reuse. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{}) tx := db.Where("name = ?", "jinzhu").WithContext(context.Background()) tx := db.Where("name = ?", "jinzhu").Debug() -// `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions +// `Session`, `WithContext`, `Debug` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions. -// good case +// Good case tx.Where("age = ?", 18).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 -// good case +// Good case tx.Where("age = ?", 28).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28; ``` + +In this example, using New Session Methods `Session`, `WithContext`, `Debug` correctly initializes a `*gorm.DB` instance for each logical operation, preventing condition pollution and ensuring each query is distinct and based on the specific conditions provided. + +Overall, these examples illustrate the importance of understanding GORM's behavior with respect to method chaining and instance management to ensure accurate and efficient database querying. diff --git a/pages/zh_CN/docs/models.md b/pages/zh_CN/docs/models.md index c331b2a0716..21ef55ceb50 100644 --- a/pages/zh_CN/docs/models.md +++ b/pages/zh_CN/docs/models.md @@ -3,35 +3,52 @@ title: 模型定义 layout: page --- +GORM simplifies database interactions by mapping Go structs to database tables. Understanding how to declare models in GORM is fundamental for leveraging its full capabilities. + ## 模型定义 -模型是标准的 struct,由 Go 的基本数据类型、实现了 [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) 和 [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) 接口的自定义类型及其指针或别名组成 +Models are defined using normal structs. These structs can contain fields with basic Go types, pointers or aliases of these types, or even custom types, as long as they implement the [Scanner](https://pkg.go.dev/database/sql/?tab=doc#Scanner) and [Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interfaces from the `database/sql` package -例如: +Consider the following example of a `User` model: ```go type User struct { - ID uint - Name string - Email *string - Age uint8 - Birthday *time.Time - MemberNumber sql.NullString - ActivatedAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time + ID uint // Standard field for the primary key + Name string // A regular string field + Email *string // A pointer to a string, allowing for null values + Age uint8 // An unsigned 8-bit integer + Birthday *time.Time // A pointer to time.Time, can be null + MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings + ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields + CreatedAt time.Time // Automatically managed by GORM for creation time + UpdatedAt time.Time // Automatically managed by GORM for update time } ``` -## 约定 +In this model: + +- Basic data types like `uint`, `string`, and `uint8` are used directly. +- Pointers to types like `*string` and `*time.Time` indicate nullable fields. +- `sql.NullString` and `sql.NullTime` from the `database/sql` package are used for nullable fields with more control. +- `CreatedAt` and `UpdatedAt` are special fields that GORM automatically populates with the current time when a record is created or updated. + +In addition to the fundamental features of model declaration in GORM, it's important to highlight the support for serialization through the serializer tag. This feature enhances the flexibility of how data is stored and retrieved from the database, especially for fields that require custom serialization logic, See [Serializer](serializer.html) for a detailed explanation + +### 约定 -GORM 倾向于约定优于配置 默认情况下,GORM 使用 `ID` 作为主键,使用结构体名的 `蛇形复数` 作为表名,字段名的 `蛇形` 作为列名,并使用 `CreatedAt`、`UpdatedAt` 字段追踪创建、更新时间 +1. **Primary Key**: GORM uses a field named `ID` as the default primary key for each model. -如果您遵循 GORM 的约定,您就可以少写的配置、代码。 如果约定不符合您的实际要求,[GORM 允许你配置它们](conventions.html) +2. **Table Names**: By default, GORM converts struct names to `snake_case` and pluralizes them for table names. For instance, a `User` struct becomes `users` in the database. -## gorm.Model +3. **Column Names**: GORM automatically converts struct field names to `snake_case` for column names in the database. -GORM 定义一个 `gorm.Model` 结构体,其包括字段 `ID`、`CreatedAt`、`UpdatedAt`、`DeletedAt` +4. **Timestamp Fields**: GORM uses fields named `CreatedAt` and `UpdatedAt` to automatically track the creation and update times of records. + +Following these conventions can greatly reduce the amount of configuration or code you need to write. However, GORM is also flexible, allowing you to customize these settings if the default conventions don't fit your requirements. You can learn more about customizing these conventions in GORM's documentation on [conventions](conventions.html). + +### `gorm.Model` + +GORM provides a predefined struct named `gorm.Model`, which includes commonly used fields: ```go // gorm.Model 的定义 @@ -43,7 +60,13 @@ type Model struct { } ``` -您可以将它嵌入到您的结构体中,以包含这几个字段,详情请参考 [嵌入结构体](#embedded_struct) +- **Embedding in Your Struct**: You can embed `gorm.Model` directly in your structs to include these fields automatically. This is useful for maintaining consistency across different models and leveraging GORM's built-in conventions, refer [Embedded Struct](#embedded_struct) + +- **Fields Included**: + - `ID`: A unique identifier for each record (primary key). + - `CreatedAt`: Automatically set to the current time when a record is created. + - `UpdatedAt`: Automatically updated to the current time whenever a record is updated. + - `DeletedAt`: Used for soft deletes (marking records as deleted without actually removing them from the database). ## 高级选项 diff --git a/pages/zh_CN/docs/security.md b/pages/zh_CN/docs/security.md index ffc500c2647..4b646875ec1 100644 --- a/pages/zh_CN/docs/security.md +++ b/pages/zh_CN/docs/security.md @@ -37,14 +37,14 @@ db.First(&user, fmt.Sprintf("name = %v", userInput)) ```go userInputID := "1=1;drop table users;" -// 安全的,返回 err -id,err := strconv.Atoi(userInputID) +// safe, return error +id, err := strconv.Atoi(userInputID) if err != nil { - return error + return err } db.First(&user, id) -// SQL 注入 +// SQL injection db.First(&user, userInputID) // SELECT * FROM users WHERE 1=1;drop table users; ``` diff --git a/pages/zh_CN/docs/transactions.md b/pages/zh_CN/docs/transactions.md index 85ab19d5f80..8e2a9c0662a 100644 --- a/pages/zh_CN/docs/transactions.md +++ b/pages/zh_CN/docs/transactions.md @@ -54,8 +54,8 @@ db.Transaction(func(tx *gorm.DB) error { return errors.New("rollback user2") // Rollback user2 }) - tx.Transaction(func(tx2 *gorm.DB) error { - tx2.Create(&user3) + tx.Transaction(func(tx3 *gorm.DB) error { + tx3.Create(&user3) return nil }) diff --git a/pages/zh_CN/docs/update.md b/pages/zh_CN/docs/update.md index 1de1d869f5f..0dc0b42c844 100644 --- a/pages/zh_CN/docs/update.md +++ b/pages/zh_CN/docs/update.md @@ -16,7 +16,7 @@ db.Save(&user) // UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111; ``` -`Save` is a combination function. If save value does not contain primary key, it will execute `Create`, otherwise it will execute `Update` (with all fields). +`保存` 是一个组合函数。 如果保存值不包含主键,它将执行 `Create`,否则它将执行 `Update` (包含所有字段)。 ```go db.Save(&User{Name: "jinzhu", Age: 100}) @@ -27,72 +27,72 @@ db.Save(&User{ID: 1, Name: "jinzhu", Age: 100}) ``` {% note warn %} -**NOTE** Don't use `Save` with `Model`, it's an **Undefined Behavior**. +**NOTE**不要将 `Save` 和 `Model`一同使用, 这是 **为定义的行为**。 {% endnote %} ## 更新单个列 -When updating a single column with `Update`, it needs to have any conditions or it will raise error `ErrMissingWhereClause`, checkout [Block Global Updates](#block_global_updates) for details. When using the `Model` method and its value has a primary value, the primary key will be used to build the condition, for example: +当使用 `Update` 更新单列时,需要有一些条件,否则将会引起`ErrMissingWhereClause` 错误,查看 [阻止全局更新](#block_global_updates) 了解详情。 当使用 `Model` 方法,并且它有主键值时,主键将会被用于构建条件,例如: ```go -// Update with conditions +// 根据条件更新 db.Model(&User{}).Where("active = ?", true).Update("name", "hello") // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true; -// User's ID is `111`: +// User 的 ID 是 `111` db.Model(&user).Update("name", "hello") // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111; -// Update with conditions and model value +// 根据条件和 model 的值进行更新 db.Model(&user).Where("active = ?", true).Update("name", "hello") // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true; ``` ## 更新多列 -`Updates` supports updating with `struct` or `map[string]interface{}`, when updating with `struct` it will only update non-zero fields by default +`Updates` 方法支持 `struct` 和 `map[string]interface{}` 参数。当使用 `struct` 更新时,默认情况下GORM 只会更新非零值的字段 ```go -// Update attributes with `struct`, will only update non-zero fields +// 根据 `struct` 更新属性,只会更新非零值的字段 db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false}) // UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111; -// Update attributes with `map` +// 根据 `map` 更新属性 db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) // UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111; ``` {% note warn %} -**NOTE** When updating with struct, GORM will only update non-zero fields. You might want to use `map` to update attributes or use `Select` to specify fields to update +**注意** 使用 struct 更新时, GORM 将只更新非零值字段。 你可能想用 `map` 来更新属性,或者使用 `Select` 声明字段来更新 {% endnote %} ## 更新选定字段 -If you want to update selected fields or ignore some fields when updating, you can use `Select`, `Omit` +如果您想要在更新时选择、忽略某些字段,您可以使用 `Select`、`Omit` ```go -// Select with Map -// User's ID is `111`: +// 选择 Map 的字段 +// User 的 ID 是 `111`: db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) // UPDATE users SET name='hello' WHERE id=111; db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111; -// Select with Struct (select zero value fields) +// 选择 Struct 的字段(会选中零值的字段) db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0}) // UPDATE users SET name='new_name', age=0 WHERE id=111; -// Select all fields (select all fields include zero value fields) +// 选择所有字段(选择包括零值字段的所有字段) db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0}) -// Select all fields but omit Role (select all fields include zero value fields) +// 选择除 Role 外的所有字段(包括零值字段的所有字段) db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0}) ``` ## 更新 Hook -GORM allows the hooks `BeforeSave`, `BeforeUpdate`, `AfterSave`, `AfterUpdate`. Those methods will be called when updating a record, refer [Hooks](hooks.html) for details +GORM 支持的 hook 包括:`BeforeSave`, `BeforeUpdate`, `AfterSave`, `AfterUpdate`. 更新记录时将调用这些方法,查看 [Hooks](hooks.html) 获取详细信息 ```go func (u *User) BeforeUpdate(tx *gorm.DB) (err error) { diff --git a/pages/zh_CN/docs/write_driver.md b/pages/zh_CN/docs/write_driver.md index e06b6d09178..b1ea8394dd4 100644 --- a/pages/zh_CN/docs/write_driver.md +++ b/pages/zh_CN/docs/write_driver.md @@ -3,25 +3,101 @@ title: 编写驱动 layout: page --- -## 编写新驱动 +GORM offers built-in support for popular databases like `SQLite`, `MySQL`, `Postgres`, `SQLServer`, and `ClickHouse`. However, when you need to integrate GORM with databases that are not directly supported or have unique features, you can create a custom driver. This involves implementing the `Dialector` interface provided by GORM. -GORM 官方支持 `sqlite`、`mysql`、`postgres`、`sqlserver`。 +## Compatibility with MySQL or Postgres Dialects -有些数据库可能兼容 `mysql`、`postgres` 的方言,在这种情况下,你可以直接使用这些数据库的方言。 +For databases that closely resemble the behavior of `MySQL` or `Postgres`, you can often use the respective dialects directly. However, if your database significantly deviates from these dialects or offers additional features, developing a custom driver is recommended. -对于其它不兼容的情况,您可以自行编写一个新驱动,这需要实现 [方言接口](https://pkg.go.dev/gorm.io/gorm?tab=doc#Dialector)。 +## Implementing the Dialector + +The `Dialector` interface in GORM consists of methods that a database driver must implement to facilitate communication between the database and GORM. Let's break down the key methods: ```go type Dialector interface { - Name() string - Initialize(*DB) error - Migrator(db *DB) Migrator - DataTypeOf(*schema.Field) string - DefaultValueOf(*schema.Field) clause.Expression - BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) - QuoteTo(clause.Writer, string) - Explain(sql string, vars ...interface{}) string + Name() string // Returns the name of the database dialect + Initialize(*DB) error // Initializes the database connection + Migrator(db *DB) Migrator // Provides the database migration tool + DataTypeOf(*schema.Field) string // Determines the data type for a schema field + DefaultValueOf(*schema.Field) clause.Expression // Provides the default value for a schema field + BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // Handles variable binding in SQL statements + QuoteTo(clause.Writer, string) // Manages quoting of identifiers + Explain(sql string, vars ...interface{}) string // Formats SQL statements with variables +} +``` + +Each method in this interface serves a crucial role in how GORM interacts with the database, from establishing connections to handling queries and migrations. + +### Nested Transaction Support + +If your database supports savepoints, you can implement the `SavePointerDialectorInterface` to get the `Nested Transaction Support` and `SavePoint` support. + +```go +type SavePointerDialectorInterface interface { + SavePoint(tx *DB, name string) error // Saves a savepoint within a transaction + RollbackTo(tx *DB, name string) error // Rolls back a transaction to the specified savepoint } ``` -查看 [MySQL 驱动](https://github.com/go-gorm/mysql) 的例子 +By implementing these methods, you enable support for savepoints and nested transactions, offering advanced transaction management capabilities. + +### Custom Clause Builders + +Defining custom clause builders in GORM allows you to extend the query capabilities for specific database operations. In this example, we'll go through the steps to define a custom clause builder for the "LIMIT" clause, which may have database-specific behavior. + +- **Step 1: Define a Custom Clause Builder Function**: + +To create a custom clause builder, you need to define a function that adheres to the `clause.ClauseBuilder` interface. This function will be responsible for constructing the SQL clause for a specific operation. In our example, we'll create a custom "LIMIT" clause builder. + +Here's the basic structure of a custom "LIMIT" clause builder function: + +```go +func MyCustomLimitBuilder(c clause.Clause, builder clause.Builder) { + if limit, ok := c.Expression.(clause.Limit); ok { + // Handle the "LIMIT" clause logic here + // You can access the limit values using limit.Limit and limit.Offset + builder.WriteString("MYLIMIT") + } +} +``` + +- The function takes two parameters: `c` of type `clause.Clause` and `builder` of type `clause.Builder`. +- Inside the function, we check if the `c.Expression` is a `clause.Limit`. If it is, we proceed to handle the "LIMIT" clause logic. + +Replace `MYLIMIT` with the actual SQL logic for your database. This is where you can implement database-specific behavior for the "LIMIT" clause. + +- **Step 2: Register the Custom Clause Builder**: + +To make your custom "LIMIT" clause builder available to GORM, register it with the `db.ClauseBuilders` map, typically during driver initialization. Here's how to register the custom "LIMIT" clause builder: + +```go +func (d *MyDialector) Initialize(db *gorm.DB) error { + // Register the custom "LIMIT" clause builder + db.ClauseBuilders["LIMIT"] = MyCustomLimitBuilder + + //... +} +``` + +In this code, we use the key `"LIMIT"` to register our custom clause builder in the `db.ClauseBuilders` map, associating our custom builder with the "LIMIT" clause. + +- **Step 3: Use the Custom Clause Builder**: + +After registering the custom clause builder, GORM will call it when generating SQL statements that involve the "LIMIT" clause. You can use your custom logic to generate the SQL clause as needed. + +Here's an example of how you can use the custom "LIMIT" clause builder in a GORM query: + +```go +query := db.Model(&User{}) + +// Apply the custom "LIMIT" clause using the Limit method +query = query.Limit(10) // You can also provide an offset, e.g., query.Limit(10).Offset(5) + +// Execute the query +result := query.Find(&results) +// SQL: SELECT * FROM users MYLIMIT +``` + +In this example, we use the Limit method with GORM, and behind the scenes, our custom "LIMIT" clause builder (MyCustomLimitBuilder) will be invoked to handle the generation of the "LIMIT" clause. + +For inspiration and guidance, examining the [MySQL Driver](https://github.com/go-gorm/mysql) can be helpful. This driver demonstrates how the `Dialector` interface is implemented to suit the specific needs of the MySQL database. diff --git a/pages/zh_CN/docs/write_plugins.md b/pages/zh_CN/docs/write_plugins.md index 3caca49c6a1..b84c7d826b0 100644 --- a/pages/zh_CN/docs/write_plugins.md +++ b/pages/zh_CN/docs/write_plugins.md @@ -5,13 +5,13 @@ layout: page ## Callbacks -GORM 自身也是基于 `Callbacks` 的,包括 `Create`、`Query`、`Update`、`Delete`、`Row`、`Raw`。此外,您也完全可以根据自己的意愿自定义 GORM +GORM leverages `Callbacks` to power its core functionalities. These callbacks provide hooks for various database operations like `Create`, `Query`, `Update`, `Delete`, `Row`, and `Raw`, allowing for extensive customization of GORM's behavior. -回调会注册到全局 `*gorm.DB`,而不是会话级别。如果您想要 `*gorm.DB` 具有不同的回调,您需要初始化另一个 `*gorm.DB` +Callbacks are registered at the global `*gorm.DB` level, not on a session basis. This means if you need different callback behaviors, you should initialize a separate `*gorm.DB` instance. -### 注册 Callback +### Registering a Callback -注册 callback 至 callbacks +You can register a callback for specific operations. For example, to add a custom image cropping functionality: ```go func cropImage(db *gorm.DB) { @@ -60,65 +60,69 @@ func cropImage(db *gorm.DB) { } } +// Register the callback for the Create operation db.Callback().Create().Register("crop_image", cropImage) -// register a callback for Create process ``` -### 删除 Callback +### Deleting a Callback -从 callbacks 中删除回调 +If a callback is no longer needed, it can be removed: ```go +// Remove the 'gorm:create' callback from Create operations db.Callback().Create().Remove("gorm:create") -// 从 Create 的 callbacks 中删除 `gorm:create` ``` -### 替换 Callback +### Replacing a Callback -用一个新的回调替换已有的同名回调 +Callbacks with the same name can be replaced with a new function: ```go +// Replace the 'gorm:create' callback with a new function db.Callback().Create().Replace("gorm:create", newCreateFunction) -// 用新函数 `newCreateFunction` 替换 Create 流程目前的 `gorm:create` ``` -### 注册带顺序的 Callback +### Ordering Callbacks -注册带顺序的 Callback +Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle. ```go -// gorm:create 之前 +// Register to execute before the 'gorm:create' callback db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated) -// gorm:create 之后 +// Register to execute after the 'gorm:create' callback db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated) -// gorm:query 之后 +// Register to execute after the 'gorm:query' callback db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery) -// gorm:delete 之后 +// Register to execute after the 'gorm:delete' callback db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete) -// gorm:update 之前 +// Register to execute before the 'gorm:update' callback db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate) -// 位于 gorm:before_create 之后 gorm:create 之前 +// Register to execute before 'gorm:create' and after 'gorm:before_create' db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate) -// 所有其它 callback 之前 +// Register to execute before any other callbacks db.Callback().Create().Before("*").Register("update_created_at", updateCreated) -// 所有其它 callback 之后 +// Register to execute after any other callbacks db.Callback().Create().After("*").Register("update_created_at", updateCreated) ``` -### 预定义 Callback +### Predefined Callbacks -GORM 已经定义了 [一些 callback](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) 来支持当前的 GORM 功能,在启动您的插件之前可以先看看这些 callback +GORM comes with a set of predefined callbacks that drive its standard features. It's recommended to review these [defined callbacks](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go) before creating custom plugins or additional callback functions. -## 插件 +## Plugins -GORM 提供了 `Use` 方法来注册插件,插件需要实现 `Plugin` 接口 +GORM's plugin system allows for easy extensibility and customization of its core functionalities, enhancing your application's capabilities while maintaining a modular architecture. + +### The `Plugin` Interface + +To create a plugin for GORM, you need to define a struct that implements the `Plugin` interface: ```go type Plugin interface { @@ -127,10 +131,36 @@ type Plugin interface { } ``` -当插件首次注册到 GORM 时将调用 `Initialize` 方法,且 GORM 会保存已注册的插件,你可以这样访问访问: +- **`Name` Method**: Returns a unique string identifier for the plugin. +- **`Initialize` Method**: Contains the logic to set up the plugin. This method is called when the plugin is registered with GORM for the first time. + +### Registering a Plugin + +Once your plugin conforms to the `Plugin` interface, you can register it with a GORM instance: + +```go +// Example of registering a plugin +db.Use(MyCustomPlugin{}) +``` + +### Accessing Registered Plugins + +After a plugin is registered, it is stored in GORM's configuration. You can access registered plugins via the `Plugins` map: + +```go +// Access a registered plugin by its name +plugin := db.Config.Plugins[pluginName] +``` + +### Practical Example + +An example of a GORM plugin is the Prometheus plugin, which integrates Prometheus monitoring with GORM: ```go -db.Config.Plugins[pluginName] +// Registering the Prometheus plugin +db.Use(prometheus.New(prometheus.Config{ + // Configuration options here +})) ``` -查看 [Prometheus](prometheus.html) 的例子 +[Prometheus plugin documentation](prometheus.html) provides detailed information on its implementation and usage. diff --git a/pages/zh_CN/gen/associations.md b/pages/zh_CN/gen/associations.md index 05c587c42ec..9db35ea73c6 100644 --- a/pages/zh_CN/gen/associations.md +++ b/pages/zh_CN/gen/associations.md @@ -57,14 +57,14 @@ type creditCard struct{ ### 关联数据库中的表。 -关联必须由 `gen.FieldRelate` 指定。 +The association have to be specified by `gen.FieldRelate` ```go card := g.GenerateModel("credit_cards") customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -80,7 +80,7 @@ type Customer struct { CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"` - CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer" json:"credit_cards"` + CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"` } @@ -100,7 +100,7 @@ type CreditCard struct { customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, &field.RelateConfig{ // RelateSlice: true, - GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}}, + GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}}, }), ) @@ -157,7 +157,7 @@ u.WithContext(ctx).Omit(field.AssociationFields).Create(&user) // Skip all associations when creating a user ``` -方法 ` Field ` 将通过 '.'进行连接, 例如: `u.BillingAddress.Field("Address1", "Street")` 等于 `BillingAddress.Address.Address.Address.Street` +Method `Field` will join a serious field name with '', for example: `u.BillingAddress.Field("Address1", "Street")` equals to `BillingAddress.Address1.Street` ### 查找关联 @@ -202,7 +202,7 @@ u.Languages.Model(&user).Replace(&languageZH, &languageEN) ### 删除关联 -如果存在,则删除源模型与参数之间的关系,只会删除引用,不会从数据库中删除这些对象。 +Remove the relationship between source & arguments if exists, only delete the reference, won’t delete those objects from DB. ```go u := query.Use(db).User @@ -230,7 +230,7 @@ u.Languages.Model(&user).Count() ### Delete with Select -你可以在删除记录时通过 `Select` 来删除具有 has one、has many、many2many 关系的记录,例如: +You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: ```go u := query.Use(db).User @@ -284,7 +284,7 @@ users, err := u.WithContext(ctx).Preload(u.Orders).Preload(u.Profile).Preload(u. ### Preload All -与创建、更新时使用 `Select` 类似,`clause.Associations` 也可以和 `Preload` 一起使用,它可以用来 `预加载` 全部关联,例如: +`clause.Associations` can work with `Preload` similar like `Select` when creating/updating, you can use it to `Preload` all associations, for example: ```go type User struct { @@ -299,20 +299,21 @@ type User struct { users, err := u.WithContext(ctx).Preload(field.Associations).Find() ``` -`clause.Associations`不会预加载嵌套的关联关系,但是你可以将其与[Nested Preloading](#nested-preloading)一起使用, 例如: +`clause.Associations` won’t preload nested associations, but you can use it with [Nested Preloading](#nested-preloading) together, e.g: ```go users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find() ``` -To include soft deleted records in all associactions use relation scope `field.RelationFieldUnscoped`, e.g: +To include soft deleted records in all associations use relation scope `field.RelationFieldUnscoped`, e.g: + ```go users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find() ``` ### Preload with select -使用方法 `Select` 指定加载的列. 。 外键必须选择。 +使用方法 `Select` 指定加载的列. 。 Foreign key must be selected. ```go type User struct { @@ -381,4 +382,3 @@ db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users) // And GEN won't preload unmatched order's OrderItems then db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users) ``` - diff --git a/pages/zh_CN/gen/database_to_structs.md b/pages/zh_CN/gen/database_to_structs.md index fecc4e2c4be..7aed8e27219 100644 --- a/pages/zh_CN/gen/database_to_structs.md +++ b/pages/zh_CN/gen/database_to_structs.md @@ -1,5 +1,5 @@ --- -title: Database To Structs +title: 从数据库生成结构 layout: page --- @@ -15,31 +15,30 @@ import "gorm.io/gen" func main() { g := gen.NewGenerator(gen.Config{ OutPath: "../query", - Mode: gen.WithoutContext|gen.WithDefaultQuery|gen.WithQueryInterface, // generate mode + Mode: gen.WithoutContext|gen.WithDefaultQuery|gen.WithQueryInterface, // 生成模式 }) // gormdb, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local")) g.UseDB(gormdb) // reuse your gorm db - // Generate basic type-safe DAO API for struct `model.User` following conventions - + // 按照约定为结构体 `model.User` 生成类型安全的 DAO API g.ApplyBasic( - // Generate struct `User` based on table `users` - g.GenerateModel("users"), - - // Generate struct `Employee` based on table `users` - g.GenerateModelAs("users", "Employee"), + // 根据 `user` 表生成结构 `User` + g.GenerateModel("users"), + // 根据 `user` 表生成结构 `Employee` + g.GenerateModelAs("users", "Employee"), -// Generate struct `User` based on table `users` and generating options -g.GenerateModel("users", gen.FieldIgnore("address"), gen.FieldType("id", "int64")), + // 根据 `user` 表和生成时选项生成结构 `User` + g.GenerateModel("users", gen.FieldIgnore("address"), gen.FieldType("id", "int64")), + ) + g.ApplyBasic( + // 从当前数据库中生成所有表的结构 + g.GenerateAllTable()..., ) -g.ApplyBasic( -// Generate structs from all tables of current database -g.GenerateAllTable()..., -) - // Generate the code + + // 生成代码 g.Execute() } @@ -69,17 +68,17 @@ func (m *CommonMethod) GetName() string { return *m.Name } -// Add IsEmpty method to the generated `People` struct +// 为生成的 `People` 结构添加 `IsEmpty` 方法 g.GenerateModel("people", gen.WithMethod(CommonMethod{}.IsEmpty)) -// Add all methods defined on `CommonMethod` to the generated `User` struct +// 将 `CommonMethod` 上定义的所有方法添加到生成的 `User` 结构中 g.GenerateModel("user", gen.WithMethod(CommonMethod{})) ``` -The generated code would look like this: +生成的代码看起来像这样: ```go -// Generated Person struct +// 生成的 Person 结构 type Person struct { // ... } @@ -92,7 +91,7 @@ func (m *Person) IsEmpty() bool { } -// Generated User struct +// 生成的 User 结构 type User struct { // ... } @@ -145,32 +144,32 @@ g.WithOpts(gen.WithMethod(gen.DefaultMethodTableWithNamer)) ``` -## Field Options +## 字段选项 -Following are options that can be used during `GenerateModel`/`GenerateModelAs` +以下是调用 `GenerateModel`/`GenerateModelAs` 时可以使用的选项 ```go -FieldNew // create new a field -FieldIgnore // ignore field -FieldIgnoreReg // ignore field (match with regexp) -FieldRename // rename field in the struct -FieldComment // specify field comment in generated struct -FieldType // specify the field type -FieldTypeReg // specify field type (match with regexp) -FieldGenType // specify field gen type -FieldGenTypeReg // specify field gen type (match with regexp) -FieldTag // specify gorm and json tag -FieldJSONTag // specify json tag -FieldJSONTagWithNS // specify json tag with name strategy -FieldGORMTag // specify gorm tag -FieldNewTag // append new tag -FieldNewTagWithNS // specify the new tag with name strategy -FieldTrimPrefix // trim column prefix -FieldTrimSuffix // trim column suffix -FieldAddPrefix // add the prefix to struct field's name -FieldAddSuffix // add the suffix to struct field's name -FieldRelate // specify relationship with other tables -FieldRelateModel // specify the relationship with existing models +FieldNew // 创建一个新字段 +FieldIgnore // 忽略字段 +FieldIgnoreReg // 忽略字段 (与正则匹配的) +FieldRename // 在结构中重命名字段 +FieldComment // 在生成的结构中指定字段注释 +FieldType // 指定字段类型 +FieldTypeReg // 指定字段类型 (与正则匹配的) +FieldGenType // 指定字段 gen 类型 +FieldGenTypeReg // 指定字段 gen 类型 (与正则匹配的) +FieldTag // 指定 gorm 和 json tag +FieldJSONTag // 指定 json tag +FieldJSONTagWithNS // 使用命名策略指定 json tag +FieldGORMTag // 指定 gorm tag +FieldNewTag // 添加新 tag +FieldNewTagWithNS // 使用命令策略指定新 tag +FieldTrimPrefix // 去除列前缀 +FieldTrimSuffix // 去除列后缀 +FieldAddPrefix // 在结构字段名上添加前缀 +FieldAddSuffix // 在结构字体名上添加后缀 +FieldRelate // 指定与其它表的关系 +FieldRelateModel // 指定与现有模型的关系 ``` ## 全局生成选项 @@ -179,45 +178,45 @@ Gen 有一些全局选项可以在 `gen.Config`中设置: ```go g := gen.NewGenerator(gen.Config{ - // if you want the nullable field generation property to be pointer type, set FieldNullable true + // 如果你希望为可为null的字段生成属性为指针类型, 设置 FieldNullable 为 true FieldNullable: true, - // if you want to assign field which has a default value in the `Create` API, set FieldCoverable true, reference: https://gorm.io/docs/create.html#Default-Values + // 如果你希望在 `Create` API 中为字段分配默认值, 设置 FieldCoverable 为 true, 参考: https://gorm.io/docs/create.html#Default-Values FieldCoverable: true, - // if you want to generate field with unsigned integer type, set FieldSignable true + // 如果你希望生成无符号整数类型字段, 设置 FieldSignable 为 true FieldSignable: true, - // if you want to generate index tags from database, set FieldWithIndexTag true + // 如果你希望从数据库生成索引标记, 设置 FieldWithIndexTag 为 true FieldWithIndexTag: true, - // if you want to generate type tags from database, set FieldWithTypeTag true + // 如果你希望从数据库生成类型标记, 设置 FieldWithTypeTag 为 true FieldWithTypeTag: true, - // if you need unit tests for query code, set WithUnitTest true + // 如果你需要对查询代码进行单元测试, 设置 WithUnitTest 为 true WithUnitTest: true, }) ``` ```go -// WithDbNameOpts set get database name function +// WithDbNameOpts 设置获取数据库名称方法 WithDbNameOpts(opts ...model.SchemaNameOpt) -// WithTableNameStrategy specify table name naming strategy, only work when syncing table from db +// WithTableNameStrategy 指定表名命名策略, 仅在从数据库同步表时工作 WithTableNameStrategy(ns func(tableName string) (targetTableName string)) -// WithModelNameStrategy specify model struct name naming strategy, only work when syncing table from db +// WithModelNameStrategy 指定 model 结构名命名策略, 仅在从数据库同步表时工作 // If an empty string is returned, the table will be ignored WithModelNameStrategy(ns func(tableName string) (modelName string)) -// WithFileNameStrategy specify file name naming strategy, only work when syncing table from db +// WithFileNameStrategy 指定文件名命名策略, 仅在从数据库同步表时工作 WithFileNameStrategy(ns func(tableName string) (fileName string)) -// WithJSONTagNameStrategy specify json tag naming strategy +// WithJSONTagNameStrategy 指定 json tag 命名策略 WithJSONTagNameStrategy(ns func(columnName string) (tagContent string)) -// WithDataTypeMap specify data type mapping relationship, only work when syncing table from db +// WithDataTypeMap 指定数据类型命名策略, 仅在从数据库同步表时工作 WithDataTypeMap(newMap map[string]func(gorm.ColumnType) (dataType string)) -// WithImportPkgPath specify import package path +// WithImportPkgPath 指定导入包路径 WithImportPkgPath(paths ...string) -// WithOpts specify global model options +// WithOpts 指定全局 model 选项 WithOpts(opts ...ModelOpt) ``` @@ -249,7 +248,7 @@ WithOpts(opts ...ModelOpt) ``` ## Generate From Sql -Gen supports generate structs from sql following GORM conventions, it can be used like: +Gen 支持根据 GORM 约定从 SQL 生成 structs,其使用方式如下: ```go package main @@ -260,36 +259,37 @@ import ( ) func main() { - g := gen.NewGenerator(gen.Config{ - OutPath: "../query", - Mode: gen.WithoutContext|gen.WithDefaultQuery|gen.WithQueryInterface, // generate mode - }) - // https://github.com/go-gorm/rawsql/blob/master/tests/gen_test.go - gormdb, _ := gorm.Open(rawsql.New(rawsql.Config{ - //SQL: rawsql, //create table sql + g := gen.NewGenerator(gen.Config{ + OutPath: "../query", + Mode: gen.WithoutContext|gen.WithDefaultQuery|gen.WithQueryInterface, // 生成模式 + }) + + // https://github.com/go-gorm/rawsql/blob/master/tests/gen_test.go + gormdb, _ := gorm.Open(rawsql.New(rawsql.Config{ + //SQL: rawsql, // 建表sql FilePath: []string{ - //"./sql/user.sql", // create table sql file - "./test_sql", // create table sql file directory + //"./sql/user.sql", // 建表sql文件 + "./test_sql", // 建表sql目录 }, })) - g.UseDB(gormdb) // reuse your gorm db + g.UseDB(gormdb) // 重新引用你的 gorm db - // Generate basic type-safe DAO API for struct `model.User` following conventions + // 按照约定为结构 `model.User` 生成基本类型安全的DAO API + g.ApplyBasic( + // 基于 `user` 表生成 `User` 结构 + g.GenerateModel("users"), - g.ApplyBasic( - // Generate struct `User` based on table `users` - g.GenerateModel("users"), + // 基于 `user` 表生成 `Employee` 结构 + g.GenerateModelAs("users", "Employee"), + ) - // Generate struct `Employee` based on table `users` - g.GenerateModelAs("users", "Employee"), + g.ApplyBasic( + // 从当前数据库生成所有表结构 + g.GenerateAllTable()..., + ) - ) -g.ApplyBasic( -// Generate structs from all tables of current database -g.GenerateAllTable()..., -) - // Generate the code - g.Execute() -} + // 生成代码 + g.Execute() +}` ``` diff --git a/pages/zh_CN/gen/delete.md b/pages/zh_CN/gen/delete.md index 7b5f82f3ff4..79366d22421 100644 --- a/pages/zh_CN/gen/delete.md +++ b/pages/zh_CN/gen/delete.md @@ -26,16 +26,16 @@ err // 错误 ## 通过主键删除 -GEN allows to delete objects using primary key(s) with inline condition, it works with numbers. +GEN 允许使用带有内联条件的主键删除对象,它适用于数字 ```go u.WithContext(ctx).Where(u.ID.In(1,2,3)).Delete() // DELETE FROM users WHERE id IN (1,2,3); ``` -## Batch Delete +## 批量删除 -The specified value has no primary value, GEN will perform a batch delete, it will delete all matched records +如果指定值不包括主键,GEN 将执行批量删除,删除所有匹配记录 ```go e := query.Email @@ -44,23 +44,23 @@ e.WithContext(ctx).Where(e.Name.Like("%modi%")).Delete() // DELETE from emails where email LIKE "%modi%"; ``` -## Soft Delete +## 软删除 -If your model includes a `gorm.DeletedAt` field (which is included in `gorm.Model`), it will get soft delete ability automatically! +如果你的模型包含了 `gorm.DeletedAt`字段(在`gorm.Model`中),那么该模型将会自动获得软删除的能力! -When calling `Delete`, the record WON’T be removed from the database, but GORM will set the `DeletedAt`‘s value to the current time, and the data is not findable with normal Query methods anymore. +当调用`Delete`时,GORM并不会从数据库中删除该记录,而是将该记录的`DeleteAt`设置为当前时间,之后普通查询方法将无法查找到此条记录。 ```go -// Batch Delete +// 批量删除 u.WithContext(ctx).Where(u.Age.Eq(20)).Delete() // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20; -// Soft deleted records will be ignored when querying +// 查询时将忽略被软删除的记录 users, err := u.WithContext(ctx).Where(u.Age.Eq(20)).Find() // SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL; ``` -If you don’t want to include `gorm.Model`, you can enable the soft delete feature like: +如果你不想嵌入 `gorm.Model`,你也可以这样启用软删除特性: ```go type User struct { @@ -70,25 +70,25 @@ type User struct { } ``` -## Find soft deleted records +## 查找被软删除的记录 -You can find soft deleted records with `Unscoped` +你可以使用 `Unscoped` 找到被软删除的记录 ```go users, err := db.WithContext(ctx).Unscoped().Where(u.Age.Eq(20)).Find() // SELECT * FROM users WHERE age = 20; ``` -## Delete permanently +## 永久删除 -You can delete matched records permanently with `Unscoped` +你可以使用 `Unscoped`来永久删除匹配的记录 ```go o.WithContext(ctx).Unscoped().Where(o.ID.Eq(10)).Delete() // DELETE FROM orders WHERE id=10; ``` -### Delete Associations +### 删除关联 Remove the relationship between source & arguments if exists, only delete the reference, won’t delete those objects from DB. @@ -100,19 +100,19 @@ u.Languages.Model(&user).Delete(&languageZH, &languageEN) u.Languages.Model(&user).Delete([]*Language{&languageZH, &languageEN}...) ``` -### Delete with Select +### 带 Select 的删除 -You are allowed to delete selected has one/has many/many2many relations with `Select` when deleting records, for example: +你可以在删除记录时通过 `Select` 来删除具有 has one、has many、many2many 关系的记录,例如: ```go u := query.User -// delete user's account when deleting user +// 删除 user 时,也删除 user 关联的 account 数据 u.Select(u.Account).Delete(&user) -// delete user's Orders, CreditCards relations when deleting user +// 删除 user 时,也删除 user 关联的 Orders, CreditCards 数据 db.Select(u.Orders.Field(), u.CreditCards.Field()).Delete(&user) -// delete user's has one/many/many2many relations when deleting user +// 删除 user 时,也删除 user 关联的 has one/many/many2many 关系数据 db.Select(field.AssociationsFields).Delete(&user) ``` diff --git a/pages/zh_CN/gen/index.md b/pages/zh_CN/gen/index.md index 491f273a25a..3709b39ab48 100644 --- a/pages/zh_CN/gen/index.md +++ b/pages/zh_CN/gen/index.md @@ -3,7 +3,7 @@ title: Gen Guides layout: page --- -## GEN Guides +## GEN 指南 [GEN](https://github.com/go-gorm/gen): 更友好 & 更安全 [GORM](https://github.com/go-gorm/gorm) 代码生成。 @@ -20,11 +20,11 @@ layout: page go get -u gorm.io/gen ``` -## Quick start +## 快速入门 -It is quite straightforward to use `gen` for your application, here is how it works: +在程序中使用 `gen` 非常简单,具体操作如下: -**1. Write the configuration in golang** +**1. 在 Go 中写入配置** ```go package main @@ -57,11 +57,11 @@ func main() { } ``` -**2. Generate Code** +**2. 生成代码** `go run main.go` -**3. Use the generated code in your project** +**3. 在您的项目中使用生成的代码** ```go import "your_project/query" diff --git a/pages/zh_CN/gen/query.md b/pages/zh_CN/gen/query.md index a3e6c931afb..8590ecae21b 100644 --- a/pages/zh_CN/gen/query.md +++ b/pages/zh_CN/gen/query.md @@ -408,7 +408,7 @@ name.Like("%modi%") name.Regexp(".*") // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang") name.FindInSet("modi,jinzhu,zhangqiang") -// `uesr`.`name` CONCAT("[",name,"]") +// `user`.`name` CONCAT("[",name,"]") name.Concat("[", "]") ``` @@ -454,7 +454,7 @@ users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name). // Select users with orders between 100 and 200 subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100)) subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200)) -u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find() +u.WithContext(ctx).Where(gen.Exists(subQuery1)).Not(gen.Exists(subQuery2)).Find() // SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL ``` diff --git a/pages/zh_CN/gen/sql_annotation.md b/pages/zh_CN/gen/sql_annotation.md index de3346b2020..cbf6dae844b 100644 --- a/pages/zh_CN/gen/sql_annotation.md +++ b/pages/zh_CN/gen/sql_annotation.md @@ -62,11 +62,11 @@ type Querier interface { Gen 提供了一些占位符来生成动态且安全的 SQL -| Name | Description | -| ---------------- | ---------------------------------------------- | -| `@@table` | escaped & quoted table name | -| `@@` | escaped & quoted table/column name from params | -| `@` | SQL query params from params | +| Name | Description | +| ---------------- | ---------------- | +| `@@table` | 被转义并引用的表名 | +| `@@` | 被转义并引用为表或列名称的参数名 | +| `@` | 用于SQL查询语句参数的参数名 | e.g: @@ -105,7 +105,7 @@ Gen 提供强大的表达式支持动态条件SQL,目前支持以下表达式 ### `if/else` -The `if/else` expression allows to use golang syntax as condition, it can be written like: +`if/else`表达式中可以使用golang句法的条件语句, 可以像这样写为: ``` {{if cond1}} @@ -155,7 +155,7 @@ query.User.QueryWith(&User{Name: "zhangqiang"}) ### `where` -The `where` expression make you write the `WHERE` clause for the SQL query easier, let take a simple case as example: +`where`表达式可以让你更轻松的写出SQL查询语句中的`WHERE`子句, 下例为一个简单示例: ```go type Querier interface { @@ -174,7 +174,7 @@ query.User.Query(10) // SELECT * FROM users WHERE id=10 ``` -Here is another complicated case, in this case, you will learn the `WHERE` clause only be inserted if there are any children expressions matched and it can smartly trim uncessary `and`, `or`, `xor`, `,` inside the `where` clause. +这是另一个复杂的案例,在这种情况下,你将了解到只有在有任何子表达式匹配时才插入`WHERE`子句,并且它可以巧妙地修剪`where`子句内不必要的`and`, `or`, `xor`, `,`。 ```go type Querier interface { @@ -191,7 +191,7 @@ type Querier interface { } ``` -The generated code can be used like: +生成的代码可以像这样使用: ```go var ( @@ -215,7 +215,7 @@ query.User.FilterWithTime(zero, zero) ### `set` -The `set` expression used to generate the `SET` clause for the SQL query, it will trim uncessary `,` automatically, for example: +`set`表达式用来生成SQL语句中的`SET`子句, 它会自动修剪不必要的`,`, 比如: ```go // UPDATE @@table @@ -228,7 +228,7 @@ The `set` expression used to generate the `SET` clause for the SQL query, it wil Update(user gen.T, id int) (gen.RowsAffected, error) ``` -The generated code can be used like: +生成的代码可以像这样使用: ```go query.User.Update(User{Name: "jinzhu", Age: 18}, 10) @@ -243,32 +243,14 @@ query.User.Update(User{Age: 0}, 10) ### `for` -The `for` expression iterates over a slice to generate the SQL, let's explain by example +`for`表达式遍历切片生成SQL,用下例说明 ```go -// SELECT * FROM @@table -// {{where}} -// {{for _,user:=range user}} -// {{if user.Name !="" && user.Age >0}} -// (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR -// {{end}} -// {{end}} -// {{end}} -Filter(users []gen.T) ([]gen.T, error) + ``` -Usage: +用法: ```go -query.User.Filter([]User{ - {Name: "jinzhu", Age: 18, Role: "admin"}, - {Name: "zhangqiang", Age: 18, Role: "admin"}, - {Name: "modi", Age: 18, Role: "admin"}, - {Name: "songyuan", Age: 18, Role: "admin"}, -}) -// SELECT * FROM users WHERE -// (username = "jinzhu" AND age=18 AND role LIKE concat("%","admin","%")) OR -// (username = "zhangqiang" AND age=18 AND role LIKE concat("%","admin","%")) -// (username = "modi" AND age=18 AND role LIKE concat("%","admin","%")) OR -// (username = "songyuan" AND age=18 AND role LIKE concat("%","admin","%")) + ```