From c493f5c759b3c93bb66df5eed4681e2fe3da80b7 Mon Sep 17 00:00:00 2001 From: jinzhu Date: Thu, 18 Jan 2024 01:49:22 +0000 Subject: [PATCH] deploy: bf0b9f572e31b4881d9691a535553696fea88d2e --- 404.html | 4 +- ar_SA/index.html | 4 +- az_AZ/404.html | 4 +- az_AZ/index.html | 4 +- community.html | 6 +- contribute.html | 6 +- de_DE/404.html | 4 +- de_DE/community.html | 6 +- de_DE/contribute.html | 6 +- de_DE/datatypes.html | 6 +- de_DE/docs/advanced_query.html | 138 ++++++++------ de_DE/docs/associations.html | 195 ++++++++++++-------- de_DE/docs/belongs_to.html | 6 +- de_DE/docs/changelog.html | 6 +- de_DE/docs/composite_primary_key.html | 6 +- de_DE/docs/connecting_to_the_database.html | 6 +- de_DE/docs/constraints.html | 6 +- de_DE/docs/context.html | 36 ++-- de_DE/docs/conventions.html | 8 +- de_DE/docs/create.html | 8 +- de_DE/docs/data_types.html | 6 +- de_DE/docs/dbresolver.html | 8 +- de_DE/docs/delete.html | 6 +- de_DE/docs/error_handling.html | 57 ++++-- de_DE/docs/generic_interface.html | 6 +- de_DE/docs/gorm_config.html | 6 +- de_DE/docs/has_many.html | 6 +- de_DE/docs/has_one.html | 6 +- de_DE/docs/hints.html | 6 +- de_DE/docs/hooks.html | 6 +- de_DE/docs/index.html | 6 +- de_DE/docs/indexes.html | 6 +- de_DE/docs/logger.html | 6 +- de_DE/docs/many_to_many.html | 8 +- de_DE/docs/method_chaining.html | 92 ++++++---- de_DE/docs/migration.html | 6 +- de_DE/docs/models.html | 57 ++++-- de_DE/docs/performance.html | 6 +- de_DE/docs/preload.html | 6 +- de_DE/docs/prometheus.html | 6 +- de_DE/docs/query.html | 6 +- de_DE/docs/scopes.html | 6 +- de_DE/docs/security.html | 8 +- de_DE/docs/serializer.html | 6 +- de_DE/docs/session.html | 6 +- de_DE/docs/settings.html | 6 +- de_DE/docs/sharding.html | 6 +- de_DE/docs/sql_builder.html | 6 +- de_DE/docs/transactions.html | 8 +- de_DE/docs/update.html | 6 +- de_DE/docs/v2_release_note.html | 6 +- de_DE/docs/write_driver.html | 57 ++++-- de_DE/docs/write_plugins.html | 53 +++--- de_DE/gen.html | 6 +- de_DE/gen/associations.html | 21 ++- de_DE/gen/clause.html | 6 +- de_DE/gen/create.html | 6 +- de_DE/gen/dao.html | 6 +- de_DE/gen/database_to_structs.html | 6 +- de_DE/gen/delete.html | 6 +- de_DE/gen/dynamic_sql.html | 6 +- de_DE/gen/gen_tool.html | 6 +- de_DE/gen/index.html | 6 +- de_DE/gen/query.html | 10 +- de_DE/gen/rawsql_driver.html | 6 +- de_DE/gen/sql_annotation.html | 8 +- de_DE/gen/transaction.html | 6 +- de_DE/gen/update.html | 6 +- de_DE/gorm.html | 6 +- de_DE/gormx.html | 6 +- de_DE/hints.html | 6 +- de_DE/index.html | 4 +- de_DE/rawsql.html | 6 +- de_DE/rawsql_driver.html | 6 +- de_DE/sharding.html | 6 +- de_DE/stats.html | 6 +- docs/advanced_query.html | 6 +- docs/associations.html | 6 +- docs/belongs_to.html | 6 +- docs/changelog.html | 6 +- docs/composite_primary_key.html | 6 +- docs/connecting_to_the_database.html | 6 +- docs/constraints.html | 6 +- docs/context.html | 6 +- docs/conventions.html | 6 +- docs/create.html | 6 +- docs/data_types.html | 6 +- docs/dbresolver.html | 6 +- docs/delete.html | 6 +- docs/error_handling.html | 6 +- docs/generic_interface.html | 6 +- docs/gorm_config.html | 6 +- docs/has_many.html | 6 +- docs/has_one.html | 6 +- docs/hints.html | 6 +- docs/hooks.html | 6 +- docs/index.html | 6 +- docs/indexes.html | 6 +- docs/logger.html | 6 +- docs/many_to_many.html | 6 +- docs/method_chaining.html | 6 +- docs/migration.html | 6 +- docs/models.html | 6 +- docs/performance.html | 6 +- docs/preload.html | 6 +- docs/prometheus.html | 6 +- docs/query.html | 6 +- docs/scopes.html | 6 +- docs/security.html | 6 +- docs/serializer.html | 6 +- docs/session.html | 6 +- docs/settings.html | 6 +- docs/sharding.html | 6 +- docs/sql_builder.html | 6 +- docs/transactions.html | 6 +- docs/update.html | 6 +- docs/v2_release_note.html | 6 +- docs/write_driver.html | 6 +- docs/write_plugins.html | 6 +- es_ES/404.html | 4 +- es_ES/community.html | 6 +- es_ES/contribute.html | 6 +- es_ES/datatypes.html | 6 +- es_ES/docs/advanced_query.html | 138 ++++++++------ es_ES/docs/associations.html | 195 ++++++++++++-------- es_ES/docs/belongs_to.html | 6 +- es_ES/docs/changelog.html | 6 +- es_ES/docs/composite_primary_key.html | 6 +- es_ES/docs/connecting_to_the_database.html | 6 +- es_ES/docs/constraints.html | 6 +- es_ES/docs/context.html | 36 ++-- es_ES/docs/conventions.html | 26 +-- es_ES/docs/create.html | 8 +- es_ES/docs/data_types.html | 6 +- es_ES/docs/dbresolver.html | 8 +- es_ES/docs/delete.html | 6 +- es_ES/docs/error_handling.html | 57 ++++-- es_ES/docs/generic_interface.html | 6 +- es_ES/docs/gorm_config.html | 6 +- es_ES/docs/has_many.html | 6 +- es_ES/docs/has_one.html | 6 +- es_ES/docs/hints.html | 6 +- es_ES/docs/hooks.html | 6 +- es_ES/docs/index.html | 6 +- es_ES/docs/indexes.html | 6 +- es_ES/docs/logger.html | 6 +- es_ES/docs/many_to_many.html | 8 +- es_ES/docs/method_chaining.html | 92 ++++++---- es_ES/docs/migration.html | 6 +- es_ES/docs/models.html | 57 ++++-- es_ES/docs/performance.html | 6 +- es_ES/docs/preload.html | 6 +- es_ES/docs/prometheus.html | 6 +- es_ES/docs/query.html | 6 +- es_ES/docs/scopes.html | 6 +- es_ES/docs/security.html | 8 +- es_ES/docs/serializer.html | 6 +- es_ES/docs/session.html | 6 +- es_ES/docs/settings.html | 6 +- es_ES/docs/sharding.html | 6 +- es_ES/docs/sql_builder.html | 6 +- es_ES/docs/transactions.html | 8 +- es_ES/docs/update.html | 6 +- es_ES/docs/v2_release_note.html | 6 +- es_ES/docs/write_driver.html | 57 ++++-- es_ES/docs/write_plugins.html | 53 +++--- es_ES/gen.html | 6 +- es_ES/gen/associations.html | 21 ++- es_ES/gen/clause.html | 6 +- es_ES/gen/create.html | 6 +- es_ES/gen/dao.html | 6 +- es_ES/gen/database_to_structs.html | 6 +- es_ES/gen/delete.html | 6 +- es_ES/gen/dynamic_sql.html | 6 +- es_ES/gen/gen_tool.html | 6 +- es_ES/gen/index.html | 6 +- es_ES/gen/query.html | 10 +- es_ES/gen/rawsql_driver.html | 6 +- es_ES/gen/sql_annotation.html | 8 +- es_ES/gen/transaction.html | 6 +- es_ES/gen/update.html | 6 +- es_ES/gorm.html | 6 +- es_ES/gormx.html | 6 +- es_ES/hints.html | 6 +- es_ES/index.html | 4 +- es_ES/rawsql.html | 6 +- es_ES/rawsql_driver.html | 6 +- es_ES/sharding.html | 6 +- es_ES/stats.html | 6 +- fa_IR/404.html | 4 +- fa_IR/index.html | 4 +- fr_FR/404.html | 4 +- fr_FR/community.html | 6 +- fr_FR/contribute.html | 6 +- fr_FR/datatypes.html | 6 +- fr_FR/docs/advanced_query.html | 138 ++++++++------ fr_FR/docs/associations.html | 195 ++++++++++++-------- fr_FR/docs/belongs_to.html | 6 +- fr_FR/docs/changelog.html | 6 +- fr_FR/docs/composite_primary_key.html | 6 +- fr_FR/docs/connecting_to_the_database.html | 6 +- fr_FR/docs/constraints.html | 6 +- fr_FR/docs/context.html | 36 ++-- fr_FR/docs/conventions.html | 8 +- fr_FR/docs/create.html | 8 +- fr_FR/docs/data_types.html | 6 +- fr_FR/docs/dbresolver.html | 8 +- fr_FR/docs/delete.html | 6 +- fr_FR/docs/error_handling.html | 57 ++++-- fr_FR/docs/generic_interface.html | 6 +- fr_FR/docs/gorm_config.html | 6 +- fr_FR/docs/has_many.html | 6 +- fr_FR/docs/has_one.html | 6 +- fr_FR/docs/hints.html | 6 +- fr_FR/docs/hooks.html | 6 +- fr_FR/docs/index.html | 6 +- fr_FR/docs/indexes.html | 6 +- fr_FR/docs/logger.html | 6 +- fr_FR/docs/many_to_many.html | 8 +- fr_FR/docs/method_chaining.html | 92 ++++++---- fr_FR/docs/migration.html | 6 +- fr_FR/docs/models.html | 57 ++++-- fr_FR/docs/performance.html | 6 +- fr_FR/docs/preload.html | 6 +- fr_FR/docs/prometheus.html | 6 +- fr_FR/docs/query.html | 6 +- fr_FR/docs/scopes.html | 6 +- fr_FR/docs/security.html | 8 +- fr_FR/docs/serializer.html | 6 +- fr_FR/docs/session.html | 6 +- fr_FR/docs/settings.html | 6 +- fr_FR/docs/sharding.html | 6 +- fr_FR/docs/sql_builder.html | 6 +- fr_FR/docs/transactions.html | 8 +- fr_FR/docs/update.html | 6 +- fr_FR/docs/v2_release_note.html | 6 +- fr_FR/docs/write_driver.html | 57 ++++-- fr_FR/docs/write_plugins.html | 53 +++--- fr_FR/gen.html | 6 +- fr_FR/gen/associations.html | 21 ++- fr_FR/gen/clause.html | 6 +- fr_FR/gen/create.html | 6 +- fr_FR/gen/dao.html | 6 +- fr_FR/gen/database_to_structs.html | 6 +- fr_FR/gen/delete.html | 6 +- fr_FR/gen/dynamic_sql.html | 6 +- fr_FR/gen/gen_tool.html | 6 +- fr_FR/gen/index.html | 6 +- fr_FR/gen/query.html | 10 +- fr_FR/gen/rawsql_driver.html | 6 +- fr_FR/gen/sql_annotation.html | 8 +- fr_FR/gen/transaction.html | 6 +- fr_FR/gen/update.html | 6 +- fr_FR/gorm.html | 6 +- fr_FR/gormx.html | 6 +- fr_FR/hints.html | 6 +- fr_FR/index.html | 4 +- fr_FR/rawsql.html | 6 +- fr_FR/rawsql_driver.html | 6 +- fr_FR/sharding.html | 6 +- fr_FR/stats.html | 6 +- gen/associations.html | 6 +- gen/clause.html | 6 +- gen/create.html | 6 +- gen/dao.html | 6 +- gen/database_to_structs.html | 6 +- gen/delete.html | 6 +- gen/dynamic_sql.html | 6 +- gen/gen_tool.html | 6 +- gen/index.html | 6 +- gen/query.html | 6 +- gen/rawsql_driver.html | 6 +- gen/sql_annotation.html | 6 +- gen/transaction.html | 6 +- gen/update.html | 6 +- hi_IN/404.html | 4 +- hi_IN/community.html | 6 +- hi_IN/contribute.html | 6 +- hi_IN/datatypes.html | 6 +- hi_IN/docs/advanced_query.html | 155 ++++++++-------- hi_IN/docs/associations.html | 199 +++++++++++++-------- hi_IN/docs/belongs_to.html | 6 +- hi_IN/docs/changelog.html | 6 +- hi_IN/docs/composite_primary_key.html | 6 +- hi_IN/docs/connecting_to_the_database.html | 6 +- hi_IN/docs/constraints.html | 6 +- hi_IN/docs/context.html | 68 ++----- hi_IN/docs/conventions.html | 8 +- hi_IN/docs/create.html | 8 +- hi_IN/docs/data_types.html | 6 +- hi_IN/docs/dbresolver.html | 8 +- hi_IN/docs/delete.html | 6 +- hi_IN/docs/error_handling.html | 57 ++++-- hi_IN/docs/generic_interface.html | 6 +- hi_IN/docs/gorm_config.html | 6 +- hi_IN/docs/has_many.html | 6 +- hi_IN/docs/has_one.html | 6 +- hi_IN/docs/hints.html | 6 +- hi_IN/docs/hooks.html | 6 +- hi_IN/docs/index.html | 6 +- hi_IN/docs/indexes.html | 6 +- hi_IN/docs/logger.html | 6 +- hi_IN/docs/many_to_many.html | 8 +- hi_IN/docs/method_chaining.html | 92 ++++++---- hi_IN/docs/migration.html | 6 +- hi_IN/docs/models.html | 57 ++++-- hi_IN/docs/performance.html | 6 +- hi_IN/docs/preload.html | 6 +- hi_IN/docs/prometheus.html | 6 +- hi_IN/docs/query.html | 6 +- hi_IN/docs/scopes.html | 6 +- hi_IN/docs/security.html | 8 +- hi_IN/docs/serializer.html | 6 +- hi_IN/docs/session.html | 6 +- hi_IN/docs/settings.html | 6 +- hi_IN/docs/sharding.html | 6 +- hi_IN/docs/sql_builder.html | 6 +- hi_IN/docs/transactions.html | 8 +- hi_IN/docs/update.html | 6 +- hi_IN/docs/v2_release_note.html | 6 +- hi_IN/docs/write_driver.html | 57 ++++-- hi_IN/docs/write_plugins.html | 53 +++--- hi_IN/gen.html | 6 +- hi_IN/gen/associations.html | 21 ++- hi_IN/gen/clause.html | 6 +- hi_IN/gen/create.html | 6 +- hi_IN/gen/dao.html | 6 +- hi_IN/gen/database_to_structs.html | 6 +- hi_IN/gen/delete.html | 6 +- hi_IN/gen/dynamic_sql.html | 6 +- hi_IN/gen/gen_tool.html | 6 +- hi_IN/gen/index.html | 6 +- hi_IN/gen/query.html | 10 +- hi_IN/gen/rawsql_driver.html | 6 +- hi_IN/gen/sql_annotation.html | 8 +- hi_IN/gen/transaction.html | 6 +- hi_IN/gen/update.html | 6 +- hi_IN/gorm.html | 6 +- hi_IN/gormx.html | 6 +- hi_IN/hints.html | 6 +- hi_IN/index.html | 4 +- hi_IN/rawsql.html | 6 +- hi_IN/rawsql_driver.html | 6 +- hi_IN/sharding.html | 6 +- hi_IN/stats.html | 6 +- id_ID/404.html | 4 +- id_ID/community.html | 6 +- id_ID/contribute.html | 6 +- id_ID/datatypes.html | 6 +- id_ID/docs/advanced_query.html | 138 ++++++++------ id_ID/docs/associations.html | 195 ++++++++++++-------- id_ID/docs/belongs_to.html | 6 +- id_ID/docs/changelog.html | 6 +- id_ID/docs/composite_primary_key.html | 6 +- id_ID/docs/connecting_to_the_database.html | 6 +- id_ID/docs/constraints.html | 6 +- id_ID/docs/context.html | 36 ++-- id_ID/docs/conventions.html | 8 +- id_ID/docs/create.html | 8 +- id_ID/docs/data_types.html | 6 +- id_ID/docs/dbresolver.html | 8 +- id_ID/docs/delete.html | 6 +- id_ID/docs/error_handling.html | 57 ++++-- id_ID/docs/generic_interface.html | 6 +- id_ID/docs/gorm_config.html | 6 +- id_ID/docs/has_many.html | 6 +- id_ID/docs/has_one.html | 6 +- id_ID/docs/hints.html | 6 +- id_ID/docs/hooks.html | 6 +- id_ID/docs/index.html | 6 +- id_ID/docs/indexes.html | 6 +- id_ID/docs/logger.html | 6 +- id_ID/docs/many_to_many.html | 8 +- id_ID/docs/method_chaining.html | 92 ++++++---- id_ID/docs/migration.html | 6 +- id_ID/docs/models.html | 75 +++++--- id_ID/docs/performance.html | 6 +- id_ID/docs/preload.html | 6 +- id_ID/docs/prometheus.html | 6 +- id_ID/docs/query.html | 6 +- id_ID/docs/scopes.html | 6 +- id_ID/docs/security.html | 8 +- id_ID/docs/serializer.html | 6 +- id_ID/docs/session.html | 6 +- id_ID/docs/settings.html | 6 +- id_ID/docs/sharding.html | 6 +- id_ID/docs/sql_builder.html | 6 +- id_ID/docs/transactions.html | 8 +- id_ID/docs/update.html | 6 +- id_ID/docs/v2_release_note.html | 6 +- id_ID/docs/write_driver.html | 57 ++++-- id_ID/docs/write_plugins.html | 53 +++--- id_ID/gen.html | 6 +- id_ID/gen/associations.html | 21 ++- id_ID/gen/clause.html | 6 +- id_ID/gen/create.html | 6 +- id_ID/gen/dao.html | 6 +- id_ID/gen/database_to_structs.html | 6 +- id_ID/gen/delete.html | 6 +- id_ID/gen/dynamic_sql.html | 6 +- id_ID/gen/gen_tool.html | 6 +- id_ID/gen/index.html | 6 +- id_ID/gen/query.html | 10 +- id_ID/gen/rawsql_driver.html | 6 +- id_ID/gen/sql_annotation.html | 8 +- id_ID/gen/transaction.html | 6 +- id_ID/gen/update.html | 6 +- id_ID/gorm.html | 6 +- id_ID/gormx.html | 6 +- id_ID/hints.html | 6 +- id_ID/index.html | 4 +- id_ID/rawsql.html | 6 +- id_ID/rawsql_driver.html | 6 +- id_ID/sharding.html | 6 +- id_ID/stats.html | 6 +- index.html | 4 +- it_IT/404.html | 6 +- it_IT/community.html | 6 +- it_IT/contribute.html | 8 +- it_IT/datatypes.html | 6 +- it_IT/docs/advanced_query.html | 138 ++++++++------ it_IT/docs/associations.html | 195 ++++++++++++-------- it_IT/docs/belongs_to.html | 6 +- it_IT/docs/changelog.html | 6 +- it_IT/docs/composite_primary_key.html | 6 +- it_IT/docs/connecting_to_the_database.html | 6 +- it_IT/docs/constraints.html | 6 +- it_IT/docs/context.html | 36 ++-- it_IT/docs/conventions.html | 8 +- it_IT/docs/create.html | 8 +- it_IT/docs/data_types.html | 6 +- it_IT/docs/dbresolver.html | 8 +- it_IT/docs/delete.html | 6 +- it_IT/docs/error_handling.html | 57 ++++-- it_IT/docs/generic_interface.html | 6 +- it_IT/docs/gorm_config.html | 6 +- it_IT/docs/has_many.html | 6 +- it_IT/docs/has_one.html | 6 +- it_IT/docs/hints.html | 6 +- it_IT/docs/hooks.html | 6 +- it_IT/docs/index.html | 6 +- it_IT/docs/indexes.html | 6 +- it_IT/docs/logger.html | 6 +- it_IT/docs/many_to_many.html | 8 +- it_IT/docs/method_chaining.html | 92 ++++++---- it_IT/docs/migration.html | 6 +- it_IT/docs/models.html | 57 ++++-- it_IT/docs/performance.html | 6 +- it_IT/docs/preload.html | 6 +- it_IT/docs/prometheus.html | 6 +- it_IT/docs/query.html | 6 +- it_IT/docs/scopes.html | 6 +- it_IT/docs/security.html | 8 +- it_IT/docs/serializer.html | 6 +- it_IT/docs/session.html | 6 +- it_IT/docs/settings.html | 6 +- it_IT/docs/sharding.html | 6 +- it_IT/docs/sql_builder.html | 6 +- it_IT/docs/transactions.html | 8 +- it_IT/docs/update.html | 6 +- it_IT/docs/v2_release_note.html | 6 +- it_IT/docs/write_driver.html | 57 ++++-- it_IT/docs/write_plugins.html | 53 +++--- it_IT/gen.html | 6 +- it_IT/gen/associations.html | 21 ++- it_IT/gen/clause.html | 6 +- it_IT/gen/create.html | 6 +- it_IT/gen/dao.html | 6 +- it_IT/gen/database_to_structs.html | 6 +- it_IT/gen/delete.html | 6 +- it_IT/gen/dynamic_sql.html | 6 +- it_IT/gen/gen_tool.html | 6 +- it_IT/gen/index.html | 6 +- it_IT/gen/query.html | 10 +- it_IT/gen/rawsql_driver.html | 6 +- it_IT/gen/sql_annotation.html | 8 +- it_IT/gen/transaction.html | 6 +- it_IT/gen/update.html | 6 +- it_IT/gorm.html | 6 +- it_IT/gormx.html | 6 +- it_IT/hints.html | 6 +- it_IT/index.html | 20 +-- it_IT/rawsql.html | 6 +- it_IT/rawsql_driver.html | 6 +- it_IT/sharding.html | 6 +- it_IT/stats.html | 6 +- ja_JP/404.html | 6 +- ja_JP/community.html | 6 +- ja_JP/contribute.html | 6 +- ja_JP/datatypes.html | 6 +- ja_JP/docs/advanced_query.html | 138 ++++++++------ ja_JP/docs/associations.html | 195 ++++++++++++-------- ja_JP/docs/belongs_to.html | 6 +- ja_JP/docs/changelog.html | 6 +- ja_JP/docs/composite_primary_key.html | 6 +- ja_JP/docs/connecting_to_the_database.html | 6 +- ja_JP/docs/constraints.html | 6 +- ja_JP/docs/context.html | 36 ++-- ja_JP/docs/conventions.html | 8 +- ja_JP/docs/create.html | 8 +- ja_JP/docs/data_types.html | 6 +- ja_JP/docs/dbresolver.html | 8 +- ja_JP/docs/delete.html | 6 +- ja_JP/docs/error_handling.html | 57 ++++-- ja_JP/docs/generic_interface.html | 6 +- ja_JP/docs/gorm_config.html | 6 +- ja_JP/docs/has_many.html | 6 +- ja_JP/docs/has_one.html | 6 +- ja_JP/docs/hints.html | 6 +- ja_JP/docs/hooks.html | 6 +- ja_JP/docs/index.html | 6 +- ja_JP/docs/indexes.html | 6 +- ja_JP/docs/logger.html | 6 +- ja_JP/docs/many_to_many.html | 8 +- ja_JP/docs/method_chaining.html | 92 ++++++---- ja_JP/docs/migration.html | 6 +- ja_JP/docs/models.html | 57 ++++-- ja_JP/docs/performance.html | 6 +- ja_JP/docs/preload.html | 6 +- ja_JP/docs/prometheus.html | 6 +- ja_JP/docs/query.html | 6 +- ja_JP/docs/scopes.html | 6 +- ja_JP/docs/security.html | 8 +- ja_JP/docs/serializer.html | 6 +- ja_JP/docs/session.html | 6 +- ja_JP/docs/settings.html | 6 +- ja_JP/docs/sharding.html | 6 +- ja_JP/docs/sql_builder.html | 6 +- ja_JP/docs/transactions.html | 8 +- ja_JP/docs/update.html | 6 +- ja_JP/docs/v2_release_note.html | 6 +- ja_JP/docs/write_driver.html | 57 ++++-- ja_JP/docs/write_plugins.html | 53 +++--- ja_JP/gen.html | 6 +- ja_JP/gen/associations.html | 21 ++- ja_JP/gen/clause.html | 6 +- ja_JP/gen/create.html | 6 +- ja_JP/gen/dao.html | 6 +- ja_JP/gen/database_to_structs.html | 6 +- ja_JP/gen/delete.html | 6 +- ja_JP/gen/dynamic_sql.html | 6 +- ja_JP/gen/gen_tool.html | 6 +- ja_JP/gen/index.html | 6 +- ja_JP/gen/query.html | 10 +- ja_JP/gen/rawsql_driver.html | 6 +- ja_JP/gen/sql_annotation.html | 8 +- ja_JP/gen/transaction.html | 6 +- ja_JP/gen/update.html | 6 +- ja_JP/gorm.html | 6 +- ja_JP/gormx.html | 6 +- ja_JP/hints.html | 6 +- ja_JP/index.html | 4 +- ja_JP/rawsql.html | 6 +- ja_JP/rawsql_driver.html | 6 +- ja_JP/sharding.html | 6 +- ja_JP/stats.html | 6 +- ko_KR/404.html | 6 +- ko_KR/community.html | 6 +- ko_KR/contribute.html | 6 +- ko_KR/datatypes.html | 6 +- ko_KR/docs/advanced_query.html | 138 ++++++++------ ko_KR/docs/associations.html | 195 ++++++++++++-------- ko_KR/docs/belongs_to.html | 6 +- ko_KR/docs/changelog.html | 6 +- ko_KR/docs/composite_primary_key.html | 6 +- ko_KR/docs/connecting_to_the_database.html | 6 +- ko_KR/docs/constraints.html | 6 +- ko_KR/docs/context.html | 36 ++-- ko_KR/docs/conventions.html | 8 +- ko_KR/docs/create.html | 8 +- ko_KR/docs/data_types.html | 6 +- ko_KR/docs/dbresolver.html | 8 +- ko_KR/docs/delete.html | 6 +- ko_KR/docs/error_handling.html | 57 ++++-- ko_KR/docs/generic_interface.html | 6 +- ko_KR/docs/gorm_config.html | 12 +- ko_KR/docs/has_many.html | 6 +- ko_KR/docs/has_one.html | 6 +- ko_KR/docs/hints.html | 6 +- ko_KR/docs/hooks.html | 6 +- ko_KR/docs/index.html | 6 +- ko_KR/docs/indexes.html | 6 +- ko_KR/docs/logger.html | 6 +- ko_KR/docs/many_to_many.html | 8 +- ko_KR/docs/method_chaining.html | 92 ++++++---- ko_KR/docs/migration.html | 6 +- ko_KR/docs/models.html | 57 ++++-- ko_KR/docs/performance.html | 6 +- ko_KR/docs/preload.html | 6 +- ko_KR/docs/prometheus.html | 6 +- ko_KR/docs/query.html | 6 +- ko_KR/docs/scopes.html | 6 +- ko_KR/docs/security.html | 8 +- ko_KR/docs/serializer.html | 6 +- ko_KR/docs/session.html | 6 +- ko_KR/docs/settings.html | 6 +- ko_KR/docs/sharding.html | 6 +- ko_KR/docs/sql_builder.html | 6 +- ko_KR/docs/transactions.html | 8 +- ko_KR/docs/update.html | 6 +- ko_KR/docs/v2_release_note.html | 6 +- ko_KR/docs/write_driver.html | 57 ++++-- ko_KR/docs/write_plugins.html | 53 +++--- ko_KR/gen.html | 6 +- ko_KR/gen/associations.html | 21 ++- ko_KR/gen/clause.html | 6 +- ko_KR/gen/create.html | 6 +- ko_KR/gen/dao.html | 6 +- ko_KR/gen/database_to_structs.html | 6 +- ko_KR/gen/delete.html | 6 +- ko_KR/gen/dynamic_sql.html | 6 +- ko_KR/gen/gen_tool.html | 6 +- ko_KR/gen/index.html | 6 +- ko_KR/gen/query.html | 10 +- ko_KR/gen/rawsql_driver.html | 6 +- ko_KR/gen/sql_annotation.html | 8 +- ko_KR/gen/transaction.html | 6 +- ko_KR/gen/update.html | 6 +- ko_KR/gorm.html | 6 +- ko_KR/gormx.html | 6 +- ko_KR/hints.html | 6 +- ko_KR/index.html | 4 +- ko_KR/rawsql.html | 6 +- ko_KR/rawsql_driver.html | 6 +- ko_KR/sharding.html | 6 +- ko_KR/stats.html | 6 +- pl_PL/404.html | 4 +- pl_PL/index.html | 4 +- pt_BR/404.html | 4 +- pt_BR/index.html | 4 +- ru_RU/404.html | 4 +- ru_RU/community.html | 6 +- ru_RU/contribute.html | 6 +- ru_RU/datatypes.html | 6 +- ru_RU/docs/advanced_query.html | 138 ++++++++------ ru_RU/docs/associations.html | 195 ++++++++++++-------- ru_RU/docs/belongs_to.html | 6 +- ru_RU/docs/changelog.html | 6 +- ru_RU/docs/composite_primary_key.html | 6 +- ru_RU/docs/connecting_to_the_database.html | 6 +- ru_RU/docs/constraints.html | 6 +- ru_RU/docs/context.html | 36 ++-- ru_RU/docs/conventions.html | 8 +- ru_RU/docs/create.html | 8 +- ru_RU/docs/data_types.html | 6 +- ru_RU/docs/dbresolver.html | 8 +- ru_RU/docs/delete.html | 6 +- ru_RU/docs/error_handling.html | 57 ++++-- ru_RU/docs/generic_interface.html | 6 +- ru_RU/docs/gorm_config.html | 6 +- ru_RU/docs/has_many.html | 6 +- ru_RU/docs/has_one.html | 6 +- ru_RU/docs/hints.html | 6 +- ru_RU/docs/hooks.html | 6 +- ru_RU/docs/index.html | 6 +- ru_RU/docs/indexes.html | 6 +- ru_RU/docs/logger.html | 6 +- ru_RU/docs/many_to_many.html | 8 +- ru_RU/docs/method_chaining.html | 92 ++++++---- ru_RU/docs/migration.html | 6 +- ru_RU/docs/models.html | 57 ++++-- ru_RU/docs/performance.html | 6 +- ru_RU/docs/preload.html | 6 +- ru_RU/docs/prometheus.html | 6 +- ru_RU/docs/query.html | 6 +- ru_RU/docs/scopes.html | 6 +- ru_RU/docs/security.html | 8 +- ru_RU/docs/serializer.html | 6 +- ru_RU/docs/session.html | 6 +- ru_RU/docs/settings.html | 6 +- ru_RU/docs/sharding.html | 6 +- ru_RU/docs/sql_builder.html | 6 +- ru_RU/docs/transactions.html | 8 +- ru_RU/docs/update.html | 6 +- ru_RU/docs/v2_release_note.html | 6 +- ru_RU/docs/write_driver.html | 57 ++++-- ru_RU/docs/write_plugins.html | 53 +++--- ru_RU/gen.html | 6 +- ru_RU/gen/associations.html | 21 ++- ru_RU/gen/clause.html | 6 +- ru_RU/gen/create.html | 6 +- ru_RU/gen/dao.html | 6 +- ru_RU/gen/database_to_structs.html | 6 +- ru_RU/gen/delete.html | 6 +- ru_RU/gen/dynamic_sql.html | 6 +- ru_RU/gen/gen_tool.html | 6 +- ru_RU/gen/index.html | 6 +- ru_RU/gen/query.html | 10 +- ru_RU/gen/rawsql_driver.html | 6 +- ru_RU/gen/sql_annotation.html | 8 +- ru_RU/gen/transaction.html | 6 +- ru_RU/gen/update.html | 6 +- ru_RU/gorm.html | 6 +- ru_RU/gormx.html | 6 +- ru_RU/hints.html | 6 +- ru_RU/index.html | 4 +- ru_RU/rawsql.html | 6 +- ru_RU/rawsql_driver.html | 6 +- ru_RU/sharding.html | 6 +- ru_RU/stats.html | 6 +- stats.html | 6 +- tr_TR/404.html | 4 +- tr_TR/index.html | 4 +- zh_CN/404.html | 4 +- zh_CN/community.html | 6 +- zh_CN/contribute.html | 6 +- zh_CN/datatypes.html | 6 +- zh_CN/docs/advanced_query.html | 138 ++++++++------ zh_CN/docs/associations.html | 195 ++++++++++++-------- zh_CN/docs/belongs_to.html | 6 +- zh_CN/docs/changelog.html | 6 +- zh_CN/docs/composite_primary_key.html | 6 +- zh_CN/docs/connecting_to_the_database.html | 14 +- zh_CN/docs/constraints.html | 6 +- zh_CN/docs/context.html | 36 ++-- zh_CN/docs/conventions.html | 8 +- zh_CN/docs/create.html | 8 +- zh_CN/docs/data_types.html | 6 +- zh_CN/docs/dbresolver.html | 8 +- zh_CN/docs/delete.html | 6 +- zh_CN/docs/error_handling.html | 57 ++++-- zh_CN/docs/generic_interface.html | 6 +- zh_CN/docs/gorm_config.html | 6 +- zh_CN/docs/has_many.html | 6 +- zh_CN/docs/has_one.html | 6 +- zh_CN/docs/hints.html | 6 +- zh_CN/docs/hooks.html | 6 +- zh_CN/docs/index.html | 6 +- zh_CN/docs/indexes.html | 6 +- zh_CN/docs/logger.html | 6 +- zh_CN/docs/many_to_many.html | 8 +- zh_CN/docs/method_chaining.html | 92 ++++++---- zh_CN/docs/migration.html | 6 +- zh_CN/docs/models.html | 57 ++++-- zh_CN/docs/performance.html | 6 +- zh_CN/docs/preload.html | 6 +- zh_CN/docs/prometheus.html | 6 +- zh_CN/docs/query.html | 6 +- zh_CN/docs/scopes.html | 6 +- zh_CN/docs/security.html | 8 +- zh_CN/docs/serializer.html | 6 +- zh_CN/docs/session.html | 6 +- zh_CN/docs/settings.html | 6 +- zh_CN/docs/sharding.html | 6 +- zh_CN/docs/sql_builder.html | 6 +- zh_CN/docs/transactions.html | 8 +- zh_CN/docs/update.html | 26 +-- zh_CN/docs/v2_release_note.html | 6 +- zh_CN/docs/write_driver.html | 57 ++++-- zh_CN/docs/write_plugins.html | 53 +++--- zh_CN/gen.html | 6 +- zh_CN/gen/associations.html | 29 ++- zh_CN/gen/clause.html | 6 +- zh_CN/gen/create.html | 6 +- zh_CN/gen/dao.html | 6 +- zh_CN/gen/database_to_structs.html | 34 ++-- zh_CN/gen/delete.html | 30 ++-- zh_CN/gen/dynamic_sql.html | 6 +- zh_CN/gen/gen_tool.html | 6 +- zh_CN/gen/index.html | 22 +-- zh_CN/gen/query.html | 10 +- zh_CN/gen/rawsql_driver.html | 6 +- zh_CN/gen/sql_annotation.html | 32 ++-- zh_CN/gen/transaction.html | 6 +- zh_CN/gen/update.html | 6 +- zh_CN/gorm.html | 6 +- zh_CN/gormx.html | 6 +- zh_CN/hints.html | 6 +- zh_CN/index.html | 4 +- zh_CN/rawsql.html | 6 +- zh_CN/rawsql_driver.html | 6 +- zh_CN/sharding.html | 6 +- zh_CN/stats.html | 6 +- 773 files changed, 6651 insertions(+), 4868 deletions(-) diff --git a/404.html b/404.html index 87d2a3923c1..f308db2acd1 100644 --- a/404.html +++ b/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/ar_SA/index.html b/ar_SA/index.html index 15761cf9407..9348201f339 100644 --- a/ar_SA/index.html +++ b/ar_SA/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/az_AZ/404.html b/az_AZ/404.html index 35c502d7a37..732dd7257c0 100644 --- a/az_AZ/404.html +++ b/az_AZ/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/az_AZ/index.html b/az_AZ/index.html index 8c42433c3bb..04d2ca85c7a 100644 --- a/az_AZ/index.html +++ b/az_AZ/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/community.html b/community.html index 1d4cbc3b3e9..68f729ba8e1 100644 --- a/community.html +++ b/community.html @@ -56,8 +56,8 @@ - - + + @@ -184,7 +184,7 @@

- + diff --git a/contribute.html b/contribute.html index f36924ed677..f16aca4f889 100644 --- a/contribute.html +++ b/contribute.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/de_DE/404.html b/de_DE/404.html index ba28c1e599b..0458191acf0 100644 --- a/de_DE/404.html +++ b/de_DE/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/de_DE/community.html b/de_DE/community.html index 650d6b4a36c..e20924aef79 100644 --- a/de_DE/community.html +++ b/de_DE/community.html @@ -56,8 +56,8 @@ - - + + @@ -184,7 +184,7 @@

- + diff --git a/de_DE/contribute.html b/de_DE/contribute.html index 1d6bbc37548..689d1cde7b8 100644 --- a/de_DE/contribute.html +++ b/de_DE/contribute.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/de_DE/datatypes.html b/de_DE/datatypes.html index c1273756ea0..6dc156d7792 100644 --- a/de_DE/datatypes.html +++ b/de_DE/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/de_DE/docs/advanced_query.html b/de_DE/docs/advanced_query.html index a679fb467b8..32994323604 100644 --- a/de_DE/docs/advanced_query.html +++ b/de_DE/docs/advanced_query.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,80 +141,106 @@

Erweiterte Abfrage

-

Intelligente Feldauswahl

GORM allows selecting specific fields with Select, 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:

-
type User struct {
ID uint
Name string
Age int
Gender string
// hunderte weitere Felder
}

type APIUser struct {
ID uint
Name string
}

// Wähle `id`, `name` bei Abfragen automatisch aus
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
+

Intelligente Feldauswahl

In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

+
type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// GORM will automatically select `id`, `name` fields when querying
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SQL: SELECT `id`, `name` FROM `users` LIMIT 10
-

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.

-
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

db.Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // mit dieser Option

// Session Modus
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users`
+
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

// Default behavior with QueryFields set to true
db.Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

// Using Session Mode with QueryFields
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
-

Locking (für Updates)

GORM unterstützt verschiedene Locks. Beispielsweise:

-
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATE

db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`

db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
+

Locking

GORM unterstützt verschiedene Locks. Beispielsweise:

+
// Basic FOR UPDATE lock
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE
-

Siehe Raw SQL und SQL Builder für weitere Informationen

-

SubQuery

Eine Unterabfrage kann innerhalb einer Abfrage verschachtelt werden, GORM kann Unterabfrage erzeugen, wenn ein *gorm.DB Objekt als Parameter verwendet wird

-
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

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%")
+

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.

+
db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SQL: SELECT * FROM `users` FOR SHARE OF `users`
-

From SubQuery

GORM ermöglicht die Nutzung von Unterabfragen in FROM Blöcken mit der Table Methode:

-
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

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
+

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.

+
db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
-

Gruppierungsbedingungen

Gruppierungsbedingungen erleichtern das Schreiben komplexer SQL Abfragen

-
db.Where(
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")
+

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.

+

SubQuery

Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

+
// Simple subquery
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
-

IN mit mehreren Spalten

Auswahl von IN mit mehreren Spalten

-
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"));
+

From SubQuery

GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

+
// Using subquery in FROM clause
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// 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{})
// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
-

Benannte Argumente

GORM unterstützt benannte Argumente mit sql.NamedArg oder map[string]interface{}{}

-
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

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
+

Gruppierungsbedingungen

Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

+
// Complex SQL query using Group Conditions
db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
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")
-

Check out Raw SQL and SQL Builder for more detail

-

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:

-
result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)

var results []map[string]interface{}
db.Table("users").Find(&results)
+

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.

+
// Using IN with multiple columns
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
-

FirstOrInit

Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions)

-
// User not found, initialize it with give conditions
db.FirstOrInit(&user, User{Name: "non_existing"})
// user -> User{Name: "non_existing"}

// Found user with `name` = `jinzhu`
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}

// Found user with `name` = `jinzhu`
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}
+

Benannte Argumente

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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

+
// Example using sql.NamedArg for named arguments
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// 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)
// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
-

Initialize struct with more attributes if record not found, those Attrs won’t be used to build the SQL query

-
// User not found, initialize it with give conditions and Attrs
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}

// Found user with `name` = `jinzhu`, attributes will be 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}
+

For more examples and details, see Raw SQL and SQL Builder

+

Ergebnisse in Maps speichern

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.

+
// 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`
-

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

-
// User not found, initialize it with give conditions and Assign attributes
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
// user -> User{Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, update it 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}
+

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.

+
// 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"} if not found

// Retrieving a user named "jinzhu"
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

// Using a map to specify the search condition
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

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

-
// User not found, create a new record with give conditions
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1

// Found user with `name` = `jinzhu`
result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
// user -> User{ID: 111, Name: "jinzhu", "Age": 18}
// result.RowsAffected // => 0
+

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.

+
// If no User is found, initialize with given conditions and additional attributes
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20} if not found

// If a User named "Jinzhu" is found, `Attrs` are ignored
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

Create struct with more attributes if record not found, those Attrs won’t be used to build SQL query

-
// User not found, create it with give conditions and Attrs
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, attributes will be ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
+

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.

+
// 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} if not found

// If a User named "Jinzhu" is found, update the struct with Assign attributes
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
-

Assign attributes to the record regardless it is found or not and save them back to the database.

-
// User not found, initialize it with give conditions and Assign attributes
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, update it 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}
+

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

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.

+
// Create a new record if not found
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// SQL: INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1 (record created)

// 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)
-

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:

-
import "gorm.io/hints"

db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
+

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.

+
// Create a new record with additional attributes if not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// 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}

// If the user is found, `Attrs` are ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
-

Index hints allow passing index hints to the database in case the query planner gets confused.

-
import "gorm.io/hints"

db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)

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`)"
+

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.

+
// Initialize and save new record with `Assign` attributes if not found
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// 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}

// Update found record with `Assign` attributes
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// SQL: UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20}
-

Refer Optimizer Hints/Index/Comment for more details

-

Iteration

GORM supports iterating through Rows

-
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
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
db.ScanRows(rows, &user)

// do something
}
+

Optimizer/Index 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.

+
import "gorm.io/hints"

// Using an optimizer hint to set a maximum execution time
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
-

FindInBatches

Query and process records in batch

-
// batch size 100
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// batch processing found records
}

tx.Save(&results)

tx.RowsAffected // number of records in this batch

batch // Batch 1, 2, 3

// returns error will stop future batches
return nil
})

result.Error // returned error
result.RowsAffected // processed records count in all batches
+

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.

+
import "gorm.io/hints"

// Suggesting the use of a specific index
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// 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{})
// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
-

Query Hooks

GORM allows hooks AfterFind for a query, it will be called when querying a record, refer Hooks for details

-
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.Role == "" {
u.Role = "user"
}
return
}
+

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 in the GORM documentation.

+

Iteration

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.

+
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()

for rows.Next() {
var user User
// ScanRows scans a row into a struct
db.ScanRows(rows, &user)

// Perform operations on each user
}
-

Pluck

Query single column from database and scan into a slice, if you want to query multiple columns, use Select with Scan instead

-
var ages []int64
db.Model(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

// Distinct Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SELECT DISTINCT `name` FROM `users`

// Requesting more than one column, use `Scan` or `Find` like this:
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+

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.

+
// 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 provides the count of records in the current batch
// The variable 'batch' indicates the current batch number

// Returning an error will stop further batch processing
return nil
})

// result.Error contains any errors encountered during batch processing
// result.RowsAffected provides the count of all processed records across batches
-

Scopes

Scopes allows you to specify commonly-used queries which can be referenced as method calls

-
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}

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 {
return db.Where("status IN (?)", status)
}
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// Find all credit card orders and amount greater than 1000

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// Find all COD orders and amount greater than 1000

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// Find all paid, shipped orders that amount greater than 1000
+

FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

+

Query Hooks

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 in the GORM documentation.

+
func (u *User) AfterFind(tx *gorm.DB) (err error) {
// Custom logic after finding a user
if u.Role == "" {
u.Role = "user" // Set default role if not specified
}
return
}

// Usage of AfterFind hook happens automatically when a User is queried
-

Checkout Scopes for details

-

Count

Get matched records count

-
var count int64
db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

db.Table("deleted_users").Count(&count)
// SELECT count(1) FROM deleted_users;

// Count with Distinct
db.Model(&User{}).Distinct("name").Count(&count)
// SELECT COUNT(DISTINCT(`name`)) FROM `users`

db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SELECT count(distinct(name)) FROM deleted_users

// Count with Group
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
count // => 3
+

Pluck

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 or Find instead.

+
// Retrieving ages of all users
var ages []int64
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)

// Using Distinct with Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SQL: SELECT DISTINCT `name` FROM `users`

// Querying multiple columns
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+ +

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.

+
// 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")
}

// 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.

+
// 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)
+ +

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 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.

+
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'

// Counting records in a different table
db.Table("deleted_users").Count(&count)
// SQL: SELECT count(1) FROM deleted_users
+ +

Count with Distinct and Group

GORM also allows counting distinct values and grouping results.

+
// Counting distinct names
db.Model(&User{}).Distinct("name").Count(&count)
// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

// Counting distinct values with a custom select
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SQL: SELECT count(distinct(name)) FROM deleted_users

// Counting grouped records
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
// Count after grouping by name
// count => 3
@@ -227,7 +253,7 @@

- + @@ -341,7 +367,7 @@

Gold Sponsors

Contents -
  1. Intelligente Feldauswahl
  2. Locking (für Updates)
  3. SubQuery
    1. From SubQuery
  4. Gruppierungsbedingungen
  5. IN mit mehreren Spalten
  6. Benannte Argumente
  7. Ergebnisse in Maps speichern
  8. FirstOrInit
  9. FirstOrCreate
  10. Optimizer/Index Hints
  11. Iteration
  12. FindInBatches
  13. Query Hooks
  14. Pluck
  15. Scopes
  16. Count
+
  1. Intelligente Feldauswahl
  2. Locking
  3. SubQuery
    1. From SubQuery
  4. Gruppierungsbedingungen
  5. IN mit mehreren Spalten
  6. Benannte Argumente
  7. Ergebnisse in Maps speichern
  8. FirstOrInit
    1. Using Attrs for Initialization
    2. Using Assign for Attributes
  9. FirstOrCreate
    1. Using Attrs with FirstOrCreate
    2. Using Assign with FirstOrCreate
  10. Optimizer/Index Hints
    1. Index Hints
  11. Iteration
  12. FindInBatches
  13. Query Hooks
  14. Pluck
  15. Scopes
    1. Defining Scopes
    2. Applying Scopes in Queries
  16. Count
    1. Getting the Count of Matched Records
    2. Count with Distinct and Group
diff --git a/de_DE/docs/associations.html b/de_DE/docs/associations.html index 1372eb778b8..82393efb771 100644 --- a/de_DE/docs/associations.html +++ b/de_DE/docs/associations.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,62 +141,101 @@

Associations

-

Automatisch erstellen/aktualisieren

GORM will auto-save associations and its reference using Upsert when creating/updating a record.

-
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"},
},
}

db.Create(&user)
// BEGIN TRANSACTION;
// 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 ('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)
- -

If you want to update associations’s data, you should use the FullSaveAssociations mode:

-
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);
// ...
- -

Automatisches Erstellen/Aktualisieren überspringen

To skip the auto save when creating/updating, you can use Select or Omit, for example:

-
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"},
},
}

db.Select("Name").Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

db.Omit("BillingAddress").Create(&user)
// Skip create BillingAddress when creating a user

db.Omit(clause.Associations).Create(&user)
// Skip all associations when creating a user
- -

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:

-
db.Omit("Languages.*").Create(&user)
- -

The following code will skip the creation of the association and its references

-
db.Omit("Languages").Create(&user)
- -

Select/Omit Association fields

user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
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
db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
- -

Association Mode

Association Mode contains some commonly used helper methods to handle relationships

-
// 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
- -

Find Associations

Find matched associations

-
db.Model(&user).Association("Languages").Find(&languages)
- -

Find associations 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

Append new associations for many to many, has many, replace current association for has one, belongs to

-
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
- -

Replace Associations

Replace current associations with new ones

-
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
- -

Delete Associations

Remove the relationship between source & arguments if exists, only delete the reference, won’t delete those objects from DB.

-
db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
- -

Clear Associations

Remove all reference between source & association, won’t delete those associations

-
db.Model(&user).Association("Languages").Clear()
- -

Count Associations

Return the count of current associations

-
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

Association Mode supports batch data, e.g:

-
// Find all roles for all users
db.Model(&users).Association("Role").Find(&roles)

// Delete User A from all user's team
db.Model(&users).Association("Team").Delete(&userA)

// Get distinct count of all users' teams
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
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
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
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).

-

How to delete is decided by gorm.DB.

-
// Soft delete
// UPDATE `languages` SET `deleted_at`= ...
db.Model(&user).Association("Languages").Unscoped().Clear()

// Delete permanently
// DELETE FROM `languages` WHERE ...
db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
- -

Delete with Select

You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

-
// 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)
- -

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

-
// 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
- -

Association Tags

+

Automatisch erstellen/aktualisieren

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.

+
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"},
},
}

// 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;
// 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 ('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)
+ +

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.

+
// Update a user and fully update all its associations
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
// 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

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.

+
user := User{
// User and associated data
}

// Only include the 'Name' field when creating the user
db.Select("Name").Create(&user)
// 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.

+
// Skip creating the 'BillingAddress' when creating the user
db.Omit("BillingAddress").Create(&user)

// Skip all associations when creating the user
db.Omit(clause.Associations).Create(&user)
+ +

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 .*:

+
// Skip upserting 'Languages' associations
db.Omit("Languages.*").Create(&user)
+ +

To skip creating both the association and its references:

+
// Skip creating 'Languages' associations and their references
db.Omit("Languages").Create(&user)
+ +

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.

+
user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
}

// 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.

+
// 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: 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.

+
// 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
+ +

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.

+
var user User
db.Model(&user).Association("Languages")
// Check for errors
error := db.Model(&user).Association("Languages").Error
+ +

Finding Associations

Retrieve associated records with or without additional conditions.

+
// Simple find
db.Model(&user).Association("Languages").Find(&languages)

// Find with conditions
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).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.

+
// Append new languages
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

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.

+
// 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.

+
// 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.

+
// Clear all languages
db.Model(&user).Association("Languages").Clear()
+ +

Counting Associations

Get the count of current associations, with or without conditions.

+
// 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.
  • +
+
db.Model(&users).Association("Role").Find(&roles)
+ +
    +
  • Deleting Associations: Remove specific associations across multiple records.
  • +
+
db.Model(&users).Association("Team").Delete(&userA)
+ +
    +
  • Counting Associations: Get the count of associations for a batch of records.
  • +
+
db.Model(&users).Association("Team").Count()
+ +
    +
  • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
  • +
+
var users = []User{user1, user2, 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})

// 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

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.

+
    +
  • 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.
  • +
+

Modifying Deletion Behavior with Unscoped

For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

+
    +
  • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
  • +
+
db.Model(&user).Association("Languages").Unscoped().Clear()
+ +
    +
  • Permanent Delete: Physically deletes the association records from the database.
  • +
+
// db.Unscoped().Model(&user)
db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
+ +

Association Tags

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.

+ @@ -204,36 +243,36 @@

- - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
Tag
foreignKeySpecifies column name of the current model that is used as a foreign key to the join tableforeignKeySpecifies the column name of the current model used as a foreign key in the join table.
referencesSpecifies column name of the reference’s table that is mapped to the foreign key of the join tablereferencesIndicates the column name in the reference table that the foreign key of the join table maps to.
polymorphicSpecifies polymorphic type such as model namepolymorphicDefines the polymorphic type, typically the model name.
polymorphicValueSpecifies polymorphic value, default table namepolymorphicValueSets the polymorphic value, usually the table name, if not specified otherwise.
many2manySpecifies join table namemany2manyNames the join table used in a many-to-many relationship.
joinForeignKeySpecifies foreign key column name of join table that maps to the current tablejoinForeignKeyIdentifies the foreign key column in the join table that maps back to the current model’s table.
joinReferencesSpecifies foreign key column name of join table that maps to the reference’s tablejoinReferencesPoints to the foreign key column in the join table that links to the reference model’s table.
constraintRelations constraint, e.g: OnUpdate,OnDeleteconstraintSpecifies relational constraints like OnUpdate, OnDelete for the association.
@@ -248,7 +287,7 @@

@@ -362,7 +401,7 @@

Gold Sponsors

Contents -
  1. Automatisch erstellen/aktualisieren
  2. Automatisches Erstellen/Aktualisieren überspringen
  3. Select/Omit Association fields
  4. Association Mode
    1. Find Associations
    2. Append Associations
    3. Replace Associations
    4. Delete Associations
    5. Clear Associations
    6. Count Associations
    7. Batch Data
  5. Delete Association Record
  6. Delete with Select
  7. Association Tags
+
  1. Automatisch erstellen/aktualisieren
    1. Auto-Saving Associations on Create
    2. Updating Associations with FullSaveAssociations
  2. Automatisches Erstellen/Aktualisieren überspringen
    1. Using Select to Include Specific Fields
    2. Using Omit to Exclude Fields or Associations
  3. Select/Omit Association fields
  4. Delete Associations
  5. Association Mode
    1. Finding Associations
    2. Appending Associations
    3. Replacing Associations
    4. Deleting Associations
    5. Clearing Associations
    6. Counting Associations
    7. Batch Data Handling
  6. Delete Association Record
    1. Modifying Deletion Behavior with Unscoped
  7. Association Tags
diff --git a/de_DE/docs/belongs_to.html b/de_DE/docs/belongs_to.html index 415755cfad4..86e96a7b0c7 100644 --- a/de_DE/docs/belongs_to.html +++ b/de_DE/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

- + diff --git a/de_DE/docs/changelog.html b/de_DE/docs/changelog.html index 8cc42d6dcce..63def64448f 100644 --- a/de_DE/docs/changelog.html +++ b/de_DE/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/de_DE/docs/composite_primary_key.html b/de_DE/docs/composite_primary_key.html index cc4a8b74d19..e2a05c1e99d 100644 --- a/de_DE/docs/composite_primary_key.html +++ b/de_DE/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

Composite Primary Key

- +
diff --git a/de_DE/docs/connecting_to_the_database.html b/de_DE/docs/connecting_to_the_database.html index 38810116f1d..3eae23a2517 100644 --- a/de_DE/docs/connecting_to_the_database.html +++ b/de_DE/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

- + diff --git a/de_DE/docs/constraints.html b/de_DE/docs/constraints.html index 6f03bf5c061..48a59103fd0 100644 --- a/de_DE/docs/constraints.html +++ b/de_DE/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/de_DE/docs/context.html b/de_DE/docs/context.html index ab97e647073..bbc46065cd4 100644 --- a/de_DE/docs/context.html +++ b/de_DE/docs/context.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,25 @@

Context

-

GORM provides Context support, you can use it with method WithContext

-

Single Session Mode

Single session mode usually used when you want to perform a single operation

+

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 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.

db.WithContext(ctx).Find(&users)
-

Continuous session mode

Continuous session mode is usually used when you want to perform a group of operations, for example:

+

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.

tx := db.WithContext(ctx)
tx.First(&user, 1)
tx.Model(&user).Update("role", "admin")
-

Context timeout

You can pass in a context with a timeout to db.WithContext to set timeout for long running queries, for example:

+

Context Timeout

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.

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

db.WithContext(ctx).Find(&users)
-

Context in Hooks/Callbacks

You can access the Context object from the current Statement, for example:

-
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ...
return
}
+

Context in Hooks/Callbacks

The context can also be accessed within GORM’s hooks/callbacks. This enables contextual information to be used during these lifecycle events.

+
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ... use context
return
}
-

Chi Middleware Example

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:

-
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

r := chi.NewRouter()
r.Use(SetDBMiddleware)

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
})

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
})
+

Integration with 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.

+
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

// Router setup
r := chi.NewRouter()
r.Use(SetDBMiddleware)

// Route handlers
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})

r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})
-

NOTE Setting Context with WithContext is goroutine-safe, refer Session for details

-
- -

Logger

Logger accepts Context too, you can use it for log tracking, refer Logger for details

+

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 in GORM.

+

Logger Integration

GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

+

Refer to Logger documentation for more details.

@@ -174,7 +172,7 @@

- + @@ -288,7 +286,7 @@

Gold Sponsors

Contents -
  1. Single Session Mode
  2. Continuous session mode
  3. Context timeout
  4. Context in Hooks/Callbacks
  5. Chi Middleware Example
  6. Logger
+
  1. Single Session Mode
  2. Continuous Session Mode
  3. Context Timeout
  4. Context in Hooks/Callbacks
  5. Integration with Chi Middleware
  6. Logger Integration
diff --git a/de_DE/docs/conventions.html b/de_DE/docs/conventions.html index f7e7a7be80c..0beea31ad07 100644 --- a/de_DE/docs/conventions.html +++ b/de_DE/docs/conventions.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

// Create table `deleted_users` with struct User's fields
db.Table("deleted_users").AutoMigrate(&User{})

// Query data from another table
var deletedUsers []User
db.Table("deleted_users").Find(&deletedUsers)
// SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
// DELETE FROM deleted_users WHERE name = 'jinzhu';

Check out From SubQuery for how to use SubQuery in FROM clause

-

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 for details

+

NamingStrategy

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 for details

Column Name

Column db name uses the field’s name’s snake_case by convention.

type User struct {
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}
@@ -194,7 +194,7 @@

- + diff --git a/de_DE/docs/create.html b/de_DE/docs/create.html index bc6bbed3b33..89453370c0f 100644 --- a/de_DE/docs/create.html +++ b/de_DE/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -155,7 +155,7 @@

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
-

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.

+

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 split into multiple batches.

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

You can specify batch size when creating with CreateInBatches, e.g:

@@ -220,7 +220,7 @@

- + diff --git a/de_DE/docs/data_types.html b/de_DE/docs/data_types.html index 7607ded192a..099f82c106e 100644 --- a/de_DE/docs/data_types.html +++ b/de_DE/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

- + diff --git a/de_DE/docs/dbresolver.html b/de_DE/docs/dbresolver.html index b5037be0919..e2b0da9a956 100644 --- a/de_DE/docs/dbresolver.html +++ b/de_DE/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

Transaction

When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

But you can specifies which DB to use before starting a transaction, for example:

-
// Start transaction based on default replicas db
tx := DB.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := DB.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
+
// Start transaction based on default replicas db
tx := db.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := db.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

Load Balancing

GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

type Policy interface {
Resolve([]gorm.ConnPool) gorm.ConnPool
}
@@ -183,7 +183,7 @@

diff --git a/de_DE/docs/delete.html b/de_DE/docs/delete.html index b135e3c4d3b..349f534aa62 100644 --- a/de_DE/docs/delete.html +++ b/de_DE/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

- + diff --git a/de_DE/docs/error_handling.html b/de_DE/docs/error_handling.html index fdfa90e742f..e816fd962db 100644 --- a/de_DE/docs/error_handling.html +++ b/de_DE/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

Error Handling

-

In Go, error handling is important.

-

You are encouraged to do error check after any Finisher Methods

-

Error Handling

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:

-
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// error handling...
}
- -

Or

-
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// error handling...
}
- -

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:

-
// Check if returns RecordNotFound error
err := db.First(&user, 100).Error
errors.Is(err, gorm.ErrRecordNotFound)
-

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.

+

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.

+

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.

+

After a chain of methods, it’s crucial to check the Error field:

+
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// Handle error...
}
+ +

Or alternatively:

+
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// Handle error...
}
+ +

ErrRecordNotFound

GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

+
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

+
    +
  • Example: Handling MySQL Error Codes
  • +
+
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.

db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
-

Errors

Errors List

+
    +
  • ErrDuplicatedKey
  • +
+

This error occurs when an insert operation violates a unique constraint:

+
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:

+
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

For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

@@ -168,7 +187,7 @@

- + @@ -282,7 +301,7 @@

Gold Sponsors

Contents -
  1. Error Handling
  2. ErrRecordNotFound
  3. Dialect Translated Errors
  4. Errors
+
  1. Basic Error Handling
  2. ErrRecordNotFound
  3. Handling Error Codes
  4. Dialect Translated Errors
  5. Errors
diff --git a/de_DE/docs/generic_interface.html b/de_DE/docs/generic_interface.html index cc905f7e983..fdad5366a31 100644 --- a/de_DE/docs/generic_interface.html +++ b/de_DE/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

diff --git a/de_DE/docs/gorm_config.html b/de_DE/docs/gorm_config.html index 51ae89b1a9f..e001cefd2a1 100644 --- a/de_DE/docs/gorm_config.html +++ b/de_DE/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

diff --git a/de_DE/docs/has_many.html b/de_DE/docs/has_many.html index 9d91a179a7f..dfee34ee4ab 100644 --- a/de_DE/docs/has_many.html +++ b/de_DE/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/de_DE/docs/has_one.html b/de_DE/docs/has_one.html index 4ad8633b658..62835856d24 100644 --- a/de_DE/docs/has_one.html +++ b/de_DE/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/de_DE/docs/hints.html b/de_DE/docs/hints.html index fa50a74eed4..c47aa226eb0 100644 --- a/de_DE/docs/hints.html +++ b/de_DE/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

- + diff --git a/de_DE/docs/hooks.html b/de_DE/docs/hooks.html index 340894e7283..a34a4a530b5 100644 --- a/de_DE/docs/hooks.html +++ b/de_DE/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

- + diff --git a/de_DE/docs/index.html b/de_DE/docs/index.html index 3b39d8d74b3..65ee73dda55 100644 --- a/de_DE/docs/index.html +++ b/de_DE/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

diff --git a/de_DE/docs/indexes.html b/de_DE/docs/indexes.html index cf574f904e3..019714fb95b 100644 --- a/de_DE/docs/indexes.html +++ b/de_DE/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

diff --git a/de_DE/docs/logger.html b/de_DE/docs/logger.html index 327aa16c610..6c7615bffe9 100644 --- a/de_DE/docs/logger.html +++ b/de_DE/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

diff --git a/de_DE/docs/many_to_many.html b/de_DE/docs/many_to_many.html index 27abad71f69..e1f0ef06d6a 100644 --- a/de_DE/docs/many_to_many.html +++ b/de_DE/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

-
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addressses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
+
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

FOREIGN KEY Constraints

You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_speaks;"`
}

type Language struct {
Code string `gorm:"primarykey"`
Name string
}

// CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
@@ -191,7 +191,7 @@

- + diff --git a/de_DE/docs/method_chaining.html b/de_DE/docs/method_chaining.html index 5e9bd6e50f2..ce2c002b681 100644 --- a/de_DE/docs/method_chaining.html +++ b/de_DE/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

Method Chaining

-

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:

db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
-

There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

-

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:

-
queryDB := DB.Where("name = ?", "jinzhu")

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// 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:

-
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 20
- -

Chain Method

Chain methods are methods to modify or add Clauses to current Statement, like:

-

Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

-

Here is the full lists, also check out the SQL Builder 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 here.

-

New Session Method

GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new 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:
// 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:
// 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:
// SELECT * FROM users;
- -

(Bad) Example 2:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is safe to 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

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
- -

Example 3:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is 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

// good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// good case
tx.Where("age = ?", 28).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
+

Method Categories

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. Also, the SQL Builder 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.

+

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 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

queryDB := DB.Where("name = ?", "jinzhu")

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query with unintended compounded condition
queryDB.Where("age > ?", 20).First(&user2)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
+ +

Example of Safe Reuse

To safely reuse a *gorm.DB instance, use a New Session Method:

+
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query, safely isolated
queryDB.Where("age > ?", 20).First(&user2)
// 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.

+

Examples for Clarity

Let’s clarify with a few examples:

+
    +
  • Example 1: Safe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
// 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)
// 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)
// Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
// SELECT * FROM users;
+ +

In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

+
    +
  • (Bad) Example 2: Unsafe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized *gorm.DB, safe for initial reuse.

tx := db.Where("name = ?", "jinzhu")
// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

// Good case
tx.Where("age = ?", 18).Find(&users)
// Reuses 'tx' correctly for a single logical operation, executing:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// Bad case
tx.Where("age = ?", 28).Find(&users)
// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
+ +

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
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

// Good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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.

@@ -180,7 +210,7 @@

- + @@ -294,7 +324,7 @@

Gold Sponsors

Contents -
  1. Chain Method
  2. Finisher Method
  3. New Session Method
+
  1. Method Categories
    1. Chain Methods
    2. Finisher Methods
    3. New Session Methods
  2. Reusability and Safety
    1. Example of Unsafe Reuse
    2. Example of Safe Reuse
  3. Examples for Clarity
diff --git a/de_DE/docs/migration.html b/de_DE/docs/migration.html index 1b7880de8ba..f9260ed7e86 100644 --- a/de_DE/docs/migration.html +++ b/de_DE/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

- + diff --git a/de_DE/docs/models.html b/de_DE/docs/models.html index 742e71417d6..d91a40c18d4 100644 --- a/de_DE/docs/models.html +++ b/de_DE/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,16 +141,45 @@

Declaring Models

-

Declaring Models

Models are normal structs with basic Go types, pointers/alias of them or custom types implementing Scanner and Valuer interfaces

-

For Example:

-
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
}
- -

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

-

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

-

gorm.Model

GORM defined a gorm.Model struct, which includes fields ID, CreatedAt, UpdatedAt, DeletedAt

+

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 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 and Valuer interfaces from the database/sql package

+

Consider the following example of a User model:

+
type User struct {
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 for a detailed explanation

+

Conventions

    +
  1. Primary Key: GORM uses a field named ID as the default primary key for each model.

    +
  2. +
  3. 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.

    +
  4. +
  5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

    +
  6. +
  7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

    +
  8. +
+

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.

+

gorm.Model

GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

// gorm.Model definition
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
-

You can embed it into your struct to include those fields, refer 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

    +
  • +
  • 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

Field-Level Permission

Exported fields have all permissions when doing CRUD with GORM, and GORM allows you to change the field-level permission with tag, so you can make a field to be read-only, write-only, create-only, update-only or ignored

NOTE ignored fields won’t be created when using GORM Migrator to create table

@@ -286,7 +315,7 @@

@@ -400,7 +429,7 @@

Gold Sponsors

Contents -
  1. Declaring Models
  2. Conventions
  3. gorm.Model
  4. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
+
  1. Declaring Models
    1. Conventions
    2. gorm.Model
  2. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
diff --git a/de_DE/docs/performance.html b/de_DE/docs/performance.html index edc82904755..91ad41c3e40 100644 --- a/de_DE/docs/performance.html +++ b/de_DE/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

- + diff --git a/de_DE/docs/preload.html b/de_DE/docs/preload.html index 2104799a336..e439d12b466 100644 --- a/de_DE/docs/preload.html +++ b/de_DE/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

- + diff --git a/de_DE/docs/prometheus.html b/de_DE/docs/prometheus.html index cffd886b26d..cd91d385b0d 100644 --- a/de_DE/docs/prometheus.html +++ b/de_DE/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- + diff --git a/de_DE/docs/query.html b/de_DE/docs/query.html index dcccc047f86..34affbb565d 100644 --- a/de_DE/docs/query.html +++ b/de_DE/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

- + diff --git a/de_DE/docs/scopes.html b/de_DE/docs/scopes.html index e863467b602..1dee7341f1c 100644 --- a/de_DE/docs/scopes.html +++ b/de_DE/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/de_DE/docs/security.html b/de_DE/docs/security.html index 49664f766b4..a6ff2539d38 100644 --- a/de_DE/docs/security.html +++ b/de_DE/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

Inline Condition

// will be escaped
db.First(&user, "name = ?", userInput)

// SQL injection
db.First(&user, fmt.Sprintf("name = %v", userInput))

When retrieving objects with number primary key by user’s input, you should check the type of variable.

-
userInputID := "1=1;drop table users;"
// safe, return error
id,err := strconv.Atoi(userInputID)
if err != nil {
return error
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;
+
userInputID := "1=1;drop table users;"
// safe, return error
id, err := strconv.Atoi(userInputID)
if err != nil {
return err
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;

SQL injection Methods

To support some features, some inputs are not escaped, be careful when using user’s input with those methods

db.Select("name; drop table users;").First(&user)
db.Distinct("name; drop table users;").First(&user)

db.Model(&user).Pluck("name; drop table users;", &names)

db.Group("name; drop table users;").First(&user)

db.Group("name").Having("1 = 1;drop table users;").First(&user)

db.Raw("select name from users; drop table users;").First(&user)

db.Exec("select name from users; drop table users;")

db.Order("name; drop table users;").First(&user)
@@ -169,7 +169,7 @@

- + diff --git a/de_DE/docs/serializer.html b/de_DE/docs/serializer.html index bb181c118fe..db3cd0f559c 100644 --- a/de_DE/docs/serializer.html +++ b/de_DE/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

- + diff --git a/de_DE/docs/session.html b/de_DE/docs/session.html index 01ba4fbba19..4f7f9b3002f 100644 --- a/de_DE/docs/session.html +++ b/de_DE/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

diff --git a/de_DE/docs/settings.html b/de_DE/docs/settings.html index 3b249f1123d..5c63feec9ed 100644 --- a/de_DE/docs/settings.html +++ b/de_DE/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/de_DE/docs/sharding.html b/de_DE/docs/sharding.html index 486fffb45cc..0519f5a54a4 100644 --- a/de_DE/docs/sharding.html +++ b/de_DE/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

- + diff --git a/de_DE/docs/sql_builder.html b/de_DE/docs/sql_builder.html index 1ae52ac7ca4..584dac6cac4 100644 --- a/de_DE/docs/sql_builder.html +++ b/de_DE/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

diff --git a/de_DE/docs/transactions.html b/de_DE/docs/transactions.html index 113e2089a5c..caa707b5fb1 100644 --- a/de_DE/docs/transactions.html +++ b/de_DE/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

db.Transaction(func(tx *gorm.DB) error {
// do some database operations in the transaction (use 'tx' from this point, not 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// return any error will rollback
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// return nil will commit the whole transaction
return nil
})

Nested Transactions

GORM supports nested transactions, you can rollback a subset of operations performed within the scope of a larger transaction, for example:

-
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3
+
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx3 *gorm.DB) error {
tx3.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3

Control the transaction manually

Gorm supports calling transaction control functions (commit / rollback) directly, for example:

// begin a transaction
tx := db.Begin()

// do some database operations in the transaction (use 'tx' from this point, not 'db')
tx.Create(...)

// ...

// rollback the transaction in case of error
tx.Rollback()

// Or commit the transaction
tx.Commit()

// ...

// begin a transaction
tx := db.Begin()

// do some database operations in the transaction (use 'tx' from this point, not 'db')
tx.Create(...)

// ...

// rollback the transaction in case of error
tx.Rollback()

// Or commit the transaction
tx.Commit()
@@ -169,7 +169,7 @@

- + diff --git a/de_DE/docs/update.html b/de_DE/docs/update.html index e6336fbe18b..22b32652f23 100644 --- a/de_DE/docs/update.html +++ b/de_DE/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

- + diff --git a/de_DE/docs/v2_release_note.html b/de_DE/docs/v2_release_note.html index a4d386ec76a..cbdc05fb548 100644 --- a/de_DE/docs/v2_release_note.html +++ b/de_DE/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

- + diff --git a/de_DE/docs/write_driver.html b/de_DE/docs/write_driver.html index 0fa268627e1..288dfacf9e2 100644 --- a/de_DE/docs/write_driver.html +++ b/de_DE/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

Write Driver

-

Write new driver

GORM provides official support for sqlite, mysql, postgres, sqlserver.

-

Some databases may be compatible with the mysql or postgres dialect, in which case you could just use the dialect for those databases.

-

For others, you can create a new driver, it needs to implement the dialect interface.

-
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
}
- -

Checkout the MySQL Driver as example

+

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.

+

Compatibility with MySQL or Postgres Dialects

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.

+

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:

+
type Dialector interface {
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.

+
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
}
+ +

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:

+
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:

+
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:

+
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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

@@ -159,7 +192,7 @@

Write Driver

- +
@@ -273,7 +306,7 @@

Gold Sponsors

Contents -
  1. Write new driver
+
  1. Compatibility with MySQL or Postgres Dialects
  2. Implementing the Dialector
    1. Nested Transaction Support
    2. Custom Clause Builders
diff --git a/de_DE/docs/write_plugins.html b/de_DE/docs/write_plugins.html index 14d4ce5f056..3d72de15e75 100644 --- a/de_DE/docs/write_plugins.html +++ b/de_DE/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

Write Plugins

-

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

-

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

-

Register Callback

Register a callback into callbacks

-
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

db.Callback().Create().Register("crop_image", cropImage)
// register a callback for Create process
+

Callbacks

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 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.

+

Registering a Callback

You can register a callback for specific operations. For example, to add a custom image cropping functionality:

+
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

// Register the callback for the Create operation
db.Callback().Create().Register("crop_image", cropImage)
-

Delete Callback

Delete a callback from callbacks

-
db.Callback().Create().Remove("gorm:create")
// delete callback `gorm:create` from Create callbacks
+

Deleting a Callback

If a callback is no longer needed, it can be removed:

+
// Remove the 'gorm:create' callback from Create operations
db.Callback().Create().Remove("gorm:create")
-

Replace Callback

Replace a callback having the same name with the new one

-
db.Callback().Create().Replace("gorm:create", newCreateFunction)
// replace callback `gorm:create` with new function `newCreateFunction` for Create process
+

Replacing a Callback

Callbacks with the same name can be replaced with a new function:

+
// Replace the 'gorm:create' callback with a new function
db.Callback().Create().Replace("gorm:create", newCreateFunction)
-

Register Callback with orders

Register callbacks with orders

-
// before gorm:create
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// after gorm:create
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// after gorm:query
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// after gorm:delete
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// before gorm:update
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
+

Ordering Callbacks

Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

+
// Register to execute before the 'gorm:create' callback
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:create' callback
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:query' callback
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// Register to execute after the 'gorm:delete' callback
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// Register to execute before the 'gorm:update' callback
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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)

// Register to execute before any other callbacks
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// Register to execute after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
-

Defined Callbacks

GORM has defined some callbacks to power current GORM features, check them out before starting your plugins

-

Plugin

GORM provides a Use method to register plugins, the plugin needs to implement the Plugin interface

+

Predefined Callbacks

GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

+

Plugins

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:

type Plugin interface {
Name() string
Initialize(*gorm.DB) error
}
-

The Initialize method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like:

-
db.Config.Plugins[pluginName]
+
    +
  • 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:

+
// Example of registering a plugin
db.Use(MyCustomPlugin{})
-

Checkout Prometheus as example

+

Accessing Registered Plugins

After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

+
// 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:

+
// Registering the Prometheus plugin
db.Use(prometheus.New(prometheus.Config{
// Configuration options here
}))
+ +

Prometheus plugin documentation provides detailed information on its implementation and usage.

@@ -175,7 +186,7 @@

- + @@ -289,7 +300,7 @@

Gold Sponsors

Contents -
  1. Callbacks
    1. Register Callback
    2. Delete Callback
    3. Replace Callback
    4. Register Callback with orders
    5. Defined Callbacks
  2. Plugin
+
  1. Callbacks
    1. Registering a Callback
    2. Deleting a Callback
    3. Replacing a Callback
    4. Ordering Callbacks
    5. Predefined Callbacks
  2. Plugins
    1. The Plugin Interface
    2. Registering a Plugin
    3. Accessing Registered Plugins
    4. Practical Example
diff --git a/de_DE/gen.html b/de_DE/gen.html index 5729f29398b..5530d6a2255 100644 --- a/de_DE/gen.html +++ b/de_DE/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/de_DE/gen/associations.html b/de_DE/gen/associations.html index 4bb58b4d420..4ce67a46206 100644 --- a/de_DE/gen/associations.html +++ b/de_DE/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

// specify model
g.ApplyBasic(model.Customer{}, model.CreditCard{})

// assoications will be detected and converted to code
package query

type customer struct {
...
CreditCards customerHasManyCreditCards
}

type creditCard struct{
...
}
-

Relate to table in database

The association have to be speified by gen.FieldRelate

-
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"}},
}),
)

g.ApplyBasic(card, custormer)
+

Relate to table in database

The association have to be specified by gen.FieldRelate

+
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"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(card, custormer)

GEN will generate models with associated field:

-
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}
+
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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;references:ID" json:"credit_cards"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}

If associated model already exists, gen.FieldRelateModel can help you build associations between them.

-
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
}),
)

g.ApplyBasic(custormer)
+
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(custormer)

Relate Config

type RelateConfig struct {
// specify field's type
RelatePointer bool // ex: CreditCard *CreditCard
RelateSlice bool // ex: CreditCards []CreditCard
RelateSlicePointer bool // ex: CreditCards []*CreditCard

JSONTag string // related field's JSON tag
GORMTag string // related field's GORM tag
NewTag string // related field's new tag
OverwriteTag string // related field's tag
}

Operation

Skip Auto Create/Update

user := model.User{
Name: "modi",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{
{Email: "modi@example.com"},
{Email: "modi-2@example.com"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name).Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
// Skip create BillingAddress when creating a user

u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
// Skip create BillingAddress.Address1 when creating a user

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

Find matched associations

u := query.Use(db).User

languages, err = u.Languages.Model(&user).Find()
@@ -198,10 +198,10 @@

Nested Preloading together, e.g:

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:

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.

+

Preload with select

Specify selected columns with method Select. Foreign key must be selected.

type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}

u := q.User
cc := q.CreditCard

// !!! Foregin key "cc.UserRefer" must be selected
users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
// SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
// SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

Preload with conditions

GEN allows Preload associations with conditions, it works similar to Inline Conditions.

@@ -209,14 +209,13 @@

Nested Preloading

GEN supports nested preloading, for example:

db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

// Customize Preload conditions for `Orders`
// 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/de_DE/gen/clause.html b/de_DE/gen/clause.html index e208b38d13d..8cd24c840c1 100644 --- a/de_DE/gen/clause.html +++ b/de_DE/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

- + diff --git a/de_DE/gen/create.html b/de_DE/gen/create.html index badc5e3da64..d1344cc4abe 100644 --- a/de_DE/gen/create.html +++ b/de_DE/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

- + diff --git a/de_DE/gen/dao.html b/de_DE/gen/dao.html index bc1e7c061f0..a5f71049e30 100644 --- a/de_DE/gen/dao.html +++ b/de_DE/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

- + diff --git a/de_DE/gen/database_to_structs.html b/de_DE/gen/database_to_structs.html index 4c1e2856571..edf085a05ff 100644 --- a/de_DE/gen/database_to_structs.html +++ b/de_DE/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

diff --git a/de_DE/gen/delete.html b/de_DE/gen/delete.html index f5e085a1665..5fb329fc3c4 100644 --- a/de_DE/gen/delete.html +++ b/de_DE/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

- + diff --git a/de_DE/gen/dynamic_sql.html b/de_DE/gen/dynamic_sql.html index 1ab6bad0b9a..21c7de69bcf 100644 --- a/de_DE/gen/dynamic_sql.html +++ b/de_DE/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/de_DE/gen/gen_tool.html b/de_DE/gen/gen_tool.html index 7c79a35ec33..3c286af7a4d 100644 --- a/de_DE/gen/gen_tool.html +++ b/de_DE/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

- + diff --git a/de_DE/gen/index.html b/de_DE/gen/index.html index fdd9654a564..e96dd96acdd 100644 --- a/de_DE/gen/index.html +++ b/de_DE/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/de_DE/gen/query.html b/de_DE/gen/query.html index 611400eb329..384858493c6 100644 --- a/de_DE/gen/query.html +++ b/de_DE/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

int/uint/float Fields

// int field
f := field.NewInt("user", "id")
// `user`.`id` = 123
f.Eq(123)
// `user`.`id` DESC
f.Desc()
// `user`.`id` AS `user_id`
f.As("user_id")
// COUNT(`user`.`id`)
f.Count()
// SUM(`user`.`id`)
f.Sum()
// SUM(`user`.`id`) > 123
f.Sum().Gt(123)
// ((`user`.`id`+1)*2)/3
f.Add(1).Mul(2).Div(3),
// `user`.`id` <<< 3
f.LeftShift(3)
-

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `uesr`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")
+

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `user`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")

Time Fields

birth := field.NewString("user", "birth")
// `user`.`birth` = ? (now)
birth.Eq(time.Now())
// DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
birth.Add(time.Duration(time.Hour).Microseconds())
// DATE_FORMAT(`user`.`birth`, "%W %M %Y")
birth.DateFormat("%W %M %Y")

Bool Fields

active := field.NewBool("user", "active")
// `user`.`active` = TRUE
active.Is(true)
// NOT `user`.`active`
active.Not()
// `user`.`active` AND TRUE
active.And(true)

SubQuery

A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

-
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
// 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
+
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

From SubQuery

GORM allows you using subquery in FROM clause with method Table, for example:

u := query.User
p := query.Pet

users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

subQuery1 := u.WithContext(ctx).Select(u.Name)
subQuery2 := p.WithContext(ctx).Select(p.Name)
users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
@@ -312,7 +312,7 @@

- + diff --git a/de_DE/gen/rawsql_driver.html b/de_DE/gen/rawsql_driver.html index 2705a862f0a..fd33f4c0132 100644 --- a/de_DE/gen/rawsql_driver.html +++ b/de_DE/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/de_DE/gen/sql_annotation.html b/de_DE/gen/sql_annotation.html index 5d9ef037f50..7da25d12c0c 100644 --- a/de_DE/gen/sql_annotation.html +++ b/de_DE/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
// UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
// UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

query.User.Update(User{Age: 0}, 10)
// UPDATE users SET is_adult=0 WHERE id=10

for

The for expression iterates over a slice to generate the SQL, let’s explain by example

-
// 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)
+
// SELECT * FROM @@table
// {{where}}
// {{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}}
// {{end}}
// {{end}}
Filter(users []gen.T) ([]gen.T, error)

Usage:

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","%"))
@@ -254,7 +254,7 @@

- + diff --git a/de_DE/gen/transaction.html b/de_DE/gen/transaction.html index cf678b95393..ac1e3a14ffd 100644 --- a/de_DE/gen/transaction.html +++ b/de_DE/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/de_DE/gen/update.html b/de_DE/gen/update.html index 8dd9456061d..e761e69a0d2 100644 --- a/de_DE/gen/update.html +++ b/de_DE/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/de_DE/gorm.html b/de_DE/gorm.html index ba4d8c53483..49427ad0252 100644 --- a/de_DE/gorm.html +++ b/de_DE/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- +
diff --git a/de_DE/gormx.html b/de_DE/gormx.html index 2f38fff6be7..ab6d248910f 100644 --- a/de_DE/gormx.html +++ b/de_DE/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/de_DE/hints.html b/de_DE/hints.html index 2559ee8ddd7..a7fc0140966 100644 --- a/de_DE/hints.html +++ b/de_DE/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/de_DE/index.html b/de_DE/index.html index d04044d2c6e..3e3787c3d6f 100644 --- a/de_DE/index.html +++ b/de_DE/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/de_DE/rawsql.html b/de_DE/rawsql.html index 56b6141471d..33afba68197 100644 --- a/de_DE/rawsql.html +++ b/de_DE/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/de_DE/rawsql_driver.html b/de_DE/rawsql_driver.html index 1ba103aecba..de409fdf87d 100644 --- a/de_DE/rawsql_driver.html +++ b/de_DE/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/de_DE/sharding.html b/de_DE/sharding.html index a974db1350b..f91ff87c53b 100644 --- a/de_DE/sharding.html +++ b/de_DE/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/de_DE/stats.html b/de_DE/stats.html index bd2ece32341..eb71a930eaf 100644 --- a/de_DE/stats.html +++ b/de_DE/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/docs/advanced_query.html b/docs/advanced_query.html index 0b0b2ab3221..fec1129bafa 100644 --- a/docs/advanced_query.html +++ b/docs/advanced_query.html @@ -56,8 +56,8 @@ - - + + @@ -253,7 +253,7 @@

- + diff --git a/docs/associations.html b/docs/associations.html index d063b56a1b3..29d4a07821b 100644 --- a/docs/associations.html +++ b/docs/associations.html @@ -56,8 +56,8 @@ - - + + @@ -287,7 +287,7 @@

diff --git a/docs/belongs_to.html b/docs/belongs_to.html index 8407c622a94..5e83493e147 100644 --- a/docs/belongs_to.html +++ b/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

- + diff --git a/docs/changelog.html b/docs/changelog.html index 4d04441f00f..6b3eb35bd93 100644 --- a/docs/changelog.html +++ b/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/docs/composite_primary_key.html b/docs/composite_primary_key.html index e9bf931c2c8..53e8e4957cc 100644 --- a/docs/composite_primary_key.html +++ b/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

Composite Primary Key

- +
diff --git a/docs/connecting_to_the_database.html b/docs/connecting_to_the_database.html index 4f258a5d3ec..ef87fff6b13 100644 --- a/docs/connecting_to_the_database.html +++ b/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

- + diff --git a/docs/constraints.html b/docs/constraints.html index 562da94f83e..e056c1ce573 100644 --- a/docs/constraints.html +++ b/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/docs/context.html b/docs/context.html index 0d36277c64f..ad86ca837bd 100644 --- a/docs/context.html +++ b/docs/context.html @@ -56,8 +56,8 @@ - - + + @@ -172,7 +172,7 @@

- + diff --git a/docs/conventions.html b/docs/conventions.html index decc521e8bf..f7a06dc7990 100644 --- a/docs/conventions.html +++ b/docs/conventions.html @@ -56,8 +56,8 @@ - - + + @@ -194,7 +194,7 @@

- + diff --git a/docs/create.html b/docs/create.html index 368636f4ff3..00c44eadcb1 100644 --- a/docs/create.html +++ b/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -220,7 +220,7 @@

- + diff --git a/docs/data_types.html b/docs/data_types.html index 0bafd66293e..134529db7e9 100644 --- a/docs/data_types.html +++ b/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

- + diff --git a/docs/dbresolver.html b/docs/dbresolver.html index c691675597b..cf64ca2e771 100644 --- a/docs/dbresolver.html +++ b/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

diff --git a/docs/delete.html b/docs/delete.html index ead3f5c32dd..8b821a117df 100644 --- a/docs/delete.html +++ b/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

- + diff --git a/docs/error_handling.html b/docs/error_handling.html index 2341d21ce93..e6d782aa27a 100644 --- a/docs/error_handling.html +++ b/docs/error_handling.html @@ -56,8 +56,8 @@ - - + + @@ -187,7 +187,7 @@

- + diff --git a/docs/generic_interface.html b/docs/generic_interface.html index 09ce70ec400..0d17e4251a0 100644 --- a/docs/generic_interface.html +++ b/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

diff --git a/docs/gorm_config.html b/docs/gorm_config.html index 8e27a118950..2ecf28d1e93 100644 --- a/docs/gorm_config.html +++ b/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

diff --git a/docs/has_many.html b/docs/has_many.html index c406d5f0c14..6f748c6a9de 100644 --- a/docs/has_many.html +++ b/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/docs/has_one.html b/docs/has_one.html index 10115fa7abf..7e49b86d35d 100644 --- a/docs/has_one.html +++ b/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/docs/hints.html b/docs/hints.html index f2eee650084..f77920dbb89 100644 --- a/docs/hints.html +++ b/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

- + diff --git a/docs/hooks.html b/docs/hooks.html index b99495778ba..5e652c7d341 100644 --- a/docs/hooks.html +++ b/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

- + diff --git a/docs/index.html b/docs/index.html index 71d65296c23..bf1954d8e33 100644 --- a/docs/index.html +++ b/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/docs/indexes.html b/docs/indexes.html index eeebc9fd7c7..42cfee6abbc 100644 --- a/docs/indexes.html +++ b/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

diff --git a/docs/logger.html b/docs/logger.html index 1a66f008192..c4101005829 100644 --- a/docs/logger.html +++ b/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

diff --git a/docs/many_to_many.html b/docs/many_to_many.html index 4b56395e198..fca9985ff6c 100644 --- a/docs/many_to_many.html +++ b/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

- + diff --git a/docs/method_chaining.html b/docs/method_chaining.html index 39e2c90c418..161d2b7aca4 100644 --- a/docs/method_chaining.html +++ b/docs/method_chaining.html @@ -56,8 +56,8 @@ - - + + @@ -210,7 +210,7 @@

- + diff --git a/docs/migration.html b/docs/migration.html index aa843abeb59..5a68391b4ab 100644 --- a/docs/migration.html +++ b/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

- + diff --git a/docs/models.html b/docs/models.html index 06fcec00b78..ff91ccf9a1a 100644 --- a/docs/models.html +++ b/docs/models.html @@ -56,8 +56,8 @@ - - + + @@ -315,7 +315,7 @@

diff --git a/docs/performance.html b/docs/performance.html index a17e9ef2744..b9e3067de9e 100644 --- a/docs/performance.html +++ b/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

- + diff --git a/docs/preload.html b/docs/preload.html index e9671ec418b..bbce37c6ec2 100644 --- a/docs/preload.html +++ b/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

- + diff --git a/docs/prometheus.html b/docs/prometheus.html index c07f830287b..ee02f627e3b 100644 --- a/docs/prometheus.html +++ b/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- + diff --git a/docs/query.html b/docs/query.html index f8de106f29d..46288aab6f2 100644 --- a/docs/query.html +++ b/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

- + diff --git a/docs/scopes.html b/docs/scopes.html index 7730d767c9e..2508ec1339b 100644 --- a/docs/scopes.html +++ b/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/docs/security.html b/docs/security.html index ce7ee7ddc77..37062d13876 100644 --- a/docs/security.html +++ b/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -169,7 +169,7 @@

- + diff --git a/docs/serializer.html b/docs/serializer.html index 1584943112c..0da95326bbc 100644 --- a/docs/serializer.html +++ b/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

- + diff --git a/docs/session.html b/docs/session.html index b5f259bf67a..d96c19ee387 100644 --- a/docs/session.html +++ b/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

diff --git a/docs/settings.html b/docs/settings.html index 115ddb9e48a..19dfb66e350 100644 --- a/docs/settings.html +++ b/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/docs/sharding.html b/docs/sharding.html index 1647b038649..662bfd71c48 100644 --- a/docs/sharding.html +++ b/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

- + diff --git a/docs/sql_builder.html b/docs/sql_builder.html index 54fef0d5842..84441407dc4 100644 --- a/docs/sql_builder.html +++ b/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

diff --git a/docs/transactions.html b/docs/transactions.html index 13594f2650c..b65df3e8ac4 100644 --- a/docs/transactions.html +++ b/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -169,7 +169,7 @@

- + diff --git a/docs/update.html b/docs/update.html index c735286ff36..4e7206da4b3 100644 --- a/docs/update.html +++ b/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

- + diff --git a/docs/v2_release_note.html b/docs/v2_release_note.html index 8b75f858ae0..b8db9f0debb 100644 --- a/docs/v2_release_note.html +++ b/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

- + diff --git a/docs/write_driver.html b/docs/write_driver.html index d0a068f2322..cd972f1c6b4 100644 --- a/docs/write_driver.html +++ b/docs/write_driver.html @@ -56,8 +56,8 @@ - - + + @@ -192,7 +192,7 @@

- + diff --git a/docs/write_plugins.html b/docs/write_plugins.html index 8c3b0e67296..571a3b5f7cf 100644 --- a/docs/write_plugins.html +++ b/docs/write_plugins.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

diff --git a/es_ES/404.html b/es_ES/404.html index caf918e97dc..e15b8dc8742 100644 --- a/es_ES/404.html +++ b/es_ES/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/es_ES/community.html b/es_ES/community.html index 8d21ab334d5..3ec87c08e0d 100644 --- a/es_ES/community.html +++ b/es_ES/community.html @@ -56,8 +56,8 @@ - - + + @@ -184,7 +184,7 @@

- + diff --git a/es_ES/contribute.html b/es_ES/contribute.html index 08684a8523b..57d9db56517 100644 --- a/es_ES/contribute.html +++ b/es_ES/contribute.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/es_ES/datatypes.html b/es_ES/datatypes.html index 04cf8432395..1cadb05ab4e 100644 --- a/es_ES/datatypes.html +++ b/es_ES/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/es_ES/docs/advanced_query.html b/es_ES/docs/advanced_query.html index 4f1095cf8ad..7235de67d50 100644 --- a/es_ES/docs/advanced_query.html +++ b/es_ES/docs/advanced_query.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,80 +117,106 @@

Consulta avanzada

-

Campos de selección inteligentes

GORM permite seleccionar campos específicos con Select, 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:

-
type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

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
+

Campos de selección inteligentes

In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

+
type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// GORM will automatically select `id`, `name` fields when querying
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SQL: SELECT `id`, `name` FROM `users` LIMIT 10
-

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.

-
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

db.Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option

// Session Mode
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users`
+
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

// Default behavior with QueryFields set to true
db.Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

// Using Session Mode with QueryFields
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
-

Bloqueo (Para Actualizaciones)

GORM soporta diferentes tipos de bloqueos, por ejemplo:

-
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATE

db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`

db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
+

Locking

GORM soporta diferentes tipos de bloqueos, por ejemplo:

+
// Basic FOR UPDATE lock
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE
-

Consulte Raw SQL y SQL Builder para más detalles

-

Subconsultas

Una subconsulta puede ser anidada dentro de una consulta, GORM puede generar subconsulta al usar un objeto *gorm.DB como parámetro

-
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

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%")
+

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.

+
db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SQL: SELECT * FROM `users` FOR SHARE OF `users`
-

Desde Subconsulta

GORM permite usar subconsultas en la cláusula FROM con el método Table, por ejemplo:

-
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

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
+

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.

+
db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
-

Condiciones de grupo

Fácil de escribir una consulta SQL complicada con las condiciones de grupo

-
db.Where(
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")
+

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.

+

Subconsultas

Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

+
// Simple subquery
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
-

IN con múltiples columnas

Seleccionando IN con múltiples columnas

-
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"));
+

Desde Subconsulta

GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

+
// Using subquery in FROM clause
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// 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{})
// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
-

Argumentos nombrados

GORM soporta argumentos nombrados con sql.NamedArg o map[string]interface{}{}, por ejemplo:

-
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

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
+

Condiciones de grupo

Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

+
// Complex SQL query using Group Conditions
db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
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")
-

Consulta Raw SQL and SQL Builder para más detalles

-

Buscar en Mapa

GORM permite escanear resultados a map[string]interface{} o []map[string]interface{}, no olvide especificar el Modelo o Tabla, por ejemplo:

-
result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)

var results []map[string]interface{}
db.Table("users").Find(&results)
+

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.

+
// Using IN with multiple columns
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
-

FirstOrInit

Obtener el primer registro coincidente o inicializar una nueva instancia con determinadas condiciones (sólo funciona con la estructura o condiciones del mapa)

-
// User not found, initialize it with give conditions
db.FirstOrInit(&user, User{Name: "non_existing"})
// user -> User{Name: "non_existing"}

// Found user with `name` = `jinzhu`
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}

// Found user with `name` = `jinzhu`
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}
+

Argumentos nombrados

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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

+
// Example using sql.NamedArg for named arguments
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// 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)
// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
-

Inicializar estructura con más atributos si no se encuentra el registro, esos Attrs no se utilizarán para construir la consulta SQL

-
// User not found, initialize it with give conditions and Attrs
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}

// Found user with `name` = `jinzhu`, attributes will be 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}
+

For more examples and details, see Raw SQL and SQL Builder

+

Buscar en Mapa

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.

+
// 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`
-

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

-
// User not found, initialize it with give conditions and Assign attributes
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
// user -> User{Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, update it 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}
+

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.

+
// 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"} if not found

// Retrieving a user named "jinzhu"
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

// Using a map to specify the search condition
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

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

-
// User not found, create a new record with give conditions
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1

// Found user with `name` = `jinzhu`
result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
// user -> User{ID: 111, Name: "jinzhu", "Age": 18}
// result.RowsAffected // => 0
+

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.

+
// If no User is found, initialize with given conditions and additional attributes
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20} if not found

// If a User named "Jinzhu" is found, `Attrs` are ignored
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

Inicializar struct con más atributos si no se encuentra el registro, esos Attrs no se utilizarán para construir la consulta SQL

-
// User not found, create it with give conditions and Attrs
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, attributes will be ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
+

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.

+
// 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} if not found

// If a User named "Jinzhu" is found, update the struct with Assign attributes
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
-

Assign atributos al registro independientemente de que se encuentre o no y guardarlos de vuelta a la base de datos.

-
// User not found, initialize it with give conditions and Assign attributes
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, update it 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}
+

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

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.

+
// Create a new record if not found
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// SQL: INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1 (record created)

// 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)
-

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:

-
import "gorm.io/hints"

db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
+

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.

+
// Create a new record with additional attributes if not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// 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}

// If the user is found, `Attrs` are ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
-

Los hints de índices permiten pasar hints de índice a la base de datos en caso de que el planificador de consultas se confunda.

-
import "gorm.io/hints"

db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)

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`)"
+

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.

+
// Initialize and save new record with `Assign` attributes if not found
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// 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}

// Update found record with `Assign` attributes
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// SQL: UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20}
-

Consulte Pistas/Index/Comentario optimizador para más detalles

-

Iteración

GORM soporta iteración a través de las filas

-
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
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
db.ScanRows(rows, &user)

// do something
}
+

Optimización/Index 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.

+
import "gorm.io/hints"

// Using an optimizer hint to set a maximum execution time
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
-

Búsqueda por Lotes

Consultar y procesar registros en lote

-
// batch size 100
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// batch processing found records
}

tx.Save(&results)

tx.RowsAffected // number of records in this batch

batch // Batch 1, 2, 3

// returns error will stop future batches
return nil
})

result.Error // returned error
result.RowsAffected // processed records count in all batches
+

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.

+
import "gorm.io/hints"

// Suggesting the use of a specific index
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// 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{})
// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
-

Consultas Hooks

GORM permite hooks AfterFind para una consulta, se llamará al consultar un registro, consulte Hooks para más detalles

-
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.Role == "" {
u.Role = "user"
}
return
}
+

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 in the GORM documentation.

+

Iteración

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.

+
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()

for rows.Next() {
var user User
// ScanRows scans a row into a struct
db.ScanRows(rows, &user)

// Perform operations on each user
}
-

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 en su lugar

-
var ages []int64
db.Model(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

// Distinct Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SELECT DISTINCT `name` FROM `users`

// Requesting more than one column, use `Scan` or `Find` like this:
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+

This approach is ideal for complex data processing that cannot be easily achieved with standard query methods.

+

Búsqueda por Lotes

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.

+
// 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 provides the count of records in the current batch
// The variable 'batch' indicates the current batch number

// Returning an error will stop further batch processing
return nil
})

// result.Error contains any errors encountered during batch processing
// result.RowsAffected provides the count of all processed records across batches
-

Ámbitos

Scopes le permite especificar consultas usadas comúnmente que pueden ser referenciadas como llamadas a métodos

-
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}

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 {
return db.Where("status IN (?)", status)
}
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// Find all credit card orders and amount greater than 1000

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// Find all COD orders and amount greater than 1000

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// Find all paid, shipped orders that amount greater than 1000
+

FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

+

Consultas Hooks

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 in the GORM documentation.

+
func (u *User) AfterFind(tx *gorm.DB) (err error) {
// Custom logic after finding a user
if u.Role == "" {
u.Role = "user" // Set default role if not specified
}
return
}

// Usage of AfterFind hook happens automatically when a User is queried
-

Consulta Scopes para más detalles

-

Recuento

Obtener recuento de registros coincidentes

-
var count int64
db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

db.Table("deleted_users").Count(&count)
// SELECT count(1) FROM deleted_users;

// Count with Distinct
db.Model(&User{}).Distinct("name").Count(&count)
// SELECT COUNT(DISTINCT(`name`)) FROM `users`

db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SELECT count(distinct(name)) FROM deleted_users

// Count with Group
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
count // => 3
+

Pluck

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 or Find instead.

+
// Retrieving ages of all users
var ages []int64
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)

// Using Distinct with Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SQL: SELECT DISTINCT `name` FROM `users`

// Querying multiple columns
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+ +

Ámbitos

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.

+
// 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")
}

// 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.

+
// 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)
+ +

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 in the GORM documentation.

+

Recuento

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.

+
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'

// Counting records in a different table
db.Table("deleted_users").Count(&count)
// SQL: SELECT count(1) FROM deleted_users
+ +

Count with Distinct and Group

GORM also allows counting distinct values and grouping results.

+
// Counting distinct names
db.Model(&User{}).Distinct("name").Count(&count)
// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

// Counting distinct values with a custom select
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SQL: SELECT count(distinct(name)) FROM deleted_users

// Counting grouped records
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
// Count after grouping by name
// count => 3
@@ -203,7 +229,7 @@

- + @@ -317,7 +343,7 @@

Gold Sponsors

Contenido -
  1. Campos de selección inteligentes
  2. Bloqueo (Para Actualizaciones)
  3. Subconsultas
    1. Desde Subconsulta
  4. Condiciones de grupo
  5. IN con múltiples columnas
  6. Argumentos nombrados
  7. Buscar en Mapa
  8. FirstOrInit
  9. FirstOrCreate
  10. Optimización/Index Hints
  11. Iteración
  12. Búsqueda por Lotes
  13. Consultas Hooks
  14. Pluck
  15. Ámbitos
  16. Recuento
+
  1. Campos de selección inteligentes
  2. Locking
  3. Subconsultas
    1. Desde Subconsulta
  4. Condiciones de grupo
  5. IN con múltiples columnas
  6. Argumentos nombrados
  7. Buscar en Mapa
  8. FirstOrInit
    1. Using Attrs for Initialization
    2. Using Assign for Attributes
  9. FirstOrCreate
    1. Using Attrs with FirstOrCreate
    2. Using Assign with FirstOrCreate
  10. Optimización/Index Hints
    1. Index Hints
  11. Iteración
  12. Búsqueda por Lotes
  13. Consultas Hooks
  14. Pluck
  15. Ámbitos
    1. Defining Scopes
    2. Applying Scopes in Queries
  16. Recuento
    1. Getting the Count of Matched Records
    2. Count with Distinct and Group
diff --git a/es_ES/docs/associations.html b/es_ES/docs/associations.html index bda27748f5b..bf371709ccf 100644 --- a/es_ES/docs/associations.html +++ b/es_ES/docs/associations.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,62 +141,101 @@

Associations

-

Crear/ actualizar Automáticamente

GORM guardará automáticamente asociaciones y su referencia al usar Upsert, cuando se crear/actualiza un registro.

-
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"},
},
}

db.Create(&user)
// BEGIN TRANSACTION;
// 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 ('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)
- -

Si desea actualizar los datos de las asociaciones, debe usar el modo FullSaveAssociations mode:

-
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);
// ...
- -

Crear/ actualizar Automáticamente

Para omitir el guardado automático al crear/actualizar, puede utilizar Select o Omit, por ejemplo:

-
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"},
},
}

db.Select("Name").Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

db.Omit("BillingAddress").Create(&user)
// Skip create BillingAddress when creating a user

db.Omit(clause.Associations).Create(&user)
// Skip all associations when creating a user
- -

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:

-
db.Omit("Languages.*").Create(&user)
- -

El siguiente código omitirá la creación de la asociación y sus referencias

-
db.Omit("Languages").Create(&user)
- -

Seleccionar/Omitir campos de asociación

user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
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
db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
- -

Modo de asociación

El modo de asociación contiene algunos métodos de ayuda comúnmente utilizados para manejar relaciones

-
// 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
- -

Buscar asociaciones

Buscar asociaciones coincidentes

-
db.Model(&user).Association("Languages").Find(&languages)
- -

Buscar asociaciones con condiciones

-
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

Añadir nuevas asociaciones para muchas a muchas, tiene muchas, reemplazar asociación actual para tiene un, pertenece a

-
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
- -

Reemplazar asociaciones

Reemplazar asociaciones actuales por nuevas

-
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
- -

Eliminar Asociaciones

Eliminar la relación entre la fuente & argumentos si existe, solo eliminar la referencia, no eliminará esos objetos de la DB.

-
db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
- -

Limpiar asociaciones

Eliminar toda referencia entre la fuente & asociación, no eliminará esas asociaciones

-
db.Model(&user).Association("Languages").Clear()
- -

Contar Asociaciones

Devolver el recuento de asociaciones actuales

-
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()
- -

Datos de Lote

El modo de asociación soporta datos por lotes, por ejemplo:

-
// Find all roles for all users
db.Model(&users).Association("Role").Find(&roles)

// Delete User A from all user's team
db.Model(&users).Association("Team").Delete(&userA)

// Get distinct count of all users' teams
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
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
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
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).

-

How to delete is decided by gorm.DB.

-
// Soft delete
// UPDATE `languages` SET `deleted_at`= ...
db.Model(&user).Association("Languages").Unscoped().Clear()

// Delete permanently
// DELETE FROM `languages` WHERE ...
db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
- -

Delete with Select

You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

-
// 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)
- -

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

-
// 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
- -

Association Tags

+

Crear/ actualizar Automáticamente

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.

+
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"},
},
}

// 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;
// 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 ('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)
+ +

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.

+
// Update a user and fully update all its associations
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
// 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

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.

+
user := User{
// User and associated data
}

// Only include the 'Name' field when creating the user
db.Select("Name").Create(&user)
// 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.

+
// Skip creating the 'BillingAddress' when creating the user
db.Omit("BillingAddress").Create(&user)

// Skip all associations when creating the user
db.Omit(clause.Associations).Create(&user)
+ +

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 .*:

+
// Skip upserting 'Languages' associations
db.Omit("Languages.*").Create(&user)
+ +

To skip creating both the association and its references:

+
// Skip creating 'Languages' associations and their references
db.Omit("Languages").Create(&user)
+ +

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.

+
user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
}

// 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.

+
// 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: 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.

+
// 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
+ +

Modo de asociación

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.

+
var user User
db.Model(&user).Association("Languages")
// Check for errors
error := db.Model(&user).Association("Languages").Error
+ +

Finding Associations

Retrieve associated records with or without additional conditions.

+
// Simple find
db.Model(&user).Association("Languages").Find(&languages)

// Find with conditions
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).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.

+
// Append new languages
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

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.

+
// 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.

+
// 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.

+
// Clear all languages
db.Model(&user).Association("Languages").Clear()
+ +

Counting Associations

Get the count of current associations, with or without conditions.

+
// 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.
  • +
+
db.Model(&users).Association("Role").Find(&roles)
+ +
    +
  • Deleting Associations: Remove specific associations across multiple records.
  • +
+
db.Model(&users).Association("Team").Delete(&userA)
+ +
    +
  • Counting Associations: Get the count of associations for a batch of records.
  • +
+
db.Model(&users).Association("Team").Count()
+ +
    +
  • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
  • +
+
var users = []User{user1, user2, 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})

// 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

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.

+
    +
  • 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.
  • +
+

Modifying Deletion Behavior with Unscoped

For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

+
    +
  • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
  • +
+
db.Model(&user).Association("Languages").Unscoped().Clear()
+ +
    +
  • Permanent Delete: Physically deletes the association records from the database.
  • +
+
// db.Unscoped().Model(&user)
db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
+ +

Association Tags

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.

+ @@ -204,36 +243,36 @@

- - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
Etiqueta
foreignKeyEspecifica el nombre de la columna del modelo actual que se utiliza como clave foránea para la tabla de uniónforeignKeySpecifies the column name of the current model used as a foreign key in the join table.
referencesEspecifica el nombre de la columna de la tabla de referencia que se asigna a la clave foránea de la tabla joinreferencesIndicates the column name in the reference table that the foreign key of the join table maps to.
polymorphicEspecifica el tipo polimórfico como el nombre del modelopolymorphicDefines the polymorphic type, typically the model name.
polymorphicValueEspecifica el valor polimórfico, nombre de tabla por defectopolymorphicValueSets the polymorphic value, usually the table name, if not specified otherwise.
many2manyEspecifica el nombre de la tabla de uniónmany2manyNames the join table used in a many-to-many relationship.
joinForeignKeyEspecifica el nombre de columna de clave foránea de la tabla de unión que mapea a la tabla actualjoinForeignKeyIdentifies the foreign key column in the join table that maps back to the current model’s table.
joinForeignKeyEspecifica el nombre de columna de clave foránea de la tabla de unión que mapea a la tabla de referenciajoinForeignKeyPoints to the foreign key column in the join table that links to the reference model’s table.
constraintRestricción de relaciones, por ejemplo: OnUpdate,OnDeleteconstraintSpecifies relational constraints like OnUpdate, OnDelete for the association.
@@ -248,7 +287,7 @@

@@ -362,7 +401,7 @@

Gold Sponsors

Contenido -
  1. Crear/ actualizar Automáticamente
  2. Crear/ actualizar Automáticamente
  3. Seleccionar/Omitir campos de asociación
  4. Modo de asociación
    1. Buscar asociaciones
    2. Añadir asociaciones
    3. Reemplazar asociaciones
    4. Eliminar Asociaciones
    5. Limpiar asociaciones
    6. Contar Asociaciones
    7. Datos de Lote
  5. Delete Association Record
  6. Delete with Select
  7. Association Tags
+
  1. Crear/ actualizar Automáticamente
    1. Auto-Saving Associations on Create
    2. Updating Associations with FullSaveAssociations
  2. Crear/ actualizar Automáticamente
    1. Using Select to Include Specific Fields
    2. Using Omit to Exclude Fields or Associations
  3. Seleccionar/Omitir campos de asociación
  4. Eliminar Asociaciones
  5. Modo de asociación
    1. Finding Associations
    2. Appending Associations
    3. Replacing Associations
    4. Deleting Associations
    5. Clearing Associations
    6. Counting Associations
    7. Batch Data Handling
  6. Delete Association Record
    1. Modifying Deletion Behavior with Unscoped
  7. Association Tags
diff --git a/es_ES/docs/belongs_to.html b/es_ES/docs/belongs_to.html index 6ac92cd483a..74f73476ad8 100644 --- a/es_ES/docs/belongs_to.html +++ b/es_ES/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

- + diff --git a/es_ES/docs/changelog.html b/es_ES/docs/changelog.html index 237cb4c62f7..98036a408ee 100644 --- a/es_ES/docs/changelog.html +++ b/es_ES/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/es_ES/docs/composite_primary_key.html b/es_ES/docs/composite_primary_key.html index feb997a4a8c..ca21ca02efc 100644 --- a/es_ES/docs/composite_primary_key.html +++ b/es_ES/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

Llave primaria compuesta

- +
diff --git a/es_ES/docs/connecting_to_the_database.html b/es_ES/docs/connecting_to_the_database.html index ed7aadf993f..e7eaec2cbc8 100644 --- a/es_ES/docs/connecting_to_the_database.html +++ b/es_ES/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

- + diff --git a/es_ES/docs/constraints.html b/es_ES/docs/constraints.html index aaf344c33a7..04f92698ae2 100644 --- a/es_ES/docs/constraints.html +++ b/es_ES/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/es_ES/docs/context.html b/es_ES/docs/context.html index 5e5805da82a..ba4b1d21efe 100644 --- a/es_ES/docs/context.html +++ b/es_ES/docs/context.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,25 @@

Context

-

GORM proporciona soporte para Context, se puede utilizar con el método WithContext

-

Modo de sesión única

El modo de sesión única se utiliza generalmente cuando se desea realizar una sola operación.

+

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

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.

db.WithContext(ctx).Find(&users)
-

Modo de sesión continua

El modo de sesión continua se utiliza generalmente cuando se desea realizar un grupo de operaciones, por ejemplo:

+

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.

tx := db.WithContext(ctx)
tx.First(&user, 1)
tx.Model(&user).Update("role", "admin")
-

Tiempo de espera del Contexto

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:

+

Context Timeout

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.

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

db.WithContext(ctx).Find(&users)
-

Contexto en Hooks/Callbacks

Puede acceder al objeto Context desde el Statement actual, por ejemplo:

-
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ...
return
}
+

Contexto en Hooks/Callbacks

The context can also be accessed within GORM’s hooks/callbacks. This enables contextual information to be used during these lifecycle events.

+
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ... use context
return
}
-

Ejemplo de Middleware Chi

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:

-
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

r := chi.NewRouter()
r.Use(SetDBMiddleware)

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
})

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
})
+

Integration with 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.

+
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

// Router setup
r := chi.NewRouter()
r.Use(SetDBMiddleware)

// Route handlers
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})

r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})
-

NOTA Establecer Context con WithContext es seguro para gorutinas, consulte Session para obtener detalles.

-
- -

Logger

Logger acepta Contexto también, puedes usarlo para el seguimiento de registros, consulta Logger para más detalles

+

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 in GORM.

+

Logger Integration

GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

+

Refer to Logger documentation for more details.

@@ -174,7 +172,7 @@

- + @@ -288,7 +286,7 @@

Gold Sponsors

Contenido -
  1. Modo de sesión única
  2. Modo de sesión continua
  3. Tiempo de espera del Contexto
  4. Contexto en Hooks/Callbacks
  5. Ejemplo de Middleware Chi
  6. Logger
+
  1. Modo de sesión única
  2. Continuous Session Mode
  3. Context Timeout
  4. Contexto en Hooks/Callbacks
  5. Integration with Chi Middleware
  6. Logger Integration
diff --git a/es_ES/docs/conventions.html b/es_ES/docs/conventions.html index d86ed157c0c..bfb685555af 100644 --- a/es_ES/docs/conventions.html +++ b/es_ES/docs/conventions.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,14 +141,14 @@

Conventions

-

ID as Primary Key

GORM uses the field with the name ID as the table’s primary key by default.

-
type User struct {
  ID   string // field named `ID` will be used as a primary field by default
  Name string
}
+

ID como Clave Primaria

GORM utiliza el campo con el nombre ID como clave principal de la tabla por defecto.

+
type User struct {
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

-
// Set field `UUID` as primary field
type Animal struct {
  ID     int64
  UUID   string `gorm:"primaryKey"`
  Name   string
  Age    int64
}
+

Puede establecer otros campos como clave primaria con la etiqueta primaryKey

+
// Establecer el campo `UUID` como campo primario
type Animal struct {
ID int64
UUID string `gorm:"primaryKey"`
Name string
Age int64
}
-

Also check out Composite Primary Key

-

Pluralized Table Name

GORM pluralizes struct name to snake_cases as table name, for struct User, its table name is users by convention

+

También echa un vistazo a Clave Primaria Compuesta

+

Nombre de tabla pluralizada

GORM pluralizes struct name to snake_cases as table name, for struct User, its table name is users by convention

TableName

You can change the default table name by implementing the Tabler interface, for example:

type Tabler interface {
TableName() string
}

// TableName overrides the table name used by User to `profiles`
func (User) TableName() string {
  return "profiles"
}
@@ -161,7 +161,7 @@

// Create table `deleted_users` with struct User's fields
db.Table("deleted_users").AutoMigrate(&User{})

// Query data from another table
var deletedUsers []User
db.Table("deleted_users").Find(&deletedUsers)
// SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
// DELETE FROM deleted_users WHERE name = 'jinzhu';

Check out From SubQuery for how to use SubQuery in FROM clause

-

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 for details

+

NamingStrategy

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 for details

Column Name

Column db name uses the field’s name’s snake_case by convention.

type User struct {
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}
@@ -194,7 +194,7 @@

- + @@ -308,7 +308,7 @@

Gold Sponsors

Contenido -
  1. ID as Primary Key
  2. Pluralized Table Name
    1. TableName
    2. Temporarily specify a name
    3. NamingStrategy
  3. Column Name
  4. Timestamp Tracking
    1. CreatedAt
    2. UpdatedAt
+
  1. ID como Clave Primaria
  2. Nombre de tabla pluralizada
    1. TableName
    2. Temporarily specify a name
    3. NamingStrategy
  3. Column Name
  4. Timestamp Tracking
    1. CreatedAt
    2. UpdatedAt
diff --git a/es_ES/docs/create.html b/es_ES/docs/create.html index 52f1f8bc73c..8a182402d27 100644 --- a/es_ES/docs/create.html +++ b/es_ES/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -155,7 +155,7 @@

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
-

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.

+

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 split into multiple batches.

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

You can specify batch size when creating with CreateInBatches, e.g:

@@ -220,7 +220,7 @@

- + diff --git a/es_ES/docs/data_types.html b/es_ES/docs/data_types.html index b2abced1fb6..4fd2893fadb 100644 --- a/es_ES/docs/data_types.html +++ b/es_ES/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

- + diff --git a/es_ES/docs/dbresolver.html b/es_ES/docs/dbresolver.html index 076054ed81d..be2b18c16b9 100644 --- a/es_ES/docs/dbresolver.html +++ b/es_ES/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

Transaction

When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

But you can specifies which DB to use before starting a transaction, for example:

-
// Start transaction based on default replicas db
tx := DB.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := DB.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
+
// Start transaction based on default replicas db
tx := db.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := db.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

Load Balancing

GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

type Policy interface {
Resolve([]gorm.ConnPool) gorm.ConnPool
}
@@ -183,7 +183,7 @@

diff --git a/es_ES/docs/delete.html b/es_ES/docs/delete.html index 27dee395b91..62d72992830 100644 --- a/es_ES/docs/delete.html +++ b/es_ES/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

- + diff --git a/es_ES/docs/error_handling.html b/es_ES/docs/error_handling.html index ee8517e9877..81e1c42aadc 100644 --- a/es_ES/docs/error_handling.html +++ b/es_ES/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

Error Handling

-

In Go, error handling is important.

-

You are encouraged to do error check after any Finisher Methods

-

Error Handling

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:

-
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// error handling...
}
- -

Or

-
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// error handling...
}
- -

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:

-
// Check if returns RecordNotFound error
err := db.First(&user, 100).Error
errors.Is(err, gorm.ErrRecordNotFound)
-

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.

+

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.

+

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.

+

After a chain of methods, it’s crucial to check the Error field:

+
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// Handle error...
}
+ +

Or alternatively:

+
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// Handle error...
}
+ +

ErrRecordNotFound

GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

+
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

+
    +
  • Example: Handling MySQL Error Codes
  • +
+
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.

db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
-

Errors

Errors List

+
    +
  • ErrDuplicatedKey
  • +
+

This error occurs when an insert operation violates a unique constraint:

+
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:

+
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

For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

@@ -168,7 +187,7 @@

- + @@ -282,7 +301,7 @@

Gold Sponsors

Contenido -
  1. Error Handling
  2. ErrRecordNotFound
  3. Dialect Translated Errors
  4. Errors
+
  1. Basic Error Handling
  2. ErrRecordNotFound
  3. Handling Error Codes
  4. Dialect Translated Errors
  5. Errors
diff --git a/es_ES/docs/generic_interface.html b/es_ES/docs/generic_interface.html index f7eff29d60c..abecc5b38ca 100644 --- a/es_ES/docs/generic_interface.html +++ b/es_ES/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

diff --git a/es_ES/docs/gorm_config.html b/es_ES/docs/gorm_config.html index 73960ed9d52..89c89a914bd 100644 --- a/es_ES/docs/gorm_config.html +++ b/es_ES/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

diff --git a/es_ES/docs/has_many.html b/es_ES/docs/has_many.html index aea1212a9d6..8f5f888ab9a 100644 --- a/es_ES/docs/has_many.html +++ b/es_ES/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/es_ES/docs/has_one.html b/es_ES/docs/has_one.html index ea5285f5923..65f79dd666f 100644 --- a/es_ES/docs/has_one.html +++ b/es_ES/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/es_ES/docs/hints.html b/es_ES/docs/hints.html index c9381c17629..f3604fcd0de 100644 --- a/es_ES/docs/hints.html +++ b/es_ES/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

- + diff --git a/es_ES/docs/hooks.html b/es_ES/docs/hooks.html index d0489fffe93..472c3d87f5e 100644 --- a/es_ES/docs/hooks.html +++ b/es_ES/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

- + diff --git a/es_ES/docs/index.html b/es_ES/docs/index.html index 4317644513c..a72f173cb82 100644 --- a/es_ES/docs/index.html +++ b/es_ES/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/es_ES/docs/indexes.html b/es_ES/docs/indexes.html index e44cda823f5..66af186cf93 100644 --- a/es_ES/docs/indexes.html +++ b/es_ES/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

diff --git a/es_ES/docs/logger.html b/es_ES/docs/logger.html index 6636bfa9864..0b3c02397d0 100644 --- a/es_ES/docs/logger.html +++ b/es_ES/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

diff --git a/es_ES/docs/many_to_many.html b/es_ES/docs/many_to_many.html index ebc76ca0409..a533230da7b 100644 --- a/es_ES/docs/many_to_many.html +++ b/es_ES/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

-
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addressses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
+
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

FOREIGN KEY Constraints

You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_speaks;"`
}

type Language struct {
Code string `gorm:"primarykey"`
Name string
}

// CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
@@ -191,7 +191,7 @@

- + diff --git a/es_ES/docs/method_chaining.html b/es_ES/docs/method_chaining.html index 7162fe005f7..4bec55c733b 100644 --- a/es_ES/docs/method_chaining.html +++ b/es_ES/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

Method Chaining

-

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:

db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
-

There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

-

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:

-
queryDB := DB.Where("name = ?", "jinzhu")

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// 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:

-
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 20
- -

Chain Method

Chain methods are methods to modify or add Clauses to current Statement, like:

-

Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

-

Here is the full lists, also check out the SQL Builder 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 here.

-

New Session Method

GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new 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:
// 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:
// 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:
// SELECT * FROM users;
- -

(Bad) Example 2:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is safe to 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

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
- -

Example 3:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is 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

// good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// good case
tx.Where("age = ?", 28).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
+

Method Categories

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. Also, the SQL Builder 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.

+

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 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

queryDB := DB.Where("name = ?", "jinzhu")

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query with unintended compounded condition
queryDB.Where("age > ?", 20).First(&user2)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
+ +

Example of Safe Reuse

To safely reuse a *gorm.DB instance, use a New Session Method:

+
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query, safely isolated
queryDB.Where("age > ?", 20).First(&user2)
// 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.

+

Examples for Clarity

Let’s clarify with a few examples:

+
    +
  • Example 1: Safe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
// 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)
// 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)
// Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
// SELECT * FROM users;
+ +

In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

+
    +
  • (Bad) Example 2: Unsafe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized *gorm.DB, safe for initial reuse.

tx := db.Where("name = ?", "jinzhu")
// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

// Good case
tx.Where("age = ?", 18).Find(&users)
// Reuses 'tx' correctly for a single logical operation, executing:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// Bad case
tx.Where("age = ?", 28).Find(&users)
// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
+ +

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
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

// Good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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.

@@ -180,7 +210,7 @@

- + @@ -294,7 +324,7 @@

Gold Sponsors

Contenido -
  1. Chain Method
  2. Finisher Method
  3. New Session Method
+
  1. Method Categories
    1. Chain Methods
    2. Finisher Methods
    3. New Session Methods
  2. Reusability and Safety
    1. Example of Unsafe Reuse
    2. Example of Safe Reuse
  3. Examples for Clarity
diff --git a/es_ES/docs/migration.html b/es_ES/docs/migration.html index c086f8e1a2f..3433d18e733 100644 --- a/es_ES/docs/migration.html +++ b/es_ES/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

- + diff --git a/es_ES/docs/models.html b/es_ES/docs/models.html index 9b179bb150e..167d398f8bf 100644 --- a/es_ES/docs/models.html +++ b/es_ES/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,16 +141,45 @@

Declaring Models

-

Declarando modelos

Models are normal structs with basic Go types, pointers/alias of them or custom types implementing Scanner and Valuer interfaces

-

Por ejemplo:

-
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
}
- -

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

-

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

-

gorm.Model

GORM ha definido una estructura gorm.Model, que incluye campos ID, CreatedAt, UpdatedAt, DeletedAt

+

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 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 and Valuer interfaces from the database/sql package

+

Consider the following example of a User model:

+
type User struct {
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 for a detailed explanation

+

Convenciones

    +
  1. Primary Key: GORM uses a field named ID as the default primary key for each model.

    +
  2. +
  3. 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.

    +
  4. +
  5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

    +
  6. +
  7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

    +
  8. +
+

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.

+

gorm.Model

GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

// gorm.Model definition
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
-

Puedes insertarlo en tu estructura para incluir esos campos, consulta Estructura incrustada

+
    +
  • 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

    +
  • +
  • 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

Field-Level Permission

Exported fields have all permissions when doing CRUD with GORM, and GORM allows you to change the field-level permission with tag, so you can make a field to be read-only, write-only, create-only, update-only or ignored

NOTE ignored fields won’t be created when using GORM Migrator to create table

@@ -286,7 +315,7 @@

@@ -400,7 +429,7 @@

Gold Sponsors

Contenido -
  1. Declarando modelos
  2. Convenciones
  3. gorm.Model
  4. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
+
  1. Declarando modelos
    1. Convenciones
    2. gorm.Model
  2. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
diff --git a/es_ES/docs/performance.html b/es_ES/docs/performance.html index e38cf352452..589da1367c1 100644 --- a/es_ES/docs/performance.html +++ b/es_ES/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

- + diff --git a/es_ES/docs/preload.html b/es_ES/docs/preload.html index 6d4410ed3aa..a8d5d30339b 100644 --- a/es_ES/docs/preload.html +++ b/es_ES/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

- + diff --git a/es_ES/docs/prometheus.html b/es_ES/docs/prometheus.html index fc1f424fba6..317e1cca7f0 100644 --- a/es_ES/docs/prometheus.html +++ b/es_ES/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- + diff --git a/es_ES/docs/query.html b/es_ES/docs/query.html index e4af91947bd..a61eb85eadf 100644 --- a/es_ES/docs/query.html +++ b/es_ES/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

- + diff --git a/es_ES/docs/scopes.html b/es_ES/docs/scopes.html index 596dcb205bf..708cefb203e 100644 --- a/es_ES/docs/scopes.html +++ b/es_ES/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/es_ES/docs/security.html b/es_ES/docs/security.html index 0e44411a3e9..ad27d227590 100644 --- a/es_ES/docs/security.html +++ b/es_ES/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

Inline Condition

// will be escaped
db.First(&user, "name = ?", userInput)

// SQL injection
db.First(&user, fmt.Sprintf("name = %v", userInput))

When retrieving objects with number primary key by user’s input, you should check the type of variable.

-
userInputID := "1=1;drop table users;"
// safe, return error
id,err := strconv.Atoi(userInputID)
if err != nil {
return error
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;
+
userInputID := "1=1;drop table users;"
// safe, return error
id, err := strconv.Atoi(userInputID)
if err != nil {
return err
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;

SQL injection Methods

To support some features, some inputs are not escaped, be careful when using user’s input with those methods

db.Select("name; drop table users;").First(&user)
db.Distinct("name; drop table users;").First(&user)

db.Model(&user).Pluck("name; drop table users;", &names)

db.Group("name; drop table users;").First(&user)

db.Group("name").Having("1 = 1;drop table users;").First(&user)

db.Raw("select name from users; drop table users;").First(&user)

db.Exec("select name from users; drop table users;")

db.Order("name; drop table users;").First(&user)
@@ -169,7 +169,7 @@

- + diff --git a/es_ES/docs/serializer.html b/es_ES/docs/serializer.html index 837c6f99a24..4e8b70f45e9 100644 --- a/es_ES/docs/serializer.html +++ b/es_ES/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

- + diff --git a/es_ES/docs/session.html b/es_ES/docs/session.html index 4a6f9be445a..3497fec2c81 100644 --- a/es_ES/docs/session.html +++ b/es_ES/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

diff --git a/es_ES/docs/settings.html b/es_ES/docs/settings.html index 37ab42773c8..262b1e97040 100644 --- a/es_ES/docs/settings.html +++ b/es_ES/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/es_ES/docs/sharding.html b/es_ES/docs/sharding.html index 5b09f7727ef..9e024d15fc3 100644 --- a/es_ES/docs/sharding.html +++ b/es_ES/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

- + diff --git a/es_ES/docs/sql_builder.html b/es_ES/docs/sql_builder.html index aca7997f34f..e8b838b666d 100644 --- a/es_ES/docs/sql_builder.html +++ b/es_ES/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

diff --git a/es_ES/docs/transactions.html b/es_ES/docs/transactions.html index 24e9f5648d9..f66915a492a 100644 --- a/es_ES/docs/transactions.html +++ b/es_ES/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

db.Transaction(func(tx *gorm.DB) error {
// do some database operations in the transaction (use 'tx' from this point, not 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// return any error will rollback
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// return nil will commit the whole transaction
return nil
})

Nested Transactions

GORM supports nested transactions, you can rollback a subset of operations performed within the scope of a larger transaction, for example:

-
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3
+
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx3 *gorm.DB) error {
tx3.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3

Control the transaction manually

Gorm supports calling transaction control functions (commit / rollback) directly, for example:

// begin a transaction
tx := db.Begin()

// do some database operations in the transaction (use 'tx' from this point, not 'db')
tx.Create(...)

// ...

// rollback the transaction in case of error
tx.Rollback()

// Or commit the transaction
tx.Commit()
@@ -169,7 +169,7 @@

- + diff --git a/es_ES/docs/update.html b/es_ES/docs/update.html index ec4f3a8e9ce..b2e176ef6f8 100644 --- a/es_ES/docs/update.html +++ b/es_ES/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

- + diff --git a/es_ES/docs/v2_release_note.html b/es_ES/docs/v2_release_note.html index f05f7754456..5e9ef62960d 100644 --- a/es_ES/docs/v2_release_note.html +++ b/es_ES/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

- + diff --git a/es_ES/docs/write_driver.html b/es_ES/docs/write_driver.html index a63d86f69e5..d894392f840 100644 --- a/es_ES/docs/write_driver.html +++ b/es_ES/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

Write Driver

-

Write new driver

GORM provides official support for sqlite, mysql, postgres, sqlserver.

-

Some databases may be compatible with the mysql or postgres dialect, in which case you could just use the dialect for those databases.

-

For others, you can create a new driver, it needs to implement the dialect interface.

-
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
}
- -

Checkout the MySQL Driver as example

+

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.

+

Compatibility with MySQL or Postgres Dialects

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.

+

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:

+
type Dialector interface {
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.

+
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
}
+ +

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:

+
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:

+
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:

+
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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

@@ -159,7 +192,7 @@

Write Driver

- +
@@ -273,7 +306,7 @@

Gold Sponsors

Contenido -
  1. Write new driver
+
  1. Compatibility with MySQL or Postgres Dialects
  2. Implementing the Dialector
    1. Nested Transaction Support
    2. Custom Clause Builders
diff --git a/es_ES/docs/write_plugins.html b/es_ES/docs/write_plugins.html index a1b2bc281da..09a9283806f 100644 --- a/es_ES/docs/write_plugins.html +++ b/es_ES/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

Write Plugins

-

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

-

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

-

Register Callback

Register a callback into callbacks

-
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

db.Callback().Create().Register("crop_image", cropImage)
// register a callback for Create process
+

Callbacks

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 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.

+

Registering a Callback

You can register a callback for specific operations. For example, to add a custom image cropping functionality:

+
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

// Register the callback for the Create operation
db.Callback().Create().Register("crop_image", cropImage)
-

Delete Callback

Delete a callback from callbacks

-
db.Callback().Create().Remove("gorm:create")
// delete callback `gorm:create` from Create callbacks
+

Deleting a Callback

If a callback is no longer needed, it can be removed:

+
// Remove the 'gorm:create' callback from Create operations
db.Callback().Create().Remove("gorm:create")
-

Replace Callback

Replace a callback having the same name with the new one

-
db.Callback().Create().Replace("gorm:create", newCreateFunction)
// replace callback `gorm:create` with new function `newCreateFunction` for Create process
+

Replacing a Callback

Callbacks with the same name can be replaced with a new function:

+
// Replace the 'gorm:create' callback with a new function
db.Callback().Create().Replace("gorm:create", newCreateFunction)
-

Register Callback with orders

Register callbacks with orders

-
// before gorm:create
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// after gorm:create
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// after gorm:query
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// after gorm:delete
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// before gorm:update
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
+

Ordering Callbacks

Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

+
// Register to execute before the 'gorm:create' callback
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:create' callback
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:query' callback
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// Register to execute after the 'gorm:delete' callback
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// Register to execute before the 'gorm:update' callback
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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)

// Register to execute before any other callbacks
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// Register to execute after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
-

Defined Callbacks

GORM has defined some callbacks to power current GORM features, check them out before starting your plugins

-

Plugin

GORM provides a Use method to register plugins, the plugin needs to implement the Plugin interface

+

Predefined Callbacks

GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

+

Plugins

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:

type Plugin interface {
Name() string
Initialize(*gorm.DB) error
}
-

The Initialize method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like:

-
db.Config.Plugins[pluginName]
+
    +
  • 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:

+
// Example of registering a plugin
db.Use(MyCustomPlugin{})
-

Checkout Prometheus as example

+

Accessing Registered Plugins

After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

+
// 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:

+
// Registering the Prometheus plugin
db.Use(prometheus.New(prometheus.Config{
// Configuration options here
}))
+ +

Prometheus plugin documentation provides detailed information on its implementation and usage.

@@ -175,7 +186,7 @@

- + @@ -289,7 +300,7 @@

Gold Sponsors

Contenido -
  1. Callbacks
    1. Register Callback
    2. Delete Callback
    3. Replace Callback
    4. Register Callback with orders
    5. Defined Callbacks
  2. Plugin
+
  1. Callbacks
    1. Registering a Callback
    2. Deleting a Callback
    3. Replacing a Callback
    4. Ordering Callbacks
    5. Predefined Callbacks
  2. Plugins
    1. The Plugin Interface
    2. Registering a Plugin
    3. Accessing Registered Plugins
    4. Practical Example
diff --git a/es_ES/gen.html b/es_ES/gen.html index faf6c3ecb97..e12993c7df5 100644 --- a/es_ES/gen.html +++ b/es_ES/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/es_ES/gen/associations.html b/es_ES/gen/associations.html index 81cbbb8bff1..28bb3f19914 100644 --- a/es_ES/gen/associations.html +++ b/es_ES/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

// specify model
g.ApplyBasic(model.Customer{}, model.CreditCard{})

// assoications will be detected and converted to code
package query

type customer struct {
...
CreditCards customerHasManyCreditCards
}

type creditCard struct{
...
}
-

Relate to table in database

The association have to be speified by gen.FieldRelate

-
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"}},
}),
)

g.ApplyBasic(card, custormer)
+

Relate to table in database

The association have to be specified by gen.FieldRelate

+
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"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(card, custormer)

GEN will generate models with associated field:

-
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}
+
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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;references:ID" json:"credit_cards"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}

If associated model already exists, gen.FieldRelateModel can help you build associations between them.

-
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
}),
)

g.ApplyBasic(custormer)
+
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(custormer)

Relate Config

type RelateConfig struct {
// specify field's type
RelatePointer bool // ex: CreditCard *CreditCard
RelateSlice bool // ex: CreditCards []CreditCard
RelateSlicePointer bool // ex: CreditCards []*CreditCard

JSONTag string // related field's JSON tag
GORMTag string // related field's GORM tag
NewTag string // related field's new tag
OverwriteTag string // related field's tag
}

Operation

Skip Auto Create/Update

user := model.User{
Name: "modi",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{
{Email: "modi@example.com"},
{Email: "modi-2@example.com"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name).Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
// Skip create BillingAddress when creating a user

u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
// Skip create BillingAddress.Address1 when creating a user

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

Find matched associations

u := query.Use(db).User

languages, err = u.Languages.Model(&user).Find()
@@ -198,10 +198,10 @@

Nested Preloading together, e.g:

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:

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.

+

Preload with select

Specify selected columns with method Select. Foreign key must be selected.

type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}

u := q.User
cc := q.CreditCard

// !!! Foregin key "cc.UserRefer" must be selected
users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
// SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
// SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

Preload with conditions

GEN allows Preload associations with conditions, it works similar to Inline Conditions.

@@ -209,14 +209,13 @@

Nested Preloading

GEN supports nested preloading, for example:

db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

// Customize Preload conditions for `Orders`
// 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/es_ES/gen/clause.html b/es_ES/gen/clause.html index 4f1a30a9107..43bd44819e8 100644 --- a/es_ES/gen/clause.html +++ b/es_ES/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

- + diff --git a/es_ES/gen/create.html b/es_ES/gen/create.html index f50948a1cfa..7d20d67b135 100644 --- a/es_ES/gen/create.html +++ b/es_ES/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

- + diff --git a/es_ES/gen/dao.html b/es_ES/gen/dao.html index 64aa045778d..2bc6a50441f 100644 --- a/es_ES/gen/dao.html +++ b/es_ES/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

- + diff --git a/es_ES/gen/database_to_structs.html b/es_ES/gen/database_to_structs.html index 3db4282988f..5d6003b03f1 100644 --- a/es_ES/gen/database_to_structs.html +++ b/es_ES/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

diff --git a/es_ES/gen/delete.html b/es_ES/gen/delete.html index 9cc10629e1c..6588dc8582a 100644 --- a/es_ES/gen/delete.html +++ b/es_ES/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

- + diff --git a/es_ES/gen/dynamic_sql.html b/es_ES/gen/dynamic_sql.html index 9fe38803881..b0351fb3efc 100644 --- a/es_ES/gen/dynamic_sql.html +++ b/es_ES/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/es_ES/gen/gen_tool.html b/es_ES/gen/gen_tool.html index 7ade008f97a..01c6b6c5055 100644 --- a/es_ES/gen/gen_tool.html +++ b/es_ES/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

- + diff --git a/es_ES/gen/index.html b/es_ES/gen/index.html index 8e399917e15..0319dd76b68 100644 --- a/es_ES/gen/index.html +++ b/es_ES/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/es_ES/gen/query.html b/es_ES/gen/query.html index 32daffc7daa..cb95ef44f87 100644 --- a/es_ES/gen/query.html +++ b/es_ES/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

int/uint/float Fields

// int field
f := field.NewInt("user", "id")
// `user`.`id` = 123
f.Eq(123)
// `user`.`id` DESC
f.Desc()
// `user`.`id` AS `user_id`
f.As("user_id")
// COUNT(`user`.`id`)
f.Count()
// SUM(`user`.`id`)
f.Sum()
// SUM(`user`.`id`) > 123
f.Sum().Gt(123)
// ((`user`.`id`+1)*2)/3
f.Add(1).Mul(2).Div(3),
// `user`.`id` <<< 3
f.LeftShift(3)
-

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `uesr`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")
+

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `user`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")

Time Fields

birth := field.NewString("user", "birth")
// `user`.`birth` = ? (now)
birth.Eq(time.Now())
// DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
birth.Add(time.Duration(time.Hour).Microseconds())
// DATE_FORMAT(`user`.`birth`, "%W %M %Y")
birth.DateFormat("%W %M %Y")

Bool Fields

active := field.NewBool("user", "active")
// `user`.`active` = TRUE
active.Is(true)
// NOT `user`.`active`
active.Not()
// `user`.`active` AND TRUE
active.And(true)

SubQuery

A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

-
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
// 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
+
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

From SubQuery

GORM allows you using subquery in FROM clause with method Table, for example:

u := query.User
p := query.Pet

users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

subQuery1 := u.WithContext(ctx).Select(u.Name)
subQuery2 := p.WithContext(ctx).Select(p.Name)
users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
@@ -312,7 +312,7 @@

- + diff --git a/es_ES/gen/rawsql_driver.html b/es_ES/gen/rawsql_driver.html index 5e799ea7b2a..ae34322fa57 100644 --- a/es_ES/gen/rawsql_driver.html +++ b/es_ES/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/es_ES/gen/sql_annotation.html b/es_ES/gen/sql_annotation.html index 860faa23b3f..fdcd4c12083 100644 --- a/es_ES/gen/sql_annotation.html +++ b/es_ES/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
// UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
// UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

query.User.Update(User{Age: 0}, 10)
// UPDATE users SET is_adult=0 WHERE id=10

for

The for expression iterates over a slice to generate the SQL, let’s explain by example

-
// 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)
+
// SELECT * FROM @@table
// {{where}}
// {{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}}
// {{end}}
// {{end}}
Filter(users []gen.T) ([]gen.T, error)

Usage:

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","%"))
@@ -254,7 +254,7 @@

- + diff --git a/es_ES/gen/transaction.html b/es_ES/gen/transaction.html index 08b2d2db72d..2a21c690826 100644 --- a/es_ES/gen/transaction.html +++ b/es_ES/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/es_ES/gen/update.html b/es_ES/gen/update.html index a7b7872080e..100c427cebc 100644 --- a/es_ES/gen/update.html +++ b/es_ES/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/es_ES/gorm.html b/es_ES/gorm.html index 60691ac7abe..d7bbbb782f3 100644 --- a/es_ES/gorm.html +++ b/es_ES/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- +
diff --git a/es_ES/gormx.html b/es_ES/gormx.html index 50308464c6d..314f61e3538 100644 --- a/es_ES/gormx.html +++ b/es_ES/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/es_ES/hints.html b/es_ES/hints.html index af510fd1140..8ca9d9377f6 100644 --- a/es_ES/hints.html +++ b/es_ES/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/es_ES/index.html b/es_ES/index.html index 9a154ecebc8..13b7bffeb3c 100644 --- a/es_ES/index.html +++ b/es_ES/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/es_ES/rawsql.html b/es_ES/rawsql.html index aada701a0ad..286e6675a87 100644 --- a/es_ES/rawsql.html +++ b/es_ES/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/es_ES/rawsql_driver.html b/es_ES/rawsql_driver.html index a1d199bafbb..92c33728f2e 100644 --- a/es_ES/rawsql_driver.html +++ b/es_ES/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/es_ES/sharding.html b/es_ES/sharding.html index ea70d11665d..d6b069f3d3f 100644 --- a/es_ES/sharding.html +++ b/es_ES/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/es_ES/stats.html b/es_ES/stats.html index 4d968ec52e2..bf669f56775 100644 --- a/es_ES/stats.html +++ b/es_ES/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/fa_IR/404.html b/fa_IR/404.html index 075f26e4729..27a7432c183 100644 --- a/fa_IR/404.html +++ b/fa_IR/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/fa_IR/index.html b/fa_IR/index.html index 0d2e22f98ec..e1b5968d7c1 100644 --- a/fa_IR/index.html +++ b/fa_IR/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/fr_FR/404.html b/fr_FR/404.html index 49a0bb5d666..9c52dbbc7cb 100644 --- a/fr_FR/404.html +++ b/fr_FR/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/fr_FR/community.html b/fr_FR/community.html index d18afe753a3..1ffeeb9c157 100644 --- a/fr_FR/community.html +++ b/fr_FR/community.html @@ -56,8 +56,8 @@ - - + + @@ -184,7 +184,7 @@

- + diff --git a/fr_FR/contribute.html b/fr_FR/contribute.html index 02e494592a3..31a90e8fd91 100644 --- a/fr_FR/contribute.html +++ b/fr_FR/contribute.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/fr_FR/datatypes.html b/fr_FR/datatypes.html index 69972a340ea..835f5cc20d2 100644 --- a/fr_FR/datatypes.html +++ b/fr_FR/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/fr_FR/docs/advanced_query.html b/fr_FR/docs/advanced_query.html index 81137987c23..4491b12f921 100644 --- a/fr_FR/docs/advanced_query.html +++ b/fr_FR/docs/advanced_query.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,80 +141,106 @@

Requête avancée

-

Champs de sélection intelligente

GORM allows selecting specific fields with Select, 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:

-
type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// Select `id`, `name` automatique lors de la requête
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
+

Champs de sélection intelligente

In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

+
type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// GORM will automatically select `id`, `name` fields when querying
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SQL: SELECT `id`, `name` FROM `users` LIMIT 10
-

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.

-
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

db.Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // avec cette option

// Mode Session
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users`
+
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

// Default behavior with QueryFields set to true
db.Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

// Using Session Mode with QueryFields
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
-

Locking (FOR UPDATE)

GORM supports different types of locks, for example:

-
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATE

db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`

db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
+

Locking

GORM supports different types of locks, for example:

+
// Basic FOR UPDATE lock
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE
-

Reportez-vous à Raw SQL et SQL Builder pour plus de détails

-

SubQuery

A subquery can be nested within a query, GORM can generate subquery when using a *gorm.DB object as param

-
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

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%")
+

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.

+
db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SQL: SELECT * FROM `users` FOR SHARE OF `users`
-

From SubQuery

GORM allows you using subquery in FROM clause with the method Table, for example:

-
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

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
+

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.

+
db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
-

Group Conditions

Easier to write complicated SQL query with Group Conditions

-
db.Where(
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")
+

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.

+

SubQuery

Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

+
// Simple subquery
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
-

IN with multiple columns

Selecting 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"));
+

From SubQuery

GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

+
// Using subquery in FROM clause
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// 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{})
// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
-

Named Argument

GORM supports named arguments with sql.NamedArg or map[string]interface{}{}, for example:

-
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

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
+

Group Conditions

Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

+
// Complex SQL query using Group Conditions
db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
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")
-

Check out Raw SQL and SQL Builder for more detail

-

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:

-
result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)

var results []map[string]interface{}
db.Table("users").Find(&results)
+

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.

+
// Using IN with multiple columns
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
-

FirstOrInit

Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions)

-
// User not found, initialize it with give conditions
db.FirstOrInit(&user, User{Name: "non_existing"})
// user -> User{Name: "non_existing"}

// Found user with `name` = `jinzhu`
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}

// Found user with `name` = `jinzhu`
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}
+

Named Argument

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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

+
// Example using sql.NamedArg for named arguments
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// 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)
// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
-

Initialize struct with more attributes if record not found, those Attrs won’t be used to build the SQL query

-
// User not found, initialize it with give conditions and Attrs
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}

// Found user with `name` = `jinzhu`, attributes will be 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}
+

For more examples and details, see Raw SQL and SQL Builder

+

Find To Map

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.

+
// 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`
-

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

-
// User not found, initialize it with give conditions and Assign attributes
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
// user -> User{Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, update it 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}
+

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.

+
// 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"} if not found

// Retrieving a user named "jinzhu"
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

// Using a map to specify the search condition
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

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

-
// User not found, create a new record with give conditions
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1

// Found user with `name` = `jinzhu`
result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
// user -> User{ID: 111, Name: "jinzhu", "Age": 18}
// result.RowsAffected // => 0
+

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.

+
// If no User is found, initialize with given conditions and additional attributes
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20} if not found

// If a User named "Jinzhu" is found, `Attrs` are ignored
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

Create struct with more attributes if record not found, those Attrs won’t be used to build SQL query

-
// User not found, create it with give conditions and Attrs
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, attributes will be ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
+

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.

+
// 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} if not found

// If a User named "Jinzhu" is found, update the struct with Assign attributes
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
-

Assign attributes to the record regardless it is found or not and save them back to the database.

-
// User not found, initialize it with give conditions and Assign attributes
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, update it 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}
+

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

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.

+
// Create a new record if not found
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// SQL: INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1 (record created)

// 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)
-

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:

-
import "gorm.io/hints"

db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
+

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.

+
// Create a new record with additional attributes if not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// 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}

// If the user is found, `Attrs` are ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
-

Index hints allow passing index hints to the database in case the query planner gets confused.

-
import "gorm.io/hints"

db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)

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`)"
+

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.

+
// Initialize and save new record with `Assign` attributes if not found
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// 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}

// Update found record with `Assign` attributes
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// SQL: UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20}
-

Refer Optimizer Hints/Index/Comment for more details

-

Iteration

GORM supports iterating through Rows

-
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
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
db.ScanRows(rows, &user)

// do something
}
+

Optimizer/Index 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.

+
import "gorm.io/hints"

// Using an optimizer hint to set a maximum execution time
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
-

FindInBatches

Query and process records in batch

-
// batch size 100
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// batch processing found records
}

tx.Save(&results)

tx.RowsAffected // number of records in this batch

batch // Batch 1, 2, 3

// returns error will stop future batches
return nil
})

result.Error // returned error
result.RowsAffected // processed records count in all batches
+

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.

+
import "gorm.io/hints"

// Suggesting the use of a specific index
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// 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{})
// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
-

Query Hooks

GORM allows hooks AfterFind for a query, it will be called when querying a record, refer Hooks for details

-
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.Role == "" {
u.Role = "user"
}
return
}
+

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 in the GORM documentation.

+

Iteration

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.

+
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()

for rows.Next() {
var user User
// ScanRows scans a row into a struct
db.ScanRows(rows, &user)

// Perform operations on each user
}
-

Pluck

Query single column from database and scan into a slice, if you want to query multiple columns, use Select with Scan instead

-
var ages []int64
db.Model(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

// Distinct Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SELECT DISTINCT `name` FROM `users`

// Requesting more than one column, use `Scan` or `Find` like this:
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+

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.

+
// 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 provides the count of records in the current batch
// The variable 'batch' indicates the current batch number

// Returning an error will stop further batch processing
return nil
})

// result.Error contains any errors encountered during batch processing
// result.RowsAffected provides the count of all processed records across batches
-

Scopes

Scopes allows you to specify commonly-used queries which can be referenced as method calls

-
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}

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 {
return db.Where("status IN (?)", status)
}
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// Find all credit card orders and amount greater than 1000

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// Find all COD orders and amount greater than 1000

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// Find all paid, shipped orders that amount greater than 1000
+

FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

+

Query Hooks

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 in the GORM documentation.

+
func (u *User) AfterFind(tx *gorm.DB) (err error) {
// Custom logic after finding a user
if u.Role == "" {
u.Role = "user" // Set default role if not specified
}
return
}

// Usage of AfterFind hook happens automatically when a User is queried
-

Checkout Scopes for details

-

Count

Get matched records count

-
var count int64
db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

db.Table("deleted_users").Count(&count)
// SELECT count(1) FROM deleted_users;

// Count with Distinct
db.Model(&User{}).Distinct("name").Count(&count)
// SELECT COUNT(DISTINCT(`name`)) FROM `users`

db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SELECT count(distinct(name)) FROM deleted_users

// Count with Group
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
count // => 3
+

Pluck

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 or Find instead.

+
// Retrieving ages of all users
var ages []int64
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)

// Using Distinct with Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SQL: SELECT DISTINCT `name` FROM `users`

// Querying multiple columns
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+ +

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.

+
// 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")
}

// 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.

+
// 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)
+ +

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 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.

+
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'

// Counting records in a different table
db.Table("deleted_users").Count(&count)
// SQL: SELECT count(1) FROM deleted_users
+ +

Count with Distinct and Group

GORM also allows counting distinct values and grouping results.

+
// Counting distinct names
db.Model(&User{}).Distinct("name").Count(&count)
// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

// Counting distinct values with a custom select
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SQL: SELECT count(distinct(name)) FROM deleted_users

// Counting grouped records
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
// Count after grouping by name
// count => 3
@@ -227,7 +253,7 @@

- + @@ -341,7 +367,7 @@

Gold Sponsors

Contenus -
  1. Champs de sélection intelligente
  2. Locking (FOR UPDATE)
  3. SubQuery
    1. From SubQuery
  4. Group Conditions
  5. IN with multiple columns
  6. Named Argument
  7. Find To Map
  8. FirstOrInit
  9. FirstOrCreate
  10. Optimizer/Index Hints
  11. Iteration
  12. FindInBatches
  13. Query Hooks
  14. Pluck
  15. Scopes
  16. Count
+
  1. Champs de sélection intelligente
  2. Locking
  3. SubQuery
    1. From SubQuery
  4. Group Conditions
  5. IN with multiple columns
  6. Named Argument
  7. Find To Map
  8. FirstOrInit
    1. Using Attrs for Initialization
    2. Using Assign for Attributes
  9. FirstOrCreate
    1. Using Attrs with FirstOrCreate
    2. Using Assign with FirstOrCreate
  10. Optimizer/Index Hints
    1. Index Hints
  11. Iteration
  12. FindInBatches
  13. Query Hooks
  14. Pluck
  15. Scopes
    1. Defining Scopes
    2. Applying Scopes in Queries
  16. Count
    1. Getting the Count of Matched Records
    2. Count with Distinct and Group
diff --git a/fr_FR/docs/associations.html b/fr_FR/docs/associations.html index 2ca4990cede..26918a7b0e8 100644 --- a/fr_FR/docs/associations.html +++ b/fr_FR/docs/associations.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,62 +141,101 @@

Associations

-

Auto Create/Update

GORM enregistrera automatiquement les associations et ses références en utilisant Upsert lors de la création/mise à jour d’un enregistrement.

-
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"},
},
}

db.Create(&user)
// BEGIN TRANSACTION;
// 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 ('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)
- -

Si vous voulez mettre à jour les données des associations, vous devez utiliser le mode FullSaveAssociations:

-
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);
// ...
- -

Skip Auto Create/Update

Pour ignorer la sauvegarde automatique lors de la création/mise à jour, vous pouvez utiliser Select ou Omit, par exemple :

-
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"},
},
}

db.Select("Name").Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

db.Omit("BillingAddress").Create(&user)
// Skip create BillingAddress when creating a user

db.Omit(clause.Associations).Create(&user)
// Skip all associations when creating a user
- -

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 :

-
db.Omit("Languages.*").Create(&user)
- -

Le code suivant ignorera la création de l’association et de ses références

-
db.Omit("Languages").Create(&user)
- -

Select/Omit Champs d’association

user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
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
db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
- -

Mode d’association

Le mode association contient des méthodes d’aide couramment utilisées pour gérer les relations

-
// 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
- -

Trouver des associations

Trouver les associations correspondantes

-
db.Model(&user).Association("Languages").Find(&languages)
- -

Trouver des associations avec des 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

Ajouter de nouvelles associations pour many to many, has many, remplacer l’association actuelle pour has one, belongs to

-
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
- -

Remplacer des associations

Remplacer les associations actuelles par de nouvelles associations

-
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
- -

Supprimer des 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.

-
db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
- -

Clear Associations

Supprimer toutes les références entre l’association source & ne supprimera pas ces associations

-
db.Model(&user).Association("Languages").Clear()
- -

Nombre d’associations

Renvoie le nombre d’associations actuelles

-
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

Le mode association supporte les données par lot, par exemple:

-
// Find all roles for all users
db.Model(&users).Association("Role").Find(&roles)

// Delete User A from all user's team
db.Model(&users).Association("Team").Delete(&userA)

// Get distinct count of all users' teams
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
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
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
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).

-

How to delete is decided by gorm.DB.

-
// Soft delete
// UPDATE `languages` SET `deleted_at`= ...
db.Model(&user).Association("Languages").Unscoped().Clear()

// Delete permanently
// DELETE FROM `languages` WHERE ...
db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
- -

Delete with Select

You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

-
// 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)
- -

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

-
// 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
- -

Association Tags

+

Auto Create/Update

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.

+
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"},
},
}

// 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;
// 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 ('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)
+ +

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.

+
// Update a user and fully update all its associations
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
// 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

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.

+
user := User{
// User and associated data
}

// Only include the 'Name' field when creating the user
db.Select("Name").Create(&user)
// 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.

+
// Skip creating the 'BillingAddress' when creating the user
db.Omit("BillingAddress").Create(&user)

// Skip all associations when creating the user
db.Omit(clause.Associations).Create(&user)
+ +

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 .*:

+
// Skip upserting 'Languages' associations
db.Omit("Languages.*").Create(&user)
+ +

To skip creating both the association and its references:

+
// Skip creating 'Languages' associations and their references
db.Omit("Languages").Create(&user)
+ +

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.

+
user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
}

// 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.

+
// 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: 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.

+
// 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
+ +

Mode d’association

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.

+
var user User
db.Model(&user).Association("Languages")
// Check for errors
error := db.Model(&user).Association("Languages").Error
+ +

Finding Associations

Retrieve associated records with or without additional conditions.

+
// Simple find
db.Model(&user).Association("Languages").Find(&languages)

// Find with conditions
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).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.

+
// Append new languages
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

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.

+
// 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.

+
// 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.

+
// Clear all languages
db.Model(&user).Association("Languages").Clear()
+ +

Counting Associations

Get the count of current associations, with or without conditions.

+
// 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.
  • +
+
db.Model(&users).Association("Role").Find(&roles)
+ +
    +
  • Deleting Associations: Remove specific associations across multiple records.
  • +
+
db.Model(&users).Association("Team").Delete(&userA)
+ +
    +
  • Counting Associations: Get the count of associations for a batch of records.
  • +
+
db.Model(&users).Association("Team").Count()
+ +
    +
  • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
  • +
+
var users = []User{user1, user2, 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})

// 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

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.

+
    +
  • 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.
  • +
+

Modifying Deletion Behavior with Unscoped

For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

+
    +
  • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
  • +
+
db.Model(&user).Association("Languages").Unscoped().Clear()
+ +
    +
  • Permanent Delete: Physically deletes the association records from the database.
  • +
+
// db.Unscoped().Model(&user)
db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
+ +

Association Tags

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.

+ @@ -204,36 +243,36 @@

- - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
Tag
foreignKeySpecifies column name of the current model that is used as a foreign key to the join tableforeignKeySpecifies the column name of the current model used as a foreign key in the join table.
referencesSpecifies column name of the reference’s table that is mapped to the foreign key of the join tablereferencesIndicates the column name in the reference table that the foreign key of the join table maps to.
polymorphicSpecifies polymorphic type such as model namepolymorphicDefines the polymorphic type, typically the model name.
polymorphicValueSpecifies polymorphic value, default table namepolymorphicValueSets the polymorphic value, usually the table name, if not specified otherwise.
many2manySpecifies join table namemany2manyNames the join table used in a many-to-many relationship.
joinForeignKeySpecifies foreign key column name of join table that maps to the current tablejoinForeignKeyIdentifies the foreign key column in the join table that maps back to the current model’s table.
joinReferencesSpecifies foreign key column name of join table that maps to the reference’s tablejoinReferencesPoints to the foreign key column in the join table that links to the reference model’s table.
constraintRelations constraint, e.g: OnUpdate,OnDeleteconstraintSpecifies relational constraints like OnUpdate, OnDelete for the association.
@@ -248,7 +287,7 @@

@@ -362,7 +401,7 @@

Gold Sponsors

Contenus -
  1. Auto Create/Update
  2. Skip Auto Create/Update
  3. Select/Omit Champs d’association
  4. Mode d’association
    1. Trouver des associations
    2. Ajouter des associations
    3. Remplacer des associations
    4. Supprimer des associations
    5. Clear Associations
    6. Nombre d’associations
    7. Batch Data
  5. Supprimer l’enregistrement de l’association
  6. Delete with Select
  7. Association Tags
+
  1. Auto Create/Update
    1. Auto-Saving Associations on Create
    2. Updating Associations with FullSaveAssociations
  2. Skip Auto Create/Update
    1. Using Select to Include Specific Fields
    2. Using Omit to Exclude Fields or Associations
  3. Select/Omit Champs d’association
  4. Supprimer des associations
  5. Mode d’association
    1. Finding Associations
    2. Appending Associations
    3. Replacing Associations
    4. Deleting Associations
    5. Clearing Associations
    6. Counting Associations
    7. Batch Data Handling
  6. Supprimer l’enregistrement de l’association
    1. Modifying Deletion Behavior with Unscoped
  7. Association Tags
diff --git a/fr_FR/docs/belongs_to.html b/fr_FR/docs/belongs_to.html index 220adf7d124..216bf16a985 100644 --- a/fr_FR/docs/belongs_to.html +++ b/fr_FR/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

- + diff --git a/fr_FR/docs/changelog.html b/fr_FR/docs/changelog.html index 81192680262..a8d14ad6676 100644 --- a/fr_FR/docs/changelog.html +++ b/fr_FR/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/fr_FR/docs/composite_primary_key.html b/fr_FR/docs/composite_primary_key.html index 26359102ee7..3d23c5ceaa2 100644 --- a/fr_FR/docs/composite_primary_key.html +++ b/fr_FR/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

Clé primaire composite

- +
diff --git a/fr_FR/docs/connecting_to_the_database.html b/fr_FR/docs/connecting_to_the_database.html index 20a8c1480d4..113590fefe9 100644 --- a/fr_FR/docs/connecting_to_the_database.html +++ b/fr_FR/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

- + diff --git a/fr_FR/docs/constraints.html b/fr_FR/docs/constraints.html index 1a4ae8cec87..8fdad0e28fb 100644 --- a/fr_FR/docs/constraints.html +++ b/fr_FR/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/fr_FR/docs/context.html b/fr_FR/docs/context.html index c3d9ae8b400..96beacadc7c 100644 --- a/fr_FR/docs/context.html +++ b/fr_FR/docs/context.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,25 @@

Context

-

GORM provides Context support, you can use it with method WithContext

-

Single Session Mode

Single session mode usually used when you want to perform a single operation

+

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 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.

db.WithContext(ctx).Find(&users)
-

Continuous session mode

Continuous session mode is usually used when you want to perform a group of operations, for example:

+

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.

tx := db.WithContext(ctx)
tx.First(&user, 1)
tx.Model(&user).Update("role", "admin")
-

Context timeout

You can pass in a context with a timeout to db.WithContext to set timeout for long running queries, for example:

+

Context Timeout

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.

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

db.WithContext(ctx).Find(&users)
-

Context in Hooks/Callbacks

You can access the Context object from the current Statement, for example:

-
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ...
return
}
+

Context in Hooks/Callbacks

The context can also be accessed within GORM’s hooks/callbacks. This enables contextual information to be used during these lifecycle events.

+
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ... use context
return
}
-

Chi Middleware Example

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:

-
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

r := chi.NewRouter()
r.Use(SetDBMiddleware)

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
})

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
})
+

Integration with 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.

+
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

// Router setup
r := chi.NewRouter()
r.Use(SetDBMiddleware)

// Route handlers
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})

r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})
-

NOTE Setting Context with WithContext is goroutine-safe, refer Session for details

-
- -

Logger

Logger accepts Context too, you can use it for log tracking, refer Logger for details

+

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 in GORM.

+

Logger Integration

GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

+

Refer to Logger documentation for more details.

@@ -174,7 +172,7 @@

- + @@ -288,7 +286,7 @@

Gold Sponsors

Contenus -
  1. Single Session Mode
  2. Continuous session mode
  3. Context timeout
  4. Context in Hooks/Callbacks
  5. Chi Middleware Example
  6. Logger
+
  1. Single Session Mode
  2. Continuous Session Mode
  3. Context Timeout
  4. Context in Hooks/Callbacks
  5. Integration with Chi Middleware
  6. Logger Integration
diff --git a/fr_FR/docs/conventions.html b/fr_FR/docs/conventions.html index 6a79b9fe5a8..bd7cb5b32b8 100644 --- a/fr_FR/docs/conventions.html +++ b/fr_FR/docs/conventions.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

// Create table `deleted_users` with struct User's fields
db.Table("deleted_users").AutoMigrate(&User{})

// Query data from another table
var deletedUsers []User
db.Table("deleted_users").Find(&deletedUsers)
// SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
// DELETE FROM deleted_users WHERE name = 'jinzhu';

Check out From SubQuery for how to use SubQuery in FROM clause

-

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 for details

+

NamingStrategy

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 for details

Column Name

Column db name uses the field’s name’s snake_case by convention.

type User struct {
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}
@@ -194,7 +194,7 @@

- + diff --git a/fr_FR/docs/create.html b/fr_FR/docs/create.html index 1ea9c14cf31..1e8561b30b8 100644 --- a/fr_FR/docs/create.html +++ b/fr_FR/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -155,7 +155,7 @@

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
-

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.

+

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 split into multiple batches.

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

You can specify batch size when creating with CreateInBatches, e.g:

@@ -220,7 +220,7 @@

- + diff --git a/fr_FR/docs/data_types.html b/fr_FR/docs/data_types.html index 56520923dc6..06830d4628e 100644 --- a/fr_FR/docs/data_types.html +++ b/fr_FR/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

- + diff --git a/fr_FR/docs/dbresolver.html b/fr_FR/docs/dbresolver.html index 3a1dc02c552..8fac67f3456 100644 --- a/fr_FR/docs/dbresolver.html +++ b/fr_FR/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

Transaction

When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

But you can specifies which DB to use before starting a transaction, for example:

-
// Start transaction based on default replicas db
tx := DB.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := DB.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
+
// Start transaction based on default replicas db
tx := db.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := db.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

Load Balancing

GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

type Policy interface {
Resolve([]gorm.ConnPool) gorm.ConnPool
}
@@ -183,7 +183,7 @@

diff --git a/fr_FR/docs/delete.html b/fr_FR/docs/delete.html index 3a157245597..a859703dea5 100644 --- a/fr_FR/docs/delete.html +++ b/fr_FR/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

- + diff --git a/fr_FR/docs/error_handling.html b/fr_FR/docs/error_handling.html index 94dd27f06a6..0f078cb99ab 100644 --- a/fr_FR/docs/error_handling.html +++ b/fr_FR/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

Error Handling

-

In Go, error handling is important.

-

You are encouraged to do error check after any Finisher Methods

-

Error Handling

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:

-
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// error handling...
}
- -

Or

-
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// error handling...
}
- -

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:

-
// Check if returns RecordNotFound error
err := db.First(&user, 100).Error
errors.Is(err, gorm.ErrRecordNotFound)
-

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.

+

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.

+

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.

+

After a chain of methods, it’s crucial to check the Error field:

+
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// Handle error...
}
+ +

Or alternatively:

+
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// Handle error...
}
+ +

ErrRecordNotFound

GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

+
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

+
    +
  • Example: Handling MySQL Error Codes
  • +
+
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.

db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
-

Errors

Errors List

+
    +
  • ErrDuplicatedKey
  • +
+

This error occurs when an insert operation violates a unique constraint:

+
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:

+
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

For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

@@ -168,7 +187,7 @@

- + @@ -282,7 +301,7 @@

Gold Sponsors

Contenus -
  1. Error Handling
  2. ErrRecordNotFound
  3. Dialect Translated Errors
  4. Errors
+
  1. Basic Error Handling
  2. ErrRecordNotFound
  3. Handling Error Codes
  4. Dialect Translated Errors
  5. Errors
diff --git a/fr_FR/docs/generic_interface.html b/fr_FR/docs/generic_interface.html index 764fa4f45b7..cb90114a063 100644 --- a/fr_FR/docs/generic_interface.html +++ b/fr_FR/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

diff --git a/fr_FR/docs/gorm_config.html b/fr_FR/docs/gorm_config.html index 7fe1d3337eb..cadcfee7b9e 100644 --- a/fr_FR/docs/gorm_config.html +++ b/fr_FR/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

diff --git a/fr_FR/docs/has_many.html b/fr_FR/docs/has_many.html index 7d8de851f56..8c85cd0ce9f 100644 --- a/fr_FR/docs/has_many.html +++ b/fr_FR/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/fr_FR/docs/has_one.html b/fr_FR/docs/has_one.html index d044dcb535d..1e531235c7e 100644 --- a/fr_FR/docs/has_one.html +++ b/fr_FR/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/fr_FR/docs/hints.html b/fr_FR/docs/hints.html index 5fac32a04af..d31115d4d74 100644 --- a/fr_FR/docs/hints.html +++ b/fr_FR/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

- + diff --git a/fr_FR/docs/hooks.html b/fr_FR/docs/hooks.html index 91c9cba36db..4bcdb27d7d9 100644 --- a/fr_FR/docs/hooks.html +++ b/fr_FR/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

- + diff --git a/fr_FR/docs/index.html b/fr_FR/docs/index.html index b85cde485e7..bd994ef8985 100644 --- a/fr_FR/docs/index.html +++ b/fr_FR/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

diff --git a/fr_FR/docs/indexes.html b/fr_FR/docs/indexes.html index 06a358df476..d5f6caec505 100644 --- a/fr_FR/docs/indexes.html +++ b/fr_FR/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

diff --git a/fr_FR/docs/logger.html b/fr_FR/docs/logger.html index d949e6127bd..f1aaa5b6a42 100644 --- a/fr_FR/docs/logger.html +++ b/fr_FR/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

diff --git a/fr_FR/docs/many_to_many.html b/fr_FR/docs/many_to_many.html index 9ea024a6579..24168dd24bb 100644 --- a/fr_FR/docs/many_to_many.html +++ b/fr_FR/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

-
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addressses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
+
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

FOREIGN KEY Constraints

You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_speaks;"`
}

type Language struct {
Code string `gorm:"primarykey"`
Name string
}

// CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
@@ -191,7 +191,7 @@

- + diff --git a/fr_FR/docs/method_chaining.html b/fr_FR/docs/method_chaining.html index 53180baf527..d5fc5d1dbb9 100644 --- a/fr_FR/docs/method_chaining.html +++ b/fr_FR/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

Method Chaining

-

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:

db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
-

There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

-

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:

-
queryDB := DB.Where("name = ?", "jinzhu")

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// 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:

-
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 20
- -

Chain Method

Chain methods are methods to modify or add Clauses to current Statement, like:

-

Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

-

Here is the full lists, also check out the SQL Builder 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 here.

-

New Session Method

GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new 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:
// 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:
// 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:
// SELECT * FROM users;
- -

(Bad) Example 2:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is safe to 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

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
- -

Example 3:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is 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

// good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// good case
tx.Where("age = ?", 28).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
+

Method Categories

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. Also, the SQL Builder 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.

+

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 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

queryDB := DB.Where("name = ?", "jinzhu")

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query with unintended compounded condition
queryDB.Where("age > ?", 20).First(&user2)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
+ +

Example of Safe Reuse

To safely reuse a *gorm.DB instance, use a New Session Method:

+
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query, safely isolated
queryDB.Where("age > ?", 20).First(&user2)
// 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.

+

Examples for Clarity

Let’s clarify with a few examples:

+
    +
  • Example 1: Safe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
// 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)
// 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)
// Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
// SELECT * FROM users;
+ +

In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

+
    +
  • (Bad) Example 2: Unsafe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized *gorm.DB, safe for initial reuse.

tx := db.Where("name = ?", "jinzhu")
// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

// Good case
tx.Where("age = ?", 18).Find(&users)
// Reuses 'tx' correctly for a single logical operation, executing:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// Bad case
tx.Where("age = ?", 28).Find(&users)
// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
+ +

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
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

// Good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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.

@@ -180,7 +210,7 @@

- + @@ -294,7 +324,7 @@

Gold Sponsors

Contenus -
  1. Chain Method
  2. Finisher Method
  3. New Session Method
+
  1. Method Categories
    1. Chain Methods
    2. Finisher Methods
    3. New Session Methods
  2. Reusability and Safety
    1. Example of Unsafe Reuse
    2. Example of Safe Reuse
  3. Examples for Clarity
diff --git a/fr_FR/docs/migration.html b/fr_FR/docs/migration.html index 613ee405604..543527bef8a 100644 --- a/fr_FR/docs/migration.html +++ b/fr_FR/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

- + diff --git a/fr_FR/docs/models.html b/fr_FR/docs/models.html index 628c68db930..3378c3bbe02 100644 --- a/fr_FR/docs/models.html +++ b/fr_FR/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,16 +141,45 @@

Declaring Models

-

Declaring Models

Models are normal structs with basic Go types, pointers/alias of them or custom types implementing Scanner and Valuer interfaces

-

For Example:

-
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
}
- -

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

-

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

-

gorm.Model

GORM defined a gorm.Model struct, which includes fields ID, CreatedAt, UpdatedAt, DeletedAt

+

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 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 and Valuer interfaces from the database/sql package

+

Consider the following example of a User model:

+
type User struct {
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 for a detailed explanation

+

Conventions

    +
  1. Primary Key: GORM uses a field named ID as the default primary key for each model.

    +
  2. +
  3. 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.

    +
  4. +
  5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

    +
  6. +
  7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

    +
  8. +
+

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.

+

gorm.Model

GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

// gorm.Model definition
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
-

You can embed it into your struct to include those fields, refer 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

    +
  • +
  • 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

Field-Level Permission

Exported fields have all permissions when doing CRUD with GORM, and GORM allows you to change the field-level permission with tag, so you can make a field to be read-only, write-only, create-only, update-only or ignored

NOTE ignored fields won’t be created when using GORM Migrator to create table

@@ -286,7 +315,7 @@

@@ -400,7 +429,7 @@

Gold Sponsors

Contenus -
  1. Declaring Models
  2. Conventions
  3. gorm.Model
  4. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
+
  1. Declaring Models
    1. Conventions
    2. gorm.Model
  2. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
diff --git a/fr_FR/docs/performance.html b/fr_FR/docs/performance.html index 985a510a2bf..da4bc6b0169 100644 --- a/fr_FR/docs/performance.html +++ b/fr_FR/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

- + diff --git a/fr_FR/docs/preload.html b/fr_FR/docs/preload.html index a68eee59d81..632178f5177 100644 --- a/fr_FR/docs/preload.html +++ b/fr_FR/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

- + diff --git a/fr_FR/docs/prometheus.html b/fr_FR/docs/prometheus.html index 53b6e5e477f..07e23e8e401 100644 --- a/fr_FR/docs/prometheus.html +++ b/fr_FR/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- + diff --git a/fr_FR/docs/query.html b/fr_FR/docs/query.html index da85ab5e849..5df6eee59d9 100644 --- a/fr_FR/docs/query.html +++ b/fr_FR/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

- + diff --git a/fr_FR/docs/scopes.html b/fr_FR/docs/scopes.html index 82a9fa8d095..c92e93c5c09 100644 --- a/fr_FR/docs/scopes.html +++ b/fr_FR/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/fr_FR/docs/security.html b/fr_FR/docs/security.html index 8be13688666..b85d8e702c9 100644 --- a/fr_FR/docs/security.html +++ b/fr_FR/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

Inline Condition

// will be escaped
db.First(&user, "name = ?", userInput)

// SQL injection
db.First(&user, fmt.Sprintf("name = %v", userInput))

When retrieving objects with number primary key by user’s input, you should check the type of variable.

-
userInputID := "1=1;drop table users;"
// safe, return error
id,err := strconv.Atoi(userInputID)
if err != nil {
return error
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;
+
userInputID := "1=1;drop table users;"
// safe, return error
id, err := strconv.Atoi(userInputID)
if err != nil {
return err
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;

SQL injection Methods

To support some features, some inputs are not escaped, be careful when using user’s input with those methods

db.Select("name; drop table users;").First(&user)
db.Distinct("name; drop table users;").First(&user)

db.Model(&user).Pluck("name; drop table users;", &names)

db.Group("name; drop table users;").First(&user)

db.Group("name").Having("1 = 1;drop table users;").First(&user)

db.Raw("select name from users; drop table users;").First(&user)

db.Exec("select name from users; drop table users;")

db.Order("name; drop table users;").First(&user)
@@ -169,7 +169,7 @@

- + diff --git a/fr_FR/docs/serializer.html b/fr_FR/docs/serializer.html index 18d76527454..a7700f98964 100644 --- a/fr_FR/docs/serializer.html +++ b/fr_FR/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

- + diff --git a/fr_FR/docs/session.html b/fr_FR/docs/session.html index 132c3f6e4dc..747faaba880 100644 --- a/fr_FR/docs/session.html +++ b/fr_FR/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

diff --git a/fr_FR/docs/settings.html b/fr_FR/docs/settings.html index 8f0984beb0e..0f527beb592 100644 --- a/fr_FR/docs/settings.html +++ b/fr_FR/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/fr_FR/docs/sharding.html b/fr_FR/docs/sharding.html index 5ed37bb7960..520d2c45655 100644 --- a/fr_FR/docs/sharding.html +++ b/fr_FR/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

- + diff --git a/fr_FR/docs/sql_builder.html b/fr_FR/docs/sql_builder.html index 11748684e47..7a14d073372 100644 --- a/fr_FR/docs/sql_builder.html +++ b/fr_FR/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

diff --git a/fr_FR/docs/transactions.html b/fr_FR/docs/transactions.html index 99cbbca2aae..2ec723a4f8d 100644 --- a/fr_FR/docs/transactions.html +++ b/fr_FR/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

db.Transaction(func(tx *gorm.DB) error {
// do some database operations in the transaction (use 'tx' from this point, not 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// return any error will rollback
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// return nil will commit the whole transaction
return nil
})

Nested Transactions

GORM supports nested transactions, you can rollback a subset of operations performed within the scope of a larger transaction, for example:

-
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3
+
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx3 *gorm.DB) error {
tx3.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3

Control the transaction manually

Gorm supports calling transaction control functions (commit / rollback) directly, for example:

// begin a transaction
tx := db.Begin()

// do some database operations in the transaction (use 'tx' from this point, not 'db')
tx.Create(...)

// ...

// rollback the transaction in case of error
tx.Rollback()

// Or commit the transaction
tx.Commit()
@@ -169,7 +169,7 @@

- + diff --git a/fr_FR/docs/update.html b/fr_FR/docs/update.html index 00342b35dcb..220c1337c4f 100644 --- a/fr_FR/docs/update.html +++ b/fr_FR/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

- + diff --git a/fr_FR/docs/v2_release_note.html b/fr_FR/docs/v2_release_note.html index 3c9bca6ab17..c56d9e56036 100644 --- a/fr_FR/docs/v2_release_note.html +++ b/fr_FR/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

- + diff --git a/fr_FR/docs/write_driver.html b/fr_FR/docs/write_driver.html index b1cdfe6ea05..a3bd51e03b9 100644 --- a/fr_FR/docs/write_driver.html +++ b/fr_FR/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

Write Driver

-

Write new driver

GORM provides official support for sqlite, mysql, postgres, sqlserver.

-

Some databases may be compatible with the mysql or postgres dialect, in which case you could just use the dialect for those databases.

-

For others, you can create a new driver, it needs to implement the dialect interface.

-
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
}
- -

Checkout the MySQL Driver as example

+

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.

+

Compatibility with MySQL or Postgres Dialects

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.

+

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:

+
type Dialector interface {
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.

+
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
}
+ +

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:

+
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:

+
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:

+
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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

@@ -159,7 +192,7 @@

Write Driver

- +
@@ -273,7 +306,7 @@

Gold Sponsors

Contenus -
  1. Write new driver
+
  1. Compatibility with MySQL or Postgres Dialects
  2. Implementing the Dialector
    1. Nested Transaction Support
    2. Custom Clause Builders
diff --git a/fr_FR/docs/write_plugins.html b/fr_FR/docs/write_plugins.html index 776da68a84e..600bc98899c 100644 --- a/fr_FR/docs/write_plugins.html +++ b/fr_FR/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

Write Plugins

-

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

-

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

-

Register Callback

Register a callback into callbacks

-
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

db.Callback().Create().Register("crop_image", cropImage)
// register a callback for Create process
+

Callbacks

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 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.

+

Registering a Callback

You can register a callback for specific operations. For example, to add a custom image cropping functionality:

+
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

// Register the callback for the Create operation
db.Callback().Create().Register("crop_image", cropImage)
-

Delete Callback

Delete a callback from callbacks

-
db.Callback().Create().Remove("gorm:create")
// delete callback `gorm:create` from Create callbacks
+

Deleting a Callback

If a callback is no longer needed, it can be removed:

+
// Remove the 'gorm:create' callback from Create operations
db.Callback().Create().Remove("gorm:create")
-

Replace Callback

Replace a callback having the same name with the new one

-
db.Callback().Create().Replace("gorm:create", newCreateFunction)
// replace callback `gorm:create` with new function `newCreateFunction` for Create process
+

Replacing a Callback

Callbacks with the same name can be replaced with a new function:

+
// Replace the 'gorm:create' callback with a new function
db.Callback().Create().Replace("gorm:create", newCreateFunction)
-

Register Callback with orders

Register callbacks with orders

-
// before gorm:create
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// after gorm:create
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// after gorm:query
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// after gorm:delete
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// before gorm:update
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
+

Ordering Callbacks

Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

+
// Register to execute before the 'gorm:create' callback
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:create' callback
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:query' callback
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// Register to execute after the 'gorm:delete' callback
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// Register to execute before the 'gorm:update' callback
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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)

// Register to execute before any other callbacks
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// Register to execute after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
-

Defined Callbacks

GORM has defined some callbacks to power current GORM features, check them out before starting your plugins

-

Plugin

GORM provides a Use method to register plugins, the plugin needs to implement the Plugin interface

+

Predefined Callbacks

GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

+

Plugins

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:

type Plugin interface {
Name() string
Initialize(*gorm.DB) error
}
-

The Initialize method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like:

-
db.Config.Plugins[pluginName]
+
    +
  • 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:

+
// Example of registering a plugin
db.Use(MyCustomPlugin{})
-

Checkout Prometheus as example

+

Accessing Registered Plugins

After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

+
// 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:

+
// Registering the Prometheus plugin
db.Use(prometheus.New(prometheus.Config{
// Configuration options here
}))
+ +

Prometheus plugin documentation provides detailed information on its implementation and usage.

@@ -175,7 +186,7 @@

- + @@ -289,7 +300,7 @@

Gold Sponsors

Contenus -
  1. Callbacks
    1. Register Callback
    2. Delete Callback
    3. Replace Callback
    4. Register Callback with orders
    5. Defined Callbacks
  2. Plugin
+
  1. Callbacks
    1. Registering a Callback
    2. Deleting a Callback
    3. Replacing a Callback
    4. Ordering Callbacks
    5. Predefined Callbacks
  2. Plugins
    1. The Plugin Interface
    2. Registering a Plugin
    3. Accessing Registered Plugins
    4. Practical Example
diff --git a/fr_FR/gen.html b/fr_FR/gen.html index 6fbf915a6dc..280c6e08a27 100644 --- a/fr_FR/gen.html +++ b/fr_FR/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/fr_FR/gen/associations.html b/fr_FR/gen/associations.html index a4e2ecee5a1..0ea824e192d 100644 --- a/fr_FR/gen/associations.html +++ b/fr_FR/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

// specify model
g.ApplyBasic(model.Customer{}, model.CreditCard{})

// assoications will be detected and converted to code
package query

type customer struct {
...
CreditCards customerHasManyCreditCards
}

type creditCard struct{
...
}
-

Relate to table in database

The association have to be speified by gen.FieldRelate

-
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"}},
}),
)

g.ApplyBasic(card, custormer)
+

Relate to table in database

The association have to be specified by gen.FieldRelate

+
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"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(card, custormer)

GEN will generate models with associated field:

-
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}
+
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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;references:ID" json:"credit_cards"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}

If associated model already exists, gen.FieldRelateModel can help you build associations between them.

-
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
}),
)

g.ApplyBasic(custormer)
+
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(custormer)

Relate Config

type RelateConfig struct {
// specify field's type
RelatePointer bool // ex: CreditCard *CreditCard
RelateSlice bool // ex: CreditCards []CreditCard
RelateSlicePointer bool // ex: CreditCards []*CreditCard

JSONTag string // related field's JSON tag
GORMTag string // related field's GORM tag
NewTag string // related field's new tag
OverwriteTag string // related field's tag
}

Operation

Skip Auto Create/Update

user := model.User{
Name: "modi",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{
{Email: "modi@example.com"},
{Email: "modi-2@example.com"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name).Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
// Skip create BillingAddress when creating a user

u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
// Skip create BillingAddress.Address1 when creating a user

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

Find matched associations

u := query.Use(db).User

languages, err = u.Languages.Model(&user).Find()
@@ -198,10 +198,10 @@

Nested Preloading together, e.g:

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:

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.

+

Preload with select

Specify selected columns with method Select. Foreign key must be selected.

type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}

u := q.User
cc := q.CreditCard

// !!! Foregin key "cc.UserRefer" must be selected
users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
// SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
// SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

Preload with conditions

GEN allows Preload associations with conditions, it works similar to Inline Conditions.

@@ -209,14 +209,13 @@

Nested Preloading

GEN supports nested preloading, for example:

db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

// Customize Preload conditions for `Orders`
// 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/fr_FR/gen/clause.html b/fr_FR/gen/clause.html index ee7f3e8bb80..2013ece20f6 100644 --- a/fr_FR/gen/clause.html +++ b/fr_FR/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

- + diff --git a/fr_FR/gen/create.html b/fr_FR/gen/create.html index 43bc6b20aa1..3609b3492fc 100644 --- a/fr_FR/gen/create.html +++ b/fr_FR/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

- + diff --git a/fr_FR/gen/dao.html b/fr_FR/gen/dao.html index 0da2b692da4..908715b7035 100644 --- a/fr_FR/gen/dao.html +++ b/fr_FR/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

- + diff --git a/fr_FR/gen/database_to_structs.html b/fr_FR/gen/database_to_structs.html index 705b70a6cf4..9edc55baa8e 100644 --- a/fr_FR/gen/database_to_structs.html +++ b/fr_FR/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

diff --git a/fr_FR/gen/delete.html b/fr_FR/gen/delete.html index 726c4cab7fc..f8d98c0d1b1 100644 --- a/fr_FR/gen/delete.html +++ b/fr_FR/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

- + diff --git a/fr_FR/gen/dynamic_sql.html b/fr_FR/gen/dynamic_sql.html index e785f345e8b..142fe801382 100644 --- a/fr_FR/gen/dynamic_sql.html +++ b/fr_FR/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/fr_FR/gen/gen_tool.html b/fr_FR/gen/gen_tool.html index 819c6ebc695..7cef6d0eb7c 100644 --- a/fr_FR/gen/gen_tool.html +++ b/fr_FR/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

- + diff --git a/fr_FR/gen/index.html b/fr_FR/gen/index.html index d37c8956625..9f1b6b3937b 100644 --- a/fr_FR/gen/index.html +++ b/fr_FR/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/fr_FR/gen/query.html b/fr_FR/gen/query.html index 35949719d98..8ad7400b6ea 100644 --- a/fr_FR/gen/query.html +++ b/fr_FR/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

int/uint/float Fields

// int field
f := field.NewInt("user", "id")
// `user`.`id` = 123
f.Eq(123)
// `user`.`id` DESC
f.Desc()
// `user`.`id` AS `user_id`
f.As("user_id")
// COUNT(`user`.`id`)
f.Count()
// SUM(`user`.`id`)
f.Sum()
// SUM(`user`.`id`) > 123
f.Sum().Gt(123)
// ((`user`.`id`+1)*2)/3
f.Add(1).Mul(2).Div(3),
// `user`.`id` <<< 3
f.LeftShift(3)
-

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `uesr`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")
+

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `user`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")

Time Fields

birth := field.NewString("user", "birth")
// `user`.`birth` = ? (now)
birth.Eq(time.Now())
// DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
birth.Add(time.Duration(time.Hour).Microseconds())
// DATE_FORMAT(`user`.`birth`, "%W %M %Y")
birth.DateFormat("%W %M %Y")

Bool Fields

active := field.NewBool("user", "active")
// `user`.`active` = TRUE
active.Is(true)
// NOT `user`.`active`
active.Not()
// `user`.`active` AND TRUE
active.And(true)

SubQuery

A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

-
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
// 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
+
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

From SubQuery

GORM allows you using subquery in FROM clause with method Table, for example:

u := query.User
p := query.Pet

users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

subQuery1 := u.WithContext(ctx).Select(u.Name)
subQuery2 := p.WithContext(ctx).Select(p.Name)
users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
@@ -312,7 +312,7 @@

- + diff --git a/fr_FR/gen/rawsql_driver.html b/fr_FR/gen/rawsql_driver.html index 64341c8e676..70b49741da2 100644 --- a/fr_FR/gen/rawsql_driver.html +++ b/fr_FR/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/fr_FR/gen/sql_annotation.html b/fr_FR/gen/sql_annotation.html index acd6ed02c2d..020bc9f1911 100644 --- a/fr_FR/gen/sql_annotation.html +++ b/fr_FR/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
// UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
// UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

query.User.Update(User{Age: 0}, 10)
// UPDATE users SET is_adult=0 WHERE id=10

for

The for expression iterates over a slice to generate the SQL, let’s explain by example

-
// 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)
+
// SELECT * FROM @@table
// {{where}}
// {{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}}
// {{end}}
// {{end}}
Filter(users []gen.T) ([]gen.T, error)

Usage:

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","%"))
@@ -254,7 +254,7 @@

- + diff --git a/fr_FR/gen/transaction.html b/fr_FR/gen/transaction.html index ba901e244b9..9e87da85a86 100644 --- a/fr_FR/gen/transaction.html +++ b/fr_FR/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/fr_FR/gen/update.html b/fr_FR/gen/update.html index 4cfb6f23d32..5529cee7f23 100644 --- a/fr_FR/gen/update.html +++ b/fr_FR/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/fr_FR/gorm.html b/fr_FR/gorm.html index e385a618294..01d26fa7733 100644 --- a/fr_FR/gorm.html +++ b/fr_FR/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- +
diff --git a/fr_FR/gormx.html b/fr_FR/gormx.html index 3df983ea319..66b0aaece5e 100644 --- a/fr_FR/gormx.html +++ b/fr_FR/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/fr_FR/hints.html b/fr_FR/hints.html index 6e0df57935a..694bc39cad1 100644 --- a/fr_FR/hints.html +++ b/fr_FR/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/fr_FR/index.html b/fr_FR/index.html index 969f2dabad7..28b4df43c5c 100644 --- a/fr_FR/index.html +++ b/fr_FR/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/fr_FR/rawsql.html b/fr_FR/rawsql.html index 47e4f828c54..bc5895d4def 100644 --- a/fr_FR/rawsql.html +++ b/fr_FR/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/fr_FR/rawsql_driver.html b/fr_FR/rawsql_driver.html index 36a4b2bc623..09318d968d9 100644 --- a/fr_FR/rawsql_driver.html +++ b/fr_FR/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/fr_FR/sharding.html b/fr_FR/sharding.html index b8960a57235..ad1a9b3d8de 100644 --- a/fr_FR/sharding.html +++ b/fr_FR/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/fr_FR/stats.html b/fr_FR/stats.html index 4bfbc154095..1ef4fa9dd27 100644 --- a/fr_FR/stats.html +++ b/fr_FR/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/gen/associations.html b/gen/associations.html index 3ebcfa0ed84..5a20e6f15b8 100644 --- a/gen/associations.html +++ b/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -215,7 +215,7 @@

diff --git a/gen/clause.html b/gen/clause.html index 6529c100c6b..71cda935c22 100644 --- a/gen/clause.html +++ b/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

- + diff --git a/gen/create.html b/gen/create.html index f30477e2573..a9c15b756f0 100644 --- a/gen/create.html +++ b/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

- + diff --git a/gen/dao.html b/gen/dao.html index ea248c6fd22..d3cf757f343 100644 --- a/gen/dao.html +++ b/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

- + diff --git a/gen/database_to_structs.html b/gen/database_to_structs.html index 0b9bd3c976b..7168c235552 100644 --- a/gen/database_to_structs.html +++ b/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

diff --git a/gen/delete.html b/gen/delete.html index f45d501899a..4bafdc62643 100644 --- a/gen/delete.html +++ b/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

- + diff --git a/gen/dynamic_sql.html b/gen/dynamic_sql.html index 580a777a3ec..f5bef6bc706 100644 --- a/gen/dynamic_sql.html +++ b/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/gen/gen_tool.html b/gen/gen_tool.html index 44599bbe7b8..2d5e5374606 100644 --- a/gen/gen_tool.html +++ b/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

- + diff --git a/gen/index.html b/gen/index.html index c56cf475b36..b74cd4c4cb3 100644 --- a/gen/index.html +++ b/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/gen/query.html b/gen/query.html index fc6c80773e2..7db85e4862f 100644 --- a/gen/query.html +++ b/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -312,7 +312,7 @@

- + diff --git a/gen/rawsql_driver.html b/gen/rawsql_driver.html index fd43f638a78..4e57118cae2 100644 --- a/gen/rawsql_driver.html +++ b/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/gen/sql_annotation.html b/gen/sql_annotation.html index 928894c1cb1..437b1575314 100644 --- a/gen/sql_annotation.html +++ b/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -254,7 +254,7 @@

- + diff --git a/gen/transaction.html b/gen/transaction.html index 61272cf5bb8..75da62fea41 100644 --- a/gen/transaction.html +++ b/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/gen/update.html b/gen/update.html index bac24dd8d87..bf8485bb6df 100644 --- a/gen/update.html +++ b/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/hi_IN/404.html b/hi_IN/404.html index c74a537a63b..2eeb44f5238 100644 --- a/hi_IN/404.html +++ b/hi_IN/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/hi_IN/community.html b/hi_IN/community.html index b998198d41d..542808f7655 100644 --- a/hi_IN/community.html +++ b/hi_IN/community.html @@ -32,8 +32,8 @@ - - + + @@ -160,7 +160,7 @@

- + diff --git a/hi_IN/contribute.html b/hi_IN/contribute.html index c14adef5deb..0ead30d7a1f 100644 --- a/hi_IN/contribute.html +++ b/hi_IN/contribute.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/hi_IN/datatypes.html b/hi_IN/datatypes.html index d29c22d85a5..cdd020ef8cc 100644 --- a/hi_IN/datatypes.html +++ b/hi_IN/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/hi_IN/docs/advanced_query.html b/hi_IN/docs/advanced_query.html index 9db26a503dd..03d427d555e 100644 --- a/hi_IN/docs/advanced_query.html +++ b/hi_IN/docs/advanced_query.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,104 +141,107 @@

Advanced Query

-

स्मार्ट सेलेक्ट फील्ड्स

जीओआरएम(GORM) चयन के साथ विशिष्ट क्षेत्रों का चयन ( Select) करने की अनुमति देता है, यदि आप अक्सर इसे अपने आवेदन में उपयोग करते हैं, तो हो सकता है कि आप एपीआई उपयोग के लिए एक छोटी संरचना को परिभाषित करना चाहते हैं जो स्वचालित रूप से विशिष्ट क्षेत्रों का चयन कर सकता है, उदाहरण के लिए |

-
type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// Select `id`, `name` automatically when querying
// / query करते समय `id`, `name` स्वचालित रूप से Select करता
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
+

स्मार्ट सेलेक्ट फील्ड्स

In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

+
type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// GORM will automatically select `id`, `name` fields when querying
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SQL: SELECT `id`, `name` FROM `users` LIMIT 10
-

NOTE QueryFields mode वर्तमान के लिए सभी फ़ील्ड्स के नाम से Select करेगा

+

NOTE In QueryFields mode, all model fields are selected by their names.

-
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

db.Find(&user)
//db वह सभी fild ढूंढता है जो अंदर है(user)
// नीचे की तरह
// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // इस विकल्प के साथ

// Session Mode
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users`
+
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

// Default behavior with QueryFields set to true
db.Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

// Using Session Mode with QueryFields
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
-

Locking (FOR UPDATE) //लॉकिंग (UPDATE के लिए)

उदाहरण के लिए GORM विभिन्न प्रकार के locks का समर्थन करता है

-
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATE

db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`

db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
+

Locking

उदाहरण के लिए GORM विभिन्न प्रकार के locks का समर्थन करता है

+
// Basic FOR UPDATE lock
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE
-

अधिक विवरण के लिए Raw SQL and SQL Builder देखें

-

SubQuery //सबक्वेरी

एक subquery को एक query के भीतर nested किया जा सकता है, param के रूप में *gorm.DB ऑब्जेक्ट का उपयोग करते समय GORM सबक्वेरी उत्पन्न कर सकता है

-
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) चुनें);

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%")
+

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.

+
db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SQL: SELECT * FROM `users` FOR SHARE OF `users`
-

From SubQuery //सबक्वेरी से

GORM आपको Table विधि के साथ FROM खंड में subquery का उपयोग करने की अनुमति देता है, उदाहरण के लिए:

-
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

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
+

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.

+
db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
-

Group Conditions //समूह की शर्तें

समूह शर्तों ( Group Conditions ) के साथ जटिल SQL query लिखना आसान

-
db.Where(
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")
+

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.

+

SubQuery //सबक्वेरी

Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

+
// Simple subquery
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
-

IN with multiple columns //IN कई कॉलम के साथ

एकाधिक कॉलम के साथ IN का चयन करना

-
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"));
+

From SubQuery //सबक्वेरी से

GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

+
// Using subquery in FROM clause
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// 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{})
// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
-

Named Argument // नामांकित तर्क

GORM नामित तर्कों का समर्थन करता है sql.NamedArg या map[ string]इंटरफ़ेस{}{}, उदाहरण के लिए:

-
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

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
+

Group Conditions //समूह की शर्तें

Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

+
// Complex SQL query using Group Conditions
db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
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")
-

अधिक विवरण के लिए रॉ SQL और SQL बिल्डर देखें

-

Find To Map //मानचित्र में खोजें

GORM परिणामों को map[string]interface{} या []map[string]interface{} में स्कैन करने की अनुमति देता है, Model निर्दिष्ट करना न भूलें > या तालिका, उदाहरण के लिए:

-
result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)

var results []map[string]interface{}
db.Table("users").Find(&results)
+

IN with multiple columns //IN कई कॉलम के साथ

GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query.

+
// Using IN with multiple columns
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
-

FirstOrInit

पहले मिलान किए गए रिकॉर्ड प्राप्त करें या दी गई शर्तों के साथ एक नया उदाहरण आरंभ करें (only works with struct or map conditions)

-
// User not found, initialize it with give conditions
db.FirstOrInit(&user, User{Name: "non_existing"})
// user -> User{Name: "non_existing"}

// Found user with `name` = `jinzhu`
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}

// Found user with `name` = `jinzhu`
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}
+

Named Argument // नामांकित तर्क

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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

+
// Example using sql.NamedArg for named arguments
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// 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)
// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
-

यदि रिकॉर्ड नहीं मिला तो अधिक विशेषताओं के साथ संरचना प्रारंभ करें, उन Attrs का उपयोग SQL query बनाने के लिए नहीं किया जाएगा

-
// User not found, initialize it with give conditions and Attrs
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}

// Found user with `name` = `jinzhu`, attributes will be 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}
+

For more examples and details, see Raw SQL and SQL Builder

+

Find To Map //मानचित्र में खोजें

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.

+
// 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`
-

Assign संरचना को विशेषताएँ मिले या न मिले, उन विशेषताओं का उपयोग SQL query बनाने के लिए नहीं किया जाएगा और अंतिम डेटा डेटाबेस में सहेजा नहीं जाएगा

-
// User not found, initialize it with give conditions and Assign attributes
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
// user -> User{Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, update it 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}
+

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.

+
// 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"} if not found

// Retrieving a user named "jinzhu"
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

// Using a map to specify the search condition
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

FirstOrCreate

पहले मिलान किए गए रिकॉर्ड प्राप्त करें या दी गई शर्तों के साथ एक नया बनाएं (only works with struct, map conditions), RowsAffected `` निर्मित/अपडेट किए गए रिकॉर्ड की संख्या लौटाता है

-
// User not found, create a new record with give conditions
-result := db.FirstOrCreate(&user, User{Name: "non_existing"})
-// INSERT INTO "users" (name) VALUES ("non_existing");
-// user -> User{ID: 112, Name: "non_existing"}
-// result.RowsAffected // => 1
+

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.

+
// If no User is found, initialize with given conditions and additional attributes
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20} if not found

// If a User named "Jinzhu" is found, `Attrs` are ignored
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-// Found user with `name` = `jinzhu` -result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user) -// user -> User{ID: 111, Name: "jinzhu", "Age": 18} -// result.RowsAffected // => 0 -``
+

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.

+
// 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} if not found

// If a User named "Jinzhu" is found, update the struct with Assign attributes
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
-

यदि रिकॉर्ड नहीं मिला तो अधिक विशेषताओं के साथ संरचना बनाएं, उन Attrs का उपयोग SQL query बनाने के लिए नहीं किया जाएगा

-
// User not found, create it with give conditions and Attrs
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, attributes will be ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
+

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

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.

+
// Create a new record if not found
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// SQL: INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1 (record created)

// 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)
-

रिकॉर्ड के लिए विशेषताएँ Assign चाहे वह मिले या नहीं और उन्हें डेटाबेस में वापस सहेजें (save)।

-
// User not found, initialize it with give conditions and Assign attributes
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `jinzhu`, update it 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}
+

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.

+
// Create a new record with additional attributes if not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// 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}

// If the user is found, `Attrs` are ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
-

Optimizer/Index Hints

Optimizer संकेत query Optimizer को एक certain query execution योजना चुनने के लिए नियंत्रित करने की अनुमति देते हैं, GORM इसे gorm.io/hints के साथ समर्थन करता है, उदा:

-
import "gorm.io/hints"

db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
+

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.

+
// Initialize and save new record with `Assign` attributes if not found
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// 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}

// Update found record with `Assign` attributes
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// SQL: UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20}
-

Query planner के भ्रमित होने की स्थिति में इंडेक्स संकेत डेटाबेस को इंडेक्स संकेत पास करने की अनुमति देते हैं।

-
import "gorm.io/hints"

db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)

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`)"
+

Optimizer/Index 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.

+
import "gorm.io/hints"

// Using an optimizer hint to set a maximum execution time
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
-

अधिक विवरण के लिए Optimizer Hints/Index/Comment देखें

-

Iteration

GORM Rows के माध्यम से पुनरावृति(iterating) का समर्थन करता है

-
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
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
db.ScanRows(rows, &user)

// do something
}
+

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.

+
import "gorm.io/hints"

// Suggesting the use of a specific index
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// 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{})
// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
+

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 in the GORM documentation.

+

Iteration

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.

+
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()

for rows.Next() {
var user User
// 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.

+
// 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 provides the count of records in the current batch
// The variable 'batch' indicates the current batch number

// Returning an error will stop further batch processing
return nil
})

// 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 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 in the GORM documentation.

+
func (u *User) AfterFind(tx *gorm.DB) (err error) {
// Custom logic after finding a user
if u.Role == "" {
u.Role = "user" // Set default role if not specified
}
return
}

// Usage of AfterFind hook happens automatically when a User is queried
-

FindInBatches

batch में Query और process रिकॉर्ड

-
// batch size 100
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// बैच प्रोसेसिंग में रिकॉर्ड मिले
}

tx.Save(&results)

tx.RowsAffected // इस batch में रिकॉर्ड की संख्या

batch // Batch 1, 2, 3

// रिटर्न err भविष्य के बैचों को रोक देगा
return nil
})

result.Error // returned error
result.RowsAffected // संसाधित रिकॉर्ड सभी बैचों(batch ) में गिने जाते हैं
+

Pluck

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 or Find instead.

+
// Retrieving ages of all users
var ages []int64
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)

// Using Distinct with Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SQL: SELECT DISTINCT `name` FROM `users`

// Querying multiple columns
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+

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.

+
// 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")
}

// 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.

+
// 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)
+

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 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.

+
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'

// Counting records in a different table
db.Table("deleted_users").Count(&count)
// SQL: SELECT count(1) FROM deleted_users
-

Query Hooks

GORM एक प्रश्न के लिए हुकAfterFind की अनुमति देता है, रिकॉर्ड की क्वेरी करते समय इसे कॉल किया जाएगा, विवरण के लिए हुक देखें

-
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.Role == "" {
u.Role = "user"
}
return
}
- - - - -

Pluck

डेटाबेस से एकल कॉलम को क्वेरी करें और एक स्लाइस में स्कैन करें, यदि आप कई कॉलमों को query करना चाहते हैं, तो Scanके साथ Select का उपयोग करें। इसके बजाय

-
var ages []int64
db.Model(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

// Distinct Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SELECT DISTINCT `name` FROM `users`

// Requesting more than one column, use `Scan` or `Find` like this:
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
- - - - -

Scopes

Scopes आपको आमतौर पर उपयोग की जाने वाली queries निर्दिष्ट करने की अनुमति देता है जिसे विधि कॉल(method calls) के रूप में संदर्भित(referenced) किया जा सकता है

-
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}

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 {
return db.Where("status IN (?)", status)
}
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// Find all credit card orders and amount greater than 1000
// सभी क्रेडिट कार्ड ऑर्डर और 1000 से अधिक राशि का पता लगाएं

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// Find all COD orders and amount greater than 1000

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// Find all paid, shipped orders that amount greater than 1000
- - -

विवरण के लिए Scopes चेकआउट करें

-

Count

मिलान किए गए रिकॉर्ड की संख्या प्राप्त करें

-
db.Table("deleted_users").Count(&count)
// SELECT count(1) FROM deleted_users;

// Count with Distinct
db.Model(&User{}).Distinct("name").Count(&count)
// SELECT COUNT(DISTINCT(`name`)) FROM `users`

db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SELECT count(distinct(name)) FROM deleted_users

// Count with Group
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
count // => 3
-
+

Count with Distinct and Group

GORM also allows counting distinct values and grouping results.

+
// Counting distinct names
db.Model(&User{}).Distinct("name").Count(&count)
// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

// Counting distinct values with a custom select
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SQL: SELECT count(distinct(name)) FROM deleted_users

// Counting grouped records
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
// Count after grouping by name
// count => 3
+
@@ -250,7 +253,7 @@

- + @@ -364,7 +367,7 @@

Gold Sponsors

Contents -
  1. स्मार्ट सेलेक्ट फील्ड्स
  2. Locking (FOR UPDATE) //लॉकिंग (UPDATE के लिए)
  3. SubQuery //सबक्वेरी
    1. From SubQuery //सबक्वेरी से
  4. Group Conditions //समूह की शर्तें
  5. IN with multiple columns //IN कई कॉलम के साथ
  6. Named Argument // नामांकित तर्क
  7. Find To Map //मानचित्र में खोजें
  8. FirstOrInit
  9. FirstOrCreate
  10. Optimizer/Index Hints
  11. Iteration
  12. FindInBatches
  13. Query Hooks
  14. Pluck
  15. Scopes
  16. Count
+
  1. स्मार्ट सेलेक्ट फील्ड्स
  2. Locking
  3. SubQuery //सबक्वेरी
    1. From SubQuery //सबक्वेरी से
  4. Group Conditions //समूह की शर्तें
  5. IN with multiple columns //IN कई कॉलम के साथ
  6. Named Argument // नामांकित तर्क
  7. Find To Map //मानचित्र में खोजें
  8. FirstOrInit
    1. Using Attrs for Initialization
    2. Using Assign for Attributes
  9. FirstOrCreate
    1. Using Attrs with FirstOrCreate
    2. Using Assign with FirstOrCreate
  10. Optimizer/Index Hints
    1. Index Hints
  11. Iteration
  12. FindInBatches
  13. Query Hooks
  14. Pluck
  15. Scopes
    1. Defining Scopes
    2. Applying Scopes in Queries
  16. Count
    1. Getting the Count of Matched Records
    2. Count with Distinct and Group
diff --git a/hi_IN/docs/associations.html b/hi_IN/docs/associations.html index d26d2ea18c8..4635e0bbe3b 100644 --- a/hi_IN/docs/associations.html +++ b/hi_IN/docs/associations.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,62 +117,101 @@

Associations //संघों

-

Auto Create/Update //ऑटो बनाएं/अपडेट करें

रिकॉर्ड बनाते/अपडेट(creating/updating) करते समय GORM Upsert का उपयोग करके एसोसिएशन(associations) और उसके संदर्भ को अपने आप सहेज लेगा।

-
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"},
},
}

db.Create(&user)
// BEGIN TRANSACTION;
// 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 ('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 मोड का इस्तेमाल करना चाहिए:

-
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);
// ...
- -

Skip Auto Create/Update // ऑटो क्रिएट/अपडेट छोड़ें

बनाते/अपडेट(creating/updating) करते समय ऑटो सेव को छोड़ने के लिए, आप Select या Omit का उपयोग कर सकते हैं, उदाहरण के लिए:

-
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"},
},
}

db.Select("Name").Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

db.Omit("BillingAddress").Create(&user)
// Skip create BillingAddress when creating a user
// उपयोगकर्ता बनाते समय बिलिंग एड्रेस बनाना छोड़ें

db.Omit(clause.Associations).Create(&user)
// Skip all associations when creating a user
// उपयोगकर्ता बनाते समय सभी संघों को छोड़ दें
- -

**नोट:**कई से कई associations के लिए, GORM सम्मिलित तालिका संदर्भ बनाने से पहले associations को अपसेट करेगा, यदि आप associations के अप्सर्टिंग को छोड़ना चाहते हैं, तो आप इसे इस तरह छोड़ सकते हैं:

-
db.Omit("Languages.*").Create(&user)
- -

निम्नलिखित कोड एसोसिएशन (association) और उसके संदर्भों(references) के निर्माण को छोड़ देगा

-
db.Omit("Languages").Create(&user)
- -

Select/Omit Association fields // एसोसिएशन फ़ील्ड का चयन करें/छोड़ दें

user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
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 फ़ील्ड का उपयोग करें और अन्य को छोड़ दें
db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
- -

Association Mode

Association मोड में रिश्तों को संभालने के लिए आमतौर पर इस्तेमाल की जाने वाली कुछ सहायक विधियाँ(helper methods) होती हैं

-
// 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
- -

Find Associations

(Find matched associations) मिलान किए गए संघों को खोजें

-
db.Model(&user).Association("Languages").Find(&languages)
- -

Find associations 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 //संघों को जोड़ें

अनेक से अनेक(many to many) के लिए नए संबंध जोड़ें, has many<code>, has one के लिए वर्तमान संबंध बदलें, belongs to

-
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
- -

Replace Associations // संघों को बदलें

मौजूदा associations को नए के साथ बदलें

-
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
- -

Delete Associations // संघों को हटाएं

स्रोत(source) और तर्क (मौजूद हैं, तो केवल reference हटाएं, उन objects को DB से नहीं हटाएंगे।

-
db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
- -

Clear Associations // स्पष्ट संघ

Source और association के बीच सभी reference निकालें, उन associations को नहीं हटाएंगे

-
db.Model(&user).Association("Languages").Clear()
- -

Count Associations //संघों की गणना करें

वर्तमान associations की गिनती लौटाएँ

-
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 // बैच डेटा

एसोसिएशन मोड बैच डेटा का समर्थन करता है, जैसे:

-
// Find all roles for all users //सभी उपयोगकर्ताओं के लिए सभी भूमिकाएँ खोजें
db.Model(&users).Association("Role").Find(&roles)

// Delete User A from all user's team
//उपयोगकर्ता ए को सभी उपयोगकर्ता की टीम से हटाएं
db.Model(&users).Association("Team").Delete(&userA)

// Get distinct count of all users' teams
// सभी उपयोगकर्ताओं की टीमों की अलग-अलग गिनती प्राप्त करें
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
// बैच डेटा के साथ `संलग्न`, `बदलें` के लिए, तर्कों की लंबाई डेटा की लंबाई के बराबर होनी चाहिए अन्यथा यह एक त्रुटि लौटाएगा
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 की टीम में जोड़ें
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 पर रीसेट करें
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).

-

How to delete is decided by gorm.DB.

-
// Soft delete
// UPDATE `languages` SET `deleted_at`= ...
db.Model(&user).Association("Languages").Unscoped().Clear()

// Delete permanently
// DELETE FROM `languages` WHERE ...
db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
- -

Delete with Select

You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

-
// 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)
- -

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

-
// 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
- -

Association Tags

+

Auto Create/Update //ऑटो बनाएं/अपडेट करें

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.

+
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"},
},
}

// 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;
// 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 ('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)
+ +

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.

+
// Update a user and fully update all its associations
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
// 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 // ऑटो क्रिएट/अपडेट छोड़ें

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.

+
user := User{
// User and associated data
}

// Only include the 'Name' field when creating the user
db.Select("Name").Create(&user)
// 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.

+
// Skip creating the 'BillingAddress' when creating the user
db.Omit("BillingAddress").Create(&user)

// Skip all associations when creating the user
db.Omit(clause.Associations).Create(&user)
+ +

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 .*:

+
// Skip upserting 'Languages' associations
db.Omit("Languages.*").Create(&user)
+ +

To skip creating both the association and its references:

+
// Skip creating 'Languages' associations and their references
db.Omit("Languages").Create(&user)
+ +

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.

+
user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
}

// 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.

+
// 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: 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.

+
// 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
+ +

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.

+
var user User
db.Model(&user).Association("Languages")
// Check for errors
error := db.Model(&user).Association("Languages").Error
+ +

Finding Associations

Retrieve associated records with or without additional conditions.

+
// Simple find
db.Model(&user).Association("Languages").Find(&languages)

// Find with conditions
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).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.

+
// Append new languages
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

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.

+
// 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.

+
// 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.

+
// Clear all languages
db.Model(&user).Association("Languages").Clear()
+ +

Counting Associations

Get the count of current associations, with or without conditions.

+
// 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.
  • +
+
db.Model(&users).Association("Role").Find(&roles)
+ +
    +
  • Deleting Associations: Remove specific associations across multiple records.
  • +
+
db.Model(&users).Association("Team").Delete(&userA)
+ +
    +
  • Counting Associations: Get the count of associations for a batch of records.
  • +
+
db.Model(&users).Association("Team").Count()
+ +
    +
  • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
  • +
+
var users = []User{user1, user2, 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})

// 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

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.

+
    +
  • 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.
  • +
+

Modifying Deletion Behavior with Unscoped

For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

+
    +
  • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
  • +
+
db.Model(&user).Association("Languages").Unscoped().Clear()
+ +
    +
  • Permanent Delete: Physically deletes the association records from the database.
  • +
+
// db.Unscoped().Model(&user)
db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
+ +

Association Tags

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.

+ @@ -180,36 +219,40 @@

- - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
टैग
foreignKeyवर्तमान मॉडल का column name Specifies करता है जिसका उपयोग तालिका में शामिल होने के लिए foreign key के रूप में किया जाता हैforeignKeySpecifies the column name of the current model used as a foreign key in the join table.
referencesIndicates the column name in the reference table that the foreign key of the join table maps to.
referencesReference की table के column नाम को Specifies करता है जिसे सम्मिलित table की foreign key से मैप किया जाता है`polymorphic //
polymorphic // बहुरूपीमॉडल नाम जैसे बहुरूपी (polymorphic type)प्रकार निर्दिष्ट(Specifies) करता हैबहुरूपी`Defines the polymorphic type, typically the model name.
polymorphicValue(polymorphic value) बहुरूपी मान, डिफ़ॉल्ट तालिका(table) नाम निर्दिष्ट(Specifies) करता हैpolymorphicValueSets the polymorphic value, usually the table name, if not specified otherwise.
many2manyज्वाइन टेबल नाम निर्दिष्ट(Specifies) करता हैmany2manyNames the join table used in a many-to-many relationship.
joinForeignKeyज्वाइन टेबल का foreign key कॉलम नाम निर्दिष्ट करता है जो वर्तमान टेबल में मैप करता हैjoinForeignKeyIdentifies the foreign key column in the join table that maps back to the current model’s table.
joinReferencesज्वाइन टेबल का foreign key कॉलम नाम निर्दिष्ट करता है जो reference की table में मैप करता हैjoinReferencesPoints to the foreign key column in the join table that links to the reference model’s table.
constraintसंबंध बाधा, उदा: OnUpdate,OnDeleteconstraintSpecifies relational constraints like OnUpdate, OnDelete for the association.
@@ -224,7 +267,7 @@

@@ -338,7 +381,7 @@

Gold Sponsors

Contents -
  1. Auto Create/Update //ऑटो बनाएं/अपडेट करें
  2. Skip Auto Create/Update // ऑटो क्रिएट/अपडेट छोड़ें
  3. Select/Omit Association fields // एसोसिएशन फ़ील्ड का चयन करें/छोड़ दें
  4. Association Mode
    1. Find Associations
    2. Append Associations //संघों को जोड़ें
    3. Replace Associations // संघों को बदलें
    4. Delete Associations // संघों को हटाएं
    5. Clear Associations // स्पष्ट संघ
    6. Count Associations //संघों की गणना करें
    7. Batch Data // बैच डेटा
  5. Delete Association Record
  6. Delete with Select
  7. Association Tags
+
  1. Auto Create/Update //ऑटो बनाएं/अपडेट करें
    1. Auto-Saving Associations on Create
    2. Updating Associations with FullSaveAssociations
  2. Skip Auto Create/Update // ऑटो क्रिएट/अपडेट छोड़ें
    1. Using Select to Include Specific Fields
    2. Using Omit to Exclude Fields or Associations
  3. Select/Omit Association fields // एसोसिएशन फ़ील्ड का चयन करें/छोड़ दें
  4. Delete Associations // संघों को हटाएं
  5. Association Mode
    1. Finding Associations
    2. Appending Associations
    3. Replacing Associations
    4. Deleting Associations
    5. Clearing Associations
    6. Counting Associations
    7. Batch Data Handling
  6. Delete Association Record
    1. Modifying Deletion Behavior with Unscoped
  7. Association Tags
diff --git a/hi_IN/docs/belongs_to.html b/hi_IN/docs/belongs_to.html index 6d07c802aa3..a6db6920265 100644 --- a/hi_IN/docs/belongs_to.html +++ b/hi_IN/docs/belongs_to.html @@ -34,8 +34,8 @@ - - + + @@ -156,7 +156,7 @@

- + diff --git a/hi_IN/docs/changelog.html b/hi_IN/docs/changelog.html index f8ea2f8afab..d4bc53b5018 100644 --- a/hi_IN/docs/changelog.html +++ b/hi_IN/docs/changelog.html @@ -32,8 +32,8 @@ - - + + @@ -158,7 +158,7 @@

- + diff --git a/hi_IN/docs/composite_primary_key.html b/hi_IN/docs/composite_primary_key.html index 05cf58d558e..2d330e07b42 100644 --- a/hi_IN/docs/composite_primary_key.html +++ b/hi_IN/docs/composite_primary_key.html @@ -32,8 +32,8 @@ - - + + @@ -138,7 +138,7 @@

समग्र प्राथमि
- +
diff --git a/hi_IN/docs/connecting_to_the_database.html b/hi_IN/docs/connecting_to_the_database.html index 87458d7a5c7..18408d1edd6 100644 --- a/hi_IN/docs/connecting_to_the_database.html +++ b/hi_IN/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -181,7 +181,7 @@

- + diff --git a/hi_IN/docs/constraints.html b/hi_IN/docs/constraints.html index 2e2862e1da9..8c136105e1f 100644 --- a/hi_IN/docs/constraints.html +++ b/hi_IN/docs/constraints.html @@ -32,8 +32,8 @@ - - + + @@ -139,7 +139,7 @@

- + diff --git a/hi_IN/docs/context.html b/hi_IN/docs/context.html index 84fc51a762e..55c6fd11f45 100644 --- a/hi_IN/docs/context.html +++ b/hi_IN/docs/context.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,58 +117,26 @@

Context

-

GORM कॉन्टेक्स्ट सपोर्ट प्रदान करता है, आप इसे WithContext method के साथ उपयोग कर सकते हैं

-

Single Session Mode

सिंगल सेशन मोड आमतौर पर तब उपयोग किया जाता है जब आप एक ही ऑपरेशन करना चाहते हैं

+

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 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.

db.WithContext(ctx).Find(&users)
-

Continuous session mode

Continuous session मोड का उपयोग आमतौर पर तब किया जाता है जब आप operations का एक समूह perform करना चाहते हैं, उदाहरण के लिए:

+

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.

tx := db.WithContext(ctx)
tx.First(&user, 1)
tx.Model(&user).Update("role", "admin")
-

Context timeout

लंबे समय तक चलने वाले प्रश्नों(queries) के लिए टाइमआउट सेट करने के लिए आप db.WithContext के टाइमआउट के साथ संदर्भ(context) में पास कर सकते हैं, उदाहरण के लिए:

+

Context Timeout

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.

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

db.WithContext(ctx).Find(&users)
-

Context in Hooks/Callbacks

आप Context ऑब्जेक्ट को वर्तमान स्टेटमेंट से एक्सेस कर सकते हैं, उदाहरण के लिए:

-
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ...
return
}
+

Context in Hooks/Callbacks

The context can also be accessed within GORM’s hooks/callbacks. This enables contextual information to be used during these lifecycle events.

+
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ... use context
return
}
-

Chi Middleware Example

Continuous session मोड जो API अनुरोधों(requests) को संभालने में सहायक हो सकता है, उदाहरण के लिए, आप मिडलवेयर में टाइमआउट संदर्भ के साथ *gorm.DB सेटअप कर सकते हैं और फिर `*gorm.DB</code का उपयोग कर सकते हैं। > सभी अनुरोधों को संसाधित करते समय

-

Following is a Chi middleware example:

+

Integration with 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.

+
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

// Router setup
r := chi.NewRouter()
r.Use(SetDBMiddleware)

// Route handlers
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})

r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})
-
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))
-    next.ServeHTTP(w, r.WithContext(ctx))
-  })
-}
-
-r := chi.NewRouter()
-r.Use(SetDBMiddleware)
-
-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
-})
-
-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
-})
-`
- -{% note %} -**ध्यान दें** `Context` को `WithContext` के साथ सेट करना गोरूटीन-सुरक्षित है, [Session देखें ](session.html) विवरण के लिए -{% endnote %} - -

Logger

Logger Context भी स्वीकार करता है, आप इसे लॉग ट्रैकिंग के लिए उपयोग कर सकते हैं, विवरण के लिए Logger देखें

-
+

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 in GORM.

+

Logger Integration

GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

+

Refer to Logger documentation for more details.

+
@@ -180,7 +148,7 @@

- + @@ -294,7 +262,7 @@

Gold Sponsors

Contents -
  1. Single Session Mode
  2. Continuous session mode
  3. Context timeout
  4. Context in Hooks/Callbacks
  5. Chi Middleware Example
  6. Logger
+
  1. Single Session Mode
  2. Continuous Session Mode
  3. Context Timeout
  4. Context in Hooks/Callbacks
  5. Integration with Chi Middleware
  6. Logger Integration
diff --git a/hi_IN/docs/conventions.html b/hi_IN/docs/conventions.html index ce315da0d52..accd1beaac8 100644 --- a/hi_IN/docs/conventions.html +++ b/hi_IN/docs/conventions.html @@ -32,8 +32,8 @@ - - + + @@ -150,7 +150,7 @@

// Create table `deleted_users` with struct User's fields
db.Table("deleted_users").AutoMigrate(&User{})

// Query data from another table
var deletedUsers []User
db.Table("deleted_users").Find(&deletedUsers)
// SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
// DELETE FROM deleted_users WHERE name = 'jinzhu';

FROM clause में SubQuery का उपयोग कैसे करें, इसके लिए SubQuery से देखें

-

NamingStrategy

GORM users को डिफॉल्ट NamingStrategy को ओवरराइड करके डिफॉल्ट नेमिंग कन्वेंशन को बदलने की अनुमति देता है, जिसका उपयोग TableName, ColumnName, JoinTableName बनाने के लिए किया जाता है। code>, RelationshipFKName, CheckerName, IndexName, GORM Config देखें जानकारी के लिए

+

NamingStrategy

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 for details

Column Name

कॉलम db name convention द्वारा फ़ील्ड के नाम snake_case का उपयोग करता है।

type User struct {
ID uint // column name is `id`
Name string // column name is `name`
Birthday time.Time // column name is `birthday`
CreatedAt time.Time // column name is `created_at`
}
@@ -183,7 +183,7 @@

- + diff --git a/hi_IN/docs/create.html b/hi_IN/docs/create.html index b59f5802f3d..e3eae99781f 100644 --- a/hi_IN/docs/create.html +++ b/hi_IN/docs/create.html @@ -32,8 +32,8 @@ - - + + @@ -131,7 +131,7 @@

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
-

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.

+

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 split into multiple batches.

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

You can specify batch size when creating with CreateInBatches, e.g:

@@ -196,7 +196,7 @@

- + diff --git a/hi_IN/docs/data_types.html b/hi_IN/docs/data_types.html index e57193343c7..2b991f0c0b4 100644 --- a/hi_IN/docs/data_types.html +++ b/hi_IN/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

- + diff --git a/hi_IN/docs/dbresolver.html b/hi_IN/docs/dbresolver.html index 9a8c7b477ba..10744b634d4 100644 --- a/hi_IN/docs/dbresolver.html +++ b/hi_IN/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

Transaction

When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

But you can specifies which DB to use before starting a transaction, for example:

-
// Start transaction based on default replicas db
tx := DB.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := DB.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
+
// Start transaction based on default replicas db
tx := db.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := db.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

Load Balancing

GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

type Policy interface {
Resolve([]gorm.ConnPool) gorm.ConnPool
}
@@ -183,7 +183,7 @@

diff --git a/hi_IN/docs/delete.html b/hi_IN/docs/delete.html index 6f499dd16ac..f2b3b614172 100644 --- a/hi_IN/docs/delete.html +++ b/hi_IN/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

- + diff --git a/hi_IN/docs/error_handling.html b/hi_IN/docs/error_handling.html index 182871bb305..28d32f60f5d 100644 --- a/hi_IN/docs/error_handling.html +++ b/hi_IN/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

Error Handling

-

In Go, error handling is important.

-

You are encouraged to do error check after any Finisher Methods

-

Error Handling

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:

-
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// error handling...
}
- -

Or

-
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// error handling...
}
- -

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:

-
// Check if returns RecordNotFound error
err := db.First(&user, 100).Error
errors.Is(err, gorm.ErrRecordNotFound)
-

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.

+

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.

+

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.

+

After a chain of methods, it’s crucial to check the Error field:

+
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// Handle error...
}
+ +

Or alternatively:

+
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// Handle error...
}
+ +

ErrRecordNotFound

GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

+
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

+
    +
  • Example: Handling MySQL Error Codes
  • +
+
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.

db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
-

Errors

Errors List

+
    +
  • ErrDuplicatedKey
  • +
+

This error occurs when an insert operation violates a unique constraint:

+
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:

+
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

For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

@@ -168,7 +187,7 @@

- + @@ -282,7 +301,7 @@

Gold Sponsors

Contents -
  1. Error Handling
  2. ErrRecordNotFound
  3. Dialect Translated Errors
  4. Errors
+
  1. Basic Error Handling
  2. ErrRecordNotFound
  3. Handling Error Codes
  4. Dialect Translated Errors
  5. Errors
diff --git a/hi_IN/docs/generic_interface.html b/hi_IN/docs/generic_interface.html index 9f4c6ca9a33..641e8d8463a 100644 --- a/hi_IN/docs/generic_interface.html +++ b/hi_IN/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

diff --git a/hi_IN/docs/gorm_config.html b/hi_IN/docs/gorm_config.html index fd32de7c298..2e9b1705091 100644 --- a/hi_IN/docs/gorm_config.html +++ b/hi_IN/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

diff --git a/hi_IN/docs/has_many.html b/hi_IN/docs/has_many.html index 4523287ab9f..1474c6d0517 100644 --- a/hi_IN/docs/has_many.html +++ b/hi_IN/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/hi_IN/docs/has_one.html b/hi_IN/docs/has_one.html index e28509b03a5..3235d328cd8 100644 --- a/hi_IN/docs/has_one.html +++ b/hi_IN/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/hi_IN/docs/hints.html b/hi_IN/docs/hints.html index efc3c9051d8..fc2bc09cbb0 100644 --- a/hi_IN/docs/hints.html +++ b/hi_IN/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

- + diff --git a/hi_IN/docs/hooks.html b/hi_IN/docs/hooks.html index 4729d700934..3b515b936f0 100644 --- a/hi_IN/docs/hooks.html +++ b/hi_IN/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

- + diff --git a/hi_IN/docs/index.html b/hi_IN/docs/index.html index 09e65215b4f..90309cd8ce1 100644 --- a/hi_IN/docs/index.html +++ b/hi_IN/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/hi_IN/docs/indexes.html b/hi_IN/docs/indexes.html index df0d43ddd9d..c6a4144246e 100644 --- a/hi_IN/docs/indexes.html +++ b/hi_IN/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

diff --git a/hi_IN/docs/logger.html b/hi_IN/docs/logger.html index 78b1b8e444d..ec58511b4e4 100644 --- a/hi_IN/docs/logger.html +++ b/hi_IN/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

diff --git a/hi_IN/docs/many_to_many.html b/hi_IN/docs/many_to_many.html index ea4c22618ec..3f61207fdc0 100644 --- a/hi_IN/docs/many_to_many.html +++ b/hi_IN/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

-
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addressses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
+
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

FOREIGN KEY Constraints

You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_speaks;"`
}

type Language struct {
Code string `gorm:"primarykey"`
Name string
}

// CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
@@ -191,7 +191,7 @@

- + diff --git a/hi_IN/docs/method_chaining.html b/hi_IN/docs/method_chaining.html index bc8e56c28de..c0f1f612ba8 100644 --- a/hi_IN/docs/method_chaining.html +++ b/hi_IN/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

Method Chaining

-

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:

db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
-

There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

-

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:

-
queryDB := DB.Where("name = ?", "jinzhu")

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// 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:

-
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 20
- -

Chain Method

Chain methods are methods to modify or add Clauses to current Statement, like:

-

Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

-

Here is the full lists, also check out the SQL Builder 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 here.

-

New Session Method

GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new 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:
// 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:
// 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:
// SELECT * FROM users;
- -

(Bad) Example 2:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is safe to 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

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
- -

Example 3:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is 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

// good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// good case
tx.Where("age = ?", 28).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
+

Method Categories

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. Also, the SQL Builder 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.

+

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 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

queryDB := DB.Where("name = ?", "jinzhu")

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query with unintended compounded condition
queryDB.Where("age > ?", 20).First(&user2)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
+ +

Example of Safe Reuse

To safely reuse a *gorm.DB instance, use a New Session Method:

+
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query, safely isolated
queryDB.Where("age > ?", 20).First(&user2)
// 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.

+

Examples for Clarity

Let’s clarify with a few examples:

+
    +
  • Example 1: Safe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
// 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)
// 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)
// Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
// SELECT * FROM users;
+ +

In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

+
    +
  • (Bad) Example 2: Unsafe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized *gorm.DB, safe for initial reuse.

tx := db.Where("name = ?", "jinzhu")
// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

// Good case
tx.Where("age = ?", 18).Find(&users)
// Reuses 'tx' correctly for a single logical operation, executing:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// Bad case
tx.Where("age = ?", 28).Find(&users)
// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
+ +

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
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

// Good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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.

@@ -180,7 +210,7 @@

- + @@ -294,7 +324,7 @@

Gold Sponsors

Contents -
  1. Chain Method
  2. Finisher Method
  3. New Session Method
+
  1. Method Categories
    1. Chain Methods
    2. Finisher Methods
    3. New Session Methods
  2. Reusability and Safety
    1. Example of Unsafe Reuse
    2. Example of Safe Reuse
  3. Examples for Clarity
diff --git a/hi_IN/docs/migration.html b/hi_IN/docs/migration.html index 5ccf802e812..3ecc4763311 100644 --- a/hi_IN/docs/migration.html +++ b/hi_IN/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

- + diff --git a/hi_IN/docs/models.html b/hi_IN/docs/models.html index 4e9c65dd229..51bfed97282 100644 --- a/hi_IN/docs/models.html +++ b/hi_IN/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,16 +141,45 @@

Declaring Models

-

Declaring Models

Models are normal structs with basic Go types, pointers/alias of them or custom types implementing Scanner and Valuer interfaces

-

For Example:

-
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
}
- -

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

-

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

-

gorm.Model

GORM defined a gorm.Model struct, which includes fields ID, CreatedAt, UpdatedAt, DeletedAt

+

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 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 and Valuer interfaces from the database/sql package

+

Consider the following example of a User model:

+
type User struct {
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 for a detailed explanation

+

Conventions

    +
  1. Primary Key: GORM uses a field named ID as the default primary key for each model.

    +
  2. +
  3. 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.

    +
  4. +
  5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

    +
  6. +
  7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

    +
  8. +
+

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.

+

gorm.Model

GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

// gorm.Model definition
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
-

You can embed it into your struct to include those fields, refer 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

    +
  • +
  • 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

Field-Level Permission

Exported fields have all permissions when doing CRUD with GORM, and GORM allows you to change the field-level permission with tag, so you can make a field to be read-only, write-only, create-only, update-only or ignored

NOTE ignored fields won’t be created when using GORM Migrator to create table

@@ -286,7 +315,7 @@

@@ -400,7 +429,7 @@

Gold Sponsors

Contents -
  1. Declaring Models
  2. Conventions
  3. gorm.Model
  4. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
+
  1. Declaring Models
    1. Conventions
    2. gorm.Model
  2. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
diff --git a/hi_IN/docs/performance.html b/hi_IN/docs/performance.html index 3ec127f07c4..070bdb9c4ec 100644 --- a/hi_IN/docs/performance.html +++ b/hi_IN/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

- + diff --git a/hi_IN/docs/preload.html b/hi_IN/docs/preload.html index f5ad04965ed..7ac3b17fd32 100644 --- a/hi_IN/docs/preload.html +++ b/hi_IN/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

- + diff --git a/hi_IN/docs/prometheus.html b/hi_IN/docs/prometheus.html index f9d61146a42..f537a91467d 100644 --- a/hi_IN/docs/prometheus.html +++ b/hi_IN/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- + diff --git a/hi_IN/docs/query.html b/hi_IN/docs/query.html index de637da5248..2fd7f0faa4f 100644 --- a/hi_IN/docs/query.html +++ b/hi_IN/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

- + diff --git a/hi_IN/docs/scopes.html b/hi_IN/docs/scopes.html index 2299facdd1d..2adf3a1e4c2 100644 --- a/hi_IN/docs/scopes.html +++ b/hi_IN/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/hi_IN/docs/security.html b/hi_IN/docs/security.html index a3524aa4845..9e07e55c5e2 100644 --- a/hi_IN/docs/security.html +++ b/hi_IN/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

Inline Condition

// will be escaped
db.First(&user, "name = ?", userInput)

// SQL injection
db.First(&user, fmt.Sprintf("name = %v", userInput))

When retrieving objects with number primary key by user’s input, you should check the type of variable.

-
userInputID := "1=1;drop table users;"
// safe, return error
id,err := strconv.Atoi(userInputID)
if err != nil {
return error
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;
+
userInputID := "1=1;drop table users;"
// safe, return error
id, err := strconv.Atoi(userInputID)
if err != nil {
return err
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;

SQL injection Methods

To support some features, some inputs are not escaped, be careful when using user’s input with those methods

db.Select("name; drop table users;").First(&user)
db.Distinct("name; drop table users;").First(&user)

db.Model(&user).Pluck("name; drop table users;", &names)

db.Group("name; drop table users;").First(&user)

db.Group("name").Having("1 = 1;drop table users;").First(&user)

db.Raw("select name from users; drop table users;").First(&user)

db.Exec("select name from users; drop table users;")

db.Order("name; drop table users;").First(&user)
@@ -169,7 +169,7 @@

- + diff --git a/hi_IN/docs/serializer.html b/hi_IN/docs/serializer.html index fe4b2edde40..ef3ad5b679f 100644 --- a/hi_IN/docs/serializer.html +++ b/hi_IN/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

- + diff --git a/hi_IN/docs/session.html b/hi_IN/docs/session.html index db56a559f54..b2698b800b7 100644 --- a/hi_IN/docs/session.html +++ b/hi_IN/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

diff --git a/hi_IN/docs/settings.html b/hi_IN/docs/settings.html index 0b29555045b..5143a25d968 100644 --- a/hi_IN/docs/settings.html +++ b/hi_IN/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/hi_IN/docs/sharding.html b/hi_IN/docs/sharding.html index f94d0f72d32..12c3f2f7136 100644 --- a/hi_IN/docs/sharding.html +++ b/hi_IN/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

- + diff --git a/hi_IN/docs/sql_builder.html b/hi_IN/docs/sql_builder.html index c97b576a0b7..f50c66679f4 100644 --- a/hi_IN/docs/sql_builder.html +++ b/hi_IN/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

diff --git a/hi_IN/docs/transactions.html b/hi_IN/docs/transactions.html index b63ac731463..893f604e104 100644 --- a/hi_IN/docs/transactions.html +++ b/hi_IN/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

db.Transaction(func(tx *gorm.DB) error {
// do some database operations in the transaction (use 'tx' from this point, not 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// return any error will rollback
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// return nil will commit the whole transaction
return nil
})

Nested Transactions

GORM supports nested transactions, you can rollback a subset of operations performed within the scope of a larger transaction, for example:

-
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3
+
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx3 *gorm.DB) error {
tx3.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3

Control the transaction manually

Gorm supports calling transaction control functions (commit / rollback) directly, for example:

// begin a transaction
tx := db.Begin()

// do some database operations in the transaction (use 'tx' from this point, not 'db')
tx.Create(...)

// ...

// rollback the transaction in case of error
tx.Rollback()

// Or commit the transaction
tx.Commit()
@@ -169,7 +169,7 @@

- + diff --git a/hi_IN/docs/update.html b/hi_IN/docs/update.html index 66208135eab..f0a896bda97 100644 --- a/hi_IN/docs/update.html +++ b/hi_IN/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

- + diff --git a/hi_IN/docs/v2_release_note.html b/hi_IN/docs/v2_release_note.html index 08ae27c3959..88fa661b88a 100644 --- a/hi_IN/docs/v2_release_note.html +++ b/hi_IN/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

- + diff --git a/hi_IN/docs/write_driver.html b/hi_IN/docs/write_driver.html index 22d67afcd80..81b1ec1a6b7 100644 --- a/hi_IN/docs/write_driver.html +++ b/hi_IN/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

Write Driver

-

Write new driver

GORM provides official support for sqlite, mysql, postgres, sqlserver.

-

Some databases may be compatible with the mysql or postgres dialect, in which case you could just use the dialect for those databases.

-

For others, you can create a new driver, it needs to implement the dialect interface.

-
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
}
- -

Checkout the MySQL Driver as example

+

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.

+

Compatibility with MySQL or Postgres Dialects

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.

+

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:

+
type Dialector interface {
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.

+
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
}
+ +

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:

+
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:

+
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:

+
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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

@@ -159,7 +192,7 @@

Write Driver

- +
@@ -273,7 +306,7 @@

Gold Sponsors

Contents -
  1. Write new driver
+
  1. Compatibility with MySQL or Postgres Dialects
  2. Implementing the Dialector
    1. Nested Transaction Support
    2. Custom Clause Builders
diff --git a/hi_IN/docs/write_plugins.html b/hi_IN/docs/write_plugins.html index f29408f66da..c24ff5db82c 100644 --- a/hi_IN/docs/write_plugins.html +++ b/hi_IN/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

Write Plugins

-

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

-

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

-

Register Callback

Register a callback into callbacks

-
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

db.Callback().Create().Register("crop_image", cropImage)
// register a callback for Create process
+

Callbacks

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 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.

+

Registering a Callback

You can register a callback for specific operations. For example, to add a custom image cropping functionality:

+
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

// Register the callback for the Create operation
db.Callback().Create().Register("crop_image", cropImage)
-

Delete Callback

Delete a callback from callbacks

-
db.Callback().Create().Remove("gorm:create")
// delete callback `gorm:create` from Create callbacks
+

Deleting a Callback

If a callback is no longer needed, it can be removed:

+
// Remove the 'gorm:create' callback from Create operations
db.Callback().Create().Remove("gorm:create")
-

Replace Callback

Replace a callback having the same name with the new one

-
db.Callback().Create().Replace("gorm:create", newCreateFunction)
// replace callback `gorm:create` with new function `newCreateFunction` for Create process
+

Replacing a Callback

Callbacks with the same name can be replaced with a new function:

+
// Replace the 'gorm:create' callback with a new function
db.Callback().Create().Replace("gorm:create", newCreateFunction)
-

Register Callback with orders

Register callbacks with orders

-
// before gorm:create
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// after gorm:create
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// after gorm:query
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// after gorm:delete
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// before gorm:update
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
+

Ordering Callbacks

Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

+
// Register to execute before the 'gorm:create' callback
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:create' callback
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:query' callback
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// Register to execute after the 'gorm:delete' callback
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// Register to execute before the 'gorm:update' callback
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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)

// Register to execute before any other callbacks
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// Register to execute after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
-

Defined Callbacks

GORM has defined some callbacks to power current GORM features, check them out before starting your plugins

-

Plugin

GORM provides a Use method to register plugins, the plugin needs to implement the Plugin interface

+

Predefined Callbacks

GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

+

Plugins

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:

type Plugin interface {
Name() string
Initialize(*gorm.DB) error
}
-

The Initialize method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like:

-
db.Config.Plugins[pluginName]
+
    +
  • 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:

+
// Example of registering a plugin
db.Use(MyCustomPlugin{})
-

Checkout Prometheus as example

+

Accessing Registered Plugins

After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

+
// 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:

+
// Registering the Prometheus plugin
db.Use(prometheus.New(prometheus.Config{
// Configuration options here
}))
+ +

Prometheus plugin documentation provides detailed information on its implementation and usage.

@@ -175,7 +186,7 @@

- + @@ -289,7 +300,7 @@

Gold Sponsors

Contents -
  1. Callbacks
    1. Register Callback
    2. Delete Callback
    3. Replace Callback
    4. Register Callback with orders
    5. Defined Callbacks
  2. Plugin
+
  1. Callbacks
    1. Registering a Callback
    2. Deleting a Callback
    3. Replacing a Callback
    4. Ordering Callbacks
    5. Predefined Callbacks
  2. Plugins
    1. The Plugin Interface
    2. Registering a Plugin
    3. Accessing Registered Plugins
    4. Practical Example
diff --git a/hi_IN/gen.html b/hi_IN/gen.html index 97a309e7313..fe59d7e33c8 100644 --- a/hi_IN/gen.html +++ b/hi_IN/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/hi_IN/gen/associations.html b/hi_IN/gen/associations.html index 336423cf683..e4648cf9391 100644 --- a/hi_IN/gen/associations.html +++ b/hi_IN/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

// specify model
g.ApplyBasic(model.Customer{}, model.CreditCard{})

// assoications will be detected and converted to code
package query

type customer struct {
...
CreditCards customerHasManyCreditCards
}

type creditCard struct{
...
}
-

Relate to table in database

The association have to be speified by gen.FieldRelate

-
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"}},
}),
)

g.ApplyBasic(card, custormer)
+

Relate to table in database

The association have to be specified by gen.FieldRelate

+
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"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(card, custormer)

GEN will generate models with associated field:

-
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}
+
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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;references:ID" json:"credit_cards"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}

If associated model already exists, gen.FieldRelateModel can help you build associations between them.

-
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
}),
)

g.ApplyBasic(custormer)
+
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(custormer)

Relate Config

type RelateConfig struct {
// specify field's type
RelatePointer bool // ex: CreditCard *CreditCard
RelateSlice bool // ex: CreditCards []CreditCard
RelateSlicePointer bool // ex: CreditCards []*CreditCard

JSONTag string // related field's JSON tag
GORMTag string // related field's GORM tag
NewTag string // related field's new tag
OverwriteTag string // related field's tag
}

Operation

Skip Auto Create/Update

user := model.User{
Name: "modi",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{
{Email: "modi@example.com"},
{Email: "modi-2@example.com"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name).Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
// Skip create BillingAddress when creating a user

u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
// Skip create BillingAddress.Address1 when creating a user

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

Find matched associations

u := query.Use(db).User

languages, err = u.Languages.Model(&user).Find()
@@ -198,10 +198,10 @@

Nested Preloading together, e.g:

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:

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.

+

Preload with select

Specify selected columns with method Select. Foreign key must be selected.

type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}

u := q.User
cc := q.CreditCard

// !!! Foregin key "cc.UserRefer" must be selected
users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
// SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
// SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

Preload with conditions

GEN allows Preload associations with conditions, it works similar to Inline Conditions.

@@ -209,14 +209,13 @@

Nested Preloading

GEN supports nested preloading, for example:

db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

// Customize Preload conditions for `Orders`
// 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/hi_IN/gen/clause.html b/hi_IN/gen/clause.html index bf603769aaf..d112e9dd025 100644 --- a/hi_IN/gen/clause.html +++ b/hi_IN/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

- + diff --git a/hi_IN/gen/create.html b/hi_IN/gen/create.html index 92178ef1ce9..5fa72badcd7 100644 --- a/hi_IN/gen/create.html +++ b/hi_IN/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

- + diff --git a/hi_IN/gen/dao.html b/hi_IN/gen/dao.html index 1962d4f94a6..ebc5332e002 100644 --- a/hi_IN/gen/dao.html +++ b/hi_IN/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

- + diff --git a/hi_IN/gen/database_to_structs.html b/hi_IN/gen/database_to_structs.html index 1b5e82720e7..c18d48135ce 100644 --- a/hi_IN/gen/database_to_structs.html +++ b/hi_IN/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

diff --git a/hi_IN/gen/delete.html b/hi_IN/gen/delete.html index b34d92cde89..725ca88b155 100644 --- a/hi_IN/gen/delete.html +++ b/hi_IN/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

- + diff --git a/hi_IN/gen/dynamic_sql.html b/hi_IN/gen/dynamic_sql.html index 1300faba7db..bad44328987 100644 --- a/hi_IN/gen/dynamic_sql.html +++ b/hi_IN/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/hi_IN/gen/gen_tool.html b/hi_IN/gen/gen_tool.html index bfcf80d3a79..bfcad0ab061 100644 --- a/hi_IN/gen/gen_tool.html +++ b/hi_IN/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

- + diff --git a/hi_IN/gen/index.html b/hi_IN/gen/index.html index dfb4e3c5a78..b4ba83d9a92 100644 --- a/hi_IN/gen/index.html +++ b/hi_IN/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/hi_IN/gen/query.html b/hi_IN/gen/query.html index 072da1c995c..1b3d769c095 100644 --- a/hi_IN/gen/query.html +++ b/hi_IN/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

int/uint/float Fields

// int field
f := field.NewInt("user", "id")
// `user`.`id` = 123
f.Eq(123)
// `user`.`id` DESC
f.Desc()
// `user`.`id` AS `user_id`
f.As("user_id")
// COUNT(`user`.`id`)
f.Count()
// SUM(`user`.`id`)
f.Sum()
// SUM(`user`.`id`) > 123
f.Sum().Gt(123)
// ((`user`.`id`+1)*2)/3
f.Add(1).Mul(2).Div(3),
// `user`.`id` <<< 3
f.LeftShift(3)
-

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `uesr`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")
+

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `user`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")

Time Fields

birth := field.NewString("user", "birth")
// `user`.`birth` = ? (now)
birth.Eq(time.Now())
// DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
birth.Add(time.Duration(time.Hour).Microseconds())
// DATE_FORMAT(`user`.`birth`, "%W %M %Y")
birth.DateFormat("%W %M %Y")

Bool Fields

active := field.NewBool("user", "active")
// `user`.`active` = TRUE
active.Is(true)
// NOT `user`.`active`
active.Not()
// `user`.`active` AND TRUE
active.And(true)

SubQuery

A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

-
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
// 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
+
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

From SubQuery

GORM allows you using subquery in FROM clause with method Table, for example:

u := query.User
p := query.Pet

users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

subQuery1 := u.WithContext(ctx).Select(u.Name)
subQuery2 := p.WithContext(ctx).Select(p.Name)
users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
@@ -312,7 +312,7 @@

- + diff --git a/hi_IN/gen/rawsql_driver.html b/hi_IN/gen/rawsql_driver.html index 9582c6f4007..59711b6052b 100644 --- a/hi_IN/gen/rawsql_driver.html +++ b/hi_IN/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/hi_IN/gen/sql_annotation.html b/hi_IN/gen/sql_annotation.html index 7a4b6814524..7fa8555f35a 100644 --- a/hi_IN/gen/sql_annotation.html +++ b/hi_IN/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
// UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
// UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

query.User.Update(User{Age: 0}, 10)
// UPDATE users SET is_adult=0 WHERE id=10

for

The for expression iterates over a slice to generate the SQL, let’s explain by example

-
// 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)
+
// SELECT * FROM @@table
// {{where}}
// {{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}}
// {{end}}
// {{end}}
Filter(users []gen.T) ([]gen.T, error)

Usage:

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","%"))
@@ -254,7 +254,7 @@

- + diff --git a/hi_IN/gen/transaction.html b/hi_IN/gen/transaction.html index ee6668b7cfb..cdd260b5721 100644 --- a/hi_IN/gen/transaction.html +++ b/hi_IN/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/hi_IN/gen/update.html b/hi_IN/gen/update.html index 192b47ff85c..1fb982cf127 100644 --- a/hi_IN/gen/update.html +++ b/hi_IN/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/hi_IN/gorm.html b/hi_IN/gorm.html index d57e0c05c1f..c41d9ec24d7 100644 --- a/hi_IN/gorm.html +++ b/hi_IN/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- +
diff --git a/hi_IN/gormx.html b/hi_IN/gormx.html index 9196b527a63..64c44908fe7 100644 --- a/hi_IN/gormx.html +++ b/hi_IN/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/hi_IN/hints.html b/hi_IN/hints.html index e95d9b054ba..7ad8d7e8b50 100644 --- a/hi_IN/hints.html +++ b/hi_IN/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/hi_IN/index.html b/hi_IN/index.html index 49c4781ae8b..c1db861c635 100644 --- a/hi_IN/index.html +++ b/hi_IN/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/hi_IN/rawsql.html b/hi_IN/rawsql.html index 6ec3ea7fa70..c3baa6c57f6 100644 --- a/hi_IN/rawsql.html +++ b/hi_IN/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/hi_IN/rawsql_driver.html b/hi_IN/rawsql_driver.html index 288aff4b269..e94c2cd65f5 100644 --- a/hi_IN/rawsql_driver.html +++ b/hi_IN/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/hi_IN/sharding.html b/hi_IN/sharding.html index 597e795850c..0580e508780 100644 --- a/hi_IN/sharding.html +++ b/hi_IN/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/hi_IN/stats.html b/hi_IN/stats.html index cae29b3f035..2c6fca9c116 100644 --- a/hi_IN/stats.html +++ b/hi_IN/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/id_ID/404.html b/id_ID/404.html index ce3a162debc..48c457c4aeb 100644 --- a/id_ID/404.html +++ b/id_ID/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/id_ID/community.html b/id_ID/community.html index c2a151191d1..fa8a8c9d83c 100644 --- a/id_ID/community.html +++ b/id_ID/community.html @@ -56,8 +56,8 @@ - - + + @@ -184,7 +184,7 @@

- + diff --git a/id_ID/contribute.html b/id_ID/contribute.html index c1bfab4ffd6..65724bf5047 100644 --- a/id_ID/contribute.html +++ b/id_ID/contribute.html @@ -32,8 +32,8 @@ - - + + @@ -149,7 +149,7 @@

- + diff --git a/id_ID/datatypes.html b/id_ID/datatypes.html index 1159a999e38..dbcc29e8d1f 100644 --- a/id_ID/datatypes.html +++ b/id_ID/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/id_ID/docs/advanced_query.html b/id_ID/docs/advanced_query.html index 91b757d6e5f..08e7ebf08ed 100644 --- a/id_ID/docs/advanced_query.html +++ b/id_ID/docs/advanced_query.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,80 +141,106 @@

Kueri Lanjutan

-

Bidang Pilih Pintar

GORM mengijinkan pengambilan field secara spesifik dengan Select, 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:

-
type User struct {
ID uint
Name string
Age int
Gender string
// ratusan bidang
}

type APIUser struct {
ID uint
Name string
}

// Pilih bidang `id`, `name` secara otomatis saat proses kueri
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
+

Bidang Pilih Pintar

In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

+
type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// GORM will automatically select `id`, `name` fields when querying
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SQL: SELECT `id`, `name` FROM `users` LIMIT 10
-

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.

-
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

db.Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... // Mode Sesi
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users`
+
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
QueryFields: true,
})

// Default behavior with QueryFields set to true
db.Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

// Using Session Mode with QueryFields
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
-

Penguncian (FOR UPDATE)

GORM mendukung berbagai jenis penguncian, misalnya:

-
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATE

db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`

db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
+

Locking

GORM mendukung berbagai jenis penguncian, misalnya:

+
// Basic FOR UPDATE lock
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE
-

Rujuk ke SQL mentahan dan pembuat SQL untuk detail lebih lanjut

-

Sub-Kueri

Sebuah sub-kueri dapat disarangkan dalam sebuah kueri, GORM dapat menghasilkan sub-kueri saat menggunakan objek *gorm.DB sebagai parameternya

-
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

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%")
+

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.

+
db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SQL: SELECT * FROM `users` FOR SHARE OF `users`
-

Dari Sub-Kueri

GORM allows you using subquery in FROM clause with the method Table, for example:

-
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

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
+

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.

+
db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
-

Pengelompokan Kondisi

Lebih mudah untuk menulis kueri SQL yang rumit dengan pengelompokan kondisi

-
db.Where(
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")
+

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.

+

Sub-Kueri

Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

+
// Simple subquery
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
-

IN dengan Beberapa Kolom

Memilih IN dengan beberapa kolom

-
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"));
+

Dari Sub-Kueri

GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

+
// Using subquery in FROM clause
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// 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{})
// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
-

Argumen Bernama

GORM mendukung argumen bernama dengan sql.NamedArg atau map[string]interface{}{}, misalnya:

-
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

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
+

Pengelompokan Kondisi

Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

+
// Complex SQL query using Group Conditions
db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
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")
-

Lihat ke SQL mentahan dan pembuat SQL untuk detail lebih lanjut

-

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:

-
result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)

var results []map[string]interface{}
db.Table("users").Find(&results)
+

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.

+
// Using IN with multiple columns
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
-

FirstOrInit

Dapatkan catatan pertama yang cocok atau inisialisasi instance baru dengan kondisi yang diberikan (hanya berfungsi dengan kondisi struct atau map)

-
// Pengguna tidak ditemukan, menginisiasi dengan kondisi yang diberikan
db.FirstOrInit(&user, User{Name: "non_existing"})
// user -> User{Name: "non_existing"}

// Pengguna ditemukan dengan `name` = `jinzhu`
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}

// Pengguna ditemukan dengan `name` = `jinzhu`
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}
+

Argumen Bernama

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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

+
// Example using sql.NamedArg for named arguments
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// 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)
// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
-

Initialize struct with more attributes if record not found, those Attrs won’t be used to build the SQL query

-
// Pengguna tidak ditemukan, menginisiasi dengan kondisi yang diberikan
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}

// Pengguna ditemukan dengan `name` = `jinzhu`, atribut akan diabaikan
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}
+

For more examples and details, see Raw SQL and SQL Builder

+

Pencarian ke Map

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.

+
// 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`
-

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

-
// Pengguna tidak ditemukan, menginisiasi dengan kondisi yang diberikan dan menambahkan atribut
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
// user -> User{Name: "non_existing", Age: 20}

// Pengguna ditemukan dengan `name` = `jinzhu`, memperbaruinya dengan atribut yang diberikan
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}
+

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.

+
// 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"} if not found

// Retrieving a user named "jinzhu"
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

// Using a map to specify the search condition
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

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

-
// User not found, create a new record with give conditions
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1

// Found user with `name` = `jinzhu`
result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
// user -> User{ID: 111, Name: "jinzhu", "Age": 18}
// result.RowsAffected // => 0
+

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.

+
// If no User is found, initialize with given conditions and additional attributes
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20} if not found

// If a User named "Jinzhu" is found, `Attrs` are ignored
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
-

Buat struct dengan lebih banyak atribut jika catatan tidak ditemukan, Attrs tersebut tidak akan digunakan untuk membuat kueri SQL

-
// Pengguna tidak ditemukan, membuat catatan baru dengan kondisi dan atribut yang diberikan
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Pengguna ditemukan dengan `name` = `jinzhu`, atribut akan diabaikan
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
+

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.

+
// 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} if not found

// If a User named "Jinzhu" is found, update the struct with Assign attributes
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
-

Assign atribut ke catatan, terlepas itu ditemukan atau tidak dan menyimpannya kembali ke database.

-
// Pengguna tidak ditemukan, membuat catatan baru dengan kondisi dan atribut yang diberikan
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);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Pengguna ditemukan dengan `name` = `jinzhu`, perbarui dengan atribut yang diberikan
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}
+

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

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.

+
// Create a new record if not found
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// SQL: INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1 (record created)

// 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)
-

Optimizer/Indeks Hints

Optimizer hints memungkinkan untuk mengontrol pengoptimal kueri untuk memilih rencana eksekusi kueri tertentu, GORM mendukungnya dengan gorm.io/hints, misalnya:

-
import "gorm.io/hints"

db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
+

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.

+
// Create a new record with additional attributes if not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// 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}

// If the user is found, `Attrs` are ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
-

Indeks hints memungkinkan melewatkan petunjuk indeks ke database jika perencana kueri bingung.

-
import "gorm.io/hints"

db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)

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`)"
+

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.

+
// Initialize and save new record with `Assign` attributes if not found
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// 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}

// Update found record with `Assign` attributes
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// SQL: UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20}
-

Rujuk ke Optimizer Hints/Indeks/Komentar untuk detail lebih lanjut

-

Pengulangan

GORM mendukung pengulangan melalui baris

-
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()

for rows.Next() {
var user User
// ScanRows merupakan metode dari `gorm.DB`, dapat digunakan untuk memindai suatu baris ke sebuah struct
db.ScanRows(rows, &user)

// lakukan sesuatu
}
+

Optimizer/Indeks 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.

+
import "gorm.io/hints"

// Using an optimizer hint to set a maximum execution time
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
-

FindInBatches

Meng-kueri dan memproses dalam suatu batch

-
// ukuran batch 100
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// pemroses batch menemukan catatan
}

tx.Save(&results)

tx.RowsAffected // jumlah catatan di batch ini

batch // Batch 1, 2, 3

// mengembalikan eror dan memberhentikan batch berikutnya
return nil
})

result.Error // mengembalikan eror
result.RowsAffected // catatan yang diproses dihitung di semua batch
+

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.

+
import "gorm.io/hints"

// Suggesting the use of a specific index
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// 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{})
// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
-

Hook Kueri

GORM memungkinkan hook AfterFind untuk kueri, itu akan dipanggil saat meminta catatan, lihat Hooks untuk detailnya

-
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.Role == "" {
u.Role = "user"
}
return
}
+

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 in the GORM documentation.

+

Pengulangan

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.

+
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()

for rows.Next() {
var user User
// ScanRows scans a row into a struct
db.ScanRows(rows, &user)

// Perform operations on each user
}
-

Pluck

Kueri satu kolom dari database dan pindai menjadi irisan, jika Anda ingin membuat kueri beberapa kolom, gunakan Select dengan Scan sebagai gantinya

-
var ages []int64
db.Model(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

// Pluck berbeda
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SELECT DISTINCT `name` FROM `users`

// Meminta lebih dari satu kolom, gunakan `Scan` atau `Find` seperti ini:
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+

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.

+
// 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 provides the count of records in the current batch
// The variable 'batch' indicates the current batch number

// Returning an error will stop further batch processing
return nil
})

// result.Error contains any errors encountered during batch processing
// result.RowsAffected provides the count of all processed records across batches
-

Scopes

Scopes memungkinkan Anda menentukan kueri yang umum digunakan yang dapat dirujuk sebagai pemanggilan metode

-
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}

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 {
return db.Where("status IN (?)", status)
}
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// Cari semua kartu kredit dengan jumlah lebih dari 1000

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// Cari semua pesanan COD dengan jumlah lebih dari 1000

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// Cari semua pesanan dibayar dan dikirim dengan jumlah lebih dari 1000
+

FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

+

Hook Kueri

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 in the GORM documentation.

+
func (u *User) AfterFind(tx *gorm.DB) (err error) {
// Custom logic after finding a user
if u.Role == "" {
u.Role = "user" // Set default role if not specified
}
return
}

// Usage of AfterFind hook happens automatically when a User is queried
-

Rujuk ke Scopes untuk detail lebih lanjut

-

Count

Dapatkan jumlah catatan yang sesuai

-
var count int64
db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

db.Table("deleted_users").Count(&count)
// SELECT count(1) FROM deleted_users;

// Count with Distinct
db.Model(&User{}).Distinct("name").Count(&count)
// SELECT COUNT(DISTINCT(`name`)) FROM `users`

db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SELECT count(distinct(name)) FROM deleted_users

// Hitung dalam kelompok
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
count // => 3
+

Pluck

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 or Find instead.

+
// Retrieving ages of all users
var ages []int64
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)

// Using Distinct with Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SQL: SELECT DISTINCT `name` FROM `users`

// Querying multiple columns
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
+ +

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.

+
// 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")
}

// 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.

+
// 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)
+ +

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 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.

+
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'

// Counting records in a different table
db.Table("deleted_users").Count(&count)
// SQL: SELECT count(1) FROM deleted_users
+ +

Count with Distinct and Group

GORM also allows counting distinct values and grouping results.

+
// Counting distinct names
db.Model(&User{}).Distinct("name").Count(&count)
// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

// Counting distinct values with a custom select
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SQL: SELECT count(distinct(name)) FROM deleted_users

// Counting grouped records
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
// Count after grouping by name
// count => 3
@@ -227,7 +253,7 @@

- + @@ -341,7 +367,7 @@

Gold Sponsors

Isi -
  1. Bidang Pilih Pintar
  2. Penguncian (FOR UPDATE)
  3. Sub-Kueri
    1. Dari Sub-Kueri
  4. Pengelompokan Kondisi
  5. IN dengan Beberapa Kolom
  6. Argumen Bernama
  7. Pencarian ke Map
  8. FirstOrInit
  9. FirstOrCreate
  10. Optimizer/Indeks Hints
  11. Pengulangan
  12. FindInBatches
  13. Hook Kueri
  14. Pluck
  15. Scopes
  16. Count
+
  1. Bidang Pilih Pintar
  2. Locking
  3. Sub-Kueri
    1. Dari Sub-Kueri
  4. Pengelompokan Kondisi
  5. IN dengan Beberapa Kolom
  6. Argumen Bernama
  7. Pencarian ke Map
  8. FirstOrInit
    1. Using Attrs for Initialization
    2. Using Assign for Attributes
  9. FirstOrCreate
    1. Using Attrs with FirstOrCreate
    2. Using Assign with FirstOrCreate
  10. Optimizer/Indeks Hints
    1. Index Hints
  11. Pengulangan
  12. FindInBatches
  13. Hook Kueri
  14. Pluck
  15. Scopes
    1. Defining Scopes
    2. Applying Scopes in Queries
  16. Count
    1. Getting the Count of Matched Records
    2. Count with Distinct and Group
diff --git a/id_ID/docs/associations.html b/id_ID/docs/associations.html index 12b16900147..6c757a8e66b 100644 --- a/id_ID/docs/associations.html +++ b/id_ID/docs/associations.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,62 +141,101 @@

Asosiasi

-

Buat/Pembaruan Otomatis

GORM akan menyimpan asosiasi dan referensinya secara otomatis menggunakan Upsert saat membuat/memperbarui catatan.

-
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"},
},
}

db.Create(&user)
// BEGIN TRANSACTION;
// 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 ('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)
- -

Jika Anda ingin memperbarui data asosiasi, Anda harus menggunakan mode FullSaveAssociations:

-
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);
// ...
- -

Lewati Buat/Pembaruan Otomatis

Untuk melewati simpan otomatis saat membuat/memperbarui, Anda dapat menggunakan Select atau Omit, misalnya:

-
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"},
},
}

db.Select("Name").Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

db.Omit("BillingAddress").Create(&user)
// Lewati pembuatan BillingAddress saat membuat pengguna baru

db.Omit(clause.Associations).Create(&user)
// Lewati semua asosiasi saat membuat pengguna
- -

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:

-
db.Omit("Languages.*").Create(&user)
- -

Kode berikut akan melewatkan pembuatan asosiasi dan referensinya

-
db.Omit("Languages").Create(&user)
- -

Pilih/Abaikan bidang Asosiasi

user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
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
db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
- -

Mode Asosiasi

Mode Asosiasi berisi beberapa metode pembantu yang umum digunakan untuk menangani hubungan

-
// 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
- -

Cari Asosiasi

Cari asosiasi yang sesuai

-
db.Model(&user).Association("Languages").Find(&languages)
- -

Cari asosiasi dengan kondisi

-
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

Tambahkan asosiasi baru untuk many to many, has many, ganti asosiasi saat ini untuk has one, belongs to

-
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
- -

Ubah Asosiasi

Ganti asosiasi saat ini dengan yang baru

-
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
- -

Hapus Asosiasi

Hapus hubungan antara sumber & argumen jika ada, hanya hapus referensi, tidak akan menghapus objek tersebut dari DB.

-
db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
- -

Bersihkan Asosiasi

Hapus semua referensi antara sumber & asosiasi, tidak akan menghapus asosiasi tersebut

-
db.Model(&user).Association("Languages").Clear()
- -

Hitung Asosiasi

Kembalikan jumlah asosiasi saat ini

-
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()
- -

Data Batch

Mode Asosiasi mendukung data batch, mis:

-
// Cari semua peran untuk semua pengguna
db.Model(&users).Association("Role").Find(&roles)

// Hapus Pengguna A dari semua tim pengguna
db.Model(&users).Association("Team").Delete(&userA)

// Dapatkan hitungan berbeda dari semua tim pengguna
db.Model(&users).Association("Team").Count()

// 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
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
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).

-

How to delete is decided by gorm.DB.

-
// Soft delete
// UPDATE `languages` SET `deleted_at`= ...
db.Model(&user).Association("Languages").Unscoped().Clear()

// Delete permanently
// DELETE FROM `languages` WHERE ...
db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
- -

Delete with Select

You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

-
// 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)
- -

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

-
// 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
- -

Association Tags

+

Buat/Pembaruan Otomatis

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.

+
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"},
},
}

// 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;
// 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 ('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)
+ +

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.

+
// Update a user and fully update all its associations
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
// 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

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.

+
user := User{
// User and associated data
}

// Only include the 'Name' field when creating the user
db.Select("Name").Create(&user)
// 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.

+
// Skip creating the 'BillingAddress' when creating the user
db.Omit("BillingAddress").Create(&user)

// Skip all associations when creating the user
db.Omit(clause.Associations).Create(&user)
+ +

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 .*:

+
// Skip upserting 'Languages' associations
db.Omit("Languages.*").Create(&user)
+ +

To skip creating both the association and its references:

+
// Skip creating 'Languages' associations and their references
db.Omit("Languages").Create(&user)
+ +

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.

+
user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
}

// 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.

+
// 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: 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.

+
// 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
+ +

Mode Asosiasi

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.

+
var user User
db.Model(&user).Association("Languages")
// Check for errors
error := db.Model(&user).Association("Languages").Error
+ +

Finding Associations

Retrieve associated records with or without additional conditions.

+
// Simple find
db.Model(&user).Association("Languages").Find(&languages)

// Find with conditions
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).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.

+
// Append new languages
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

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.

+
// 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.

+
// 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.

+
// Clear all languages
db.Model(&user).Association("Languages").Clear()
+ +

Counting Associations

Get the count of current associations, with or without conditions.

+
// 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.
  • +
+
db.Model(&users).Association("Role").Find(&roles)
+ +
    +
  • Deleting Associations: Remove specific associations across multiple records.
  • +
+
db.Model(&users).Association("Team").Delete(&userA)
+ +
    +
  • Counting Associations: Get the count of associations for a batch of records.
  • +
+
db.Model(&users).Association("Team").Count()
+ +
    +
  • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
  • +
+
var users = []User{user1, user2, 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})

// 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

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.

+
    +
  • 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.
  • +
+

Modifying Deletion Behavior with Unscoped

For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

+
    +
  • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
  • +
+
db.Model(&user).Association("Languages").Unscoped().Clear()
+ +
    +
  • Permanent Delete: Physically deletes the association records from the database.
  • +
+
// db.Unscoped().Model(&user)
db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
+ +

Association Tags

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.

+ @@ -204,36 +243,36 @@

- - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
Tag
foreignKeyMenentukan nama kolom dari model saat ini yang digunakan sebagai kunci asing ke tabel gabunganforeignKeySpecifies the column name of the current model used as a foreign key in the join table.
referencesMenentukan nama kolom tabel referensi yang dipetakan ke kunci asing tabel gabunganreferencesIndicates the column name in the reference table that the foreign key of the join table maps to.
polymorphicMenentukan tipe polimorfik seperti nama modelpolymorphicDefines the polymorphic type, typically the model name.
polymorphicValueMenentukan nilai polimorfik, nama tabel defaultpolymorphicValueSets the polymorphic value, usually the table name, if not specified otherwise.
many2manyMenentukan nama tabel yang digabungmany2manyNames the join table used in a many-to-many relationship.
joinForeignKeyMenentukan nama kolom kunci asing dari tabel gabungan yang memetakan ke tabel saat inijoinForeignKeyIdentifies the foreign key column in the join table that maps back to the current model’s table.
joinReferencesMenentukan nama kolom kunci asing dari tabel gabungan yang memetakan ke tabel referensijoinReferencesPoints to the foreign key column in the join table that links to the reference model’s table.
constraintBatasan relasi, mis: OnUpdate,OnDeleteconstraintSpecifies relational constraints like OnUpdate, OnDelete for the association.
@@ -248,7 +287,7 @@

@@ -362,7 +401,7 @@

Gold Sponsors

Isi -
  1. Buat/Pembaruan Otomatis
  2. Lewati Buat/Pembaruan Otomatis
  3. Pilih/Abaikan bidang Asosiasi
  4. Mode Asosiasi
    1. Cari Asosiasi
    2. Menambahkan Asosiasi
    3. Ubah Asosiasi
    4. Hapus Asosiasi
    5. Bersihkan Asosiasi
    6. Hitung Asosiasi
    7. Data Batch
  5. Delete Association Record
  6. Delete with Select
  7. Association Tags
+
  1. Buat/Pembaruan Otomatis
    1. Auto-Saving Associations on Create
    2. Updating Associations with FullSaveAssociations
  2. Lewati Buat/Pembaruan Otomatis
    1. Using Select to Include Specific Fields
    2. Using Omit to Exclude Fields or Associations
  3. Pilih/Abaikan bidang Asosiasi
  4. Hapus Asosiasi
  5. Mode Asosiasi
    1. Finding Associations
    2. Appending Associations
    3. Replacing Associations
    4. Deleting Associations
    5. Clearing Associations
    6. Counting Associations
    7. Batch Data Handling
  6. Delete Association Record
    1. Modifying Deletion Behavior with Unscoped
  7. Association Tags
diff --git a/id_ID/docs/belongs_to.html b/id_ID/docs/belongs_to.html index 24d91fb019b..e67fd3506b6 100644 --- a/id_ID/docs/belongs_to.html +++ b/id_ID/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

- + diff --git a/id_ID/docs/changelog.html b/id_ID/docs/changelog.html index dc4756fc831..9b5f4b83d38 100644 --- a/id_ID/docs/changelog.html +++ b/id_ID/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/id_ID/docs/composite_primary_key.html b/id_ID/docs/composite_primary_key.html index 828677090ee..1ae82ef623a 100644 --- a/id_ID/docs/composite_primary_key.html +++ b/id_ID/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

Composite Primary Key

diff --git a/id_ID/docs/connecting_to_the_database.html b/id_ID/docs/connecting_to_the_database.html index 08cbacc4799..4744a17b53b 100644 --- a/id_ID/docs/connecting_to_the_database.html +++ b/id_ID/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

- + diff --git a/id_ID/docs/constraints.html b/id_ID/docs/constraints.html index 19350d1c380..5fb15e02eba 100644 --- a/id_ID/docs/constraints.html +++ b/id_ID/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/id_ID/docs/context.html b/id_ID/docs/context.html index c6eac66f866..9dbae81da7b 100644 --- a/id_ID/docs/context.html +++ b/id_ID/docs/context.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,25 @@

Context

-

GORM provides Context support, you can use it with method WithContext

-

Single Session Mode

Single session mode usually used when you want to perform a single operation

+

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 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.

db.WithContext(ctx).Find(&users)
-

Continuous session mode

Continuous session mode is usually used when you want to perform a group of operations, for example:

+

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.

tx := db.WithContext(ctx)
tx.First(&user, 1)
tx.Model(&user).Update("role", "admin")
-

Context timeout

You can pass in a context with a timeout to db.WithContext to set timeout for long running queries, for example:

+

Context Timeout

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.

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

db.WithContext(ctx).Find(&users)
-

Context in Hooks/Callbacks

You can access the Context object from the current Statement, for example:

-
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ...
return
}
+

Context in Hooks/Callbacks

The context can also be accessed within GORM’s hooks/callbacks. This enables contextual information to be used during these lifecycle events.

+
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ... use context
return
}
-

Chi Middleware Example

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:

-
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

r := chi.NewRouter()
r.Use(SetDBMiddleware)

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
})

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
})
+

Integration with 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.

+
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))
next.ServeHTTP(w, r.WithContext(ctx))
})
}

// Router setup
r := chi.NewRouter()
r.Use(SetDBMiddleware)

// Route handlers
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})

r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value("DB").(*gorm.DB)
// ... db operations
})
-

NOTE Setting Context with WithContext is goroutine-safe, refer Session for details

-
- -

Logger

Logger accepts Context too, you can use it for log tracking, refer Logger for details

+

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 in GORM.

+

Logger Integration

GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

+

Refer to Logger documentation for more details.

@@ -174,7 +172,7 @@

- + @@ -288,7 +286,7 @@

Gold Sponsors

Isi -
  1. Single Session Mode
  2. Continuous session mode
  3. Context timeout
  4. Context in Hooks/Callbacks
  5. Chi Middleware Example
  6. Logger
+
  1. Single Session Mode
  2. Continuous Session Mode
  3. Context Timeout
  4. Context in Hooks/Callbacks
  5. Integration with Chi Middleware
  6. Logger Integration
diff --git a/id_ID/docs/conventions.html b/id_ID/docs/conventions.html index abc20bea8d2..3250992255d 100644 --- a/id_ID/docs/conventions.html +++ b/id_ID/docs/conventions.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

// Create table `deleted_users` with struct User's fields
db.Table("deleted_users").AutoMigrate(&User{})

// Query data from another table
var deletedUsers []User
db.Table("deleted_users").Find(&deletedUsers)
// SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
// DELETE FROM deleted_users WHERE name = 'jinzhu';

Check out From SubQuery for how to use SubQuery in FROM clause

-

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 for details

+

NamingStrategy

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 for details

Column Name

Column db name uses the field’s name’s snake_case by convention.

type User struct {
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}
@@ -194,7 +194,7 @@

- + diff --git a/id_ID/docs/create.html b/id_ID/docs/create.html index aeb6f91dbe7..deb601e7ecd 100644 --- a/id_ID/docs/create.html +++ b/id_ID/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -155,7 +155,7 @@

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
-

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.

+

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 split into multiple batches.

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

You can specify batch size when creating with CreateInBatches, e.g:

@@ -220,7 +220,7 @@

- + diff --git a/id_ID/docs/data_types.html b/id_ID/docs/data_types.html index 3fb187b3af6..87f89bc5cfb 100644 --- a/id_ID/docs/data_types.html +++ b/id_ID/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

- + diff --git a/id_ID/docs/dbresolver.html b/id_ID/docs/dbresolver.html index 2d08c867a1a..24fcae6011d 100644 --- a/id_ID/docs/dbresolver.html +++ b/id_ID/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

Transaction

When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

But you can specifies which DB to use before starting a transaction, for example:

-
// Start transaction based on default replicas db
tx := DB.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := DB.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
+
// Start transaction based on default replicas db
tx := db.Clauses(dbresolver.Read).Begin()

// Start transaction based on default sources db
tx := db.Clauses(dbresolver.Write).Begin()

// Start transaction based on `secondary`'s sources
tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

Load Balancing

GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

type Policy interface {
Resolve([]gorm.ConnPool) gorm.ConnPool
}
@@ -183,7 +183,7 @@

diff --git a/id_ID/docs/delete.html b/id_ID/docs/delete.html index 9907a351e1c..c3167f6868f 100644 --- a/id_ID/docs/delete.html +++ b/id_ID/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

- + diff --git a/id_ID/docs/error_handling.html b/id_ID/docs/error_handling.html index fd950b9e6ac..a1824b8b278 100644 --- a/id_ID/docs/error_handling.html +++ b/id_ID/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

Error Handling

-

In Go, error handling is important.

-

You are encouraged to do error check after any Finisher Methods

-

Error Handling

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:

-
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// error handling...
}
- -

Or

-
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// error handling...
}
- -

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:

-
// Check if returns RecordNotFound error
err := db.First(&user, 100).Error
errors.Is(err, gorm.ErrRecordNotFound)
-

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.

+

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.

+

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.

+

After a chain of methods, it’s crucial to check the Error field:

+
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// Handle error...
}
+ +

Or alternatively:

+
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
// Handle error...
}
+ +

ErrRecordNotFound

GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

+
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

+
    +
  • Example: Handling MySQL Error Codes
  • +
+
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.

db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
-

Errors

Errors List

+
    +
  • ErrDuplicatedKey
  • +
+

This error occurs when an insert operation violates a unique constraint:

+
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:

+
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

For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

@@ -168,7 +187,7 @@

- + @@ -282,7 +301,7 @@

Gold Sponsors

Isi -
  1. Error Handling
  2. ErrRecordNotFound
  3. Dialect Translated Errors
  4. Errors
+
  1. Basic Error Handling
  2. ErrRecordNotFound
  3. Handling Error Codes
  4. Dialect Translated Errors
  5. Errors
diff --git a/id_ID/docs/generic_interface.html b/id_ID/docs/generic_interface.html index 2664d8d93c6..94c12eaae1a 100644 --- a/id_ID/docs/generic_interface.html +++ b/id_ID/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

diff --git a/id_ID/docs/gorm_config.html b/id_ID/docs/gorm_config.html index cb53858d56d..06e63399875 100644 --- a/id_ID/docs/gorm_config.html +++ b/id_ID/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

diff --git a/id_ID/docs/has_many.html b/id_ID/docs/has_many.html index 2561627952d..b03b04be294 100644 --- a/id_ID/docs/has_many.html +++ b/id_ID/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/id_ID/docs/has_one.html b/id_ID/docs/has_one.html index 4c62207f0fd..858def66363 100644 --- a/id_ID/docs/has_one.html +++ b/id_ID/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

- + diff --git a/id_ID/docs/hints.html b/id_ID/docs/hints.html index a72cd924cf1..ec8751397b3 100644 --- a/id_ID/docs/hints.html +++ b/id_ID/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

- + diff --git a/id_ID/docs/hooks.html b/id_ID/docs/hooks.html index 65779001cc9..a609d5f9b98 100644 --- a/id_ID/docs/hooks.html +++ b/id_ID/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

- + diff --git a/id_ID/docs/index.html b/id_ID/docs/index.html index 0361d64baeb..624ee62bdb7 100644 --- a/id_ID/docs/index.html +++ b/id_ID/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

- + diff --git a/id_ID/docs/indexes.html b/id_ID/docs/indexes.html index 03fbd86e523..e0067be60b7 100644 --- a/id_ID/docs/indexes.html +++ b/id_ID/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

diff --git a/id_ID/docs/logger.html b/id_ID/docs/logger.html index d14dfebd520..d0561c55e08 100644 --- a/id_ID/docs/logger.html +++ b/id_ID/docs/logger.html @@ -32,8 +32,8 @@ - - + + @@ -142,7 +142,7 @@

diff --git a/id_ID/docs/many_to_many.html b/id_ID/docs/many_to_many.html index c915302068d..25e94820ecd 100644 --- a/id_ID/docs/many_to_many.html +++ b/id_ID/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

-
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addressses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
+
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int `gorm:"primaryKey"`
AddressID int `gorm:"primaryKey"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

FOREIGN KEY Constraints

You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_speaks;"`
}

type Language struct {
Code string `gorm:"primarykey"`
Name string
}

// CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
@@ -191,7 +191,7 @@

- + diff --git a/id_ID/docs/method_chaining.html b/id_ID/docs/method_chaining.html index 8d57dcb22da..3f93f2ebbb4 100644 --- a/id_ID/docs/method_chaining.html +++ b/id_ID/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

Method Chaining

-

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:

db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
-

There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

-

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:

-
queryDB := DB.Where("name = ?", "jinzhu")

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// 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:

-
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10

queryDB.Where("age > ?", 20).First(&user2)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 20
- -

Chain Method

Chain methods are methods to modify or add Clauses to current Statement, like:

-

Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

-

Here is the full lists, also check out the SQL Builder 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 here.

-

New Session Method

GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new 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:
// 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:
// 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:
// SELECT * FROM users;
- -

(Bad) Example 2:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is safe to 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

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
- -

Example 3:

-
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is a new initialized *gorm.DB, which is 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

// good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// good case
tx.Where("age = ?", 28).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
+

Method Categories

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. Also, the SQL Builder 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.

+

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 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

queryDB := DB.Where("name = ?", "jinzhu")

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query with unintended compounded condition
queryDB.Where("age > ?", 20).First(&user2)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
+ +

Example of Safe Reuse

To safely reuse a *gorm.DB instance, use a New Session Method:

+
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

// First query
queryDB.Where("age > ?", 10).First(&user)
// SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

// Second query, safely isolated
queryDB.Where("age > ?", 20).First(&user2)
// 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.

+

Examples for Clarity

Let’s clarify with a few examples:

+
    +
  • Example 1: Safe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
// 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)
// 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)
// Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
// SELECT * FROM users;
+ +

In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

+
    +
  • (Bad) Example 2: Unsafe Instance Reuse
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 'db' is a newly initialized *gorm.DB, safe for initial reuse.

tx := db.Where("name = ?", "jinzhu")
// `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

// Good case
tx.Where("age = ?", 18).Find(&users)
// Reuses 'tx' correctly for a single logical operation, executing:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// Bad case
tx.Where("age = ?", 28).Find(&users)
// Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
+ +

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
  • +
+
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

// Good case
tx.Where("age = ?", 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

// 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.

@@ -180,7 +210,7 @@

- + @@ -294,7 +324,7 @@

Gold Sponsors

Isi -
  1. Chain Method
  2. Finisher Method
  3. New Session Method
+
  1. Method Categories
    1. Chain Methods
    2. Finisher Methods
    3. New Session Methods
  2. Reusability and Safety
    1. Example of Unsafe Reuse
    2. Example of Safe Reuse
  3. Examples for Clarity
diff --git a/id_ID/docs/migration.html b/id_ID/docs/migration.html index 47f09387367..413a9790605 100644 --- a/id_ID/docs/migration.html +++ b/id_ID/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

- + diff --git a/id_ID/docs/models.html b/id_ID/docs/models.html index a0129a5b8b0..c0d660a4758 100644 --- a/id_ID/docs/models.html +++ b/id_ID/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,48 @@

Declaring Models

-

Deklarasi Model

Model adalah struct biasa dengan type basic Go, pointer/aliasnya type custom yang mengimplementasikan interface Scanner dan Valuer

-

Contoh:

-
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
}
- -

Konvensi

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

-

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

-

gorm.Model

GORM mendefinisikan struct gorm.Model, yang mencakup field ID, CreatedAt, UpdatedAt, ``DeletedAt< /kode>

-
// definisi gorm.Model
-type Model struct {
-  ID        uint           `gorm:"primaryKey"`
-  CreatedAt time.Time
-  UpdatedAt time.Time
-  DeletedAt gorm.DeletedAt `gorm:"index"`
-}
-``
- -

Anda dapat menyematkannya ke dalam struct Anda untuk menyertakan field tersebut, lihat Embedded Struct

+

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

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 and Valuer interfaces from the database/sql package

+

Consider the following example of a User model:

+
type User struct {
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 for a detailed explanation

+

Konvensi

    +
  1. Primary Key: GORM uses a field named ID as the default primary key for each model.

    +
  2. +
  3. 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.

    +
  4. +
  5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

    +
  6. +
  7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

    +
  8. +
+

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.

+

gorm.Model

GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

+
// 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

    +
  • +
  • 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

Field-Level Permission

Exported fields have all permissions when doing CRUD with GORM, and GORM allows you to change the field-level permission with tag, so you can make a field to be read-only, write-only, create-only, update-only or ignored

-{% note warn %} -**CATATAN** *field* yang diabaikan tidak akan dibuat saat menggunakan GORM Migrator untuk membuat tabel -{% endnote %} +

CATATAN field yang diabaikan tidak akan dibuat saat menggunakan GORM Migrator untuk membuat tabel

+
type User struct {
Name string `gorm:"<-:create"` // allow read and create
Name string `gorm:"<-:update"` // allow read and update
Name string `gorm:"<-"` // allow read and write (create and update)
Name string `gorm:"<-:false"` // allow read, disable write permission
Name string `gorm:"->"` // readonly (disable write permission unless it configured)
Name string `gorm:"->;<-:create"` // allow read and create
Name string `gorm:"->:false;<-:create"` // createonly (disabled read from db)
Name string `gorm:"-"` // ignore this field when write and read with struct
Name string `gorm:"-:all"` // ignore this field when write, read and migrate with struct
Name string `gorm:"-:migration"` // ignore this field when migrate with struct
}
@@ -282,7 +303,7 @@

Associations Tags

GORM allows configure foreign keys, constraints, many2many table through tags for Associations, check out the Associations section for details

-
+
@@ -294,7 +315,7 @@

@@ -408,7 +429,7 @@

Gold Sponsors

Isi -
  1. Deklarasi Model
  2. Konvensi
  3. gorm.Model
  4. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
+
  1. Deklarasi Model
    1. Konvensi
    2. gorm.Model
  2. Advanced
    1. Field-Level Permission
    2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
    3. Embedded Struct
    4. Fields Tags
    5. Associations Tags
diff --git a/id_ID/docs/performance.html b/id_ID/docs/performance.html index 4519c466a11..ef893436b95 100644 --- a/id_ID/docs/performance.html +++ b/id_ID/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

- + diff --git a/id_ID/docs/preload.html b/id_ID/docs/preload.html index fc74e3a07eb..61f37852c5c 100644 --- a/id_ID/docs/preload.html +++ b/id_ID/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

- + diff --git a/id_ID/docs/prometheus.html b/id_ID/docs/prometheus.html index a1479e627ec..76f44fc3b5f 100644 --- a/id_ID/docs/prometheus.html +++ b/id_ID/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- + diff --git a/id_ID/docs/query.html b/id_ID/docs/query.html index 4a638f7b1d4..6bdff36c9bb 100644 --- a/id_ID/docs/query.html +++ b/id_ID/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

- + diff --git a/id_ID/docs/scopes.html b/id_ID/docs/scopes.html index 4b882e67fbf..5fbec8156f4 100644 --- a/id_ID/docs/scopes.html +++ b/id_ID/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/id_ID/docs/security.html b/id_ID/docs/security.html index 4d6163ff37d..d815fe7a77d 100644 --- a/id_ID/docs/security.html +++ b/id_ID/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

Inline Condition

// will be escaped
db.First(&user, "name = ?", userInput)

// SQL injection
db.First(&user, fmt.Sprintf("name = %v", userInput))

When retrieving objects with number primary key by user’s input, you should check the type of variable.

-
userInputID := "1=1;drop table users;"
// safe, return error
id,err := strconv.Atoi(userInputID)
if err != nil {
return error
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;
+
userInputID := "1=1;drop table users;"
// safe, return error
id, err := strconv.Atoi(userInputID)
if err != nil {
return err
}
db.First(&user, id)

// SQL injection
db.First(&user, userInputID)
// SELECT * FROM users WHERE 1=1;drop table users;

SQL injection Methods

To support some features, some inputs are not escaped, be careful when using user’s input with those methods

db.Select("name; drop table users;").First(&user)
db.Distinct("name; drop table users;").First(&user)

db.Model(&user).Pluck("name; drop table users;", &names)

db.Group("name; drop table users;").First(&user)

db.Group("name").Having("1 = 1;drop table users;").First(&user)

db.Raw("select name from users; drop table users;").First(&user)

db.Exec("select name from users; drop table users;")

db.Order("name; drop table users;").First(&user)
@@ -169,7 +169,7 @@

- + diff --git a/id_ID/docs/serializer.html b/id_ID/docs/serializer.html index e5f09fb0100..e53c4b9472a 100644 --- a/id_ID/docs/serializer.html +++ b/id_ID/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

- + diff --git a/id_ID/docs/session.html b/id_ID/docs/session.html index e1523d79e3b..93dcb869545 100644 --- a/id_ID/docs/session.html +++ b/id_ID/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

diff --git a/id_ID/docs/settings.html b/id_ID/docs/settings.html index 6642ec41331..92b8c79c520 100644 --- a/id_ID/docs/settings.html +++ b/id_ID/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- + diff --git a/id_ID/docs/sharding.html b/id_ID/docs/sharding.html index 8a67bbe8ee2..b1ce209287f 100644 --- a/id_ID/docs/sharding.html +++ b/id_ID/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

- + diff --git a/id_ID/docs/sql_builder.html b/id_ID/docs/sql_builder.html index 83ba1ef6e24..f813e137bf5 100644 --- a/id_ID/docs/sql_builder.html +++ b/id_ID/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

diff --git a/id_ID/docs/transactions.html b/id_ID/docs/transactions.html index a483ab14709..759c1a9f983 100644 --- a/id_ID/docs/transactions.html +++ b/id_ID/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

db.Transaction(func(tx *gorm.DB) error {
// do some database operations in the transaction (use 'tx' from this point, not 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// return any error will rollback
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// return nil will commit the whole transaction
return nil
})

Nested Transactions

GORM mendukung transaksi nested, Anda dapat melakukan rollback subset operasi yang dapat dilakukan dalam cakupan transaksi yang lebih besar, misalnya:

-
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3
+
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})

tx.Transaction(func(tx3 *gorm.DB) error {
tx3.Create(&user3)
return nil
})

return nil
})

// Commit user1, user3

Control the transaction manually

Gorm supports calling transaction control functions (commit / rollback) directly, for example:

// begin a transaction
tx := db.Begin()

// do some database operations in the transaction (use 'tx' from this point, not 'db')
tx.Create(...)

// ...

// rollback the transaction in case of error
tx.Rollback()

// Or commit the transaction
tx.Commit()
@@ -169,7 +169,7 @@

- + diff --git a/id_ID/docs/update.html b/id_ID/docs/update.html index 3bf7b64cbb1..a1c848b4572 100644 --- a/id_ID/docs/update.html +++ b/id_ID/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

- + diff --git a/id_ID/docs/v2_release_note.html b/id_ID/docs/v2_release_note.html index 1de9674d0ee..cf89b74438b 100644 --- a/id_ID/docs/v2_release_note.html +++ b/id_ID/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

- + diff --git a/id_ID/docs/write_driver.html b/id_ID/docs/write_driver.html index d887e989011..f1f84f4b29d 100644 --- a/id_ID/docs/write_driver.html +++ b/id_ID/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

Write Driver

-

Write new driver

GORM provides official support for sqlite, mysql, postgres, sqlserver.

-

Some databases may be compatible with the mysql or postgres dialect, in which case you could just use the dialect for those databases.

-

For others, you can create a new driver, it needs to implement the dialect interface.

-
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
}
- -

Checkout the MySQL Driver as example

+

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.

+

Compatibility with MySQL or Postgres Dialects

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.

+

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:

+
type Dialector interface {
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.

+
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
}
+ +

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:

+
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:

+
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:

+
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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

@@ -159,7 +192,7 @@

Write Driver

@@ -273,7 +306,7 @@

Gold Sponsors

Isi -
  1. Write new driver
+
  1. Compatibility with MySQL or Postgres Dialects
  2. Implementing the Dialector
    1. Nested Transaction Support
    2. Custom Clause Builders
diff --git a/id_ID/docs/write_plugins.html b/id_ID/docs/write_plugins.html index 6dcee9c4fa0..7b3f5fe5e00 100644 --- a/id_ID/docs/write_plugins.html +++ b/id_ID/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

Membuat Plugin

-

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

-

Callback terdaftar ke *gorm.DB global, bukan tingkat sesi, jika Anda memerlukan *gorm.DB dengan callback yang berbeda, Anda perlu menginisialisasi *gorm.DB lain

-

Mendaftarkan Panggilan Balik

Daftarkan panggilan balik ke panggilan balik

-
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

db.Callback().Create().Register("crop_image", cropImage)
// register a callback for Create process
+

Panggilan Balik

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 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.

+

Registering a Callback

You can register a callback for specific operations. For example, to add a custom image cropping functionality:

+
func cropImage(db *gorm.DB) {
if db.Statement.Schema != nil {
// crop image fields and upload them to CDN, dummy code
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}
}
case reflect.Struct:
// Get value from field
if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
if crop, ok := fieldValue.(CropInterface); ok {
crop.Crop()
}
}

// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
}
}

// All fields for current model
db.Statement.Schema.Fields

// All primary key fields for current model
db.Statement.Schema.PrimaryFields

// Prioritized primary key field: field with DB name `id` or the first defined primary key
db.Statement.Schema.PrioritizedPrimaryField

// All relationships for current model
db.Statement.Schema.Relationships

// Find field with field name or db name
field := db.Statement.Schema.LookUpField("Name")

// processing
}
}

// Register the callback for the Create operation
db.Callback().Create().Register("crop_image", cropImage)
-

Menhapus Panggilan Balik

Hapus panggilan balik dari panggilan balik

-
db.Callback().Create().Remove("gorm:create")
// hapus panggilan balik `gorm: create` dari Buat panggilan balik
+

Deleting a Callback

If a callback is no longer needed, it can be removed:

+
// Remove the 'gorm:create' callback from Create operations
db.Callback().Create().Remove("gorm:create")
-

Mengganti Panggilan Balik

Ganti panggilan balik yang memiliki nama yang sama dengan yang baru

-
db.Callback().Create().Replace("gorm:create", newCreateFunction)
// ganti callback `gorm:create` dengan fungsi baru `newCreateFunction` untuk proses Create
+

Replacing a Callback

Callbacks with the same name can be replaced with a new function:

+
// Replace the 'gorm:create' callback with a new function
db.Callback().Create().Replace("gorm:create", newCreateFunction)
-

Daftarkan Panggilan Balik dengan Orders

Daftarkan Panggilan Balik dengan Orders

-
// sebelum gorm:create
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// setelah gorm:create
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// setelah gorm:query
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// setelah gorm:delete
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// sebelum gorm: perbarui
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// sebelum gorm:buat dan setelah gorm:sebelum_buat
db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate)

// sebelum panggilan balik lainnya
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// setelah panggilan balik lainnya
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
+

Ordering Callbacks

Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

+
// Register to execute before the 'gorm:create' callback
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:create' callback
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

// Register to execute after the 'gorm:query' callback
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

// Register to execute after the 'gorm:delete' callback
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

// Register to execute before the 'gorm:update' callback
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

// 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)

// Register to execute before any other callbacks
db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

// Register to execute after any other callbacks
db.Callback().Create().After("*").Register("update_created_at", updateCreated)
-

Mendefinisikan Panggilan Balik

GORM telah menetapkan beberapa panggilan balik untuk mengaktifkan fitur GORM saat ini, periksa sebelum memulai plugin Anda

-

Plugin

GORM menyediakan metode Use untuk mendaftarkan plugin, plugin perlu mengimplementasikan antarmuka Plugin

+

Predefined Callbacks

GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

+

Plugins

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:

type Plugin interface {
Name() string
Initialize(*gorm.DB) error
}
-

Metode Initialize akan dipanggil saat mendaftarkan plugin ke GORM pertama kali, dan GORM akan menyimpan plugin yang terdaftar, mengaksesnya seperti:

-
db.Config.Plugins[pluginName]
+
    +
  • 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:

+
// Example of registering a plugin
db.Use(MyCustomPlugin{})
-

Lihat Prometheus sebagai contoh

+

Accessing Registered Plugins

After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

+
// 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:

+
// Registering the Prometheus plugin
db.Use(prometheus.New(prometheus.Config{
// Configuration options here
}))
+ +

Prometheus plugin documentation provides detailed information on its implementation and usage.

@@ -175,7 +186,7 @@

- + @@ -289,7 +300,7 @@

Gold Sponsors

Isi -
  1. Panggilan Balik
    1. Mendaftarkan Panggilan Balik
    2. Menhapus Panggilan Balik
    3. Mengganti Panggilan Balik
    4. Daftarkan Panggilan Balik dengan Orders
    5. Mendefinisikan Panggilan Balik
  2. Plugin
+
  1. Panggilan Balik
    1. Registering a Callback
    2. Deleting a Callback
    3. Replacing a Callback
    4. Ordering Callbacks
    5. Predefined Callbacks
  2. Plugins
    1. The Plugin Interface
    2. Registering a Plugin
    3. Accessing Registered Plugins
    4. Practical Example
diff --git a/id_ID/gen.html b/id_ID/gen.html index 1305733bb88..ff0ac348b48 100644 --- a/id_ID/gen.html +++ b/id_ID/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/id_ID/gen/associations.html b/id_ID/gen/associations.html index 4df477007f7..b14aad49af9 100644 --- a/id_ID/gen/associations.html +++ b/id_ID/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

// specify model
g.ApplyBasic(model.Customer{}, model.CreditCard{})

// assoications will be detected and converted to code
package query

type customer struct {
...
CreditCards customerHasManyCreditCards
}

type creditCard struct{
...
}
-

Relate to table in database

The association have to be speified by gen.FieldRelate

-
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"}},
}),
)

g.ApplyBasic(card, custormer)
+

Relate to table in database

The association have to be specified by gen.FieldRelate

+
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"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(card, custormer)

GEN will generate models with associated field:

-
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}
+
// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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;references:ID" json:"credit_cards"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
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"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}

If associated model already exists, gen.FieldRelateModel can help you build associations between them.

-
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
}),
)

g.ApplyBasic(custormer)
+
customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(custormer)

Relate Config

type RelateConfig struct {
// specify field's type
RelatePointer bool // ex: CreditCard *CreditCard
RelateSlice bool // ex: CreditCards []CreditCard
RelateSlicePointer bool // ex: CreditCards []*CreditCard

JSONTag string // related field's JSON tag
GORMTag string // related field's GORM tag
NewTag string // related field's new tag
OverwriteTag string // related field's tag
}

Operation

Skip Auto Create/Update

user := model.User{
Name: "modi",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{
{Email: "modi@example.com"},
{Email: "modi-2@example.com"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name).Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
// Skip create BillingAddress when creating a user

u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
// Skip create BillingAddress.Address1 when creating a user

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

Find matched associations

u := query.Use(db).User

languages, err = u.Languages.Model(&user).Find()
@@ -198,10 +198,10 @@

Nested Preloading together, e.g:

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:

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.

+

Preload with select

Specify selected columns with method Select. Foreign key must be selected.

type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}

u := q.User
cc := q.CreditCard

// !!! Foregin key "cc.UserRefer" must be selected
users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
// SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
// SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

Preload with conditions

GEN allows Preload associations with conditions, it works similar to Inline Conditions.

@@ -209,14 +209,13 @@

Nested Preloading

GEN supports nested preloading, for example:

db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

// Customize Preload conditions for `Orders`
// 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/id_ID/gen/clause.html b/id_ID/gen/clause.html index 11b8f2760d9..8bcf4fcc6de 100644 --- a/id_ID/gen/clause.html +++ b/id_ID/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

- + diff --git a/id_ID/gen/create.html b/id_ID/gen/create.html index 5d1ed6c9a7d..17128341434 100644 --- a/id_ID/gen/create.html +++ b/id_ID/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

- + diff --git a/id_ID/gen/dao.html b/id_ID/gen/dao.html index 30cbf1584a3..13e28350ee4 100644 --- a/id_ID/gen/dao.html +++ b/id_ID/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

- + diff --git a/id_ID/gen/database_to_structs.html b/id_ID/gen/database_to_structs.html index ad78d23b610..f2799eeb05e 100644 --- a/id_ID/gen/database_to_structs.html +++ b/id_ID/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

diff --git a/id_ID/gen/delete.html b/id_ID/gen/delete.html index d1155791b46..a91d42a5387 100644 --- a/id_ID/gen/delete.html +++ b/id_ID/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

- + diff --git a/id_ID/gen/dynamic_sql.html b/id_ID/gen/dynamic_sql.html index 16025f5840c..d9e3d38e6e3 100644 --- a/id_ID/gen/dynamic_sql.html +++ b/id_ID/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/id_ID/gen/gen_tool.html b/id_ID/gen/gen_tool.html index 845d28bc266..9632c9c7aec 100644 --- a/id_ID/gen/gen_tool.html +++ b/id_ID/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

- + diff --git a/id_ID/gen/index.html b/id_ID/gen/index.html index 7b4b045a0bb..331ef888678 100644 --- a/id_ID/gen/index.html +++ b/id_ID/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/id_ID/gen/query.html b/id_ID/gen/query.html index fa5b0eefe4f..9952d3a4fad 100644 --- a/id_ID/gen/query.html +++ b/id_ID/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

int/uint/float Fields

// int field
f := field.NewInt("user", "id")
// `user`.`id` = 123
f.Eq(123)
// `user`.`id` DESC
f.Desc()
// `user`.`id` AS `user_id`
f.As("user_id")
// COUNT(`user`.`id`)
f.Count()
// SUM(`user`.`id`)
f.Sum()
// SUM(`user`.`id`) > 123
f.Sum().Gt(123)
// ((`user`.`id`+1)*2)/3
f.Add(1).Mul(2).Div(3),
// `user`.`id` <<< 3
f.LeftShift(3)
-

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `uesr`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")
+

String Fields

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `user`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")

Time Fields

birth := field.NewString("user", "birth")
// `user`.`birth` = ? (now)
birth.Eq(time.Now())
// DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
birth.Add(time.Duration(time.Hour).Microseconds())
// DATE_FORMAT(`user`.`birth`, "%W %M %Y")
birth.DateFormat("%W %M %Y")

Bool Fields

active := field.NewBool("user", "active")
// `user`.`active` = TRUE
active.Is(true)
// NOT `user`.`active`
active.Not()
// `user`.`active` AND TRUE
active.And(true)

SubQuery

A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

-
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
// 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
+
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

From SubQuery

GORM allows you using subquery in FROM clause with method Table, for example:

u := query.User
p := query.Pet

users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

subQuery1 := u.WithContext(ctx).Select(u.Name)
subQuery2 := p.WithContext(ctx).Select(p.Name)
users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
@@ -312,7 +312,7 @@

- + diff --git a/id_ID/gen/rawsql_driver.html b/id_ID/gen/rawsql_driver.html index 17f8701dff4..46593fc4173 100644 --- a/id_ID/gen/rawsql_driver.html +++ b/id_ID/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/id_ID/gen/sql_annotation.html b/id_ID/gen/sql_annotation.html index 5a300dd4001..95d7a7e993d 100644 --- a/id_ID/gen/sql_annotation.html +++ b/id_ID/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
// UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
// UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

query.User.Update(User{Age: 0}, 10)
// UPDATE users SET is_adult=0 WHERE id=10

for

The for expression iterates over a slice to generate the SQL, let’s explain by example

-
// 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)
+
// SELECT * FROM @@table
// {{where}}
// {{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}}
// {{end}}
// {{end}}
Filter(users []gen.T) ([]gen.T, error)

Usage:

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","%"))
@@ -254,7 +254,7 @@

- + diff --git a/id_ID/gen/transaction.html b/id_ID/gen/transaction.html index 861d6e83224..add70e0d312 100644 --- a/id_ID/gen/transaction.html +++ b/id_ID/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- + diff --git a/id_ID/gen/update.html b/id_ID/gen/update.html index 6c1ca6fccf6..b0178d6f7a2 100644 --- a/id_ID/gen/update.html +++ b/id_ID/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

- + diff --git a/id_ID/gorm.html b/id_ID/gorm.html index 7ff5751f3e8..38f79ff66b2 100644 --- a/id_ID/gorm.html +++ b/id_ID/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

- +
diff --git a/id_ID/gormx.html b/id_ID/gormx.html index a454b412ca3..0dacaf48870 100644 --- a/id_ID/gormx.html +++ b/id_ID/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/id_ID/hints.html b/id_ID/hints.html index 02a33414d37..6546f9cab16 100644 --- a/id_ID/hints.html +++ b/id_ID/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

- +
diff --git a/id_ID/index.html b/id_ID/index.html index 75df51a3c7f..b1d8e3e70c6 100644 --- a/id_ID/index.html +++ b/id_ID/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/id_ID/rawsql.html b/id_ID/rawsql.html index 8ac5f997528..57120eabda6 100644 --- a/id_ID/rawsql.html +++ b/id_ID/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/id_ID/rawsql_driver.html b/id_ID/rawsql_driver.html index 1523f14a240..d028609665b 100644 --- a/id_ID/rawsql_driver.html +++ b/id_ID/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/id_ID/sharding.html b/id_ID/sharding.html index 07a4f9231b3..367bd768c39 100644 --- a/id_ID/sharding.html +++ b/id_ID/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

- +
diff --git a/id_ID/stats.html b/id_ID/stats.html index 9a161651c5e..4e9d8571f75 100644 --- a/id_ID/stats.html +++ b/id_ID/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

- + diff --git a/index.html b/index.html index 85427483616..ba9dd83f169 100644 --- a/index.html +++ b/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/it_IT/404.html b/it_IT/404.html index ea0db14e614..3f8b58e663c 100644 --- a/it_IT/404.html +++ b/it_IT/404.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

404 - + diff --git a/it_IT/community.html b/it_IT/community.html index 4fbcdb3f0a7..a4689a8b01d 100644 --- a/it_IT/community.html +++ b/it_IT/community.html @@ -56,8 +56,8 @@ - - + + @@ -184,7 +184,7 @@

- + diff --git a/it_IT/contribute.html b/it_IT/contribute.html index 9711ca60f03..65397e742f2 100644 --- a/it_IT/contribute.html +++ b/it_IT/contribute.html @@ -32,8 +32,8 @@ - - + + @@ -133,7 +133,7 @@

Github per correggere un problema, una nuova funzionalità
  • Crea open-source plugin per GORM (Non dimenticare di aggiungerli alla lista)
  • -

    Donazioni

    Your kindness and generosity is greatly appreciated, many thanks to all our sponsors!

    +

    Donazioni

    La vostra gentilezza e la vostra generosità sono molto apprezzate, grazie mille a tutti i nostri sponsor!

    • Github Sponsors (Platinum, Gold Sponsors etc)
    @@ -149,7 +149,7 @@

    - + diff --git a/it_IT/datatypes.html b/it_IT/datatypes.html index e51ac227dbf..12b3869a0ff 100644 --- a/it_IT/datatypes.html +++ b/it_IT/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/it_IT/docs/advanced_query.html b/it_IT/docs/advanced_query.html index e0c593fb0d7..05073f57996 100644 --- a/it_IT/docs/advanced_query.html +++ b/it_IT/docs/advanced_query.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,80 +117,106 @@

    Ricerca Avanzata

    -

    Facile Selezione dei Campi

    GORM allows selecting specific fields with Select, 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:

    -
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // hundreds of fields
    }

    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
    +

    Facile Selezione dei Campi

    In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

    +
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // hundreds of fields
    }

    type APIUser struct {
    ID uint
    Name string
    }

    // GORM will automatically select `id`, `name` fields when querying
    db.Model(&User{}).Limit(10).Find(&APIUser{})
    // SQL: SELECT `id`, `name` FROM `users` LIMIT 10
    -

    NOTE QueryFields mode will select by all fields’ name for current model

    +

    NOTE In QueryFields mode, all model fields are selected by their names.

    -
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    db.Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option

    // Session Mode
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    +
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    // Default behavior with QueryFields set to true
    db.Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

    // Using Session Mode with QueryFields
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    -

    Locking (FOR UPDATE)

    GORM supports different types of locks, for example:

    -
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SELECT * FROM `users` FOR UPDATE

    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SELECT * FROM `users` FOR SHARE OF `users`

    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SELECT * FROM `users` FOR UPDATE NOWAIT
    +

    Locking

    GORM supports different types of locks, for example:

    +
    // Basic FOR UPDATE lock
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE
    -

    Refer Raw SQL and SQL Builder for more detail

    -

    SubQuery

    A subquery can be nested within a query, GORM can generate subquery when using a *gorm.DB object as param

    -
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    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%")
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR SHARE OF `users`
    -

    From SubQuery

    GORM allows you using subquery in FROM clause with the method Table, for example:

    -
    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

    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
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
    -

    Group Conditions

    Easier to write complicated SQL query with Group Conditions

    -
    db.Where(
    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")
    +

    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.

    +

    SubQuery

    Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

    +
    // Simple subquery
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
    // SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
    -

    IN with multiple columns

    Selecting 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"));
    +

    From SubQuery

    GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

    +
    // Using subquery in FROM clause
    db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    -

    Named Argument

    GORM supports named arguments with sql.NamedArg or map[string]interface{}{}, for example:

    -
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

    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
    +

    Group Conditions

    Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

    +
    // Complex SQL query using Group Conditions
    db.Where(
    db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
    ).Or(
    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")
    -

    Check out Raw SQL and SQL Builder for more detail

    -

    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:

    -
    result := map[string]interface{}{}
    db.Model(&User{}).First(&result, "id = ?", 1)

    var results []map[string]interface{}
    db.Table("users").Find(&results)
    +

    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.

    +
    // Using IN with multiple columns
    db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
    // SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
    -

    FirstOrInit

    Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions)

    -
    // User not found, initialize it with give conditions
    db.FirstOrInit(&user, User{Name: "non_existing"})
    // user -> User{Name: "non_existing"}

    // Found user with `name` = `jinzhu`
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}

    // Found user with `name` = `jinzhu`
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
    +

    Named Argument

    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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

    +
    // Example using sql.NamedArg for named arguments
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // 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)
    // SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
    -

    Initialize struct with more attributes if record not found, those Attrs won’t be used to build the SQL query

    -
    // User not found, initialize it with give conditions and Attrs
    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}

    // Found user with `name` = `jinzhu`, attributes will be 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}
    +

    For more examples and details, see Raw SQL and SQL Builder

    +

    Find To Map

    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.

    +
    // 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`
    -

    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

    -
    // User not found, initialize it with give conditions and Assign attributes
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // user -> User{Name: "non_existing", Age: 20}

    // Found user with `name` = `jinzhu`, update it 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}
    +

    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.

    +
    // 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"} if not found

    // Retrieving a user named "jinzhu"
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

    // Using a map to specify the search condition
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    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

    -
    // User not found, create a new record with give conditions
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1

    // Found user with `name` = `jinzhu`
    result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
    // user -> User{ID: 111, Name: "jinzhu", "Age": 18}
    // result.RowsAffected // => 0
    +

    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.

    +
    // If no User is found, initialize with given conditions and additional attributes
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // user -> User{Name: "non_existing", Age: 20} if not found

    // If a User named "Jinzhu" is found, `Attrs` are ignored
    db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    Create struct with more attributes if record not found, those Attrs won’t be used to build SQL query

    -
    // User not found, create it with give conditions and Attrs
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // Found user with `name` = `jinzhu`, attributes will be ignored
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    +

    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.

    +
    // 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} if not found

    // If a User named "Jinzhu" is found, update the struct with Assign attributes
    db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
    -

    Assign attributes to the record regardless it is found or not and save them back to the database.

    -
    // User not found, initialize it with give conditions and Assign attributes
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // Found user with `name` = `jinzhu`, update it 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}
    +

    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

    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.

    +
    // Create a new record if not found
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // SQL: INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1 (record created)

    // 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)
    -

    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:

    -
    import "gorm.io/hints"

    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    +

    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.

    +
    // Create a new record with additional attributes if not found
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // If the user is found, `Attrs` are ignored
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu';
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    -

    Index hints allow passing index hints to the database in case the query planner gets confused.

    -
    import "gorm.io/hints"

    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // SELECT * FROM `users` USE INDEX (`idx_user_name`)

    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`)"
    +

    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.

    +
    // Initialize and save new record with `Assign` attributes if not found
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // Update found record with `Assign` attributes
    db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu';
    // SQL: UPDATE users SET age=20 WHERE id = 111;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 20}
    -

    Refer Optimizer Hints/Index/Comment for more details

    -

    Iteration

    GORM supports iterating through Rows

    -
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    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
    db.ScanRows(rows, &user)

    // do something
    }
    +

    Optimizer/Index 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.

    +
    import "gorm.io/hints"

    // Using an optimizer hint to set a maximum execution time
    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    -

    FindInBatches

    Query and process records in batch

    -
    // batch size 100
    result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
    for _, result := range results {
    // batch processing found records
    }

    tx.Save(&results)

    tx.RowsAffected // number of records in this batch

    batch // Batch 1, 2, 3

    // returns error will stop future batches
    return nil
    })

    result.Error // returned error
    result.RowsAffected // processed records count in all batches
    +

    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.

    +
    import "gorm.io/hints"

    // Suggesting the use of a specific index
    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
    -

    Query Hooks

    GORM allows hooks AfterFind for a query, it will be called when querying a record, refer Hooks for details

    -
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    if u.Role == "" {
    u.Role = "user"
    }
    return
    }
    +

    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 in the GORM documentation.

    +

    Iteration

    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.

    +
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    defer rows.Close()

    for rows.Next() {
    var user User
    // ScanRows scans a row into a struct
    db.ScanRows(rows, &user)

    // Perform operations on each user
    }
    -

    Pluck

    Query single column from database and scan into a slice, if you want to query multiple columns, use Select with Scan instead

    -
    var ages []int64
    db.Model(&users).Pluck("age", &ages)

    var names []string
    db.Model(&User{}).Pluck("name", &names)

    db.Table("deleted_users").Pluck("name", &names)

    // Distinct Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SELECT DISTINCT `name` FROM `users`

    // Requesting more than one column, use `Scan` or `Find` like this:
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    +

    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.

    +
    // 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 provides the count of records in the current batch
    // The variable 'batch' indicates the current batch number

    // Returning an error will stop further batch processing
    return nil
    })

    // result.Error contains any errors encountered during batch processing
    // result.RowsAffected provides the count of all processed records across batches
    -

    Scopes

    Scopes allows you to specify commonly-used queries which can be referenced as method calls

    -
    func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
    return db.Where("amount > ?", 1000)
    }

    func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
    return db.Where("pay_mode_sign = ?", "C")
    }

    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 {
    return db.Where("status IN (?)", status)
    }
    }

    db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
    // Find all credit card orders and amount greater than 1000

    db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
    // Find all COD orders and amount greater than 1000

    db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
    // Find all paid, shipped orders that amount greater than 1000
    +

    FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

    +

    Query Hooks

    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 in the GORM documentation.

    +
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    // Custom logic after finding a user
    if u.Role == "" {
    u.Role = "user" // Set default role if not specified
    }
    return
    }

    // Usage of AfterFind hook happens automatically when a User is queried
    -

    Checkout Scopes for details

    -

    Count

    Get matched records count

    -
    var count int64
    db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

    db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

    db.Table("deleted_users").Count(&count)
    // SELECT count(1) FROM deleted_users;

    // Count with Distinct
    db.Model(&User{}).Distinct("name").Count(&count)
    // SELECT COUNT(DISTINCT(`name`)) FROM `users`

    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SELECT count(distinct(name)) FROM deleted_users

    // Count with Group
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    count // => 3
    +

    Pluck

    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 or Find instead.

    +
    // Retrieving ages of all users
    var ages []int64
    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)

    // Using Distinct with Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SQL: SELECT DISTINCT `name` FROM `users`

    // Querying multiple columns
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    + +

    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.

    +
    // 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")
    }

    // 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.

    +
    // 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)
    + +

    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 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.

    +
    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'

    // Counting records in a different table
    db.Table("deleted_users").Count(&count)
    // SQL: SELECT count(1) FROM deleted_users
    + +

    Count with Distinct and Group

    GORM also allows counting distinct values and grouping results.

    +
    // Counting distinct names
    db.Model(&User{}).Distinct("name").Count(&count)
    // SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

    // Counting distinct values with a custom select
    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SQL: SELECT count(distinct(name)) FROM deleted_users

    // Counting grouped records
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    // Count after grouping by name
    // count => 3
    @@ -203,7 +229,7 @@

    - + @@ -317,7 +343,7 @@

    Gold Sponsors

    Contents -
    1. Facile Selezione dei Campi
    2. Locking (FOR UPDATE)
    3. SubQuery
      1. From SubQuery
    4. Group Conditions
    5. IN with multiple columns
    6. Named Argument
    7. Find To Map
    8. FirstOrInit
    9. FirstOrCreate
    10. Optimizer/Index Hints
    11. Iteration
    12. FindInBatches
    13. Query Hooks
    14. Pluck
    15. Scopes
    16. Count
    +
    1. Facile Selezione dei Campi
    2. Locking
    3. SubQuery
      1. From SubQuery
    4. Group Conditions
    5. IN with multiple columns
    6. Named Argument
    7. Find To Map
    8. FirstOrInit
      1. Using Attrs for Initialization
      2. Using Assign for Attributes
    9. FirstOrCreate
      1. Using Attrs with FirstOrCreate
      2. Using Assign with FirstOrCreate
    10. Optimizer/Index Hints
      1. Index Hints
    11. Iteration
    12. FindInBatches
    13. Query Hooks
    14. Pluck
    15. Scopes
      1. Defining Scopes
      2. Applying Scopes in Queries
    16. Count
      1. Getting the Count of Matched Records
      2. Count with Distinct and Group
    diff --git a/it_IT/docs/associations.html b/it_IT/docs/associations.html index c4674b2e696..868208a4778 100644 --- a/it_IT/docs/associations.html +++ b/it_IT/docs/associations.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,62 +141,101 @@

    Associations

    -

    Auto Create/Update

    GORM will auto-save associations and its reference using Upsert when creating/updating a record.

    -
    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"},
    },
    }

    db.Create(&user)
    // BEGIN TRANSACTION;
    // 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 ('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)
    - -

    If you want to update associations’s data, you should use the FullSaveAssociations mode:

    -
    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);
    // ...
    - -

    Skip Auto Create/Update

    To skip the auto save when creating/updating, you can use Select or Omit, for example:

    -
    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"},
    },
    }

    db.Select("Name").Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    db.Omit("BillingAddress").Create(&user)
    // Skip create BillingAddress when creating a user

    db.Omit(clause.Associations).Create(&user)
    // Skip all associations when creating a user
    - -

    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:

    -
    db.Omit("Languages.*").Create(&user)
    - -

    The following code will skip the creation of the association and its references

    -
    db.Omit("Languages").Create(&user)
    - -

    Select/Omit Association fields

    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    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
    db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

    db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
    - -

    Association Mode

    Association Mode contains some commonly used helper methods to handle relationships

    -
    // 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
    - -

    Find Associations

    Find matched associations

    -
    db.Model(&user).Association("Languages").Find(&languages)
    - -

    Find associations 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

    Append new associations for many to many, has many, replace current association for has one, belongs to

    -
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

    db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
    - -

    Replace Associations

    Replace current associations with new ones

    -
    db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
    - -

    Delete Associations

    Remove the relationship between source & arguments if exists, only delete the reference, won’t delete those objects from DB.

    -
    db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
    db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
    - -

    Clear Associations

    Remove all reference between source & association, won’t delete those associations

    -
    db.Model(&user).Association("Languages").Clear()
    - -

    Count Associations

    Return the count of current associations

    -
    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

    Association Mode supports batch data, e.g:

    -
    // Find all roles for all users
    db.Model(&users).Association("Role").Find(&roles)

    // Delete User A from all user's team
    db.Model(&users).Association("Team").Delete(&userA)

    // Get distinct count of all users' teams
    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
    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
    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
    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).

    -

    How to delete is decided by gorm.DB.

    -
    // Soft delete
    // UPDATE `languages` SET `deleted_at`= ...
    db.Model(&user).Association("Languages").Unscoped().Clear()

    // Delete permanently
    // DELETE FROM `languages` WHERE ...
    db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
    - -

    Delete with Select

    You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

    -
    // 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)
    - -

    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

    -
    // 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
    - -

    Association Tags

    +

    Auto Create/Update

    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.

    +
    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"},
    },
    }

    // 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;
    // 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 ('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)
    + +

    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.

    +
    // Update a user and fully update all its associations
    db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
    // 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

    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.

    +
    user := User{
    // User and associated data
    }

    // Only include the 'Name' field when creating the user
    db.Select("Name").Create(&user)
    // 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.

    +
    // Skip creating the 'BillingAddress' when creating the user
    db.Omit("BillingAddress").Create(&user)

    // Skip all associations when creating the user
    db.Omit(clause.Associations).Create(&user)
    + +

    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 .*:

    +
    // Skip upserting 'Languages' associations
    db.Omit("Languages.*").Create(&user)
    + +

    To skip creating both the association and its references:

    +
    // Skip creating 'Languages' associations and their references
    db.Omit("Languages").Create(&user)
    + +

    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.

    +
    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
    }

    // 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.

    +
    // 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: 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.

    +
    // 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
    + +

    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.

    +
    var user User
    db.Model(&user).Association("Languages")
    // Check for errors
    error := db.Model(&user).Association("Languages").Error
    + +

    Finding Associations

    Retrieve associated records with or without additional conditions.

    +
    // Simple find
    db.Model(&user).Association("Languages").Find(&languages)

    // Find with conditions
    codes := []string{"zh-CN", "en-US", "ja-JP"}
    db.Model(&user).Where("code IN ?", codes).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.

    +
    // Append new languages
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    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.

    +
    // 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.

    +
    // 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.

    +
    // Clear all languages
    db.Model(&user).Association("Languages").Clear()
    + +

    Counting Associations

    Get the count of current associations, with or without conditions.

    +
    // 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.
    • +
    +
    db.Model(&users).Association("Role").Find(&roles)
    + +
      +
    • Deleting Associations: Remove specific associations across multiple records.
    • +
    +
    db.Model(&users).Association("Team").Delete(&userA)
    + +
      +
    • Counting Associations: Get the count of associations for a batch of records.
    • +
    +
    db.Model(&users).Association("Team").Count()
    + +
      +
    • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
    • +
    +
    var users = []User{user1, user2, 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})

    // 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

    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.

    +
      +
    • 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.
    • +
    +

    Modifying Deletion Behavior with Unscoped

    For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

    +
      +
    • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
    • +
    +
    db.Model(&user).Association("Languages").Unscoped().Clear()
    + +
      +
    • Permanent Delete: Physically deletes the association records from the database.
    • +
    +
    // db.Unscoped().Model(&user)
    db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
    + +

    Association Tags

    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.

    + @@ -204,36 +243,36 @@

    - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
    Tag
    foreignKeySpecifies column name of the current model that is used as a foreign key to the join tableforeignKeySpecifies the column name of the current model used as a foreign key in the join table.
    referencesSpecifies column name of the reference’s table that is mapped to the foreign key of the join tablereferencesIndicates the column name in the reference table that the foreign key of the join table maps to.
    polymorphicSpecifies polymorphic type such as model namepolymorphicDefines the polymorphic type, typically the model name.
    polymorphicValueSpecifies polymorphic value, default table namepolymorphicValueSets the polymorphic value, usually the table name, if not specified otherwise.
    many2manySpecifies join table namemany2manyNames the join table used in a many-to-many relationship.
    joinForeignKeySpecifies foreign key column name of join table that maps to the current tablejoinForeignKeyIdentifies the foreign key column in the join table that maps back to the current model’s table.
    joinReferencesSpecifies foreign key column name of join table that maps to the reference’s tablejoinReferencesPoints to the foreign key column in the join table that links to the reference model’s table.
    constraintRelations constraint, e.g: OnUpdate,OnDeleteconstraintSpecifies relational constraints like OnUpdate, OnDelete for the association.
    @@ -248,7 +287,7 @@

    @@ -362,7 +401,7 @@

    Gold Sponsors

    Contents -
    1. Auto Create/Update
    2. Skip Auto Create/Update
    3. Select/Omit Association fields
    4. Association Mode
      1. Find Associations
      2. Append Associations
      3. Replace Associations
      4. Delete Associations
      5. Clear Associations
      6. Count Associations
      7. Batch Data
    5. Delete Association Record
    6. Delete with Select
    7. Association Tags
    +
    1. Auto Create/Update
      1. Auto-Saving Associations on Create
      2. Updating Associations with FullSaveAssociations
    2. Skip Auto Create/Update
      1. Using Select to Include Specific Fields
      2. Using Omit to Exclude Fields or Associations
    3. Select/Omit Association fields
    4. Delete Associations
    5. Association Mode
      1. Finding Associations
      2. Appending Associations
      3. Replacing Associations
      4. Deleting Associations
      5. Clearing Associations
      6. Counting Associations
      7. Batch Data Handling
    6. Delete Association Record
      1. Modifying Deletion Behavior with Unscoped
    7. Association Tags
    diff --git a/it_IT/docs/belongs_to.html b/it_IT/docs/belongs_to.html index 23e5ff4c51d..d9ad3d93282 100644 --- a/it_IT/docs/belongs_to.html +++ b/it_IT/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

    - + diff --git a/it_IT/docs/changelog.html b/it_IT/docs/changelog.html index a52bfa759c7..aae2987bb9d 100644 --- a/it_IT/docs/changelog.html +++ b/it_IT/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/it_IT/docs/composite_primary_key.html b/it_IT/docs/composite_primary_key.html index e4f8cae9adb..cd3cfbfa669 100644 --- a/it_IT/docs/composite_primary_key.html +++ b/it_IT/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

    Composite Primary Key

    - +
    diff --git a/it_IT/docs/connecting_to_the_database.html b/it_IT/docs/connecting_to_the_database.html index 4d4705085b1..a228ac6afc1 100644 --- a/it_IT/docs/connecting_to_the_database.html +++ b/it_IT/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

    - + diff --git a/it_IT/docs/constraints.html b/it_IT/docs/constraints.html index 9489e9deac2..83249846110 100644 --- a/it_IT/docs/constraints.html +++ b/it_IT/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - + diff --git a/it_IT/docs/context.html b/it_IT/docs/context.html index c35a135c9e4..c075a015b4e 100644 --- a/it_IT/docs/context.html +++ b/it_IT/docs/context.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,25 @@

    Context

    -

    GORM provides Context support, you can use it with method WithContext

    -

    Single Session Mode

    Single session mode usually used when you want to perform a single operation

    +

    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 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.

    db.WithContext(ctx).Find(&users)
    -

    Continuous session mode

    Continuous session mode is usually used when you want to perform a group of operations, for example:

    +

    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.

    tx := db.WithContext(ctx)
    tx.First(&user, 1)
    tx.Model(&user).Update("role", "admin")
    -

    Context timeout

    You can pass in a context with a timeout to db.WithContext to set timeout for long running queries, for example:

    +

    Context Timeout

    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.

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    db.WithContext(ctx).Find(&users)
    -

    Context in Hooks/Callbacks

    You can access the Context object from the current Statement, for example:

    -
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ...
    return
    }
    +

    Context in Hooks/Callbacks

    The context can also be accessed within GORM’s hooks/callbacks. This enables contextual information to be used during these lifecycle events.

    +
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ... use context
    return
    }
    -

    Chi Middleware Example

    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:

    -
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    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
    })

    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
    })
    +

    Integration with 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.

    +
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    // Router setup
    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    // Route handlers
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })

    r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })
    -

    NOTE Setting Context with WithContext is goroutine-safe, refer Session for details

    -
    - -

    Logger

    Logger accepts Context too, you can use it for log tracking, refer Logger for details

    +

    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 in GORM.

    +

    Logger Integration

    GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

    +

    Refer to Logger documentation for more details.

    @@ -174,7 +172,7 @@

    - + @@ -288,7 +286,7 @@

    Gold Sponsors

    Contents -
    1. Single Session Mode
    2. Continuous session mode
    3. Context timeout
    4. Context in Hooks/Callbacks
    5. Chi Middleware Example
    6. Logger
    +
    1. Single Session Mode
    2. Continuous Session Mode
    3. Context Timeout
    4. Context in Hooks/Callbacks
    5. Integration with Chi Middleware
    6. Logger Integration
    diff --git a/it_IT/docs/conventions.html b/it_IT/docs/conventions.html index 2db01065ffd..a8a9d76d7c9 100644 --- a/it_IT/docs/conventions.html +++ b/it_IT/docs/conventions.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    // Create table `deleted_users` with struct User's fields
    db.Table("deleted_users").AutoMigrate(&User{})

    // Query data from another table
    var deletedUsers []User
    db.Table("deleted_users").Find(&deletedUsers)
    // SELECT * FROM deleted_users;

    db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
    // DELETE FROM deleted_users WHERE name = 'jinzhu';

    Check out From SubQuery for how to use SubQuery in FROM clause

    -

    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 for details

    +

    NamingStrategy

    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 for details

    Column Name

    Column db name uses the field’s name’s snake_case by convention.

    type User struct {
      ID        uint      // column name is `id`
      Name      string    // column name is `name`
      Birthday  time.Time // column name is `birthday`
      CreatedAt time.Time // column name is `created_at`
    }
    @@ -194,7 +194,7 @@

    - + diff --git a/it_IT/docs/create.html b/it_IT/docs/create.html index f53b4f20984..4f99e3e947c 100644 --- a/it_IT/docs/create.html +++ b/it_IT/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -155,7 +155,7 @@

    db.Omit("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
    -

    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.

    +

    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 split into multiple batches.

    var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
    db.Create(&users)

    for _, user := range users {
    user.ID // 1,2,3
    }

    You can specify batch size when creating with CreateInBatches, e.g:

    @@ -220,7 +220,7 @@

    - + diff --git a/it_IT/docs/data_types.html b/it_IT/docs/data_types.html index aaa6f8c7b76..1555eef4869 100644 --- a/it_IT/docs/data_types.html +++ b/it_IT/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

    - + diff --git a/it_IT/docs/dbresolver.html b/it_IT/docs/dbresolver.html index dc1ca9f1997..47cd44d4a82 100644 --- a/it_IT/docs/dbresolver.html +++ b/it_IT/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    Transaction

    When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

    But you can specifies which DB to use before starting a transaction, for example:

    -
    // Start transaction based on default replicas db
    tx := DB.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := DB.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
    +
    // Start transaction based on default replicas db
    tx := db.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := db.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

    Load Balancing

    GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

    type Policy interface {
    Resolve([]gorm.ConnPool) gorm.ConnPool
    }
    @@ -183,7 +183,7 @@

    diff --git a/it_IT/docs/delete.html b/it_IT/docs/delete.html index 1a5ac9d8e09..b2ac5f9593c 100644 --- a/it_IT/docs/delete.html +++ b/it_IT/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

    - + diff --git a/it_IT/docs/error_handling.html b/it_IT/docs/error_handling.html index 136918e3535..8d8066260b0 100644 --- a/it_IT/docs/error_handling.html +++ b/it_IT/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

    Error Handling

    -

    In Go, error handling is important.

    -

    You are encouraged to do error check after any Finisher Methods

    -

    Error Handling

    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:

    -
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // error handling...
    }
    - -

    Or

    -
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // error handling...
    }
    - -

    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:

    -
    // Check if returns RecordNotFound error
    err := db.First(&user, 100).Error
    errors.Is(err, gorm.ErrRecordNotFound)
    -

    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.

    +

    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.

    +

    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.

    +

    After a chain of methods, it’s crucial to check the Error field:

    +
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // Handle error...
    }
    + +

    Or alternatively:

    +
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // Handle error...
    }
    + +

    ErrRecordNotFound

    GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

    +
    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

    +
      +
    • Example: Handling MySQL Error Codes
    • +
    +
    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.

    db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
    -

    Errors

    Errors List

    +
      +
    • ErrDuplicatedKey
    • +
    +

    This error occurs when an insert operation violates a unique constraint:

    +
    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:

    +
    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

    For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

    @@ -168,7 +187,7 @@

    - + @@ -282,7 +301,7 @@

    Gold Sponsors

    Contents -
    1. Error Handling
    2. ErrRecordNotFound
    3. Dialect Translated Errors
    4. Errors
    +
    1. Basic Error Handling
    2. ErrRecordNotFound
    3. Handling Error Codes
    4. Dialect Translated Errors
    5. Errors
    diff --git a/it_IT/docs/generic_interface.html b/it_IT/docs/generic_interface.html index 7bf06772009..f402f34c429 100644 --- a/it_IT/docs/generic_interface.html +++ b/it_IT/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

    diff --git a/it_IT/docs/gorm_config.html b/it_IT/docs/gorm_config.html index 85c497e3545..2726c0088c6 100644 --- a/it_IT/docs/gorm_config.html +++ b/it_IT/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    diff --git a/it_IT/docs/has_many.html b/it_IT/docs/has_many.html index 55ee37d66b6..86de8383cb5 100644 --- a/it_IT/docs/has_many.html +++ b/it_IT/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

    - + diff --git a/it_IT/docs/has_one.html b/it_IT/docs/has_one.html index 955fba644c3..e6e9b4e79dc 100644 --- a/it_IT/docs/has_one.html +++ b/it_IT/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

    - + diff --git a/it_IT/docs/hints.html b/it_IT/docs/hints.html index d954b6abea8..66fa4fa30d6 100644 --- a/it_IT/docs/hints.html +++ b/it_IT/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

    - + diff --git a/it_IT/docs/hooks.html b/it_IT/docs/hooks.html index 27e727ecc6f..09c0519ba93 100644 --- a/it_IT/docs/hooks.html +++ b/it_IT/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

    - + diff --git a/it_IT/docs/index.html b/it_IT/docs/index.html index 2887f7adf97..f7c3a6f50b1 100644 --- a/it_IT/docs/index.html +++ b/it_IT/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

    - + diff --git a/it_IT/docs/indexes.html b/it_IT/docs/indexes.html index cd9a2cc55f8..b6068679dbf 100644 --- a/it_IT/docs/indexes.html +++ b/it_IT/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

    diff --git a/it_IT/docs/logger.html b/it_IT/docs/logger.html index d1fe197f684..76b0cbb9630 100644 --- a/it_IT/docs/logger.html +++ b/it_IT/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

    diff --git a/it_IT/docs/many_to_many.html b/it_IT/docs/many_to_many.html index 27ba2188b7e..bb00de64666 100644 --- a/it_IT/docs/many_to_many.html +++ b/it_IT/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

    -
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addressses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // Change model Person's field Addresses' join table to PersonAddress
    // PersonAddress must defined all required foreign keys or it will raise error
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
    +
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addresses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // Change model Person's field Addresses' join table to PersonAddress
    // PersonAddress must defined all required foreign keys or it will raise error
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

    FOREIGN KEY Constraints

    You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

    type User struct {
    gorm.Model
    Languages []Language `gorm:"many2many:user_speaks;"`
    }

    type Language struct {
    Code string `gorm:"primarykey"`
    Name string
    }

    // CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
    @@ -191,7 +191,7 @@

    - + diff --git a/it_IT/docs/method_chaining.html b/it_IT/docs/method_chaining.html index bb26be3183a..7eb14d8d833 100644 --- a/it_IT/docs/method_chaining.html +++ b/it_IT/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

    Method Chaining

    -

    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:

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
    -

    There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

    -

    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:

    -
    queryDB := DB.Where("name = ?", "jinzhu")

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // 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:

    -
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 20
    - -

    Chain Method

    Chain methods are methods to modify or add Clauses to current Statement, like:

    -

    Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

    -

    Here is the full lists, also check out the SQL Builder 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 here.

    -

    New Session Method

    GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new 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:
    // 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:
    // 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:
    // SELECT * FROM users;
    - -

    (Bad) Example 2:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is safe to 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

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    - -

    Example 3:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is 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

    // good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // good case
    tx.Where("age = ?", 28).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
    +

    Method Categories

    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. Also, the SQL Builder 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.

    +

    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 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

    queryDB := DB.Where("name = ?", "jinzhu")

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query with unintended compounded condition
    queryDB.Where("age > ?", 20).First(&user2)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
    + +

    Example of Safe Reuse

    To safely reuse a *gorm.DB instance, use a New Session Method:

    +
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query, safely isolated
    queryDB.Where("age > ?", 20).First(&user2)
    // 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.

    +

    Examples for Clarity

    Let’s clarify with a few examples:

    +
      +
    • Example 1: Safe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
    // 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)
    // 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)
    // Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
    // SELECT * FROM users;
    + +

    In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

    +
      +
    • (Bad) Example 2: Unsafe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized *gorm.DB, safe for initial reuse.

    tx := db.Where("name = ?", "jinzhu")
    // `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // Reuses 'tx' correctly for a single logical operation, executing:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // Bad case
    tx.Where("age = ?", 28).Find(&users)
    // Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    + +

    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
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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.

    @@ -180,7 +210,7 @@

    - + @@ -294,7 +324,7 @@

    Gold Sponsors

    Contents -
    1. Chain Method
    2. Finisher Method
    3. New Session Method
    +
    1. Method Categories
      1. Chain Methods
      2. Finisher Methods
      3. New Session Methods
    2. Reusability and Safety
      1. Example of Unsafe Reuse
      2. Example of Safe Reuse
    3. Examples for Clarity
    diff --git a/it_IT/docs/migration.html b/it_IT/docs/migration.html index 972e832fc2e..fbd80a81c72 100644 --- a/it_IT/docs/migration.html +++ b/it_IT/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

    - + diff --git a/it_IT/docs/models.html b/it_IT/docs/models.html index 47ff2a29844..3cb2d2deffc 100644 --- a/it_IT/docs/models.html +++ b/it_IT/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,16 +141,45 @@

    Declaring Models

    -

    Declaring Models

    Models are normal structs with basic Go types, pointers/alias of them or custom types implementing Scanner and Valuer interfaces

    -

    For Example:

    -
    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
    }
    - -

    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

    -

    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

    -

    gorm.Model

    GORM defined a gorm.Model struct, which includes fields ID, CreatedAt, UpdatedAt, DeletedAt

    +

    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 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 and Valuer interfaces from the database/sql package

    +

    Consider the following example of a User model:

    +
    type User struct {
    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 for a detailed explanation

    +

    Conventions

      +
    1. Primary Key: GORM uses a field named ID as the default primary key for each model.

      +
    2. +
    3. 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.

      +
    4. +
    5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

      +
    6. +
    7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

      +
    8. +
    +

    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.

    +

    gorm.Model

    GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

    // gorm.Model definition
    type Model struct {
    ID uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    }
    -

    You can embed it into your struct to include those fields, refer 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

      +
    • +
    • 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

    Field-Level Permission

    Exported fields have all permissions when doing CRUD with GORM, and GORM allows you to change the field-level permission with tag, so you can make a field to be read-only, write-only, create-only, update-only or ignored

    NOTE ignored fields won’t be created when using GORM Migrator to create table

    @@ -286,7 +315,7 @@

    @@ -400,7 +429,7 @@

    Gold Sponsors

    Contents -
    1. Declaring Models
    2. Conventions
    3. gorm.Model
    4. Advanced
      1. Field-Level Permission
      2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
      3. Embedded Struct
      4. Fields Tags
      5. Associations Tags
    +
    1. Declaring Models
      1. Conventions
      2. gorm.Model
    2. Advanced
      1. Field-Level Permission
      2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking
      3. Embedded Struct
      4. Fields Tags
      5. Associations Tags
    diff --git a/it_IT/docs/performance.html b/it_IT/docs/performance.html index 45956f15f87..93dc12ef338 100644 --- a/it_IT/docs/performance.html +++ b/it_IT/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

    - + diff --git a/it_IT/docs/preload.html b/it_IT/docs/preload.html index a062b25da08..59ffefa8bc1 100644 --- a/it_IT/docs/preload.html +++ b/it_IT/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

    - + diff --git a/it_IT/docs/prometheus.html b/it_IT/docs/prometheus.html index 1ad7726e89e..0a3b152c91a 100644 --- a/it_IT/docs/prometheus.html +++ b/it_IT/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - + diff --git a/it_IT/docs/query.html b/it_IT/docs/query.html index a94333a3f2a..ef7de47caf5 100644 --- a/it_IT/docs/query.html +++ b/it_IT/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

    - + diff --git a/it_IT/docs/scopes.html b/it_IT/docs/scopes.html index bdfd3227e9c..1b4956d8e8f 100644 --- a/it_IT/docs/scopes.html +++ b/it_IT/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/it_IT/docs/security.html b/it_IT/docs/security.html index b6b921f79d9..7a8bbd2efc2 100644 --- a/it_IT/docs/security.html +++ b/it_IT/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

    Inline Condition

    // will be escaped
    db.First(&user, "name = ?", userInput)

    // SQL injection
    db.First(&user, fmt.Sprintf("name = %v", userInput))

    When retrieving objects with number primary key by user’s input, you should check the type of variable.

    -
    userInputID := "1=1;drop table users;"
    // safe, return error
    id,err := strconv.Atoi(userInputID)
    if err != nil {
    return error
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;
    +
    userInputID := "1=1;drop table users;"
    // safe, return error
    id, err := strconv.Atoi(userInputID)
    if err != nil {
    return err
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;

    SQL injection Methods

    To support some features, some inputs are not escaped, be careful when using user’s input with those methods

    db.Select("name; drop table users;").First(&user)
    db.Distinct("name; drop table users;").First(&user)

    db.Model(&user).Pluck("name; drop table users;", &names)

    db.Group("name; drop table users;").First(&user)

    db.Group("name").Having("1 = 1;drop table users;").First(&user)

    db.Raw("select name from users; drop table users;").First(&user)

    db.Exec("select name from users; drop table users;")

    db.Order("name; drop table users;").First(&user)
    @@ -169,7 +169,7 @@

    - + diff --git a/it_IT/docs/serializer.html b/it_IT/docs/serializer.html index 89537ad1d1b..ae1ecbacbb9 100644 --- a/it_IT/docs/serializer.html +++ b/it_IT/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

    - + diff --git a/it_IT/docs/session.html b/it_IT/docs/session.html index 8e35cd5200e..5010e82a3da 100644 --- a/it_IT/docs/session.html +++ b/it_IT/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

    diff --git a/it_IT/docs/settings.html b/it_IT/docs/settings.html index 9106b901a19..c4e9293aa4d 100644 --- a/it_IT/docs/settings.html +++ b/it_IT/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - + diff --git a/it_IT/docs/sharding.html b/it_IT/docs/sharding.html index 216b98ba0f5..9a783acf64e 100644 --- a/it_IT/docs/sharding.html +++ b/it_IT/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    - + diff --git a/it_IT/docs/sql_builder.html b/it_IT/docs/sql_builder.html index 64aa784ce1c..017dfc1e5de 100644 --- a/it_IT/docs/sql_builder.html +++ b/it_IT/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

    diff --git a/it_IT/docs/transactions.html b/it_IT/docs/transactions.html index 8ae5f92c588..3dd0e09b551 100644 --- a/it_IT/docs/transactions.html +++ b/it_IT/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

    db.Transaction(func(tx *gorm.DB) error {
    // do some database operations in the transaction (use 'tx' from this point, not 'db')
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // return any error will rollback
    return err
    }

    if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
    }

    // return nil will commit the whole transaction
    return nil
    })

    Nested Transactions

    GORM supports nested transactions, you can rollback a subset of operations performed within the scope of a larger transaction, for example:

    -
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3
    +
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx3 *gorm.DB) error {
    tx3.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3

    Control the transaction manually

    Gorm supports calling transaction control functions (commit / rollback) directly, for example:

    // begin a transaction
    tx := db.Begin()

    // do some database operations in the transaction (use 'tx' from this point, not 'db')
    tx.Create(...)

    // ...

    // rollback the transaction in case of error
    tx.Rollback()

    // Or commit the transaction
    tx.Commit()
    @@ -169,7 +169,7 @@

    - + diff --git a/it_IT/docs/update.html b/it_IT/docs/update.html index bc4855ce09a..5cc6dc05f49 100644 --- a/it_IT/docs/update.html +++ b/it_IT/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

    - + diff --git a/it_IT/docs/v2_release_note.html b/it_IT/docs/v2_release_note.html index ae852622aad..e606c69f9cc 100644 --- a/it_IT/docs/v2_release_note.html +++ b/it_IT/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

    - + diff --git a/it_IT/docs/write_driver.html b/it_IT/docs/write_driver.html index ddafd834fb8..ebfc0bddbf9 100644 --- a/it_IT/docs/write_driver.html +++ b/it_IT/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

    Write Driver

    -

    Write new driver

    GORM provides official support for sqlite, mysql, postgres, sqlserver.

    -

    Some databases may be compatible with the mysql or postgres dialect, in which case you could just use the dialect for those databases.

    -

    For others, you can create a new driver, it needs to implement the dialect interface.

    -
    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
    }
    - -

    Checkout the MySQL Driver as example

    +

    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.

    +

    Compatibility with MySQL or Postgres Dialects

    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.

    +

    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:

    +
    type Dialector interface {
    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.

    +
    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
    }
    + +

    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:

    +
    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:

    +
    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:

    +
    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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

    @@ -159,7 +192,7 @@

    Write Driver

    - +
    @@ -273,7 +306,7 @@

    Gold Sponsors

    Contents -
    1. Write new driver
    +
    1. Compatibility with MySQL or Postgres Dialects
    2. Implementing the Dialector
      1. Nested Transaction Support
      2. Custom Clause Builders
    diff --git a/it_IT/docs/write_plugins.html b/it_IT/docs/write_plugins.html index 740bf823c27..2ce940ced88 100644 --- a/it_IT/docs/write_plugins.html +++ b/it_IT/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

    Write Plugins

    -

    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

    -

    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

    -

    Register Callback

    Register a callback into callbacks

    -
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    db.Callback().Create().Register("crop_image", cropImage)
    // register a callback for Create process
    +

    Callbacks

    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 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.

    +

    Registering a Callback

    You can register a callback for specific operations. For example, to add a custom image cropping functionality:

    +
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    // Register the callback for the Create operation
    db.Callback().Create().Register("crop_image", cropImage)
    -

    Delete Callback

    Delete a callback from callbacks

    -
    db.Callback().Create().Remove("gorm:create")
    // delete callback `gorm:create` from Create callbacks
    +

    Deleting a Callback

    If a callback is no longer needed, it can be removed:

    +
    // Remove the 'gorm:create' callback from Create operations
    db.Callback().Create().Remove("gorm:create")
    -

    Replace Callback

    Replace a callback having the same name with the new one

    -
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    // replace callback `gorm:create` with new function `newCreateFunction` for Create process
    +

    Replacing a Callback

    Callbacks with the same name can be replaced with a new function:

    +
    // Replace the 'gorm:create' callback with a new function
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    -

    Register Callback with orders

    Register callbacks with orders

    -
    // before gorm:create
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // after gorm:create
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // after gorm:query
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // after gorm:delete
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // before gorm:update
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    +

    Ordering Callbacks

    Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

    +
    // Register to execute before the 'gorm:create' callback
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:create' callback
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:query' callback
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // Register to execute after the 'gorm:delete' callback
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // Register to execute before the 'gorm:update' callback
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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)

    // Register to execute before any other callbacks
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // Register to execute after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    -

    Defined Callbacks

    GORM has defined some callbacks to power current GORM features, check them out before starting your plugins

    -

    Plugin

    GORM provides a Use method to register plugins, the plugin needs to implement the Plugin interface

    +

    Predefined Callbacks

    GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

    +

    Plugins

    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:

    type Plugin interface {
    Name() string
    Initialize(*gorm.DB) error
    }
    -

    The Initialize method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like:

    -
    db.Config.Plugins[pluginName]
    +
      +
    • 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:

    +
    // Example of registering a plugin
    db.Use(MyCustomPlugin{})
    -

    Checkout Prometheus as example

    +

    Accessing Registered Plugins

    After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

    +
    // 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:

    +
    // Registering the Prometheus plugin
    db.Use(prometheus.New(prometheus.Config{
    // Configuration options here
    }))
    + +

    Prometheus plugin documentation provides detailed information on its implementation and usage.

    @@ -175,7 +186,7 @@

    - + @@ -289,7 +300,7 @@

    Gold Sponsors

    Contents -
    1. Callbacks
      1. Register Callback
      2. Delete Callback
      3. Replace Callback
      4. Register Callback with orders
      5. Defined Callbacks
    2. Plugin
    +
    1. Callbacks
      1. Registering a Callback
      2. Deleting a Callback
      3. Replacing a Callback
      4. Ordering Callbacks
      5. Predefined Callbacks
    2. Plugins
      1. The Plugin Interface
      2. Registering a Plugin
      3. Accessing Registered Plugins
      4. Practical Example
    diff --git a/it_IT/gen.html b/it_IT/gen.html index 5a03a7a811f..e6a33de61c0 100644 --- a/it_IT/gen.html +++ b/it_IT/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/it_IT/gen/associations.html b/it_IT/gen/associations.html index b36e7dd1411..4f7629d1ebf 100644 --- a/it_IT/gen/associations.html +++ b/it_IT/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

    // specify model
    g.ApplyBasic(model.Customer{}, model.CreditCard{})

    // assoications will be detected and converted to code
    package query

    type customer struct {
    ...
    CreditCards customerHasManyCreditCards
    }

    type creditCard struct{
    ...
    }
    -

    Relate to table in database

    The association have to be speified by gen.FieldRelate

    -
    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"}},
    }),
    )

    g.ApplyBasic(card, custormer)
    +

    Relate to table in database

    The association have to be specified by gen.FieldRelate

    +
    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"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(card, custormer)

    GEN will generate models with associated field:

    -
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }
    +
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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;references:ID" json:"credit_cards"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }

    If associated model already exists, gen.FieldRelateModel can help you build associations between them.

    -
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
    }),
    )

    g.ApplyBasic(custormer)
    +
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(custormer)

    Relate Config

    type RelateConfig struct {
    // specify field's type
    RelatePointer bool // ex: CreditCard *CreditCard
    RelateSlice bool // ex: CreditCards []CreditCard
    RelateSlicePointer bool // ex: CreditCards []*CreditCard

    JSONTag string // related field's JSON tag
    GORMTag string // related field's GORM tag
    NewTag string // related field's new tag
    OverwriteTag string // related field's tag
    }

    Operation

    Skip Auto Create/Update

    user := model.User{
    Name: "modi",
    BillingAddress: Address{Address1: "Billing Address - Address 1"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
    Emails: []Email{
    {Email: "modi@example.com"},
    {Email: "modi-2@example.com"},
    },
    Languages: []Language{
    {Name: "ZH"},
    {Name: "EN"},
    },
    }

    u := query.Use(db).User

    u.WithContext(ctx).Select(u.Name).Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
    // Skip create BillingAddress when creating a user

    u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
    // Skip create BillingAddress.Address1 when creating a user

    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

    Find matched associations

    u := query.Use(db).User

    languages, err = u.Languages.Model(&user).Find()
    @@ -198,10 +198,10 @@

    Nested Preloading together, e.g:

    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:

    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.

    +

    Preload with select

    Specify selected columns with method Select. Foreign key must be selected.

    type User struct {
    gorm.Model
    CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
    }

    type CreditCard struct {
    gorm.Model
    Number string
    UserRefer uint
    }

    u := q.User
    cc := q.CreditCard

    // !!! Foregin key "cc.UserRefer" must be selected
    users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
    // SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
    // SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

    Preload with conditions

    GEN allows Preload associations with conditions, it works similar to Inline Conditions.

    @@ -209,14 +209,13 @@

    Nested Preloading

    GEN supports nested preloading, for example:

    db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

    // Customize Preload conditions for `Orders`
    // 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/it_IT/gen/clause.html b/it_IT/gen/clause.html index 0a72635678d..007a475aa40 100644 --- a/it_IT/gen/clause.html +++ b/it_IT/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

    - + diff --git a/it_IT/gen/create.html b/it_IT/gen/create.html index a18cba20e04..c01f704aea0 100644 --- a/it_IT/gen/create.html +++ b/it_IT/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

    - + diff --git a/it_IT/gen/dao.html b/it_IT/gen/dao.html index b392d0c4c5b..bd88b464a05 100644 --- a/it_IT/gen/dao.html +++ b/it_IT/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

    - + diff --git a/it_IT/gen/database_to_structs.html b/it_IT/gen/database_to_structs.html index 4c96a8818ad..03d6c69425a 100644 --- a/it_IT/gen/database_to_structs.html +++ b/it_IT/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

    diff --git a/it_IT/gen/delete.html b/it_IT/gen/delete.html index 2dc0dfa8ea4..60afdcaf23b 100644 --- a/it_IT/gen/delete.html +++ b/it_IT/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

    - + diff --git a/it_IT/gen/dynamic_sql.html b/it_IT/gen/dynamic_sql.html index b3101df5092..a93536ac0be 100644 --- a/it_IT/gen/dynamic_sql.html +++ b/it_IT/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/it_IT/gen/gen_tool.html b/it_IT/gen/gen_tool.html index d6fd1c6c196..6a926d3fc18 100644 --- a/it_IT/gen/gen_tool.html +++ b/it_IT/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

    - + diff --git a/it_IT/gen/index.html b/it_IT/gen/index.html index f026d264bf3..114ee7fdfe5 100644 --- a/it_IT/gen/index.html +++ b/it_IT/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/it_IT/gen/query.html b/it_IT/gen/query.html index 776a447e53e..73cd435d44e 100644 --- a/it_IT/gen/query.html +++ b/it_IT/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

    int/uint/float Fields

    // int field
    f := field.NewInt("user", "id")
    // `user`.`id` = 123
    f.Eq(123)
    // `user`.`id` DESC
    f.Desc()
    // `user`.`id` AS `user_id`
    f.As("user_id")
    // COUNT(`user`.`id`)
    f.Count()
    // SUM(`user`.`id`)
    f.Sum()
    // SUM(`user`.`id`) > 123
    f.Sum().Gt(123)
    // ((`user`.`id`+1)*2)/3
    f.Add(1).Mul(2).Div(3),
    // `user`.`id` <<< 3
    f.LeftShift(3)
    -

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `uesr`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")
    +

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `user`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")

    Time Fields

    birth := field.NewString("user", "birth")
    // `user`.`birth` = ? (now)
    birth.Eq(time.Now())
    // DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
    birth.Add(time.Duration(time.Hour).Microseconds())
    // DATE_FORMAT(`user`.`birth`, "%W %M %Y")
    birth.DateFormat("%W %M %Y")

    Bool Fields

    active := field.NewBool("user", "active")
    // `user`.`active` = TRUE
    active.Is(true)
    // NOT `user`.`active`
    active.Not()
    // `user`.`active` AND TRUE
    active.And(true)

    SubQuery

    A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

    -
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
    // 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
    +
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

    From SubQuery

    GORM allows you using subquery in FROM clause with method Table, for example:

    u := query.User
    p := query.Pet

    users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
    // SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

    subQuery1 := u.WithContext(ctx).Select(u.Name)
    subQuery2 := p.WithContext(ctx).Select(p.Name)
    users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
    db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
    // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    @@ -312,7 +312,7 @@

    - + diff --git a/it_IT/gen/rawsql_driver.html b/it_IT/gen/rawsql_driver.html index db400a2412f..5a4ec44ecdd 100644 --- a/it_IT/gen/rawsql_driver.html +++ b/it_IT/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/it_IT/gen/sql_annotation.html b/it_IT/gen/sql_annotation.html index fdda5784175..7484a5389eb 100644 --- a/it_IT/gen/sql_annotation.html +++ b/it_IT/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

    query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
    // UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

    query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
    // UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

    query.User.Update(User{Age: 0}, 10)
    // UPDATE users SET is_adult=0 WHERE id=10

    for

    The for expression iterates over a slice to generate the SQL, let’s explain by example

    -
    // 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)
    +
    // SELECT * FROM @@table
    // {{where}}
    // {{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}}
    // {{end}}
    // {{end}}
    Filter(users []gen.T) ([]gen.T, error)

    Usage:

    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","%"))
    @@ -254,7 +254,7 @@

    - + diff --git a/it_IT/gen/transaction.html b/it_IT/gen/transaction.html index d4e0242b2c4..30bdb2ab27d 100644 --- a/it_IT/gen/transaction.html +++ b/it_IT/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/it_IT/gen/update.html b/it_IT/gen/update.html index 5b4c1393b5f..408efa5985d 100644 --- a/it_IT/gen/update.html +++ b/it_IT/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/it_IT/gorm.html b/it_IT/gorm.html index cc885bdfce8..62298f8bdf4 100644 --- a/it_IT/gorm.html +++ b/it_IT/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - +
    diff --git a/it_IT/gormx.html b/it_IT/gormx.html index efb0676e38b..afe18e41f9d 100644 --- a/it_IT/gormx.html +++ b/it_IT/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/it_IT/hints.html b/it_IT/hints.html index cd983b0db7d..d06e041cf11 100644 --- a/it_IT/hints.html +++ b/it_IT/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/it_IT/index.html b/it_IT/index.html index 79a3d07aa56..d81890e6a03 100644 --- a/it_IT/index.html +++ b/it_IT/index.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -129,7 +129,7 @@

    -

    便利なフィールドの選択

    GORMでは Select で選択するフィールド指定することができます。アプリケーションでこれを頻繁に使用する場合は、特定のフィールドを自動的に選択できる、用途に適した構造体を定義するとよいでしょう。例:

    -
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // hundreds of fields
    }

    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
    +

    便利なフィールドの選択

    In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

    +
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // hundreds of fields
    }

    type APIUser struct {
    ID uint
    Name string
    }

    // GORM will automatically select `id`, `name` fields when querying
    db.Model(&User{}).Limit(10).Find(&APIUser{})
    // SQL: SELECT `id`, `name` FROM `users` LIMIT 10
    -

    注意 QueryFields モードを有効にすると、モデルのすべてのフィールド名を選択するようになります。

    +

    NOTE In QueryFields mode, all model fields are selected by their names.

    -
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    db.Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option

    // Session Mode
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    +
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    // Default behavior with QueryFields set to true
    db.Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

    // Using Session Mode with QueryFields
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    -

    ロック (FOR UPDATE)

    GORMは数種類のロック処理をサポートしています。例:

    -
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SELECT * FROM `users` FOR UPDATE

    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SELECT * FROM `users` FOR SHARE OF `users`

    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SELECT * FROM `users` FOR UPDATE NOWAIT
    +

    Locking

    GORMは数種類のロック処理をサポートしています。例:

    +
    // Basic FOR UPDATE lock
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE
    -

    詳細については、Raw SQL and SQL Builderを参照してください。

    -

    サブクエリ

    クエリ内にサブクエリをネストすることができます。GORMは、パラメータとして *gorm.DB オブジェクトを使用するとサブクエリを生成できます。

    -
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    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%")
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR SHARE OF `users`
    -

    From句でのサブクエリ

    GORMでは、Tableを用いることで、FROM句でサブクエリを使用することができます。例:

    -
    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

    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
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
    -

    条件をグループ化する

    グループ条件で複雑な SQL クエリを簡単に記述できます。

    -
    db.Where(
    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")
    +

    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.

    +

    サブクエリ

    Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

    +
    // Simple subquery
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
    // SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
    -

    複数カラムでのIN

    IN句で複数カラムを指定してレコードを取得することができます。

    -
    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"));
    +

    From句でのサブクエリ

    GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

    +
    // Using subquery in FROM clause
    db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    -

    名前付き引数

    GORMはsql.NamedArgmap[string]interface{}{}を使用した名前付き引数をサポートしています 。例:

    -
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

    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
    +

    条件をグループ化する

    Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

    +
    // Complex SQL query using Group Conditions
    db.Where(
    db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
    ).Or(
    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")
    -

    より詳細については、 Raw SQL and SQL Builder も参照してみてください。

    -

    取得結果をマップに代入

    GORMでは取得結果を map[string]interface{}[]map[string]interface{} に代入することができます。その際 ModelTable の指定を忘れないでください。例:

    -
    result := map[string]interface{}{}
    db.Model(&User{}).First(&result, "id = ?", 1)

    var results []map[string]interface{}
    db.Table("users").Find(&results)
    +

    複数カラムでのIN

    GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query.

    +
    // Using IN with multiple columns
    db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
    // SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
    -

    FirstOrInit

    条件に最初に一致するレコードを取得するか、指定された条件を使用して構造体のインスタンスを初期化します (構造体、map条件でのみ動作します)。

    -
    // ユーザを取得できないため、与えられた条件でユーザを初期化
    db.FirstOrInit(&user, User{Name: "non_existing"})
    // user -> User{Name: "non_existing"}

    // `name` = `jinzhu` の条件でユーザを取得できた
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}

    // `name` = `jinzhu` の条件でユーザを取得できた
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
    +

    名前付き引数

    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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

    +
    // Example using sql.NamedArg for named arguments
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // 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)
    // SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
    -

    レコードが見つからない場合のみ、struct を指定した属性で初期化できます。これらの Attrs(属性) はSQLクエリの生成には使用されません。

    -
    // Userが見つからないため、取得条件とAttrsで指定された属性で構造体を初期化
    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が見つからないため、取得条件と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で指定された属性は無視される
    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}
    +

    For more examples and details, see Raw SQL and SQL Builder

    +

    取得結果をマップに代入

    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.

    +
    // 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`
    -

    Assign メソッドを使用すると、レコードが見つかったかどうかにかかわらず、指定した値を構造体に割り当てます。このメソッドで指定した属性はレコード取得時のSQLクエリには使用されません。また、生成されたデータのデータベースへの登録も行われません。

    -
    // Userが見つからないため、取得条件とAssignで指定された属性で構造体を初期化
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // user -> User{Name: "non_existing", Age: 20}

    // `name` = `jinzhu` のUserが見つかったため、取得レコードの値とAssignで指定された値で構造体を生成
    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}
    +

    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.

    +
    // 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"} if not found

    // Retrieving a user named "jinzhu"
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

    // Using a map to specify the search condition
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    FirstOrCreate

    最初に一致するレコードを取得するか、または指定された条件で新しいレコードを作成します (構造体、マップ条件のみ使用可能です)。 RowsAffected は作成/更新されたレコード数を返します。

    -
    // User not found, create a new record with give conditions
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1

    // Found user with `name` = `jinzhu`
    result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
    // user -> User{ID: 111, Name: "jinzhu", "Age": 18}
    // result.RowsAffected // => 0
    +

    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.

    +
    // If no User is found, initialize with given conditions and additional attributes
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // user -> User{Name: "non_existing", Age: 20} if not found

    // If a User named "Jinzhu" is found, `Attrs` are ignored
    db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    Attrs メソッドを使用すると、レコードが見つからない場合に作成されるデータの属性をより多く指定することができます。このメソッドで指定した属性はレコード取得時のSQLクエリには使用されません。

    -
    // Userが見つからないため、取得条件とAttrsで指定された属性でレコードを作成
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // `name` = `jinzhu`のユーザが見つかったため、Attrsで指定された属性は無視される
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    +

    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.

    +
    // 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} if not found

    // If a User named "Jinzhu" is found, update the struct with Assign attributes
    db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
    -

    Assign メソッドを使用すると、レコードが見つかったどうかにかかわらず、指定した値をデータベースに登録します。

    -
    // Userが見つからないため、取得条件とAssignで指定された属性でレコードを作成
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // `name` = `jinzhu`のUserが見つかったため、Assignの値で取得したレコードを更新する
    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}
    +

    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

    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.

    +
    // Create a new record if not found
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // SQL: INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1 (record created)

    // 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)
    -

    Optimizer/Index Hints

    オプティマイザヒントを利用することで、特定のクエリ実行計画を選択するようオプティマイザを制御することができます。GORMでは、 gorm.io/hints でそれをサポートしています。例:

    -
    import "gorm.io/hints"

    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    +

    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.

    +
    // Create a new record with additional attributes if not found
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // If the user is found, `Attrs` are ignored
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu';
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    -

    クエリプランナーが最適なクエリを計画できていない場合、データベースにインデックスヒントを渡すことができます。

    -
    import "gorm.io/hints"

    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // SELECT * FROM `users` USE INDEX (`idx_user_name`)

    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`)"
    +

    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.

    +
    // Initialize and save new record with `Assign` attributes if not found
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // Update found record with `Assign` attributes
    db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // 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 Hints/Index/Comment を参照してください。

    -

    Iteration

    GORMは行ごとのイテレーション処理をサポートしています。

    -
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    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
    db.ScanRows(rows, &user)

    // do something
    }
    +

    Optimizer/Index 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.

    +
    import "gorm.io/hints"

    // Using an optimizer hint to set a maximum execution time
    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    -

    FindInBatches

    バッチ処理におけるクエリやレコード処理を行うことができます。

    -
    // batch size 100
    result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
    for _, result := range results {
    // batch processing found records
    }

    tx.Save(&results)

    tx.RowsAffected // number of records in this batch

    batch // Batch 1, 2, 3

    // returns error will stop future batches
    return nil
    })

    result.Error // returned error
    result.RowsAffected // processed records count in all batches
    +

    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.

    +
    import "gorm.io/hints"

    // Suggesting the use of a specific index
    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
    -

    Query Hooks

    GORMではレコード取得のフック処理に AfterFind を利用することができます。このメソッドはレコードを取得時に呼び出されます。 詳細は Hooks を参照してください。

    -
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    if u.Role == "" {
    u.Role = "user"
    }
    return
    }
    +

    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 in the GORM documentation.

    +

    Iteration

    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.

    +
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    defer rows.Close()

    for rows.Next() {
    var user User
    // ScanRows scans a row into a struct
    db.ScanRows(rows, &user)

    // Perform operations on each user
    }
    -

    Pluck

    1つのカラムからのみ値を取得してsliceに代入することができます。複数のカラムから値を取得する場合は、 SelectScan を代わりに利用する必要があります。

    -
    var ages []int64
    db.Model(&users).Pluck("age", &ages)

    var names []string
    db.Model(&User{}).Pluck("name", &names)

    db.Table("deleted_users").Pluck("name", &names)

    // Distinct Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SELECT DISTINCT `name` FROM `users`

    // 2つ以上のカラムを指定する場合は、以下のように `Scan` もしくは `Find` を利用します
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    +

    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.

    +
    // 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 provides the count of records in the current batch
    // The variable 'batch' indicates the current batch number

    // Returning an error will stop further batch processing
    return nil
    })

    // result.Error contains any errors encountered during batch processing
    // result.RowsAffected provides the count of all processed records across batches
    -

    Scopes

    共通で使用されるクエリ処理は関数化することができます。Scopes を使用すると、関数化されたクエリ処理を指定することができます。

    -
    func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
    return db.Where("amount > ?", 1000)
    }

    func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
    return db.Where("pay_mode_sign = ?", "C")
    }

    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 {
    return db.Where("status IN (?)", status)
    }
    }

    db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
    // Find all credit card orders and amount greater than 1000

    db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
    // Find all COD orders and amount greater than 1000

    db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
    // Find all paid, shipped orders that amount greater than 1000
    +

    FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

    +

    Query Hooks

    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 in the GORM documentation.

    +
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    // Custom logic after finding a user
    if u.Role == "" {
    u.Role = "user" // Set default role if not specified
    }
    return
    }

    // Usage of AfterFind hook happens automatically when a User is queried
    -

    詳細については Scopes を確認してください。

    -

    Count

    条件に一致したレコード数を取得することができます。

    -
    var count int64
    db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

    db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

    db.Table("deleted_users").Count(&count)
    // SELECT count(1) FROM deleted_users;

    // Count with Distinct
    db.Model(&User{}).Distinct("name").Count(&count)
    // SELECT COUNT(DISTINCT(`name`)) FROM `users`

    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SELECT count(distinct(name)) FROM deleted_users

    // Count with Group
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    count // => 3
    +

    Pluck

    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 or Find instead.

    +
    // Retrieving ages of all users
    var ages []int64
    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)

    // Using Distinct with Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SQL: SELECT DISTINCT `name` FROM `users`

    // Querying multiple columns
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    + +

    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.

    +
    // 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")
    }

    // 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.

    +
    // 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)
    + +

    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 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.

    +
    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'

    // Counting records in a different table
    db.Table("deleted_users").Count(&count)
    // SQL: SELECT count(1) FROM deleted_users
    + +

    Count with Distinct and Group

    GORM also allows counting distinct values and grouping results.

    +
    // Counting distinct names
    db.Model(&User{}).Distinct("name").Count(&count)
    // SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

    // Counting distinct values with a custom select
    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SQL: SELECT count(distinct(name)) FROM deleted_users

    // Counting grouped records
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    // Count after grouping by name
    // count => 3
    @@ -227,7 +253,7 @@

    - + @@ -341,7 +367,7 @@

    Gold Sponsors

    内容 -
    1. 便利なフィールドの選択
    2. ロック (FOR UPDATE)
    3. サブクエリ
      1. From句でのサブクエリ
    4. 条件をグループ化する
    5. 複数カラムでのIN
    6. 名前付き引数
    7. 取得結果をマップに代入
    8. FirstOrInit
    9. FirstOrCreate
    10. Optimizer/Index Hints
    11. Iteration
    12. FindInBatches
    13. Query Hooks
    14. Pluck
    15. Scopes
    16. Count
    +
    1. 便利なフィールドの選択
    2. Locking
    3. サブクエリ
      1. From句でのサブクエリ
    4. 条件をグループ化する
    5. 複数カラムでのIN
    6. 名前付き引数
    7. 取得結果をマップに代入
    8. FirstOrInit
      1. Using Attrs for Initialization
      2. Using Assign for Attributes
    9. FirstOrCreate
      1. Using Attrs with FirstOrCreate
      2. Using Assign with FirstOrCreate
    10. Optimizer/Index Hints
      1. Index Hints
    11. Iteration
    12. FindInBatches
    13. Query Hooks
    14. Pluck
    15. Scopes
      1. Defining Scopes
      2. Applying Scopes in Queries
    16. Count
      1. Getting the Count of Matched Records
      2. Count with Distinct and Group
    diff --git a/ja_JP/docs/associations.html b/ja_JP/docs/associations.html index 85de9c04818..4e1afcb76eb 100644 --- a/ja_JP/docs/associations.html +++ b/ja_JP/docs/associations.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,62 +141,101 @@

    アソシエーション

    -

    アソシエーションの自動作成/更新

    GORMはレコードの作成・更新時にUpsertを使用して自動的に関連データとその参照を保存します。

    -
    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"},
    },
    }

    db.Create(&user)
    // BEGIN TRANSACTION;
    // 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 ('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 を使用してください。

    -
    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);
    // ...
    - -

    アソシエーションの自動作成/更新をスキップ

    作成/更新時のアソシエーションレコードの自動保存をスキップするには、 Select または Omit を使用します。例:

    -
    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"},
    },
    }

    db.Select("Name").Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    db.Omit("BillingAddress").Create(&user)
    // ユーザ作成時に BillingAddress の作成をスキップする

    db.Omit(clause.Associations).Create(&user)
    // ユーザ作成時に全てのアソシエーションの保存をスキップする
    - -

    注意: many2many(多対多)のアソシエーションの場合、中間テーブルのレコードを作成するより前に、アソシエーション先テーブルのレコードをupsertします。アソシエーション先へのupsertを省略したい場合は、以下のように記載します。

    -
    db.Omit("Languages.*").Create(&user)
    - -

    次のコードはアソシエーション先のレコードの作成と中間テーブルでの参照の作成の両方をスキップします。

    -
    db.Omit("Languages").Create(&user)
    - -

    アソシエーションフィールドの選択/省略

    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    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
    db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

    db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
    - -

    Association Mode

    Association Mode には、データの関連を処理するためによく使用されるヘルパーメソッドが含まれています。

    -
    // Start Association Mode
    var user User
    db.Model(&user).Association("Languages")
    // `user` は大元のモデルであるため、主キーを含んでいる必要があります
    // `Languages` は関連フィールドの名前です
    // 上記2つの条件が揃うと、 AssociationMode は正常に開始されます。条件が揃っていない場合はエラーが返却されます。
    db.Model(&user).Association("Languages").Error
    - -

    関連の取得

    関連レコードを取得することができます。

    -
    db.Model(&user).Association("Languages").Find(&languages)
    - -

    条件を指定して関連を取得することも可能です。

    -
    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)
    - -

    関連の追加

    many to manyhas many の場合には関連を追加し、 has onebelongs to の場合には現在の関連を置き換えます。

    -
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

    db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
    - -

    関連を置き換える

    現在の関連を新しいもので置き換えることができます。

    -
    db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
    - -

    関連を削除する

    引数で指定された値との関連がある場合、その値との関連を削除します。削除されるのは参照のみであり、参照先オブジェクトのレコードはDBから削除されません。

    -
    db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
    db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
    - -

    関連を全て削除する

    関連データとの参照を全て削除することができます。削除されるのは参照のみであり、参照先のレコードは削除されません。

    -
    db.Model(&user).Association("Languages").Clear()
    - -

    関連の数を取得する

    現在の関連の数を取得することができます。

    -
    db.Model(&user).Association("Languages").Count()

    // 条件付きで件数を数える
    codes := []string{"zh-CN", "en-US", "ja-JP"}
    db.Model(&user).Where("code IN ?", codes).Association("Languages").Count()
    - -

    一括データ処理

    Association Mode はデータの一括処理もサポートしています。例:

    -
    // 全てのユーザの役割を全て取得する
    db.Model(&users).Association("Role").Find(&roles)

    // 全ユーザのチームからユーザAを削除する
    db.Model(&users).Association("Team").Delete(&userA)

    // 重複を取り除いた全ユーザのチームの件数を取得する
    db.Model(&users).Association("Team").Count()

    // 一括処理で `Append` や `Replace` を使用する場合は、それらの関数の引数とデータの数(以下でいう users の数)が一致している必要があります。
    // 一致していない場合はエラーが返却されます。
    var users = []User{user1, user2, user3}

    // 例1: 3人のユーザABCを新たに追加するとした場合、以下のコードでは user1のチームにユーザA、user2のチームにユーザB、user3のチームにユーザABCを全員追加します
    db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC})

    // 例2: user1のチームをユーザAのみに、user2のチームをユーザBのみに、user3のチームをユーザABCのみにそれぞれリセットします
    db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC})
    - -

    関連レコードを削除する

    デフォルトでは、gorm.AssociationReplace/Delete/Clearは参照のみを削除します。つまり、古いアソシエーションの外部キーをNULLに設定します。

    -

    Unscopedを使用することで、オブジェクトを削除することができます。(ManyToManyの場合は挙動に変更はありません)

    -

    削除方法は、 gorm.DB の内部で判定します。

    -
    // Soft delete
    // UPDATE `languages` SET `deleted_at`= ...
    db.Model(&user).Association("Languages").Unscoped().Clear()

    // Delete permanently
    // DELETE FROM `languages` WHERE ...
    db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
    - -

    Selectを使って削除する

    レコード削除時に Select を使用することで、has one / has many / many2many 関係にある関連も同時に削除することができます。例:

    -
    // ユーザ削除時に ユーザのアカウントも削除します
    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)
    - -

    注意: レコード削除時の主キーが非ゼロ値の場合のみ、関連レコードの削除が可能となります。GORMは指定の関連を削除するための条件として主キーを使用するためです。

    -
    // 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` のユーザが削除され、そのユーザのアカウントも削除されます
    - -

    Association Tags

    +

    アソシエーションの自動作成/更新

    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.

    +
    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"},
    },
    }

    // 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;
    // 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 ('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)
    + +

    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.

    +
    // Update a user and fully update all its associations
    db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
    // 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.

    +

    アソシエーションの自動作成/更新をスキップ

    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.

    +
    user := User{
    // User and associated data
    }

    // Only include the 'Name' field when creating the user
    db.Select("Name").Create(&user)
    // 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.

    +
    // Skip creating the 'BillingAddress' when creating the user
    db.Omit("BillingAddress").Create(&user)

    // Skip all associations when creating the user
    db.Omit(clause.Associations).Create(&user)
    + +

    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 .*:

    +
    // Skip upserting 'Languages' associations
    db.Omit("Languages.*").Create(&user)
    + +

    To skip creating both the association and its references:

    +
    // Skip creating 'Languages' associations and their references
    db.Omit("Languages").Create(&user)
    + +

    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.

    +
    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
    }

    // 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.

    +
    // 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: 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.

    +
    // 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
    + +

    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.

    +
    var user User
    db.Model(&user).Association("Languages")
    // Check for errors
    error := db.Model(&user).Association("Languages").Error
    + +

    Finding Associations

    Retrieve associated records with or without additional conditions.

    +
    // Simple find
    db.Model(&user).Association("Languages").Find(&languages)

    // Find with conditions
    codes := []string{"zh-CN", "en-US", "ja-JP"}
    db.Model(&user).Where("code IN ?", codes).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.

    +
    // Append new languages
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    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.

    +
    // 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.

    +
    // 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.

    +
    // Clear all languages
    db.Model(&user).Association("Languages").Clear()
    + +

    Counting Associations

    Get the count of current associations, with or without conditions.

    +
    // 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.
    • +
    +
    db.Model(&users).Association("Role").Find(&roles)
    + +
      +
    • Deleting Associations: Remove specific associations across multiple records.
    • +
    +
    db.Model(&users).Association("Team").Delete(&userA)
    + +
      +
    • Counting Associations: Get the count of associations for a batch of records.
    • +
    +
    db.Model(&users).Association("Team").Count()
    + +
      +
    • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
    • +
    +
    var users = []User{user1, user2, 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})

    // 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})
    + +

    関連レコードを削除する

    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.

    +
      +
    • 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.
    • +
    +

    Modifying Deletion Behavior with Unscoped

    For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

    +
      +
    • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
    • +
    +
    db.Model(&user).Association("Languages").Unscoped().Clear()
    + +
      +
    • Permanent Delete: Physically deletes the association records from the database.
    • +
    +
    // db.Unscoped().Model(&user)
    db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
    + +

    Association Tags

    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.

    + @@ -204,36 +243,36 @@

    - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
    タグ
    foreignKeyテーブル結合時に、これを指定したモデルの外部キーとして使用するフィールド名を指定できますforeignKeySpecifies the column name of the current model used as a foreign key in the join table.
    referencesテーブル結合時に、参照先テーブルの外部キーとして使用するフィールド名を指定できますreferencesIndicates the column name in the reference table that the foreign key of the join table maps to.
    polymorphicモデル名などのポリモーフィック関連の種別を指定できますpolymorphicDefines the polymorphic type, typically the model name.
    polymorphicValueポリモーフィック関連ので使用される値を指定できます。デフォルトはテーブル名です。polymorphicValueSets the polymorphic value, usually the table name, if not specified otherwise.
    many2many結合テーブル名を指摘できますmany2manyNames the join table used in a many-to-many relationship.
    joinForeignKeyテーブル結合時に、これを指定したモデルの外部キーとして使用する結合テーブルのカラム名を指定できますjoinForeignKeyIdentifies the foreign key column in the join table that maps back to the current model’s table.
    joinReferencesテーブル結合時に、参照先テーブルの外部キーとして使用する結合テーブルのカラム名を指定できますjoinReferencesPoints to the foreign key column in the join table that links to the reference model’s table.
    constraint参照制約を指定できます。例:OnUpdate, OnDeleteconstraintSpecifies relational constraints like OnUpdate, OnDelete for the association.
    @@ -248,7 +287,7 @@

    @@ -362,7 +401,7 @@

    Gold Sponsors

    内容 -
    1. アソシエーションの自動作成/更新
    2. アソシエーションの自動作成/更新をスキップ
    3. アソシエーションフィールドの選択/省略
    4. Association Mode
      1. 関連の取得
      2. 関連の追加
      3. 関連を置き換える
      4. 関連を削除する
      5. 関連を全て削除する
      6. 関連の数を取得する
      7. 一括データ処理
    5. 関連レコードを削除する
    6. Selectを使って削除する
    7. Association Tags
    +
    1. アソシエーションの自動作成/更新
      1. Auto-Saving Associations on Create
      2. Updating Associations with FullSaveAssociations
    2. アソシエーションの自動作成/更新をスキップ
      1. Using Select to Include Specific Fields
      2. Using Omit to Exclude Fields or Associations
    3. アソシエーションフィールドの選択/省略
    4. 関連を削除する
    5. Association Mode
      1. Finding Associations
      2. Appending Associations
      3. Replacing Associations
      4. Deleting Associations
      5. Clearing Associations
      6. Counting Associations
      7. Batch Data Handling
    6. 関連レコードを削除する
      1. Modifying Deletion Behavior with Unscoped
    7. Association Tags
    diff --git a/ja_JP/docs/belongs_to.html b/ja_JP/docs/belongs_to.html index 36c47b02e55..33992cb9d34 100644 --- a/ja_JP/docs/belongs_to.html +++ b/ja_JP/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

    - + diff --git a/ja_JP/docs/changelog.html b/ja_JP/docs/changelog.html index df2380cee87..4e740be6a51 100644 --- a/ja_JP/docs/changelog.html +++ b/ja_JP/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/ja_JP/docs/composite_primary_key.html b/ja_JP/docs/composite_primary_key.html index 8b49e128faf..afdcd0e0c12 100644 --- a/ja_JP/docs/composite_primary_key.html +++ b/ja_JP/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

    複合主キー

    - +
    diff --git a/ja_JP/docs/connecting_to_the_database.html b/ja_JP/docs/connecting_to_the_database.html index 132cef88c8b..058df1800c7 100644 --- a/ja_JP/docs/connecting_to_the_database.html +++ b/ja_JP/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

    - + diff --git a/ja_JP/docs/constraints.html b/ja_JP/docs/constraints.html index aa4b22e1185..ce4529783e1 100644 --- a/ja_JP/docs/constraints.html +++ b/ja_JP/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - + diff --git a/ja_JP/docs/context.html b/ja_JP/docs/context.html index 871398502d1..618c2c35781 100644 --- a/ja_JP/docs/context.html +++ b/ja_JP/docs/context.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,25 @@

    Context

    -

    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.

    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.

    tx := db.WithContext(ctx)
    tx.First(&user, 1)
    tx.Model(&user).Update("role", "admin")
    -

    Context timeout

    timeoutを設定したcontextを db.WithContext に渡すことで、時間がかかるクエリのタイムアウト時間を設定する事ができます。例:

    +

    Context Timeout

    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.

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    db.WithContext(ctx).Find(&users)
    -

    Hooks/Callbacks内でのcontextの使用

    Statement から Context にアクセスすることが可能です。例:

    -
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ...
    return
    }
    +

    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.

    +
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ... use context
    return
    }
    -

    Chi Middlewareの例

    継続セッションはAPIリクエストの処理に役立ちます。例えば、ミドルウェア内でのタイムアウト設定をしたContextを使って、*gorm.DBを設定できます。 そして、その *gorm.DB を使ってリクエストの処理を行います。

    -

    Chi ミドルウェアの例を以下に示します。

    -
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    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
    })

    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
    })
    +

    Integration with 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.

    +
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    // Router setup
    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    // Route handlers
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })

    r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })
    -

    WithContextContextを設定するのはゴルーチンセーフの処理です。 詳細はSessionを参照してください。

    -
    - -

    Logger

    Loggerも Context もに対応しており、ログのトラッキングに使用することができます。詳細については Logger を参照してください。

    +

    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 in GORM.

    +

    Logger Integration

    GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

    +

    Refer to Logger documentation for more details.

    @@ -174,7 +172,7 @@

    - + @@ -288,7 +286,7 @@

    Gold Sponsors

    内容 -
    1. シングルセッション
    2. 継続セッション
    3. Context timeout
    4. Hooks/Callbacks内でのcontextの使用
    5. Chi Middlewareの例
    6. Logger
    +
    1. シングルセッション
    2. Continuous Session Mode
    3. Context Timeout
    4. Hooks/Callbacks内でのcontextの使用
    5. Integration with Chi Middleware
    6. Logger Integration
    diff --git a/ja_JP/docs/conventions.html b/ja_JP/docs/conventions.html index ec8e00b8eea..4d0127ce833 100644 --- a/ja_JP/docs/conventions.html +++ b/ja_JP/docs/conventions.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    // Create table `deleted_users` with struct User's fields
    db.Table("deleted_users").AutoMigrate(&User{})

    // Query data from another table
    var deletedUsers []User
    db.Table("deleted_users").Find(&deletedUsers)
    // SELECT * FROM deleted_users;

    db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
    // DELETE FROM deleted_users WHERE name = 'jinzhu';

    FROM句でサブクエリを使用する方法については、 From SubQuery を参照してください。

    -

    NamingStrategy

    TableName, ColumnName, JoinTableName, RelationshipFKName, CheckerName, IndexNameの生成に使用されているNamingStrategyをオーバーライドすることで、デフォルトの命名規則を変更できます。詳細はGORM Configを参照してください。

    +

    NamingStrategy

    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 for details

    カラム名

    規約に従い、データベースのカラム名はフィールド名のsnake_caseを使用します。

    type User struct {
      ID        uint      // column name is `id`
      Name      string    // column name is `name`
      Birthday  time.Time // column name is `birthday`
      CreatedAt time.Time // column name is `created_at`
    }
    @@ -194,7 +194,7 @@

    - + diff --git a/ja_JP/docs/create.html b/ja_JP/docs/create.html index d9f53730c7d..f7f3ae22c9f 100644 --- a/ja_JP/docs/create.html +++ b/ja_JP/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -155,7 +155,7 @@

    db.Omit("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
    -

    一括作成

    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.

    var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
    db.Create(&users)

    for _, user := range users {
    user.ID // 1,2,3
    }

    You can specify batch size when creating with CreateInBatches, e.g:

    @@ -220,7 +220,7 @@

    - + diff --git a/ja_JP/docs/data_types.html b/ja_JP/docs/data_types.html index 6ea6d7e5e8f..4ab02b41fc3 100644 --- a/ja_JP/docs/data_types.html +++ b/ja_JP/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

    diff --git a/ja_JP/docs/dbresolver.html b/ja_JP/docs/dbresolver.html index db1612e60b5..49fbd187488 100644 --- a/ja_JP/docs/dbresolver.html +++ b/ja_JP/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    トランザクション

    When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

    But you can specifies which DB to use before starting a transaction, for example:

    -
    // Start transaction based on default replicas db
    tx := DB.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := DB.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
    +
    // Start transaction based on default replicas db
    tx := db.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := db.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

    負荷分散

    GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

    type Policy interface {
    Resolve([]gorm.ConnPool) gorm.ConnPool
    }
    @@ -183,7 +183,7 @@

    - + diff --git a/ja_JP/docs/delete.html b/ja_JP/docs/delete.html index d65f199081d..a97fa7e6cad 100644 --- a/ja_JP/docs/delete.html +++ b/ja_JP/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

    - + diff --git a/ja_JP/docs/error_handling.html b/ja_JP/docs/error_handling.html index 635ca57748e..d83cd080567 100644 --- a/ja_JP/docs/error_handling.html +++ b/ja_JP/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

    エラーハンドリング

    -

    Goでは、エラー処理が重要です。

    -

    Finisher Methods の後にエラーチェックを行うことをおすすめします。

    -

    Error Handling

    GORMでのエラーハンドリングは、メソッドチェーン可能なAPIのため、ふつうのGoコードとは異なります。

    -

    なんらかのエラーが発生した場合、GORMは *gorm.DBError フィールドに設定します。以下のようにチェックする必要があります:

    -
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // ここでエラーハンドリング
    }
    - -

    または

    -
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // ここでエラーハンドリング
    }
    - -

    ErrRecordNotFound

    GORMは、First, Last, Takeでデータの検索に失敗した場合にErrRecordNotFoundを返します。もし複数のエラーが発生した場合は、errors.IsErrRecordNotFoundエラーを確認することができます。たとえば以下のように使います

    -
    // RecordNotFound エラーが返却されたかチェックする
    err := db.First(&user, 100).Error
    errors.Is(err, gorm.ErrRecordNotFound)
    -

    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.

    +

    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.

    +

    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.

    +

    After a chain of methods, it’s crucial to check the Error field:

    +
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // Handle error...
    }
    + +

    Or alternatively:

    +
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // Handle error...
    }
    + +

    ErrRecordNotFound

    GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

    +
    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

    +
      +
    • Example: Handling MySQL Error Codes
    • +
    +
    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.

    db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
    -

    Errors

    Errors List

    +
      +
    • ErrDuplicatedKey
    • +
    +

    This error occurs when an insert operation violates a unique constraint:

    +
    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:

    +
    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

    For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

    @@ -168,7 +187,7 @@

    - + @@ -282,7 +301,7 @@

    Gold Sponsors

    内容 -
    1. Error Handling
    2. ErrRecordNotFound
    3. Dialect Translated Errors
    4. Errors
    +
    1. Basic Error Handling
    2. ErrRecordNotFound
    3. Handling Error Codes
    4. Dialect Translated Errors
    5. Errors
    diff --git a/ja_JP/docs/generic_interface.html b/ja_JP/docs/generic_interface.html index b804333dc70..c7bdc5ab023 100644 --- a/ja_JP/docs/generic_interface.html +++ b/ja_JP/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

    - + diff --git a/ja_JP/docs/gorm_config.html b/ja_JP/docs/gorm_config.html index 906e432e7c3..984f3d73ed8 100644 --- a/ja_JP/docs/gorm_config.html +++ b/ja_JP/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/ja_JP/docs/has_one.html b/ja_JP/docs/has_one.html index b79ce6a3036..09b25e2a345 100644 --- a/ja_JP/docs/has_one.html +++ b/ja_JP/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

    - + diff --git a/ja_JP/docs/hints.html b/ja_JP/docs/hints.html index 9604a3b6e9d..185cddd8659 100644 --- a/ja_JP/docs/hints.html +++ b/ja_JP/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

    - + diff --git a/ja_JP/docs/hooks.html b/ja_JP/docs/hooks.html index 9a18cd09be3..0dab7db6f15 100644 --- a/ja_JP/docs/hooks.html +++ b/ja_JP/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

    - + diff --git a/ja_JP/docs/index.html b/ja_JP/docs/index.html index 8bcd0f546a9..d4db1395327 100644 --- a/ja_JP/docs/index.html +++ b/ja_JP/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

    - + diff --git a/ja_JP/docs/indexes.html b/ja_JP/docs/indexes.html index cb6876097a6..fa9a599c22a 100644 --- a/ja_JP/docs/indexes.html +++ b/ja_JP/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

    - + diff --git a/ja_JP/docs/logger.html b/ja_JP/docs/logger.html index ce9397a8b7a..87fee4c31ea 100644 --- a/ja_JP/docs/logger.html +++ b/ja_JP/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

    - + diff --git a/ja_JP/docs/many_to_many.html b/ja_JP/docs/many_to_many.html index e5d6acf4168..6e25bf25097 100644 --- a/ja_JP/docs/many_to_many.html +++ b/ja_JP/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    <

    注意: 結合テーブルをカスタマイズする場合、結合テーブルの外部キーを複合主キーにする、あるいは外部キーに複合ユニークインデックスを貼る必要があります。

    -
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addressses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // Change model Person's field Addresses' join table to PersonAddress
    // PersonAddress must defined all required foreign keys or it will raise error
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
    +
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addresses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // Change model Person's field Addresses' join table to PersonAddress
    // PersonAddress must defined all required foreign keys or it will raise error
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

    外部キー制約

    constraint タグを使用することで、 OnUpdate, OnDelete の制約を掛けることができます。指定した制約はGORMを使ったマイグレーション実行時に作成されます。例:

    type User struct {
    gorm.Model
    Languages []Language `gorm:"many2many:user_speaks;"`
    }

    type Language struct {
    Code string `gorm:"primarykey"`
    Name string
    }

    // CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
    @@ -191,7 +191,7 @@

    - + diff --git a/ja_JP/docs/method_chaining.html b/ja_JP/docs/method_chaining.html index 9b3d8725f98..12c53043cbd 100644 --- a/ja_JP/docs/method_chaining.html +++ b/ja_JP/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

    Method Chaining

    -

    GORMはメソッドチェーンが可能なため、次のようなコードを書くことができます。

    +

    GORM’s method chaining feature allows for a smooth and fluent style of coding. Here’s an example:

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
    -

    GORMには Chain Method, Finisher Method, New Session Method という3種類のメソッドがあります:

    -

    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:

    -
    queryDB := DB.Where("name = ?", "jinzhu")

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // 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:

    -
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 20
    - -

    Chain Method

    Chain methods are methods to modify or add Clauses to current Statement, like:

    -

    Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

    -

    Here is the full lists, also check out the SQL Builder 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 here.

    -

    New Session Method

    GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new 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:
    // 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:
    // 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:
    // SELECT * FROM users;
    - -

    (Bad) Example 2:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is safe to 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

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    - -

    Example 3:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is 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

    // good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // good case
    tx.Where("age = ?", 28).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
    +

    Method Categories

    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. Also, the SQL Builder 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.

    +

    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 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

    queryDB := DB.Where("name = ?", "jinzhu")

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query with unintended compounded condition
    queryDB.Where("age > ?", 20).First(&user2)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
    + +

    Example of Safe Reuse

    To safely reuse a *gorm.DB instance, use a New Session Method:

    +
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query, safely isolated
    queryDB.Where("age > ?", 20).First(&user2)
    // 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.

    +

    Examples for Clarity

    Let’s clarify with a few examples:

    +
      +
    • Example 1: Safe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
    // 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)
    // 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)
    // Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
    // SELECT * FROM users;
    + +

    In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

    +
      +
    • (Bad) Example 2: Unsafe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized *gorm.DB, safe for initial reuse.

    tx := db.Where("name = ?", "jinzhu")
    // `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // Reuses 'tx' correctly for a single logical operation, executing:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // Bad case
    tx.Where("age = ?", 28).Find(&users)
    // Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    + +

    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
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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.

    @@ -180,7 +210,7 @@

    - + @@ -294,7 +324,7 @@

    Gold Sponsors

    内容 -
    1. Chain Method
    2. Finisher Method
    3. New Session Method
    +
    1. Method Categories
      1. Chain Methods
      2. Finisher Methods
      3. New Session Methods
    2. Reusability and Safety
      1. Example of Unsafe Reuse
      2. Example of Safe Reuse
    3. Examples for Clarity
    diff --git a/ja_JP/docs/migration.html b/ja_JP/docs/migration.html index 0074fbd3348..2fb2d22a810 100644 --- a/ja_JP/docs/migration.html +++ b/ja_JP/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

    - + diff --git a/ja_JP/docs/models.html b/ja_JP/docs/models.html index 0cc93e30761..6e6ad9b22ef 100644 --- a/ja_JP/docs/models.html +++ b/ja_JP/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,16 +141,45 @@

    モデルを宣言する

    -

    モデルを宣言する

    モデルは Goの基本型、(基本型の)ポインタ/エイリアス、 Scanner および Valuer インターフェイスを実装するカスタム型からなる通常の構造体です。

    -

    例:

    -
    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
    }
    - -

    規約

    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

    -

    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

    -

    gorm.Model

    GORMは gorm.Model 構造体を定義しています。これには ID, CreatedAt, UpdatedAt, DeletedAt のフィールドが含まれます。

    +

    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 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 and Valuer interfaces from the database/sql package

    +

    Consider the following example of a User model:

    +
    type User struct {
    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 for a detailed explanation

    +

    規約

      +
    1. Primary Key: GORM uses a field named ID as the default primary key for each model.

      +
    2. +
    3. 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.

      +
    4. +
    5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

      +
    6. +
    7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

      +
    8. +
    +

    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.

    +

    gorm.Model

    GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

    // gorm.Modelの定義
    type Model struct {
    ID uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    }
    -

    この構造体を埋め込むことで、これらのフィールドを自身の構造体に含めることができます。 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

      +
    • +
    • 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).
      • +
      +
    • +

    高度な機能

    フィールドレベルの権限

    Exported fields have all permissions when doing CRUD with GORM, and GORM allows you to change the field-level permission with tag, so you can make a field to be read-only, write-only, create-only, update-only or ignored

    注意 GORM Migrator を使用してテーブルを作成した場合、除外設定されたフィールドは作成されません

    @@ -286,7 +315,7 @@

    - + @@ -400,7 +429,7 @@

    Gold Sponsors

    内容 -
    1. モデルを宣言する
    2. 規約
    3. gorm.Model
    4. 高度な機能
      1. フィールドレベルの権限
      2. 作成・更新日時のトラッキング/Unix (ミリ・ナノ) 秒でのトラッキング
      3. 構造体の埋め込み
      4. フィールドに指定可能なタグ
      5. アソシエーションで使用できるタグ
    +
    1. モデルを宣言する
      1. 規約
      2. gorm.Model
    2. 高度な機能
      1. フィールドレベルの権限
      2. 作成・更新日時のトラッキング/Unix (ミリ・ナノ) 秒でのトラッキング
      3. 構造体の埋め込み
      4. フィールドに指定可能なタグ
      5. アソシエーションで使用できるタグ
    diff --git a/ja_JP/docs/performance.html b/ja_JP/docs/performance.html index 2dd3ed5af75..f0fbde9a2ab 100644 --- a/ja_JP/docs/performance.html +++ b/ja_JP/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

    diff --git a/ja_JP/docs/preload.html b/ja_JP/docs/preload.html index a2516bd77e3..a79472ed680 100644 --- a/ja_JP/docs/preload.html +++ b/ja_JP/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

    - + diff --git a/ja_JP/docs/prometheus.html b/ja_JP/docs/prometheus.html index 981d6e8f088..85525d631c2 100644 --- a/ja_JP/docs/prometheus.html +++ b/ja_JP/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - + diff --git a/ja_JP/docs/query.html b/ja_JP/docs/query.html index fca823c2777..fbb232188f1 100644 --- a/ja_JP/docs/query.html +++ b/ja_JP/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

    - + diff --git a/ja_JP/docs/scopes.html b/ja_JP/docs/scopes.html index 6b22edbb66f..29abe5603b8 100644 --- a/ja_JP/docs/scopes.html +++ b/ja_JP/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/ja_JP/docs/security.html b/ja_JP/docs/security.html index 131a7c1d351..b88cdd81c40 100644 --- a/ja_JP/docs/security.html +++ b/ja_JP/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

    インライン条件

    // エスケープされる
    db.First(&user, "name = ?", userInput)

    // SQLインジェクションが起こりうる
    db.First(&user, fmt.Sprintf("name = %v", userInput))

    ユーザ入力による主キーの数値を使用してオブジェクトを取得する場合は、変数の型を確認するべきです。

    -
    userInputID := "1=1;drop table users;"
    // safe, return error
    id,err := strconv.Atoi(userInputID)
    if err != nil {
    return error
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;
    +
    userInputID := "1=1;drop table users;"
    // safe, return error
    id, err := strconv.Atoi(userInputID)
    if err != nil {
    return err
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;

    SQLインジェクションが発生し得るメソッド

    いくつかの機能をサポートするために、一部の入力はエスケープされません。それらのメソッドでユーザーの入力を使用する場合は注意が必要です。

    db.Select("name; drop table users;").First(&user)
    db.Distinct("name; drop table users;").First(&user)

    db.Model(&user).Pluck("name; drop table users;", &names)

    db.Group("name; drop table users;").First(&user)

    db.Group("name").Having("1 = 1;drop table users;").First(&user)

    db.Raw("select name from users; drop table users;").First(&user)

    db.Exec("select name from users; drop table users;")

    db.Order("name; drop table users;").First(&user)
    @@ -169,7 +169,7 @@

    - + diff --git a/ja_JP/docs/serializer.html b/ja_JP/docs/serializer.html index 50480962d92..af76a35d321 100644 --- a/ja_JP/docs/serializer.html +++ b/ja_JP/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

    - + diff --git a/ja_JP/docs/session.html b/ja_JP/docs/session.html index c89f38eb558..1d2ad8d6d4f 100644 --- a/ja_JP/docs/session.html +++ b/ja_JP/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

    diff --git a/ja_JP/docs/settings.html b/ja_JP/docs/settings.html index 17dea71866e..bfa1e2bd7e4 100644 --- a/ja_JP/docs/settings.html +++ b/ja_JP/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - + diff --git a/ja_JP/docs/sharding.html b/ja_JP/docs/sharding.html index cba6aa61d97..a77ae1e2afc 100644 --- a/ja_JP/docs/sharding.html +++ b/ja_JP/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    - + diff --git a/ja_JP/docs/sql_builder.html b/ja_JP/docs/sql_builder.html index f5f52426d4a..dcd8291cc77 100644 --- a/ja_JP/docs/sql_builder.html +++ b/ja_JP/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

    diff --git a/ja_JP/docs/transactions.html b/ja_JP/docs/transactions.html index bc78554a0bc..865e6626df7 100644 --- a/ja_JP/docs/transactions.html +++ b/ja_JP/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

    db.Transaction(func(tx *gorm.DB) error {
    // トランザクション内でのデータベース処理を行う(ここでは `db` ではなく `tx` を利用する)
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 何らかのエラーを返却するとロールバックされる
    return err
    }

    if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
    }

    // nilが返却されるとトランザクション内の全処理がコミットされる
    return nil
    })

    トランザクションのネスト

    GORMはネストしたトランザクションをサポートしており、トランザクションのスコープ内で実行されるサブセットをロールバックすることができます。例:

    -
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3
    +
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx3 *gorm.DB) error {
    tx3.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3

    トランザクションを手動で制御する

    Gormでは、トランザクションを制御する関数 (commit / rollback) を直接呼び出すことができます。

    // begin a transaction
    tx := db.Begin()

    // do some database operations in the transaction (use 'tx' from this point, not 'db')
    tx.Create(...)

    // ...

    // rollback the transaction in case of error
    tx.Rollback()

    // Or commit the transaction
    tx.Commit()
    @@ -169,7 +169,7 @@

    - + diff --git a/ja_JP/docs/update.html b/ja_JP/docs/update.html index e51bfba9a42..b1fbd77a889 100644 --- a/ja_JP/docs/update.html +++ b/ja_JP/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

    - + diff --git a/ja_JP/docs/v2_release_note.html b/ja_JP/docs/v2_release_note.html index 687a45d9fcc..359e41aacc5 100644 --- a/ja_JP/docs/v2_release_note.html +++ b/ja_JP/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

    - + diff --git a/ja_JP/docs/write_driver.html b/ja_JP/docs/write_driver.html index bb95b859684..68fd28888bb 100644 --- a/ja_JP/docs/write_driver.html +++ b/ja_JP/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

    ドライバの作成

    -

    新しいドライバを作成する

    GORMは sqlite, mysql, postgres, sqlserver を公式にサポートしています。

    -

    いくつかのデータベースは mysql または postgres と互換性があります。 互換性がある場合は、それらのデータベース固有の文法を使用することができます。

    -

    上記以外のデータベース使用する場合、新しいドライバを作成することができます。ドライバの作成には Dialector インターフェイスを実装する必要があります。

    -
    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
    }
    - -

    例として MySQL 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.

    +

    Compatibility with MySQL or Postgres Dialects

    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.

    +

    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:

    +
    type Dialector interface {
    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.

    +
    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
    }
    + +

    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:

    +
    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:

    +
    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:

    +
    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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

    @@ -159,7 +192,7 @@

    ドライバの作成

    - +
    @@ -273,7 +306,7 @@

    Gold Sponsors

    内容 -
    1. 新しいドライバを作成する
    +
    1. Compatibility with MySQL or Postgres Dialects
    2. Implementing the Dialector
      1. Nested Transaction Support
      2. Custom Clause Builders
    diff --git a/ja_JP/docs/write_plugins.html b/ja_JP/docs/write_plugins.html index 4b928c71aa7..6bc84118fa6 100644 --- a/ja_JP/docs/write_plugins.html +++ b/ja_JP/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

    プラグインの作成

    -

    Callbacks

    GORM内部では、 Callbacks の技術が活かされています。GORMには Create, Query, Update, Delete, Row, Raw 処理のcallbackが用意されています。これらのcallbackを使うことでGORMを自由にカスタマイズすることができます。

    -

    Callbacks はグローバルな *gorm.DB に登録されます(セッションレベルではありません)。そのため、別のcallbackが登録された *gorm.DB が必要な場合は、新規の *gorm.DB を用意する必要があります。

    -

    Callbackを登録する

    独自のcallbackを登録できます

    -
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    db.Callback().Create().Register("crop_image", cropImage)
    // register a callback for Create process
    +

    Callbacks

    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 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.

    +

    Registering a Callback

    You can register a callback for specific operations. For example, to add a custom image cropping functionality:

    +
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    // Register the callback for the Create operation
    db.Callback().Create().Register("crop_image", cropImage)
    -

    Callbackを削除する

    指定したcallbackを削除することができます

    -
    db.Callback().Create().Remove("gorm:create")
    // delete callback `gorm:create` from Create callbacks
    +

    Deleting a Callback

    If a callback is no longer needed, it can be removed:

    +
    // Remove the 'gorm:create' callback from Create operations
    db.Callback().Create().Remove("gorm:create")
    -

    Callbackを置き換える

    同じ名称を指定することでcallbackを置き換えることができます

    -
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    // replace callback `gorm:create` with new function `newCreateFunction` for Create process
    +

    Replacing a Callback

    Callbacks with the same name can be replaced with a new function:

    +
    // Replace the 'gorm:create' callback with a new function
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    -

    実行順序を指定してCallbackを登録する

    実行順序を指定してCallbackを登録することができます。

    -
    // before gorm:create
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // after gorm:create
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // after gorm:query
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // after gorm:delete
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // before gorm:update
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    +

    Ordering Callbacks

    Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

    +
    // Register to execute before the 'gorm:create' callback
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:create' callback
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:query' callback
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // Register to execute after the 'gorm:delete' callback
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // Register to execute before the 'gorm:update' callback
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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)

    // Register to execute before any other callbacks
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // Register to execute after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    -

    定義済みのCallbacks

    GORMをより高機能にするために、 GORMにはすでに 定義されているCallbacks があります。プラグインを作成する前にそれらをチェックしてみるとよいでしょう。

    -

    プラグイン

    GORMにはプラグインを登録するための Use メソッドがあります。プラグインは Plugin インターフェイスを実装している必要があります。

    +

    Predefined Callbacks

    GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

    +

    Plugins

    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:

    type Plugin interface {
    Name() string
    Initialize(*gorm.DB) error
    }
    -

    GORMにプラグインを登録すると Initialize メソッドが実行されます。 GORMは登録されたプラグインを保存しているため、以下のようにアクセスすることができます:

    -
    db.Config.Plugins[pluginName]
    +
      +
    • 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:

    +
    // Example of registering a plugin
    db.Use(MyCustomPlugin{})
    -

    プラグインの例として Prometheus を参照するとよいでしょう。

    +

    Accessing Registered Plugins

    After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

    +
    // 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:

    +
    // Registering the Prometheus plugin
    db.Use(prometheus.New(prometheus.Config{
    // Configuration options here
    }))
    + +

    Prometheus plugin documentation provides detailed information on its implementation and usage.

    @@ -175,7 +186,7 @@

    @@ -289,7 +300,7 @@

    Gold Sponsors

    内容 -
    1. Callbacks
      1. Callbackを登録する
      2. Callbackを削除する
      3. Callbackを置き換える
      4. 実行順序を指定してCallbackを登録する
      5. 定義済みのCallbacks
    2. プラグイン
    +
    1. Callbacks
      1. Registering a Callback
      2. Deleting a Callback
      3. Replacing a Callback
      4. Ordering Callbacks
      5. Predefined Callbacks
    2. Plugins
      1. The Plugin Interface
      2. Registering a Plugin
      3. Accessing Registered Plugins
      4. Practical Example
    diff --git a/ja_JP/gen.html b/ja_JP/gen.html index 9b62655e29a..0ea31e7262d 100644 --- a/ja_JP/gen.html +++ b/ja_JP/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/ja_JP/gen/associations.html b/ja_JP/gen/associations.html index 447d024d061..18e06ef087a 100644 --- a/ja_JP/gen/associations.html +++ b/ja_JP/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

    // specify model
    g.ApplyBasic(model.Customer{}, model.CreditCard{})

    // assoications will be detected and converted to code
    package query

    type customer struct {
    ...
    CreditCards customerHasManyCreditCards
    }

    type creditCard struct{
    ...
    }
    -

    Relate to table in database

    The association have to be speified by gen.FieldRelate

    -
    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"}},
    }),
    )

    g.ApplyBasic(card, custormer)
    +

    Relate to table in database

    The association have to be specified by gen.FieldRelate

    +
    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"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(card, custormer)

    GEN will generate models with associated field:

    -
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }
    +
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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;references:ID" json:"credit_cards"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }

    If associated model already exists, gen.FieldRelateModel can help you build associations between them.

    -
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
    }),
    )

    g.ApplyBasic(custormer)
    +
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(custormer)

    Relate Config

    type RelateConfig struct {
    // specify field's type
    RelatePointer bool // ex: CreditCard *CreditCard
    RelateSlice bool // ex: CreditCards []CreditCard
    RelateSlicePointer bool // ex: CreditCards []*CreditCard

    JSONTag string // related field's JSON tag
    GORMTag string // related field's GORM tag
    NewTag string // related field's new tag
    OverwriteTag string // related field's tag
    }

    Operation

    Skip Auto Create/Update

    user := model.User{
    Name: "modi",
    BillingAddress: Address{Address1: "Billing Address - Address 1"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
    Emails: []Email{
    {Email: "modi@example.com"},
    {Email: "modi-2@example.com"},
    },
    Languages: []Language{
    {Name: "ZH"},
    {Name: "EN"},
    },
    }

    u := query.Use(db).User

    u.WithContext(ctx).Select(u.Name).Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
    // Skip create BillingAddress when creating a user

    u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
    // Skip create BillingAddress.Address1 when creating a user

    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

    Find matched associations

    u := query.Use(db).User

    languages, err = u.Languages.Model(&user).Find()
    @@ -198,10 +198,10 @@

    Nested Preloading together, e.g:

    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:

    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.

    +

    Preload with select

    Specify selected columns with method Select. Foreign key must be selected.

    type User struct {
    gorm.Model
    CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
    }

    type CreditCard struct {
    gorm.Model
    Number string
    UserRefer uint
    }

    u := q.User
    cc := q.CreditCard

    // !!! Foregin key "cc.UserRefer" must be selected
    users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
    // SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
    // SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

    Preload with conditions

    GEN allows Preload associations with conditions, it works similar to Inline Conditions.

    @@ -209,14 +209,13 @@

    Nested Preloading

    GEN supports nested preloading, for example:

    db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

    // Customize Preload conditions for `Orders`
    // 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/ja_JP/gen/clause.html b/ja_JP/gen/clause.html index a34e460f608..b7c6f0ed730 100644 --- a/ja_JP/gen/clause.html +++ b/ja_JP/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

    - + diff --git a/ja_JP/gen/create.html b/ja_JP/gen/create.html index b7be4a54a22..b28ce7ce3de 100644 --- a/ja_JP/gen/create.html +++ b/ja_JP/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

    - + diff --git a/ja_JP/gen/dao.html b/ja_JP/gen/dao.html index aa5f2ff16a8..2c1d80fc6e9 100644 --- a/ja_JP/gen/dao.html +++ b/ja_JP/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

    - + diff --git a/ja_JP/gen/database_to_structs.html b/ja_JP/gen/database_to_structs.html index 2ea5a48b889..989c2be2eae 100644 --- a/ja_JP/gen/database_to_structs.html +++ b/ja_JP/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

    diff --git a/ja_JP/gen/delete.html b/ja_JP/gen/delete.html index 8ec5cdcfb76..8eec10cabf2 100644 --- a/ja_JP/gen/delete.html +++ b/ja_JP/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

    - + diff --git a/ja_JP/gen/dynamic_sql.html b/ja_JP/gen/dynamic_sql.html index d32ea3ae7a4..f864423338a 100644 --- a/ja_JP/gen/dynamic_sql.html +++ b/ja_JP/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/ja_JP/gen/gen_tool.html b/ja_JP/gen/gen_tool.html index 5d4682db70d..9a3380d3ae9 100644 --- a/ja_JP/gen/gen_tool.html +++ b/ja_JP/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

    - + diff --git a/ja_JP/gen/index.html b/ja_JP/gen/index.html index 98dcada9382..252ea649a0b 100644 --- a/ja_JP/gen/index.html +++ b/ja_JP/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/ja_JP/gen/query.html b/ja_JP/gen/query.html index 63b7480d53c..79ca28ddaae 100644 --- a/ja_JP/gen/query.html +++ b/ja_JP/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

    int/uint/float Fields

    // int field
    f := field.NewInt("user", "id")
    // `user`.`id` = 123
    f.Eq(123)
    // `user`.`id` DESC
    f.Desc()
    // `user`.`id` AS `user_id`
    f.As("user_id")
    // COUNT(`user`.`id`)
    f.Count()
    // SUM(`user`.`id`)
    f.Sum()
    // SUM(`user`.`id`) > 123
    f.Sum().Gt(123)
    // ((`user`.`id`+1)*2)/3
    f.Add(1).Mul(2).Div(3),
    // `user`.`id` <<< 3
    f.LeftShift(3)
    -

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `uesr`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")
    +

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `user`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")

    Time Fields

    birth := field.NewString("user", "birth")
    // `user`.`birth` = ? (now)
    birth.Eq(time.Now())
    // DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
    birth.Add(time.Duration(time.Hour).Microseconds())
    // DATE_FORMAT(`user`.`birth`, "%W %M %Y")
    birth.DateFormat("%W %M %Y")

    Bool Fields

    active := field.NewBool("user", "active")
    // `user`.`active` = TRUE
    active.Is(true)
    // NOT `user`.`active`
    active.Not()
    // `user`.`active` AND TRUE
    active.And(true)

    SubQuery

    A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

    -
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
    // 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
    +
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

    From SubQuery

    GORM allows you using subquery in FROM clause with method Table, for example:

    u := query.User
    p := query.Pet

    users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
    // SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

    subQuery1 := u.WithContext(ctx).Select(u.Name)
    subQuery2 := p.WithContext(ctx).Select(p.Name)
    users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
    db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
    // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    @@ -312,7 +312,7 @@

    - + diff --git a/ja_JP/gen/rawsql_driver.html b/ja_JP/gen/rawsql_driver.html index e764b072a7c..c7fd3985485 100644 --- a/ja_JP/gen/rawsql_driver.html +++ b/ja_JP/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ja_JP/gen/sql_annotation.html b/ja_JP/gen/sql_annotation.html index ac780f7b09d..b94a35c2029 100644 --- a/ja_JP/gen/sql_annotation.html +++ b/ja_JP/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

    query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
    // UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

    query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
    // UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

    query.User.Update(User{Age: 0}, 10)
    // UPDATE users SET is_adult=0 WHERE id=10

    for

    The for expression iterates over a slice to generate the SQL, let’s explain by example

    -
    // 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)
    +
    // SELECT * FROM @@table
    // {{where}}
    // {{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}}
    // {{end}}
    // {{end}}
    Filter(users []gen.T) ([]gen.T, error)

    Usage:

    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","%"))
    @@ -254,7 +254,7 @@

    - + diff --git a/ja_JP/gen/transaction.html b/ja_JP/gen/transaction.html index 431c5ad48af..8d96e3f7451 100644 --- a/ja_JP/gen/transaction.html +++ b/ja_JP/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/ja_JP/gen/update.html b/ja_JP/gen/update.html index fae4178d99a..36814c80445 100644 --- a/ja_JP/gen/update.html +++ b/ja_JP/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/ja_JP/gorm.html b/ja_JP/gorm.html index 648852a69b6..e701aea59b9 100644 --- a/ja_JP/gorm.html +++ b/ja_JP/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - +
    diff --git a/ja_JP/gormx.html b/ja_JP/gormx.html index 32f855e9e71..65e27c545c0 100644 --- a/ja_JP/gormx.html +++ b/ja_JP/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ja_JP/hints.html b/ja_JP/hints.html index 51017fe9767..9d99033a39d 100644 --- a/ja_JP/hints.html +++ b/ja_JP/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/ja_JP/index.html b/ja_JP/index.html index f3c052e37c4..332c3406603 100644 --- a/ja_JP/index.html +++ b/ja_JP/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/ja_JP/rawsql.html b/ja_JP/rawsql.html index 3406e9bf8c1..09eb7409887 100644 --- a/ja_JP/rawsql.html +++ b/ja_JP/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ja_JP/rawsql_driver.html b/ja_JP/rawsql_driver.html index cd4d95d8906..2e3246bdc19 100644 --- a/ja_JP/rawsql_driver.html +++ b/ja_JP/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ja_JP/sharding.html b/ja_JP/sharding.html index 48e86315139..f5738676e88 100644 --- a/ja_JP/sharding.html +++ b/ja_JP/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ja_JP/stats.html b/ja_JP/stats.html index 050f20f927d..c2f234844e3 100644 --- a/ja_JP/stats.html +++ b/ja_JP/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/ko_KR/404.html b/ko_KR/404.html index 0e13bd574eb..c2bbe9a0ef3 100644 --- a/ko_KR/404.html +++ b/ko_KR/404.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    404 - + diff --git a/ko_KR/community.html b/ko_KR/community.html index 277e0c73571..8595f3b5062 100644 --- a/ko_KR/community.html +++ b/ko_KR/community.html @@ -56,8 +56,8 @@ - - + + @@ -184,7 +184,7 @@

    - + diff --git a/ko_KR/contribute.html b/ko_KR/contribute.html index c7b1d6d3567..2b5526d8a1d 100644 --- a/ko_KR/contribute.html +++ b/ko_KR/contribute.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

    - + diff --git a/ko_KR/datatypes.html b/ko_KR/datatypes.html index 220c5bd7eeb..e1aa9ef3a5a 100644 --- a/ko_KR/datatypes.html +++ b/ko_KR/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ko_KR/docs/advanced_query.html b/ko_KR/docs/advanced_query.html index 86faceb9854..3994807c542 100644 --- a/ko_KR/docs/advanced_query.html +++ b/ko_KR/docs/advanced_query.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,80 +141,106 @@

    Advanced Query

    -

    Smart Select Fields

    GORM allows selecting specific fields with Select, 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:

    -
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // hundreds of fields
    }

    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
    +

    Smart Select Fields

    In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

    +
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // hundreds of fields
    }

    type APIUser struct {
    ID uint
    Name string
    }

    // GORM will automatically select `id`, `name` fields when querying
    db.Model(&User{}).Limit(10).Find(&APIUser{})
    // SQL: SELECT `id`, `name` FROM `users` LIMIT 10
    -

    NOTE QueryFields 모드는 현재 모델에 대한 모든 필드 이름으로 선택됩니다.

    +

    NOTE In QueryFields mode, all model fields are selected by their names.

    -
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    db.Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option

    // Session Mode
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    +
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    // Default behavior with QueryFields set to true
    db.Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

    // Using Session Mode with QueryFields
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    -

    Locking (FOR UPDATE)

    GORM 은 다른 종류의 lock을 지원합니다.

    -
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SELECT * FROM `users` FOR UPDATE

    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SELECT * FROM `users` FOR SHARE OF `users`

    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SELECT * FROM `users` FOR UPDATE NOWAIT
    +

    Locking

    GORM 은 다른 종류의 lock을 지원합니다.

    +
    // Basic FOR UPDATE lock
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE
    -

    자세한 내용은 Raw SQL and SQL Builder 을 참고하세요.

    -

    SubQuery

    서브쿼리는 쿼리 내에 중첩될 수 있으며, GORM은 *gorm.DB 개체를 매개 변수로 사용할 때 하위 쿼리를 생성할 수 있습니다.

    -
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    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%")
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR SHARE OF `users`
    -

    From SubQuery

    GORM allows you using subquery in FROM clause with the method Table, for example:

    -
    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

    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
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
    -

    Group Conditions (그룹화 조건)

    그룹 조건을 사용하여 복잡한 SQL 쿼리를 더 쉽게 작성할 수 있습니다.

    -
    db.Where(
    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")
    +

    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.

    +

    SubQuery

    Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

    +
    // Simple subquery
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
    // SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
    -

    IN with multiple columns

    Selecting 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"));
    +

    From SubQuery

    GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

    +
    // Using subquery in FROM clause
    db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    -

    Named Argument

    GORM supports named arguments with sql.NamedArg or map[string]interface{}{}, for example:

    -
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

    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
    +

    Group Conditions (그룹화 조건)

    Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

    +
    // Complex SQL query using Group Conditions
    db.Where(
    db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
    ).Or(
    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")
    -

    Check out Raw SQL and SQL Builder for more detail

    -

    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:

    -
    result := map[string]interface{}{}
    db.Model(&User{}).First(&result, "id = ?", 1)

    var results []map[string]interface{}
    db.Table("users").Find(&results)
    +

    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.

    +
    // Using IN with multiple columns
    db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
    // SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
    -

    FirstOrInit

    Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions)

    -
    // User not found, initialize it with give conditions
    db.FirstOrInit(&user, User{Name: "non_existing"})
    // user -> User{Name: "non_existing"}

    // Found user with `name` = `jinzhu`
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}

    // Found user with `name` = `jinzhu`
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
    +

    Named Argument

    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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

    +
    // Example using sql.NamedArg for named arguments
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // 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)
    // SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
    -

    Initialize struct with more attributes if record not found, those Attrs won’t be used to build the SQL query

    -
    // User not found, initialize it with give conditions and Attrs
    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}

    // Found user with `name` = `jinzhu`, attributes will be 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}
    +

    For more examples and details, see Raw SQL and SQL Builder

    +

    Find To Map

    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.

    +
    // 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`
    -

    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

    -
    // User not found, initialize it with give conditions and Assign attributes
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // user -> User{Name: "non_existing", Age: 20}

    // Found user with `name` = `jinzhu`, update it 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}
    +

    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.

    +
    // 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"} if not found

    // Retrieving a user named "jinzhu"
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

    // Using a map to specify the search condition
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    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

    -
    // User not found, create a new record with give conditions
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1

    // Found user with `name` = `jinzhu`
    result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
    // user -> User{ID: 111, Name: "jinzhu", "Age": 18}
    // result.RowsAffected // => 0
    +

    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.

    +
    // If no User is found, initialize with given conditions and additional attributes
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // user -> User{Name: "non_existing", Age: 20} if not found

    // If a User named "Jinzhu" is found, `Attrs` are ignored
    db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    Create struct with more attributes if record not found, those Attrs won’t be used to build SQL query

    -
    // User not found, create it with give conditions and Attrs
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // Found user with `name` = `jinzhu`, attributes will be ignored
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    +

    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.

    +
    // 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} if not found

    // If a User named "Jinzhu" is found, update the struct with Assign attributes
    db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
    -

    Assign attributes to the record regardless it is found or not and save them back to the database.

    -
    // User not found, initialize it with give conditions and Assign attributes
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // Found user with `name` = `jinzhu`, update it 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}
    +

    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

    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.

    +
    // Create a new record if not found
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // SQL: INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1 (record created)

    // 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)
    -

    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:

    -
    import "gorm.io/hints"

    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    +

    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.

    +
    // Create a new record with additional attributes if not found
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // If the user is found, `Attrs` are ignored
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu';
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    -

    Index hints allow passing index hints to the database in case the query planner gets confused.

    -
    import "gorm.io/hints"

    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // SELECT * FROM `users` USE INDEX (`idx_user_name`)

    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`)"
    +

    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.

    +
    // Initialize and save new record with `Assign` attributes if not found
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // Update found record with `Assign` attributes
    db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu';
    // SQL: UPDATE users SET age=20 WHERE id = 111;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 20}
    -

    Refer Optimizer Hints/Index/Comment for more details

    -

    Iteration

    GORM supports iterating through Rows

    -
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    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
    db.ScanRows(rows, &user)

    // do something
    }
    +

    Optimizer/Index 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.

    +
    import "gorm.io/hints"

    // Using an optimizer hint to set a maximum execution time
    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    -

    FindInBatches

    Query and process records in batch

    -
    // batch size 100
    result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
    for _, result := range results {
    // batch processing found records
    }

    tx.Save(&results)

    tx.RowsAffected // number of records in this batch

    batch // Batch 1, 2, 3

    // returns error will stop future batches
    return nil
    })

    result.Error // returned error
    result.RowsAffected // processed records count in all batches
    +

    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.

    +
    import "gorm.io/hints"

    // Suggesting the use of a specific index
    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
    -

    Query Hooks

    GORM allows hooks AfterFind for a query, it will be called when querying a record, refer Hooks for details

    -
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    if u.Role == "" {
    u.Role = "user"
    }
    return
    }
    +

    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 in the GORM documentation.

    +

    Iteration

    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.

    +
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    defer rows.Close()

    for rows.Next() {
    var user User
    // ScanRows scans a row into a struct
    db.ScanRows(rows, &user)

    // Perform operations on each user
    }
    -

    Pluck

    Query single column from database and scan into a slice, if you want to query multiple columns, use Select with Scan instead

    -
    var ages []int64
    db.Model(&users).Pluck("age", &ages)

    var names []string
    db.Model(&User{}).Pluck("name", &names)

    db.Table("deleted_users").Pluck("name", &names)

    // Distinct Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SELECT DISTINCT `name` FROM `users`

    // Requesting more than one column, use `Scan` or `Find` like this:
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    +

    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.

    +
    // 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 provides the count of records in the current batch
    // The variable 'batch' indicates the current batch number

    // Returning an error will stop further batch processing
    return nil
    })

    // result.Error contains any errors encountered during batch processing
    // result.RowsAffected provides the count of all processed records across batches
    -

    Scopes

    Scopes allows you to specify commonly-used queries which can be referenced as method calls

    -
    func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
    return db.Where("amount > ?", 1000)
    }

    func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
    return db.Where("pay_mode_sign = ?", "C")
    }

    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 {
    return db.Where("status IN (?)", status)
    }
    }

    db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
    // Find all credit card orders and amount greater than 1000

    db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
    // Find all COD orders and amount greater than 1000

    db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
    // Find all paid, shipped orders that amount greater than 1000
    +

    FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

    +

    Query Hooks

    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 in the GORM documentation.

    +
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    // Custom logic after finding a user
    if u.Role == "" {
    u.Role = "user" // Set default role if not specified
    }
    return
    }

    // Usage of AfterFind hook happens automatically when a User is queried
    -

    Checkout Scopes for details

    -

    Count

    Get matched records count

    -
    var count int64
    db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

    db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

    db.Table("deleted_users").Count(&count)
    // SELECT count(1) FROM deleted_users;

    // Count with Distinct
    db.Model(&User{}).Distinct("name").Count(&count)
    // SELECT COUNT(DISTINCT(`name`)) FROM `users`

    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SELECT count(distinct(name)) FROM deleted_users

    // Count with Group
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    count // => 3
    +

    Pluck

    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 or Find instead.

    +
    // Retrieving ages of all users
    var ages []int64
    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)

    // Using Distinct with Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SQL: SELECT DISTINCT `name` FROM `users`

    // Querying multiple columns
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    + +

    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.

    +
    // 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")
    }

    // 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.

    +
    // 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)
    + +

    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 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.

    +
    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'

    // Counting records in a different table
    db.Table("deleted_users").Count(&count)
    // SQL: SELECT count(1) FROM deleted_users
    + +

    Count with Distinct and Group

    GORM also allows counting distinct values and grouping results.

    +
    // Counting distinct names
    db.Model(&User{}).Distinct("name").Count(&count)
    // SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

    // Counting distinct values with a custom select
    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SQL: SELECT count(distinct(name)) FROM deleted_users

    // Counting grouped records
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    // Count after grouping by name
    // count => 3
    @@ -227,7 +253,7 @@

    - + @@ -341,7 +367,7 @@

    Gold Sponsors

    내용 -
    1. Smart Select Fields
    2. Locking (FOR UPDATE)
    3. SubQuery
      1. From SubQuery
    4. Group Conditions (그룹화 조건)
    5. IN with multiple columns
    6. Named Argument
    7. Find To Map
    8. FirstOrInit
    9. FirstOrCreate
    10. Optimizer/Index Hints
    11. Iteration
    12. FindInBatches
    13. Query Hooks
    14. Pluck
    15. Scopes
    16. Count
    +
    1. Smart Select Fields
    2. Locking
    3. SubQuery
      1. From SubQuery
    4. Group Conditions (그룹화 조건)
    5. IN with multiple columns
    6. Named Argument
    7. Find To Map
    8. FirstOrInit
      1. Using Attrs for Initialization
      2. Using Assign for Attributes
    9. FirstOrCreate
      1. Using Attrs with FirstOrCreate
      2. Using Assign with FirstOrCreate
    10. Optimizer/Index Hints
      1. Index Hints
    11. Iteration
    12. FindInBatches
    13. Query Hooks
    14. Pluck
    15. Scopes
      1. Defining Scopes
      2. Applying Scopes in Queries
    16. Count
      1. Getting the Count of Matched Records
      2. Count with Distinct and Group
    diff --git a/ko_KR/docs/associations.html b/ko_KR/docs/associations.html index 06cf6feaeab..ea3af5f5d02 100644 --- a/ko_KR/docs/associations.html +++ b/ko_KR/docs/associations.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,62 +141,101 @@

    관계

    -

    자동 생성/갱신

    GORM은 레코드를 생성/갱신할 때 Upsert(업서트)를 사용하여 관계와 참조를 자동으로 저장합니다.

    -
    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"},
    },
    }

    db.Create(&user)
    // BEGIN TRANSACTION;
    // 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 ('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 모드를 사용해야 합니다.

    -
    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);
    // ...
    - -

    자동 생성/갱신 건너뛰기

    생성/갱신 시 자동 저장을 건너뛰려면 다음과 같이 Select 또는 Omit를 사용할 수 있습니다.

    -
    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"},
    },
    }

    db.Select("Name").Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    db.Omit("BillingAddress").Create(&user)
    // Skip create BillingAddress when creating a user

    db.Omit(clause.Associations).Create(&user)
    // Skip all associations when creating a user
    - -

    NOTE: many2many 어소시에이션의 경우 GORM이 조인 테이블 레퍼런스를 생성하기 전에 어소시에이션을 업서트합니다. 어소시에이션 업서트를 건너뛰려면 다음과 같이 할 수 있습니다.

    -
    db.Omit("Languages.*").Create(&user)
    - -

    다음 코드는 어소시에이션과 레퍼런스 생성을 건너뜁니다.

    -
    db.Omit("Languages").Create(&user)
    - -

    Select/Omit Association fields

    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    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
    db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

    db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
    - -

    Association Mode

    어소시에이션 모드는 릴레이션십(relationship)을 처리하기 위해 일반적으로 사용되는 몇 가지 헬퍼 메소드가 있습니다.

    -
    // 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
    - -

    연관된 결과(Find Association)

    일치하는 연관된 결과 찾기

    -
    db.Model(&user).Association("Languages").Find(&languages)
    - -

    조건을 통한 연관된 결과 찾기

    -
    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

    many to many, has many 로 새로운 associations 를 추가 하고, has one, belongs to 로 현재 association을 교체 합니다.

    -
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

    db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
    - -

    Replace Associations

    현재 associations 을 새로운 것으로 변경합니다.

    -
    db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
    - -

    Delete Associations

    원본과 인수 간의 관계가 존재 한다면 제거하고, 참조만 삭제하며, DB에서 해당 개체를 삭제하지 않습니다.

    -
    db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
    db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
    - -

    Clear Associations

    원본과 association 간의 모든 참조를 제거하고, association 은 제거 하지 않습니다.

    -
    db.Model(&user).Association("Languages").Clear()
    - -

    Count Associations

    현재 연관된 수 를 반환 합니다.

    -
    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

    Association Mode 는 일괄 데이터를 제공 합니다.

    -
    // Find all roles for all users
    db.Model(&users).Association("Role").Find(&roles)

    // Delete User A from all user's team
    db.Model(&users).Association("Team").Delete(&userA)

    // Get distinct count of all users' teams
    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
    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
    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
    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).

    -

    How to delete is decided by gorm.DB.

    -
    // Soft delete
    // UPDATE `languages` SET `deleted_at`= ...
    db.Model(&user).Association("Languages").Unscoped().Clear()

    // Delete permanently
    // DELETE FROM `languages` WHERE ...
    db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
    - -

    Delete with Select

    You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

    -
    // 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)
    - -

    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

    -
    // 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
    - -

    Association Tags

    +

    자동 생성/갱신

    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.

    +
    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"},
    },
    }

    // 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;
    // 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 ('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)
    + +

    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.

    +
    // Update a user and fully update all its associations
    db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
    // 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.

    +

    자동 생성/갱신 건너뛰기

    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.

    +
    user := User{
    // User and associated data
    }

    // Only include the 'Name' field when creating the user
    db.Select("Name").Create(&user)
    // 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.

    +
    // Skip creating the 'BillingAddress' when creating the user
    db.Omit("BillingAddress").Create(&user)

    // Skip all associations when creating the user
    db.Omit(clause.Associations).Create(&user)
    + +

    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 .*:

    +
    // Skip upserting 'Languages' associations
    db.Omit("Languages.*").Create(&user)
    + +

    To skip creating both the association and its references:

    +
    // Skip creating 'Languages' associations and their references
    db.Omit("Languages").Create(&user)
    + +

    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.

    +
    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
    }

    // 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.

    +
    // 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: 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.

    +
    // 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
    + +

    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.

    +
    var user User
    db.Model(&user).Association("Languages")
    // Check for errors
    error := db.Model(&user).Association("Languages").Error
    + +

    Finding Associations

    Retrieve associated records with or without additional conditions.

    +
    // Simple find
    db.Model(&user).Association("Languages").Find(&languages)

    // Find with conditions
    codes := []string{"zh-CN", "en-US", "ja-JP"}
    db.Model(&user).Where("code IN ?", codes).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.

    +
    // Append new languages
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    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.

    +
    // 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.

    +
    // 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.

    +
    // Clear all languages
    db.Model(&user).Association("Languages").Clear()
    + +

    Counting Associations

    Get the count of current associations, with or without conditions.

    +
    // 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.
    • +
    +
    db.Model(&users).Association("Role").Find(&roles)
    + +
      +
    • Deleting Associations: Remove specific associations across multiple records.
    • +
    +
    db.Model(&users).Association("Team").Delete(&userA)
    + +
      +
    • Counting Associations: Get the count of associations for a batch of records.
    • +
    +
    db.Model(&users).Association("Team").Count()
    + +
      +
    • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
    • +
    +
    var users = []User{user1, user2, 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})

    // 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

    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.

    +
      +
    • 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.
    • +
    +

    Modifying Deletion Behavior with Unscoped

    For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

    +
      +
    • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
    • +
    +
    db.Model(&user).Association("Languages").Unscoped().Clear()
    + +
      +
    • Permanent Delete: Physically deletes the association records from the database.
    • +
    +
    // db.Unscoped().Model(&user)
    db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
    + +

    Association Tags

    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.

    + @@ -204,36 +243,36 @@

    - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
    Tag
    foreignKeySpecifies column name of the current model that is used as a foreign key to the join tableforeignKeySpecifies the column name of the current model used as a foreign key in the join table.
    referencesSpecifies column name of the reference’s table that is mapped to the foreign key of the join tablereferencesIndicates the column name in the reference table that the foreign key of the join table maps to.
    polymorphicSpecifies polymorphic type such as model namepolymorphicDefines the polymorphic type, typically the model name.
    polymorphicValueSpecifies polymorphic value, default table namepolymorphicValueSets the polymorphic value, usually the table name, if not specified otherwise.
    many2manySpecifies join table namemany2manyNames the join table used in a many-to-many relationship.
    joinForeignKeySpecifies foreign key column name of join table that maps to the current tablejoinForeignKeyIdentifies the foreign key column in the join table that maps back to the current model’s table.
    joinReferencesSpecifies foreign key column name of join table that maps to the reference’s tablejoinReferencesPoints to the foreign key column in the join table that links to the reference model’s table.
    constraintRelations constraint, e.g: OnUpdate,OnDeleteconstraintSpecifies relational constraints like OnUpdate, OnDelete for the association.
    @@ -248,7 +287,7 @@

    @@ -362,7 +401,7 @@

    Gold Sponsors

    내용 -
    1. 자동 생성/갱신
    2. 자동 생성/갱신 건너뛰기
    3. Select/Omit Association fields
    4. Association Mode
      1. 연관된 결과(Find Association)
      2. Append Associations
      3. Replace Associations
      4. Delete Associations
      5. Clear Associations
      6. Count Associations
      7. Batch Data
    5. Delete Association Record
    6. Delete with Select
    7. Association Tags
    +
    1. 자동 생성/갱신
      1. Auto-Saving Associations on Create
      2. Updating Associations with FullSaveAssociations
    2. 자동 생성/갱신 건너뛰기
      1. Using Select to Include Specific Fields
      2. Using Omit to Exclude Fields or Associations
    3. Select/Omit Association fields
    4. Delete Associations
    5. Association Mode
      1. Finding Associations
      2. Appending Associations
      3. Replacing Associations
      4. Deleting Associations
      5. Clearing Associations
      6. Counting Associations
      7. Batch Data Handling
    6. Delete Association Record
      1. Modifying Deletion Behavior with Unscoped
    7. Association Tags
    diff --git a/ko_KR/docs/belongs_to.html b/ko_KR/docs/belongs_to.html index 33ad7ea804b..0aa1b227947 100644 --- a/ko_KR/docs/belongs_to.html +++ b/ko_KR/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

    - + diff --git a/ko_KR/docs/changelog.html b/ko_KR/docs/changelog.html index 6689a25e5b0..681dedd7a61 100644 --- a/ko_KR/docs/changelog.html +++ b/ko_KR/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/ko_KR/docs/composite_primary_key.html b/ko_KR/docs/composite_primary_key.html index 59f0d230a32..a93d476e65d 100644 --- a/ko_KR/docs/composite_primary_key.html +++ b/ko_KR/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

    Composite Primary Key

    - +
    diff --git a/ko_KR/docs/connecting_to_the_database.html b/ko_KR/docs/connecting_to_the_database.html index 85ffe1eb97a..53256c96345 100644 --- a/ko_KR/docs/connecting_to_the_database.html +++ b/ko_KR/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

    - + diff --git a/ko_KR/docs/constraints.html b/ko_KR/docs/constraints.html index 384b11811b2..b4c0938006b 100644 --- a/ko_KR/docs/constraints.html +++ b/ko_KR/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - + diff --git a/ko_KR/docs/context.html b/ko_KR/docs/context.html index 5630313eae1..a66d5d4dad0 100644 --- a/ko_KR/docs/context.html +++ b/ko_KR/docs/context.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,25 @@

    Context

    -

    GORM provides Context support, you can use it with method WithContext

    -

    Single Session Mode

    Single session mode usually used when you want to perform a single operation

    +

    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 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.

    db.WithContext(ctx).Find(&users)
    -

    Continuous session mode

    Continuous session mode is usually used when you want to perform a group of operations, for example:

    +

    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.

    tx := db.WithContext(ctx)
    tx.First(&user, 1)
    tx.Model(&user).Update("role", "admin")
    -

    Context timeout

    You can pass in a context with a timeout to db.WithContext to set timeout for long running queries, for example:

    +

    Context Timeout

    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.

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    db.WithContext(ctx).Find(&users)
    -

    Context in Hooks/Callbacks

    You can access the Context object from the current Statement, for example:

    -
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ...
    return
    }
    +

    Context in Hooks/Callbacks

    The context can also be accessed within GORM’s hooks/callbacks. This enables contextual information to be used during these lifecycle events.

    +
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ... use context
    return
    }
    -

    Chi Middleware Example

    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:

    -
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    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
    })

    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
    })
    +

    Integration with 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.

    +
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    // Router setup
    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    // Route handlers
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })

    r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })
    -

    NOTE Setting Context with WithContext is goroutine-safe, refer Session for details

    -
    - -

    Logger

    Logger accepts Context too, you can use it for log tracking, refer Logger for details

    +

    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 in GORM.

    +

    Logger Integration

    GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

    +

    Refer to Logger documentation for more details.

    @@ -174,7 +172,7 @@

    - + @@ -288,7 +286,7 @@

    Gold Sponsors

    내용 -
    1. Single Session Mode
    2. Continuous session mode
    3. Context timeout
    4. Context in Hooks/Callbacks
    5. Chi Middleware Example
    6. Logger
    +
    1. Single Session Mode
    2. Continuous Session Mode
    3. Context Timeout
    4. Context in Hooks/Callbacks
    5. Integration with Chi Middleware
    6. Logger Integration
    diff --git a/ko_KR/docs/conventions.html b/ko_KR/docs/conventions.html index 2779d5ea9a4..b7c6a1a2140 100644 --- a/ko_KR/docs/conventions.html +++ b/ko_KR/docs/conventions.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    // Create table `deleted_users` with struct User's fields
    db.Table("deleted_users").AutoMigrate(&User{})

    // Query data from another table
    var deletedUsers []User
    db.Table("deleted_users").Find(&deletedUsers)
    // SELECT * FROM deleted_users;

    db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
    // DELETE FROM deleted_users WHERE name = 'jinzhu';

    Check out From SubQuery for how to use SubQuery in FROM clause

    -

    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 for details

    +

    NamingStrategy

    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 for details

    Column Name

    Column db name uses the field’s name’s snake_case by convention.

    type User struct {
      ID        uint      // column name is `id`
      Name      string    // column name is `name`
      Birthday  time.Time // column name is `birthday`
      CreatedAt time.Time // column name is `created_at`
    }
    @@ -194,7 +194,7 @@

    - + diff --git a/ko_KR/docs/create.html b/ko_KR/docs/create.html index 95457870e0c..11e8d68b04c 100644 --- a/ko_KR/docs/create.html +++ b/ko_KR/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -155,7 +155,7 @@

    Create a record and ignore the values for fields passed to omit.

    db.Omit("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
    -

    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.

    +

    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 split into multiple batches.

    var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
    db.Create(&users)

    for _, user := range users {
    user.ID // 1,2,3
    }

    You can specify batch size when creating with CreateInBatches, e.g:

    @@ -220,7 +220,7 @@

    - + diff --git a/ko_KR/docs/data_types.html b/ko_KR/docs/data_types.html index 96eb12b2207..bbbe4f2c419 100644 --- a/ko_KR/docs/data_types.html +++ b/ko_KR/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

    - + diff --git a/ko_KR/docs/dbresolver.html b/ko_KR/docs/dbresolver.html index 1c10069b04f..44d7c560d4d 100644 --- a/ko_KR/docs/dbresolver.html +++ b/ko_KR/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    Transaction

    When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

    But you can specifies which DB to use before starting a transaction, for example:

    -
    // Start transaction based on default replicas db
    tx := DB.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := DB.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
    +
    // Start transaction based on default replicas db
    tx := db.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := db.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

    Load Balancing

    GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

    type Policy interface {
    Resolve([]gorm.ConnPool) gorm.ConnPool
    }
    @@ -183,7 +183,7 @@

    diff --git a/ko_KR/docs/delete.html b/ko_KR/docs/delete.html index 7fbb65a0a61..f1e2fdc9b74 100644 --- a/ko_KR/docs/delete.html +++ b/ko_KR/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

    - + diff --git a/ko_KR/docs/error_handling.html b/ko_KR/docs/error_handling.html index 87be731dcc2..9799f1ad1c1 100644 --- a/ko_KR/docs/error_handling.html +++ b/ko_KR/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

    Error Handling

    -

    In Go, error handling is important.

    -

    You are encouraged to do error check after any Finisher Methods

    -

    Error Handling

    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:

    -
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // error handling...
    }
    - -

    Or

    -
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // error handling...
    }
    - -

    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:

    -
    // Check if returns RecordNotFound error
    err := db.First(&user, 100).Error
    errors.Is(err, gorm.ErrRecordNotFound)
    -

    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.

    +

    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.

    +

    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.

    +

    After a chain of methods, it’s crucial to check the Error field:

    +
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // Handle error...
    }
    + +

    Or alternatively:

    +
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // Handle error...
    }
    + +

    ErrRecordNotFound

    GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

    +
    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

    +
      +
    • Example: Handling MySQL Error Codes
    • +
    +
    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.

    db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
    -

    Errors

    Errors List

    +
      +
    • ErrDuplicatedKey
    • +
    +

    This error occurs when an insert operation violates a unique constraint:

    +
    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:

    +
    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

    For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

    @@ -168,7 +187,7 @@

    - + @@ -282,7 +301,7 @@

    Gold Sponsors

    내용 -
    1. Error Handling
    2. ErrRecordNotFound
    3. Dialect Translated Errors
    4. Errors
    +
    1. Basic Error Handling
    2. ErrRecordNotFound
    3. Handling Error Codes
    4. Dialect Translated Errors
    5. Errors
    diff --git a/ko_KR/docs/generic_interface.html b/ko_KR/docs/generic_interface.html index 0a0c31e7489..47429d6f626 100644 --- a/ko_KR/docs/generic_interface.html +++ b/ko_KR/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

    diff --git a/ko_KR/docs/gorm_config.html b/ko_KR/docs/gorm_config.html index c26d8c0e439..751a23f94ed 100644 --- a/ko_KR/docs/gorm_config.html +++ b/ko_KR/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

    type Namer interface {
    TableName(table string) string
    SchemaName(table string) string
    ColumnName(table, column string) string
    JoinTableName(table string) string
    RelationshipFKName(Relationship) string
    CheckerName(table, column string) string
    IndexName(table, column string) string
    }

    기본 NamingStrategy도 다음과 같은 몇 가지 옵션을 제공합니다:

    -
    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
    },
    })
    +
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    NamingStrategy: schema.NamingStrategy{
    TablePrefix: "t_", // 테이블명의 접두사를 지정합니다. 예시로, User를 테이블명으로 변환할 때 t_users로 변환합니다.
    SingularTable: true, // 단수형 테이블명을 사용합니다. 기본적으로 GORM은 복수형 테이블명 규칙이 적용되는데 true로 설정하면 구조체 이름 그대로 테이블명을 생성합니다.
    NoLowerCase: true, // 소문자와 언더스코어를 사용한 스네이크 표기를 사용하지 않고, 구조체의 필드명을 그대로 사용합니다.
    NameReplacer: strings.NewReplacer("CID", "Cid"), // 구조체의 필드 이름을 DB에 넣기 전에 변환하여 테이블/열 이름으로 매핑합니다. 예시로 문자열 CID를 Cid로 변경하여 넣는다는 예시입니다.
    },
    })

    Logger

    이 옵션을 재정의하여 GORM의 기본 logger를 변경할 수 있으며, 자세한 내용은 Logger를 참조하세요.

    NowFunc

    새 타임스탬프를 만들 때 사용할 함수를 변경할 수 있습니다.

    @@ -163,9 +163,9 @@

    PrepareStmt

    PreparedStmt는 SQL을 실행할 때 prepared statement를 생성하고 향후 호출 속도를 높이기 위해 캐시합니다. 자세한 내용은 Session을 참조하세요.

    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    PrepareStmt: false,
    })
    -

    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 for details

    +

    DisableNestedTransaction

    Db 트랜잭션 내부에서 또 다른 트랜잭션을 시작할 때, GORM은 중첩 트랜잭션을 지원하기 위해서 SavePoint와 RollbackTo 메서드를 사용합니다. DisableNestedTransaction 옵션을 이용하여 비활성화할 수 있으며, 자세한 내용은 Session을 참고하세요.

    AllowGlobalUpdate

    Enable global update/delete, refer Session for details

    -

    DisableAutomaticPing

    GORM automatically ping database after initialized to check database availability, disable it by setting it to true

    +

    DisableAutomaticPing

    GORM은 초기화 후에 데이터베이스와의 연결 상태를 확인하기 위해 기본적으로 Ping을 요청합니다. 그러나 자주 변경하지 않아 기본적인 Ping 요청을 제거하거나, 자원 소모나 부하를 줄이기 위해 이 기능을 비활성화할 수 있습니다. 또한 개발자가 직접 데이터베이스 연결 상태를 통제하고자 할 때, 필요한 시점에만 Ping을 보내도록 이 옵션을 활용할 수 있습니다.

    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    DisableAutomaticPing: true,
    })

    DisableForeignKeyConstraintWhenMigrating

    GORM creates database foreign key constraints automatically when AutoMigrate or CreateTable, disable this by setting it to true, refer Migration for details

    @@ -182,7 +182,7 @@

    diff --git a/ko_KR/docs/has_many.html b/ko_KR/docs/has_many.html index 0e15193f806..246268e834c 100644 --- a/ko_KR/docs/has_many.html +++ b/ko_KR/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

    diff --git a/ko_KR/docs/has_one.html b/ko_KR/docs/has_one.html index 9e59275c7e4..6016d932065 100644 --- a/ko_KR/docs/has_one.html +++ b/ko_KR/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

    diff --git a/ko_KR/docs/hints.html b/ko_KR/docs/hints.html index 053c3896cd6..f90f026e3b9 100644 --- a/ko_KR/docs/hints.html +++ b/ko_KR/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

    - + diff --git a/ko_KR/docs/hooks.html b/ko_KR/docs/hooks.html index 60e96b437e0..661374e0052 100644 --- a/ko_KR/docs/hooks.html +++ b/ko_KR/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

    - + diff --git a/ko_KR/docs/index.html b/ko_KR/docs/index.html index 04834cf0b8b..572ba8ee275 100644 --- a/ko_KR/docs/index.html +++ b/ko_KR/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

    - + diff --git a/ko_KR/docs/indexes.html b/ko_KR/docs/indexes.html index 91c12380d9b..52cd132b143 100644 --- a/ko_KR/docs/indexes.html +++ b/ko_KR/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

    diff --git a/ko_KR/docs/logger.html b/ko_KR/docs/logger.html index fbd0aeed4d5..ac09b2c80ee 100644 --- a/ko_KR/docs/logger.html +++ b/ko_KR/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

    diff --git a/ko_KR/docs/many_to_many.html b/ko_KR/docs/many_to_many.html index 5e83ea485b6..85ece290257 100644 --- a/ko_KR/docs/many_to_many.html +++ b/ko_KR/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

    -
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addressses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // Change model Person's field Addresses' join table to PersonAddress
    // PersonAddress must defined all required foreign keys or it will raise error
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
    +
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addresses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // Change model Person's field Addresses' join table to PersonAddress
    // PersonAddress must defined all required foreign keys or it will raise error
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

    FOREIGN KEY Constraints

    You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

    type User struct {
    gorm.Model
    Languages []Language `gorm:"many2many:user_speaks;"`
    }

    type Language struct {
    Code string `gorm:"primarykey"`
    Name string
    }

    // CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
    @@ -191,7 +191,7 @@

    - + diff --git a/ko_KR/docs/method_chaining.html b/ko_KR/docs/method_chaining.html index ed956aa03d5..590875c5c07 100644 --- a/ko_KR/docs/method_chaining.html +++ b/ko_KR/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

    Method Chaining

    -

    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:

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
    -

    There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

    -

    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:

    -
    queryDB := DB.Where("name = ?", "jinzhu")

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // 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:

    -
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 20
    - -

    Chain Method

    Chain methods are methods to modify or add Clauses to current Statement, like:

    -

    Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

    -

    Here is the full lists, also check out the SQL Builder 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 here.

    -

    New Session Method

    GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new 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:
    // 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:
    // 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:
    // SELECT * FROM users;
    - -

    (Bad) Example 2:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is safe to 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

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    - -

    Example 3:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is 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

    // good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // good case
    tx.Where("age = ?", 28).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
    +

    Method Categories

    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. Also, the SQL Builder 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.

    +

    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 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

    queryDB := DB.Where("name = ?", "jinzhu")

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query with unintended compounded condition
    queryDB.Where("age > ?", 20).First(&user2)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
    + +

    Example of Safe Reuse

    To safely reuse a *gorm.DB instance, use a New Session Method:

    +
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query, safely isolated
    queryDB.Where("age > ?", 20).First(&user2)
    // 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.

    +

    Examples for Clarity

    Let’s clarify with a few examples:

    +
      +
    • Example 1: Safe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
    // 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)
    // 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)
    // Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
    // SELECT * FROM users;
    + +

    In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

    +
      +
    • (Bad) Example 2: Unsafe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized *gorm.DB, safe for initial reuse.

    tx := db.Where("name = ?", "jinzhu")
    // `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // Reuses 'tx' correctly for a single logical operation, executing:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // Bad case
    tx.Where("age = ?", 28).Find(&users)
    // Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    + +

    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
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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.

    @@ -180,7 +210,7 @@

    - + @@ -294,7 +324,7 @@

    Gold Sponsors

    내용 -
    1. Chain Method
    2. Finisher Method
    3. New Session Method
    +
    1. Method Categories
      1. Chain Methods
      2. Finisher Methods
      3. New Session Methods
    2. Reusability and Safety
      1. Example of Unsafe Reuse
      2. Example of Safe Reuse
    3. Examples for Clarity
    diff --git a/ko_KR/docs/migration.html b/ko_KR/docs/migration.html index 8683b05a196..acffe7c26ee 100644 --- a/ko_KR/docs/migration.html +++ b/ko_KR/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

    - + diff --git a/ko_KR/docs/models.html b/ko_KR/docs/models.html index 5dbfb1f920f..114a7d0f66a 100644 --- a/ko_KR/docs/models.html +++ b/ko_KR/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,16 +141,45 @@

    모델 선언

    -

    모델 선언

    모델은 기본 Go 유형, 포인터/별칭 또는 ScannerValuer인터페이스를 구현하는 사용자 정의 유형이 있는 일반 구조체입니다.

    -

    예를 들면 다음과 같습니다:

    -
    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
    }
    - -

    규칙

    GORM은 구성보다 규칙을 선호합니다. 기본적으로 GORM은 ID를 기본 키로 사용하고, 복수형 snake_cases를 구조체 이름을 테이블 이름으로, snake_case를 열 이름으로, 생성/업데이트 시간을 추적하기 위해 CreatedAt, UpdatedAt을 사용합니다.

    -

    GORM에서 채택한 규칙을 따르는 경우 구성/코드를 거의 작성하지 않아도 됩니다. 규칙이 요구 사항과 일치하지 않는 경우 GORM을 사용하여 규칙을 구성할 수 있습니다.

    -

    gorm.Model

    GORM은 ID, CreatedAt, UpdatedAt, DeletedAt 필드를 포함하는 gorm.Model 구조체를 정의합니다.

    +

    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 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 and Valuer interfaces from the database/sql package

    +

    Consider the following example of a User model:

    +
    type User struct {
    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 for a detailed explanation

    +

    규칙

      +
    1. Primary Key: GORM uses a field named ID as the default primary key for each model.

      +
    2. +
    3. 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.

      +
    4. +
    5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

      +
    6. +
    7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

      +
    8. +
    +

    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.

    +

    gorm.Model

    GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

    // gorm.Model definition
    type Model struct {
    ID uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    }
    -

    해당 필드를 포함하도록 구조체를 만들 할 수 있습니다. 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

      +
    • +
    • 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).
      • +
      +
    • +

    고급

    필드 수준 권한

    내보낸 필드는 GORM으로 CRUD를 수행할 때 모든 권한을 가지며, GORM에서는 태그를 사용하여 필드 수준 권한을 읽기 전용, 쓰기 전용, 만들기 전용, 업데이트 전용 또는 무시로 설정할 수 있습니다.

    참고 GORM 마이그레이터를 사용하여 테이블을 만들 때 무시된 필드는 생성되지 않습니다.

    @@ -286,7 +315,7 @@

    @@ -400,7 +429,7 @@

    Gold Sponsors

    내용 -
    1. 모델 선언
    2. 규칙
    3. gorm.Model
    4. 고급
      1. 필드 수준 권한
      2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking 생성/업데이트 시 시간/유닉스시간(밀리/나노) 기록
      3. Embedded Struct
      4. Fields Tags
      5. Associations Tags
    +
    1. 모델 선언
      1. 규칙
      2. gorm.Model
    2. 고급
      1. 필드 수준 권한
      2. Creating/Updating Time/Unix (Milli/Nano) Seconds Tracking 생성/업데이트 시 시간/유닉스시간(밀리/나노) 기록
      3. Embedded Struct
      4. Fields Tags
      5. Associations Tags
    diff --git a/ko_KR/docs/performance.html b/ko_KR/docs/performance.html index 0df2515db90..887de771e9e 100644 --- a/ko_KR/docs/performance.html +++ b/ko_KR/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

    - + diff --git a/ko_KR/docs/preload.html b/ko_KR/docs/preload.html index 6de36af763a..48b5fb91c30 100644 --- a/ko_KR/docs/preload.html +++ b/ko_KR/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

    - + diff --git a/ko_KR/docs/prometheus.html b/ko_KR/docs/prometheus.html index a48cd5d8df1..b1c58132a38 100644 --- a/ko_KR/docs/prometheus.html +++ b/ko_KR/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - + diff --git a/ko_KR/docs/query.html b/ko_KR/docs/query.html index 33843d6a24c..e582fd1b0db 100644 --- a/ko_KR/docs/query.html +++ b/ko_KR/docs/query.html @@ -32,8 +32,8 @@ - - + + @@ -219,7 +219,7 @@

    - + diff --git a/ko_KR/docs/scopes.html b/ko_KR/docs/scopes.html index 99922e0dbc8..c79e900628d 100644 --- a/ko_KR/docs/scopes.html +++ b/ko_KR/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/ko_KR/docs/security.html b/ko_KR/docs/security.html index b7c007c762f..882f85cd02a 100644 --- a/ko_KR/docs/security.html +++ b/ko_KR/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

    Inline Condition

    // will be escaped
    db.First(&user, "name = ?", userInput)

    // SQL injection
    db.First(&user, fmt.Sprintf("name = %v", userInput))

    When retrieving objects with number primary key by user’s input, you should check the type of variable.

    -
    userInputID := "1=1;drop table users;"
    // safe, return error
    id,err := strconv.Atoi(userInputID)
    if err != nil {
    return error
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;
    +
    userInputID := "1=1;drop table users;"
    // safe, return error
    id, err := strconv.Atoi(userInputID)
    if err != nil {
    return err
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;

    SQL injection Methods

    To support some features, some inputs are not escaped, be careful when using user’s input with those methods

    db.Select("name; drop table users;").First(&user)
    db.Distinct("name; drop table users;").First(&user)

    db.Model(&user).Pluck("name; drop table users;", &names)

    db.Group("name; drop table users;").First(&user)

    db.Group("name").Having("1 = 1;drop table users;").First(&user)

    db.Raw("select name from users; drop table users;").First(&user)

    db.Exec("select name from users; drop table users;")

    db.Order("name; drop table users;").First(&user)
    @@ -169,7 +169,7 @@

    - + diff --git a/ko_KR/docs/serializer.html b/ko_KR/docs/serializer.html index 9071b2845e1..ea8062e615d 100644 --- a/ko_KR/docs/serializer.html +++ b/ko_KR/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

    - + diff --git a/ko_KR/docs/session.html b/ko_KR/docs/session.html index 1316492f9c1..e87bbfaa0cc 100644 --- a/ko_KR/docs/session.html +++ b/ko_KR/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

    diff --git a/ko_KR/docs/settings.html b/ko_KR/docs/settings.html index c376596047d..eef00737b01 100644 --- a/ko_KR/docs/settings.html +++ b/ko_KR/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - + diff --git a/ko_KR/docs/sharding.html b/ko_KR/docs/sharding.html index dec912cffcd..2da68a6155d 100644 --- a/ko_KR/docs/sharding.html +++ b/ko_KR/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    - + diff --git a/ko_KR/docs/sql_builder.html b/ko_KR/docs/sql_builder.html index a813a6d3928..92112f7f8a3 100644 --- a/ko_KR/docs/sql_builder.html +++ b/ko_KR/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

    diff --git a/ko_KR/docs/transactions.html b/ko_KR/docs/transactions.html index c852f6d92bd..c66ae88f098 100644 --- a/ko_KR/docs/transactions.html +++ b/ko_KR/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

    db.Transaction(func(tx *gorm.DB) error {
    // do some database operations in the transaction (use 'tx' from this point, not 'db')
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // return any error will rollback
    return err
    }

    if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
    }

    // return nil will commit the whole transaction
    return nil
    })

    Nested Transactions

    GORM supports nested transactions, you can rollback a subset of operations performed within the scope of a larger transaction, for example:

    -
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3
    +
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx3 *gorm.DB) error {
    tx3.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3

    Control the transaction manually

    Gorm supports calling transaction control functions (commit / rollback) directly, for example:

    // begin a transaction
    tx := db.Begin()

    // do some database operations in the transaction (use 'tx' from this point, not 'db')
    tx.Create(...)

    // ...

    // rollback the transaction in case of error
    tx.Rollback()

    // Or commit the transaction
    tx.Commit()
    @@ -169,7 +169,7 @@

    - + diff --git a/ko_KR/docs/update.html b/ko_KR/docs/update.html index cd29961fa8a..38aeec7993e 100644 --- a/ko_KR/docs/update.html +++ b/ko_KR/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -208,7 +208,7 @@

    - + diff --git a/ko_KR/docs/v2_release_note.html b/ko_KR/docs/v2_release_note.html index 4fe911281c9..134761236d0 100644 --- a/ko_KR/docs/v2_release_note.html +++ b/ko_KR/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

    - + diff --git a/ko_KR/docs/write_driver.html b/ko_KR/docs/write_driver.html index fb29879de31..425803f52ac 100644 --- a/ko_KR/docs/write_driver.html +++ b/ko_KR/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

    Write Driver

    -

    새로운 드라이버 작성

    GORM은 sqlite, mysql, postgres, sqlserver를 공식 지원합니다.

    -

    어떤 database들은 mysql 또는 postgres 방언dialect을 그냥 사용하면 될 정도의 호환성을 가지고 있기도 합니다.

    -

    그 이외에는, the dialect interface의 구현체를 만들어, 새로운 드라이버를 만들 수 있습니다.

    -
    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
    }
    - -

    MySQL 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.

    +

    Compatibility with MySQL or Postgres Dialects

    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.

    +

    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:

    +
    type Dialector interface {
    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.

    +
    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
    }
    + +

    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:

    +
    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:

    +
    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:

    +
    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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

    @@ -159,7 +192,7 @@

    Write Driver

    - +
    @@ -273,7 +306,7 @@

    Gold Sponsors

    내용 -
    1. 새로운 드라이버 작성
    +
    1. Compatibility with MySQL or Postgres Dialects
    2. Implementing the Dialector
      1. Nested Transaction Support
      2. Custom Clause Builders
    diff --git a/ko_KR/docs/write_plugins.html b/ko_KR/docs/write_plugins.html index 241a620f97c..a86a83cbe6e 100644 --- a/ko_KR/docs/write_plugins.html +++ b/ko_KR/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

    Write Plugins

    -

    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

    -

    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

    -

    Register Callback

    Register a callback into callbacks

    -
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    db.Callback().Create().Register("crop_image", cropImage)
    // register a callback for Create process
    +

    Callbacks

    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 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.

    +

    Registering a Callback

    You can register a callback for specific operations. For example, to add a custom image cropping functionality:

    +
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    // Register the callback for the Create operation
    db.Callback().Create().Register("crop_image", cropImage)
    -

    Delete Callback

    Delete a callback from callbacks

    -
    db.Callback().Create().Remove("gorm:create")
    // delete callback `gorm:create` from Create callbacks
    +

    Deleting a Callback

    If a callback is no longer needed, it can be removed:

    +
    // Remove the 'gorm:create' callback from Create operations
    db.Callback().Create().Remove("gorm:create")
    -

    Replace Callback

    Replace a callback having the same name with the new one

    -
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    // replace callback `gorm:create` with new function `newCreateFunction` for Create process
    +

    Replacing a Callback

    Callbacks with the same name can be replaced with a new function:

    +
    // Replace the 'gorm:create' callback with a new function
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    -

    Register Callback with orders

    Register callbacks with orders

    -
    // before gorm:create
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // after gorm:create
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // after gorm:query
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // after gorm:delete
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // before gorm:update
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    +

    Ordering Callbacks

    Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

    +
    // Register to execute before the 'gorm:create' callback
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:create' callback
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:query' callback
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // Register to execute after the 'gorm:delete' callback
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // Register to execute before the 'gorm:update' callback
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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)

    // Register to execute before any other callbacks
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // Register to execute after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    -

    Defined Callbacks

    GORM has defined some callbacks to power current GORM features, check them out before starting your plugins

    -

    Plugin

    GORM provides a Use method to register plugins, the plugin needs to implement the Plugin interface

    +

    Predefined Callbacks

    GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

    +

    Plugins

    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:

    type Plugin interface {
    Name() string
    Initialize(*gorm.DB) error
    }
    -

    The Initialize method will be invoked when registering the plugin into GORM first time, and GORM will save the registered plugins, access them like:

    -
    db.Config.Plugins[pluginName]
    +
      +
    • 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:

    +
    // Example of registering a plugin
    db.Use(MyCustomPlugin{})
    -

    Checkout Prometheus as example

    +

    Accessing Registered Plugins

    After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

    +
    // 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:

    +
    // Registering the Prometheus plugin
    db.Use(prometheus.New(prometheus.Config{
    // Configuration options here
    }))
    + +

    Prometheus plugin documentation provides detailed information on its implementation and usage.

    @@ -175,7 +186,7 @@

    - + @@ -289,7 +300,7 @@

    Gold Sponsors

    내용 -
    1. Callbacks
      1. Register Callback
      2. Delete Callback
      3. Replace Callback
      4. Register Callback with orders
      5. Defined Callbacks
    2. Plugin
    +
    1. Callbacks
      1. Registering a Callback
      2. Deleting a Callback
      3. Replacing a Callback
      4. Ordering Callbacks
      5. Predefined Callbacks
    2. Plugins
      1. The Plugin Interface
      2. Registering a Plugin
      3. Accessing Registered Plugins
      4. Practical Example
    diff --git a/ko_KR/gen.html b/ko_KR/gen.html index a5e9efaed55..f749ceebef3 100644 --- a/ko_KR/gen.html +++ b/ko_KR/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/ko_KR/gen/associations.html b/ko_KR/gen/associations.html index 7012973fada..c7948efb46a 100644 --- a/ko_KR/gen/associations.html +++ b/ko_KR/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

    // specify model
    g.ApplyBasic(model.Customer{}, model.CreditCard{})

    // assoications will be detected and converted to code
    package query

    type customer struct {
    ...
    CreditCards customerHasManyCreditCards
    }

    type creditCard struct{
    ...
    }
    -

    Relate to table in database

    The association have to be speified by gen.FieldRelate

    -
    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"}},
    }),
    )

    g.ApplyBasic(card, custormer)
    +

    Relate to table in database

    The association have to be specified by gen.FieldRelate

    +
    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"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(card, custormer)

    GEN will generate models with associated field:

    -
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }
    +
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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;references:ID" json:"credit_cards"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }

    If associated model already exists, gen.FieldRelateModel can help you build associations between them.

    -
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
    }),
    )

    g.ApplyBasic(custormer)
    +
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(custormer)

    Relate Config

    type RelateConfig struct {
    // specify field's type
    RelatePointer bool // ex: CreditCard *CreditCard
    RelateSlice bool // ex: CreditCards []CreditCard
    RelateSlicePointer bool // ex: CreditCards []*CreditCard

    JSONTag string // related field's JSON tag
    GORMTag string // related field's GORM tag
    NewTag string // related field's new tag
    OverwriteTag string // related field's tag
    }

    Operation

    Skip Auto Create/Update

    user := model.User{
    Name: "modi",
    BillingAddress: Address{Address1: "Billing Address - Address 1"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
    Emails: []Email{
    {Email: "modi@example.com"},
    {Email: "modi-2@example.com"},
    },
    Languages: []Language{
    {Name: "ZH"},
    {Name: "EN"},
    },
    }

    u := query.Use(db).User

    u.WithContext(ctx).Select(u.Name).Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
    // Skip create BillingAddress when creating a user

    u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
    // Skip create BillingAddress.Address1 when creating a user

    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

    Find matched associations

    u := query.Use(db).User

    languages, err = u.Languages.Model(&user).Find()
    @@ -198,10 +198,10 @@

    Nested Preloading together, e.g:

    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:

    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.

    +

    Preload with select

    Specify selected columns with method Select. Foreign key must be selected.

    type User struct {
    gorm.Model
    CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
    }

    type CreditCard struct {
    gorm.Model
    Number string
    UserRefer uint
    }

    u := q.User
    cc := q.CreditCard

    // !!! Foregin key "cc.UserRefer" must be selected
    users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
    // SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
    // SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

    Preload with conditions

    GEN allows Preload associations with conditions, it works similar to Inline Conditions.

    @@ -209,14 +209,13 @@

    Nested Preloading

    GEN supports nested preloading, for example:

    db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

    // Customize Preload conditions for `Orders`
    // 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/ko_KR/gen/clause.html b/ko_KR/gen/clause.html index 2740c7e666c..811b070ca5b 100644 --- a/ko_KR/gen/clause.html +++ b/ko_KR/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

    - + diff --git a/ko_KR/gen/create.html b/ko_KR/gen/create.html index 458e538f6b8..bda1acaad45 100644 --- a/ko_KR/gen/create.html +++ b/ko_KR/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

    - + diff --git a/ko_KR/gen/dao.html b/ko_KR/gen/dao.html index 7c775662c34..2b77b596a1f 100644 --- a/ko_KR/gen/dao.html +++ b/ko_KR/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

    - + diff --git a/ko_KR/gen/database_to_structs.html b/ko_KR/gen/database_to_structs.html index 3523920b33c..584377ff73b 100644 --- a/ko_KR/gen/database_to_structs.html +++ b/ko_KR/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

    diff --git a/ko_KR/gen/delete.html b/ko_KR/gen/delete.html index 9c22ada7cdc..750ef9f36ec 100644 --- a/ko_KR/gen/delete.html +++ b/ko_KR/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

    - + diff --git a/ko_KR/gen/dynamic_sql.html b/ko_KR/gen/dynamic_sql.html index 42f8547123f..593a5d54cbe 100644 --- a/ko_KR/gen/dynamic_sql.html +++ b/ko_KR/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/ko_KR/gen/gen_tool.html b/ko_KR/gen/gen_tool.html index 3ba870775b1..3e9a6478d50 100644 --- a/ko_KR/gen/gen_tool.html +++ b/ko_KR/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

    - + diff --git a/ko_KR/gen/index.html b/ko_KR/gen/index.html index d1d1a014c68..6e84938676c 100644 --- a/ko_KR/gen/index.html +++ b/ko_KR/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/ko_KR/gen/query.html b/ko_KR/gen/query.html index ac543866fb6..1d4d05de993 100644 --- a/ko_KR/gen/query.html +++ b/ko_KR/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

    int/uint/float Fields

    // int field
    f := field.NewInt("user", "id")
    // `user`.`id` = 123
    f.Eq(123)
    // `user`.`id` DESC
    f.Desc()
    // `user`.`id` AS `user_id`
    f.As("user_id")
    // COUNT(`user`.`id`)
    f.Count()
    // SUM(`user`.`id`)
    f.Sum()
    // SUM(`user`.`id`) > 123
    f.Sum().Gt(123)
    // ((`user`.`id`+1)*2)/3
    f.Add(1).Mul(2).Div(3),
    // `user`.`id` <<< 3
    f.LeftShift(3)
    -

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `uesr`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")
    +

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `user`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")

    Time Fields

    birth := field.NewString("user", "birth")
    // `user`.`birth` = ? (now)
    birth.Eq(time.Now())
    // DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
    birth.Add(time.Duration(time.Hour).Microseconds())
    // DATE_FORMAT(`user`.`birth`, "%W %M %Y")
    birth.DateFormat("%W %M %Y")

    Bool Fields

    active := field.NewBool("user", "active")
    // `user`.`active` = TRUE
    active.Is(true)
    // NOT `user`.`active`
    active.Not()
    // `user`.`active` AND TRUE
    active.And(true)

    SubQuery

    A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

    -
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
    // 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
    +
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

    From SubQuery

    GORM allows you using subquery in FROM clause with method Table, for example:

    u := query.User
    p := query.Pet

    users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
    // SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

    subQuery1 := u.WithContext(ctx).Select(u.Name)
    subQuery2 := p.WithContext(ctx).Select(p.Name)
    users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
    db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
    // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    @@ -312,7 +312,7 @@

    - + diff --git a/ko_KR/gen/rawsql_driver.html b/ko_KR/gen/rawsql_driver.html index 49900ca140e..f2cb5779818 100644 --- a/ko_KR/gen/rawsql_driver.html +++ b/ko_KR/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ko_KR/gen/sql_annotation.html b/ko_KR/gen/sql_annotation.html index f66cbb519bb..9c58cc4bbc3 100644 --- a/ko_KR/gen/sql_annotation.html +++ b/ko_KR/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

    query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
    // UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

    query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
    // UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

    query.User.Update(User{Age: 0}, 10)
    // UPDATE users SET is_adult=0 WHERE id=10

    for

    The for expression iterates over a slice to generate the SQL, let’s explain by example

    -
    // 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)
    +
    // SELECT * FROM @@table
    // {{where}}
    // {{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}}
    // {{end}}
    // {{end}}
    Filter(users []gen.T) ([]gen.T, error)

    Usage:

    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","%"))
    @@ -254,7 +254,7 @@

    - + diff --git a/ko_KR/gen/transaction.html b/ko_KR/gen/transaction.html index 5b1ff1055ea..7cf186a1721 100644 --- a/ko_KR/gen/transaction.html +++ b/ko_KR/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/ko_KR/gen/update.html b/ko_KR/gen/update.html index 6b5936153de..6bbe2bcfd02 100644 --- a/ko_KR/gen/update.html +++ b/ko_KR/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/ko_KR/gorm.html b/ko_KR/gorm.html index ab064ae50b2..c03643f6d37 100644 --- a/ko_KR/gorm.html +++ b/ko_KR/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - +
    diff --git a/ko_KR/gormx.html b/ko_KR/gormx.html index b0b05e058d2..a1d49775c99 100644 --- a/ko_KR/gormx.html +++ b/ko_KR/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ko_KR/hints.html b/ko_KR/hints.html index 346fff0272c..0001745921c 100644 --- a/ko_KR/hints.html +++ b/ko_KR/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/ko_KR/index.html b/ko_KR/index.html index 926f1fe558c..27cf03d3317 100644 --- a/ko_KR/index.html +++ b/ko_KR/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/ko_KR/rawsql.html b/ko_KR/rawsql.html index c8c4137fb6b..47b8e778184 100644 --- a/ko_KR/rawsql.html +++ b/ko_KR/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ko_KR/rawsql_driver.html b/ko_KR/rawsql_driver.html index 8e6a3138c22..7fab4a5f822 100644 --- a/ko_KR/rawsql_driver.html +++ b/ko_KR/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ko_KR/sharding.html b/ko_KR/sharding.html index 307b2bbb447..7452f1e6fd6 100644 --- a/ko_KR/sharding.html +++ b/ko_KR/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ko_KR/stats.html b/ko_KR/stats.html index b2a119b91a5..280f112c8e2 100644 --- a/ko_KR/stats.html +++ b/ko_KR/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/pl_PL/404.html b/pl_PL/404.html index f621bc80f22..489152bb1cd 100644 --- a/pl_PL/404.html +++ b/pl_PL/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/pl_PL/index.html b/pl_PL/index.html index 57ff4805e6e..cbe5cef621d 100644 --- a/pl_PL/index.html +++ b/pl_PL/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/pt_BR/404.html b/pt_BR/404.html index 2876b33a2d5..fbfa04711e0 100644 --- a/pt_BR/404.html +++ b/pt_BR/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/pt_BR/index.html b/pt_BR/index.html index 50879342e4a..2e604d682ab 100644 --- a/pt_BR/index.html +++ b/pt_BR/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/ru_RU/404.html b/ru_RU/404.html index 46e4803f675..668fdf6cf64 100644 --- a/ru_RU/404.html +++ b/ru_RU/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/ru_RU/community.html b/ru_RU/community.html index 13c09cda6d6..efae87987d8 100644 --- a/ru_RU/community.html +++ b/ru_RU/community.html @@ -32,8 +32,8 @@ - - + + @@ -160,7 +160,7 @@

    - + diff --git a/ru_RU/contribute.html b/ru_RU/contribute.html index 4f8813a13b6..1b78ae15d4b 100644 --- a/ru_RU/contribute.html +++ b/ru_RU/contribute.html @@ -32,8 +32,8 @@ - - + + @@ -149,7 +149,7 @@

    - + diff --git a/ru_RU/datatypes.html b/ru_RU/datatypes.html index d1ddfd21d9e..104a1fc9bd0 100644 --- a/ru_RU/datatypes.html +++ b/ru_RU/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ru_RU/docs/advanced_query.html b/ru_RU/docs/advanced_query.html index 361bd0aa2f6..c3187c1033b 100644 --- a/ru_RU/docs/advanced_query.html +++ b/ru_RU/docs/advanced_query.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,80 +117,106 @@

    Расширенный запрос
    -

    Умный выбор полей

    GORM позволяет выбирать определенные поля с помощью Select, если вы часто используете их в своем приложении, вы можете использовать более короткий struct для выбора определенных полей автоматически:

    -
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // следующие сотни полей
    }

    type APIUser struct {
    ID uint
    Name string
    }

    // Выбор`id`, `name` автоматически при запросе
    db.Model(&User{}).Limit(10).Find(&APIUser{})
    // SELECT `id`, `name` FROM `users` LIMIT 10
    +

    Умный выбор полей

    In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

    +
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // hundreds of fields
    }

    type APIUser struct {
    ID uint
    Name string
    }

    // GORM will automatically select `id`, `name` fields when querying
    db.Model(&User{}).Limit(10).Find(&APIUser{})
    // SQL: SELECT `id`, `name` FROM `users` LIMIT 10
    -

    Примечание Режим QueryFields будет выбираться по имени всех полей для текущей модели

    +

    NOTE In QueryFields mode, all model fields are selected by their names.

    -
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    db.Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users` // с помощью этой опции

    // Режим сессии
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    +
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    // Default behavior with QueryFields set to true
    db.Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

    // Using Session Mode with QueryFields
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    -

    Блокировка (ДЛЯ ОБНОВЛЕНИЯ)

    GORM поддерживает различные типы блокировок, например:

    -
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SELECT * FROM `users` FOR UPDATE

    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SELECT * FROM `users` FOR SHARE OF `users`

    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SELECT * FROM `users` FOR UPDATE NOWAIT
    +

    Locking

    GORM поддерживает различные типы блокировок, например:

    +
    // Basic FOR UPDATE lock
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE
    -

    Смотрите Чистый SQL и Конструктор SQL для получения более подробной информации

    -

    Подзапрос

    Подзапрос может быть вложен в запрос, GORM сгенерирует подзапрос при использовании *gorm.DB объекта в качестве параметра

    -
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    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%")
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR SHARE OF `users`
    -

    Из SubQuery (под запроса)

    GORM позволяет вам использовать подзапрос в предложении FROM с помощью метода Table, например:

    -
    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

    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
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
    -

    Группировка условий

    Легче написать сложный SQL-запрос с группировкой условий

    -
    db.Where(
    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")
    +

    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.

    +

    Подзапрос

    Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

    +
    // Simple subquery
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
    // SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
    -

    IN с несколькими столбцами

    Выборка IN с несколькими столбцами

    -
    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"));
    +

    Из SubQuery (под запроса)

    GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

    +
    // Using subquery in FROM clause
    db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    -

    Именованные аргументы

    GORM поддерживает именованные аргументы при использовании sql.NamedArg или map[string]interface{}{}, например:

    -
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

    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
    +

    Группировка условий

    Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

    +
    // Complex SQL query using Group Conditions
    db.Where(
    db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
    ).Or(
    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")
    -

    Смотрите Чистый SQL и Конструктор SQL для подробностей

    -

    Find с картами

    GORM позволяет передавать результаты сканирования в map[string]interface{} или []map[string]interface{}, не забудьте указать Model или Table, например:

    -
    result := map[string]interface{}{}
    db.Model(&User{}).First(&result, "id = ?", 1)

    var results []map[string]interface{}
    db.Table("users").Find(&results)
    +

    IN с несколькими столбцами

    GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query.

    +
    // Using IN with multiple columns
    db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
    // SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
    -

    FirstOrInit

    Получить первую найденную запись, или инициализировать новую с заданными параметрами (работает только со структурой и картой)

    -
    // Пользователь не найден, создаем новую запись с данными значениями
    db.FirstOrInit(&user, User{Name: "non_existing"})
    // user -> User{Name: "non_existing"}

    // Найден пользователь с `name` = `jinzhu`
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}

    // Найден пользователь с `name` = `jinzhu`
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
    +

    Именованные аргументы

    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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

    +
    // Example using sql.NamedArg for named arguments
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // 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)
    // SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
    -

    Инициализируйте структуру с большим количеством атрибутов, если запись не найдена, эти Attrs не будут использоваться для построения SQL-запроса

    -
    // Пользователь не найден, инициализировать структуру с указанными параметрами и атрибутами Attrs
    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}

    // Пользователь не найден, инициализировать структуру с указанными параметрами и атрибутами 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 будут проигнорированы
    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}
    +

    For more examples and details, see Raw SQL and SQL Builder

    +

    Find с картами

    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.

    +
    // 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`
    -

    Метод Assign назначает атрибуты в структуру, независимо от того, найдена запись или нет, эти атрибуты не будут участвовать в генерации запроса SQL и не будут сохранены в БД

    -
    // Пользователь не найден, создать его с данными условиями и с атрибутами в Assign 
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // user -> User{Name: "non_existing", Age: 20}

    // Найден пользователь с `name` = `jinzhu`, обновить запись с атрибутами в Assign
    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}
    +

    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.

    +
    // 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"} if not found

    // Retrieving a user named "jinzhu"
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

    // Using a map to specify the search condition
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    FirstOrCreate

    Получите первую совпадающую запись или создайте новую с заданными условиями (работает только со структурой, условиями сопоставления), rowsAffected возвращает количество созданных/обновленных записей

    -
    // Пользователь не найден, создать новую запись с заданными условиями
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1

    // Найден пользователь с `name` = `jinzhu`
    result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
    // user -> User{ID: 111, Name: "jinzhu", "Age": 18}
    // result.RowsAffected // => 0
    +

    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.

    +
    // If no User is found, initialize with given conditions and additional attributes
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // user -> User{Name: "non_existing", Age: 20} if not found

    // If a User named "Jinzhu" is found, `Attrs` are ignored
    db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    Создание структуры с дополнительными атрибутами если запись не найдена. Эти Attrs атрибуты не будут использованы в построении SQL запроса

    -
    // User не найден, создать его с данными условиями и атрибутами в Attrs
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // Найден user с `name` = `jinzhu`, атрибуты в Attrs будут проигнорированы
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    +

    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.

    +
    // 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} if not found

    // If a User named "Jinzhu" is found, update the struct with Assign attributes
    db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
    -

    Метод Assign назначает атрибуты к записи, будет работать независимо от того, найдена запись или нет.

    -
    // User не найден, создать его с данными условиями и атрибутами в Assign
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // Найден user с `name` = `jinzhu`, обновить эту запись с атрибутами в Assign
    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}
    +

    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

    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.

    +
    // Create a new record if not found
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // SQL: INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1 (record created)

    // 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)
    -

    Оптимизатор/Индексы

    Подсказки оптимизатора позволяют контролировать оптимизатор запросов для выбора определенного плана выполнения запроса, GORM поддерживает его с помощью gorm.io/hints, например:

    -
    import "gorm.io/hints"

    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    +

    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.

    +
    // Create a new record with additional attributes if not found
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // If the user is found, `Attrs` are ignored
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu';
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    -

    Индексные подсказки позволяют передавать индексированные подсказки к базе данных, если планировщик запросов ошибается.

    -
    import "gorm.io/hints"

    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // SELECT * FROM `users` USE INDEX (`idx_user_name`)

    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`)"
    +

    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.

    +
    // Initialize and save new record with `Assign` attributes if not found
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // Update found record with `Assign` attributes
    db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // 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 поддерживает итерацию по строкам

    -
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    defer rows.Close()

    for rows.Next() {
    var user User
    // ScanRows метод `gorm.DB`, он может быть использован для сканирования строки в struct
    db.ScanRows(rows, &user)

    // делаем что-то
    }
    +

    Оптимизатор/Индексы

    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.

    +
    import "gorm.io/hints"

    // Using an optimizer hint to set a maximum execution time
    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    -

    FindInBatches

    Запрашивать и обрабатывать записи в пакете

    -
    // размер пакета 100
    result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
    for _, result := range results {
    // пакетная обработка найденных записей
    }

    tx.Save(&results)

    tx.RowsAffected // количество записей в этом пакете

    batch // Batch 1, 2, 3

    // возврат ошибки остановит обработку
    return nil
    })

    result.Error // возвращаемая ошибка
    result.RowsAffected // кол-во обработанных записей во всех пакетах
    +

    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.

    +
    import "gorm.io/hints"

    // Suggesting the use of a specific index
    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
    -

    Хуки запросов

    GORM позволяет использовать хуки AfterFind для запроса, который будет вызываться при выполнении запроса. Смотрите Хуки для подробностей

    -
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    if u.Role == "" {
    u.Role = "user"
    }
    return
    }
    +

    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 in the GORM documentation.

    +

    Итерация

    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.

    +
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    defer rows.Close()

    for rows.Next() {
    var user User
    // ScanRows scans a row into a struct
    db.ScanRows(rows, &user)

    // Perform operations on each user
    }
    -

    Pluck

    Запрос одного столбца из базы данных и запись его в слайс, если вы хотите получить несколько столбцов - используйте Select вместе с Scan для этого

    -
    var ages []int64
    db.Model(&users).Pluck("age", &ages)

    var names []string
    db.Model(&User{}).Pluck("name", &names)

    db.Table("deleted_users").Pluck("name", &names)

    // Distinct Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SELECT DISTINCT `name` FROM `users`

    // Запрос больше одного столбца, используйте `Scan` или `Find` как в примере:
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    +

    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.

    +
    // 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 provides the count of records in the current batch
    // The variable 'batch' indicates the current batch number

    // Returning an error will stop further batch processing
    return nil
    })

    // result.Error contains any errors encountered during batch processing
    // result.RowsAffected provides the count of all processed records across batches
    -

    Scopes

    Scopes позволяют указать часто используемые запросы, которые можно использовать позже как методы

    -
    func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
    return db.Where("amount > ?", 1000)
    }

    func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
    return db.Where("pay_mode_sign = ?", "C")
    }

    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 {
    return db.Where("status IN (?)", status)
    }
    }

    db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
    // Найдите все заказы по кредитным картам на сумму более 1000

    db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
    // Найдите все заказы наложенным платежом на сумму более 1000

    db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
    // Найдите все оплаченные, отправленные заказы на сумму более 1000
    +

    FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

    +

    Хуки запросов

    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 in the GORM documentation.

    +
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    // Custom logic after finding a user
    if u.Role == "" {
    u.Role = "user" // Set default role if not specified
    }
    return
    }

    // Usage of AfterFind hook happens automatically when a User is queried
    -

    Смотрите Scopes для получения дополнительной информации

    -

    Count

    Получение количество найденных записей

    -
    var count int64
    db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

    db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

    db.Table("deleted_users").Count(&count)
    // SELECT count(1) FROM deleted_users;

    // Count with Distinct
    db.Model(&User{}).Distinct("name").Count(&count)
    // SELECT COUNT(DISTINCT(`name`)) FROM `users`

    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SELECT count(distinct(name)) FROM deleted_users

    // Count with Group
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    count // => 3
    +

    Pluck

    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 or Find instead.

    +
    // Retrieving ages of all users
    var ages []int64
    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)

    // Using Distinct with Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SQL: SELECT DISTINCT `name` FROM `users`

    // Querying multiple columns
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    + +

    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.

    +
    // 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")
    }

    // 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.

    +
    // 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)
    + +

    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 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.

    +
    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'

    // Counting records in a different table
    db.Table("deleted_users").Count(&count)
    // SQL: SELECT count(1) FROM deleted_users
    + +

    Count with Distinct and Group

    GORM also allows counting distinct values and grouping results.

    +
    // Counting distinct names
    db.Model(&User{}).Distinct("name").Count(&count)
    // SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

    // Counting distinct values with a custom select
    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SQL: SELECT count(distinct(name)) FROM deleted_users

    // Counting grouped records
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    // Count after grouping by name
    // count => 3
    @@ -203,7 +229,7 @@

    - + @@ -317,7 +343,7 @@

    Gold Sponsors

    Содержимое -
    1. Умный выбор полей
    2. Блокировка (ДЛЯ ОБНОВЛЕНИЯ)
    3. Подзапрос
      1. Из SubQuery (под запроса)
    4. Группировка условий
    5. IN с несколькими столбцами
    6. Именованные аргументы
    7. Find с картами
    8. FirstOrInit
    9. FirstOrCreate
    10. Оптимизатор/Индексы
    11. Итерация
    12. FindInBatches
    13. Хуки запросов
    14. Pluck
    15. Scopes
    16. Count
    +
    1. Умный выбор полей
    2. Locking
    3. Подзапрос
      1. Из SubQuery (под запроса)
    4. Группировка условий
    5. IN с несколькими столбцами
    6. Именованные аргументы
    7. Find с картами
    8. FirstOrInit
      1. Using Attrs for Initialization
      2. Using Assign for Attributes
    9. FirstOrCreate
      1. Using Attrs with FirstOrCreate
      2. Using Assign with FirstOrCreate
    10. Оптимизатор/Индексы
      1. Index Hints
    11. Итерация
    12. FindInBatches
    13. Хуки запросов
    14. Pluck
    15. Scopes
      1. Defining Scopes
      2. Applying Scopes in Queries
    16. Count
      1. Getting the Count of Matched Records
      2. Count with Distinct and Group
    diff --git a/ru_RU/docs/associations.html b/ru_RU/docs/associations.html index 9ca4ea82ca8..be034f581cb 100644 --- a/ru_RU/docs/associations.html +++ b/ru_RU/docs/associations.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,62 +117,101 @@

    Связи

    -

    Автоматические Create/Update

    GORM будет автоматически сохранять связи и их ссылки с помощью Upsert при создании/обновлении записи.

    -
    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Платежный адрес - Адрес 1"},
    ShippingAddress: Address{Address1: "Адрес доставки - Адрес 1"},
    Emails: []Email{
    {Email: "jinzhu@example.com"},
    {Email: "jinzhu-2@example.com"},
    },
    Languages: []Language{
    {Name: "RU"},
    {Name: "EN"},
    },
    }

    db.Create(&user)
    // BEGIN TRANSACTION;
    // INSERT INTO "addresses" (address1) VALUES ("Платежный адрес - Адрес 1"), ("Адрес доставки - Адрес 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 "user_languages" ("user_id","language_id") VALUES (111, 1), (111, 2) ON DUPLICATE KEY DO NOTHING;
    // COMMIT;

    db.Save(&user)
    - -

    Если понадобится обновить данные связей, то следует использовать режим FullSaveAssociations:

    -
    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);
    // ...
    - -

    Пропуск автоматических Create/Update

    Чтобы пропустить автоматическое сохранение при create / update, можно воспользоваться Select либо Omit, пример:

    -
    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"},
    },
    }

    db.Select("Name").Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    db.Omit("BillingAddress").Create(&user)
    // Пропустить создание <i>BillingAddress</i> при создании <i>user</i>

    db.Omit(clause.Associations).Create(&user)
    // Пропуск всех связей при создании <i>user</i>
    - -

    Примечание: Для связей many2many GORM будет вставлять связи перед созданием ссылок на join таблицу, если понадобится пропустить вставку связей, то сделать это можно следующим образом:

    -
    db.Omit("Languages.*").Create(&user)
    - -

    Следующий код пропустит создание связи и ее ссылок

    -
    db.Omit("Languages").Create(&user)
    - -

    Выбрать/пропускать поля ассоциации

    user := User{
    Name: "jenya",
    BillingAddress: Address{Address1: "Платежный адрес - Адрес 1", Address2: "адрес2"},
    ShippingAddress: Address{Address1: "Адрес доставки - Адрес 1", Address2: "адрес2"},
    }

    // Создать пользователя его платежный адрес и адрес доставки
    // При создании BillingAddress используйте только его поля address1, address2 и опускайте другие
    db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

    db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
    - -

    Типы ассоциаций

    Режим связывания включает некоторые часто используемые вспомогательные методы для обработки отношений

    -
    // Старт режима ассоциаций
    var user User
    db.Model(&user).Association("Languages")
    // `пользователь` - это исходная модель, она должна содержать первичный ключ
    // `Languages` - это имя связанного поля
    // Если два вышеуказанных требования совпадают, AssociationMode должен быть запущен успешно, иначе он должен возвратить ошибку
    db.Model(&user).Association("Languages").Error
    - -

    Поиск связей

    Поиск подходящей ассоциации

    -
    db.Model(&user).Association("Languages").Find(&languages)
    - -

    Поиск ассоциаций с условиями

    -
    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)
    - -

    Добавление связей

    Добавление новых связей для многие-ко-многим много, заменяет текущие связи один, принадлежит

    -
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

    db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
    - -

    Замена связей

    Замена текущих связей новыми

    -
    db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
    - -

    Удаление связей

    Удаление связи между источником и связанными аргументами приводит к удалению ссылки, но сами объекты из БД не удаляются.

    -
    db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
    db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
    - -

    Очистка связей

    Удаляет все ссылки между источником и связью, не удаляя связь

    -
    db.Model(&user).Association("Languages").Clear()
    - -

    Подсчет связей

    Возвращает количество существующих связей

    -
    db.Model(&user).Association("Languages").Count()

    // Количество с учетом условий
    codes := []string{"zh-CN", "en-US", "ja-JP"}
    db.Model(&user).Where("code IN ?", codes).Association("Languages").Count()
    - -

    Пакетная обработка

    Режим связывания поддерживает пакетную обработку, пример:

    -
    // Найти все роли для всех пользователей
    db.Model(&users).Association("Role").Find(&roles)

    // Удалить пользователя A из всех команд, в которых пользователь состоит
    db.Model(&users).Association("Team").Delete(&userA)

    // Получить distinct количество всех команд пользователя
    db.Model(&users).Association("Team").Count()

    // Для `Append`, `Replace` с пакетными данными длина аргументов должна быть равна длине данных, иначе будет возвращена ошибка
    var users = []User{user1, user2, user3}

    // Например: у нас есть 3 пользователя, добавляем UserA в команду user1, UserB в команду user2, добавляем UserA, UserB и UserC в команду user3
    db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC})

    // Сбросить команду UserA у пользователя user1, сбросить команду UserB у пользователя user2, сбросить команды UserA, UserB и UserC у пользователя user3
    db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC})
    - -

    Удаление связей

    По умолчанию, Replace/Delete/Clear в gorm.Association удаляет только ссылку, то есть устанавливает для внешнего ключа значение null.

    -

    Вы можете удалить эти объекты с помощью Unscoped (это не имеет ничего общего с ManyToMany).

    -

    Способ удаления определяется в gorm.DB.

    -
    // Мягкое удаление
    // UPDATE `languages` SET `deleted_at`= ...
    db.Model(&user).Association("Languages").Unscoped().Clear()

    // Удалить безвозвратно
    // DELETE FROM `languages` WHERE ...
    db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
    - -

    Удалить с помощью Select

    Вам разрешено удалять выбранные has one/has many/many2many отношения с помощью Select при удалении записей, например:

    -
    // удалить учетную запись пользователя при удалении пользователя
    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)
    - -

    ПРИМЕЧАНИЕ: Ассоциации будут удалены только в том случае, если первичный ключ удаляемых записей не равен нулю, GORM будет использовать эти первичные ключи в качестве условий для удаления выбранных связей

    -
    // НЕ РАБОТАЕТ
    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` будет удалена
    - -

    Теги связей

    +

    Автоматические Create/Update

    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.

    +
    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"},
    },
    }

    // 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;
    // 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 ('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)
    + +

    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.

    +
    // Update a user and fully update all its associations
    db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
    // 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

    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.

    +
    user := User{
    // User and associated data
    }

    // Only include the 'Name' field when creating the user
    db.Select("Name").Create(&user)
    // 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.

    +
    // Skip creating the 'BillingAddress' when creating the user
    db.Omit("BillingAddress").Create(&user)

    // Skip all associations when creating the user
    db.Omit(clause.Associations).Create(&user)
    + +

    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 .*:

    +
    // Skip upserting 'Languages' associations
    db.Omit("Languages.*").Create(&user)
    + +

    To skip creating both the association and its references:

    +
    // Skip creating 'Languages' associations and their references
    db.Omit("Languages").Create(&user)
    + +

    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.

    +
    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
    }

    // 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.

    +
    // 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: 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.

    +
    // 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
    + +

    Типы ассоциаций

    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.

    +
    var user User
    db.Model(&user).Association("Languages")
    // Check for errors
    error := db.Model(&user).Association("Languages").Error
    + +

    Finding Associations

    Retrieve associated records with or without additional conditions.

    +
    // Simple find
    db.Model(&user).Association("Languages").Find(&languages)

    // Find with conditions
    codes := []string{"zh-CN", "en-US", "ja-JP"}
    db.Model(&user).Where("code IN ?", codes).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.

    +
    // Append new languages
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    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.

    +
    // 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.

    +
    // 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.

    +
    // Clear all languages
    db.Model(&user).Association("Languages").Clear()
    + +

    Counting Associations

    Get the count of current associations, with or without conditions.

    +
    // 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.
    • +
    +
    db.Model(&users).Association("Role").Find(&roles)
    + +
      +
    • Deleting Associations: Remove specific associations across multiple records.
    • +
    +
    db.Model(&users).Association("Team").Delete(&userA)
    + +
      +
    • Counting Associations: Get the count of associations for a batch of records.
    • +
    +
    db.Model(&users).Association("Team").Count()
    + +
      +
    • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
    • +
    +
    var users = []User{user1, user2, 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})

    // 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})
    + +

    Удаление связей

    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.

    +
      +
    • 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.
    • +
    +

    Modifying Deletion Behavior with Unscoped

    For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

    +
      +
    • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
    • +
    +
    db.Model(&user).Association("Languages").Unscoped().Clear()
    + +
      +
    • Permanent Delete: Physically deletes the association records from the database.
    • +
    +
    // db.Unscoped().Model(&user)
    db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
    + +

    Теги связей

    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.

    + @@ -180,36 +219,36 @@

    - + @@ -338,7 +377,7 @@

    Gold Sponsors

    Содержимое -
    1. Автоматические Create/Update
    2. Пропуск автоматических Create/Update
    3. Выбрать/пропускать поля ассоциации
    4. Типы ассоциаций
      1. Поиск связей
      2. Добавление связей
      3. Замена связей
      4. Удаление связей
      5. Очистка связей
      6. Подсчет связей
      7. Пакетная обработка
    5. Удаление связей
    6. Удалить с помощью Select
    7. Теги связей
    +
    1. Автоматические Create/Update
      1. Auto-Saving Associations on Create
      2. Updating Associations with FullSaveAssociations
    2. Пропуск автоматических Create/Update
      1. Using Select to Include Specific Fields
      2. Using Omit to Exclude Fields or Associations
    3. Выбрать/пропускать поля ассоциации
    4. Удаление связей
    5. Типы ассоциаций
      1. Finding Associations
      2. Appending Associations
      3. Replacing Associations
      4. Deleting Associations
      5. Clearing Associations
      6. Counting Associations
      7. Batch Data Handling
    6. Удаление связей
      1. Modifying Deletion Behavior with Unscoped
    7. Теги связей
    diff --git a/ru_RU/docs/belongs_to.html b/ru_RU/docs/belongs_to.html index dc3702c501d..9d47484f44b 100644 --- a/ru_RU/docs/belongs_to.html +++ b/ru_RU/docs/belongs_to.html @@ -32,8 +32,8 @@ - - + + @@ -153,7 +153,7 @@

    - + diff --git a/ru_RU/docs/changelog.html b/ru_RU/docs/changelog.html index 2b2953843bb..be1efeb297c 100644 --- a/ru_RU/docs/changelog.html +++ b/ru_RU/docs/changelog.html @@ -32,8 +32,8 @@ - - + + @@ -158,7 +158,7 @@

    - + diff --git a/ru_RU/docs/composite_primary_key.html b/ru_RU/docs/composite_primary_key.html index 2d4be894283..d1a9b4d8d07 100644 --- a/ru_RU/docs/composite_primary_key.html +++ b/ru_RU/docs/composite_primary_key.html @@ -32,8 +32,8 @@ - - + + @@ -134,7 +134,7 @@

    Композитный первичн
    - +
    diff --git a/ru_RU/docs/connecting_to_the_database.html b/ru_RU/docs/connecting_to_the_database.html index e902a1192cb..a5aabfe81ac 100644 --- a/ru_RU/docs/connecting_to_the_database.html +++ b/ru_RU/docs/connecting_to_the_database.html @@ -32,8 +32,8 @@ - - + + @@ -180,7 +180,7 @@

    - + diff --git a/ru_RU/docs/constraints.html b/ru_RU/docs/constraints.html index aec904d1429..733ee5c637b 100644 --- a/ru_RU/docs/constraints.html +++ b/ru_RU/docs/constraints.html @@ -32,8 +32,8 @@ - - + + @@ -139,7 +139,7 @@

    - + diff --git a/ru_RU/docs/context.html b/ru_RU/docs/context.html index 73bb0ed92d0..27b556e8cfa 100644 --- a/ru_RU/docs/context.html +++ b/ru_RU/docs/context.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,27 +117,25 @@

    Контекст

    -

    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.

    Тег
    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.

    tx := db.WithContext(ctx)
    tx.First(&user, 1)
    tx.Model(&user).Update("role", "admin")
    -

    Таймаут контекста

    Вы можете установить контекст с тайм-аутом в db.WithContext, чтобы установить тайм-аут для длительно выполняющихся запросов, например:

    +

    Context Timeout

    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.

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    db.WithContext(ctx).Find(&users)
    -

    Контекст в Hooks/Callbacks

    Вы можете получить доступ к объекту Context из текущего Statement объекта, например:

    -
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ...
    return
    }
    +

    Контекст в Hooks/Callbacks

    The context can also be accessed within GORM’s hooks/callbacks. This enables contextual information to be used during these lifecycle events.

    +
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ... use context
    return
    }
    -

    Пример Chi Middleware

    Режим непрерывного сеанса, который может быть полезен при обработке запросов API, например, вы можете настроить *gorm.DB с контекстом тайм-аута в middleware, а затем использовать *gorm.DB при обработке всех запросов

    -

    Ниже приведен пример Chi middleware:

    -
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    db, ok := ctx.Value("DB").(*gorm.DB)

    var users []User
    db.Find(&users)

    // много операций с базой данных
    })

    r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
    db, ok := ctx.Value("DB").(*gorm.DB)

    var user User
    db.First(&user)

    // много операций с базой данных
    })
    +

    Integration with 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.

    +
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    // Router setup
    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    // Route handlers
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })

    r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })
    -

    ПРИМЕЧАНИЕ Настройка контекста с помощью WithContext goroutine-safe, обратитесь к Сессия для получения подробной информации

    -
    - -

    Logger

    Logger также принимает контекст, вы можете использовать его для отслеживания событий, обратитесь к Logger для получения подробной информации

    +

    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 in GORM.

    +

    Logger Integration

    GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

    +

    Refer to Logger documentation for more details.

    @@ -150,7 +148,7 @@

    - + @@ -264,7 +262,7 @@

    Gold Sponsors

    Содержимое -
    1. Режим одной сессии
    2. Режим непрерывной сессии
    3. Таймаут контекста
    4. Контекст в Hooks/Callbacks
    5. Пример Chi Middleware
    6. Logger
    +
    1. Режим одной сессии
    2. Continuous Session Mode
    3. Context Timeout
    4. Контекст в Hooks/Callbacks
    5. Integration with Chi Middleware
    6. Logger Integration
    diff --git a/ru_RU/docs/conventions.html b/ru_RU/docs/conventions.html index 41eba638ff9..2f1a8afb496 100644 --- a/ru_RU/docs/conventions.html +++ b/ru_RU/docs/conventions.html @@ -32,8 +32,8 @@ - - + + @@ -137,7 +137,7 @@

    // Создать таблицу `deleted_users` с полями struct User
    db.Table("deleted_users").AutoMigrate(&User{})

    // Запросить данные из другой таблицы
    var deletedUsers []User
    db.Table("deleted_users").Find(&deletedUsers)
    // SELECT * FROM deleted_users;

    db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
    // DELETE FROM deleted_users WHERE name = 'jinzhu';

    Смотрите SubQuery для того, чтобы использовать SubQuery в выражении FROM

    -

    Стратегия именования

    GORM позволяет пользователям изменять стратегию именования по умолчанию, переопределяя стандартную NamingStrategy, которая используется для сборки TableName, ColumnName, JoinTableName, RelationshipFKName, CheckerName, IndexName, Смотрите Настройки GORM для подробностей

    +

    Стратегия именования

    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 for details

    Название столбца

    Имя столбца db использует имя поля в формате snake_case с преобразованием.

    type User struct {
      ID        uint      // имя столбца `id`
      Name      string    // имя столбца `name`
      Birthday  time.Time // имя столбца `birthday`
      CreatedAt time.Time // имя столбца `created_at`
    }
    @@ -170,7 +170,7 @@

    - + diff --git a/ru_RU/docs/create.html b/ru_RU/docs/create.html index 1cdc22a4086..432af642e62 100644 --- a/ru_RU/docs/create.html +++ b/ru_RU/docs/create.html @@ -32,8 +32,8 @@ - - + + @@ -131,7 +131,7 @@

    Create a record and ignore the values for fields passed to omit.

    db.Omit("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
    -

    Пакетная вставка

    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.

    var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
    db.Create(&users)

    for _, user := range users {
    user.ID // 1,2,3
    }

    You can specify batch size when creating with CreateInBatches, e.g:

    @@ -196,7 +196,7 @@

    - + diff --git a/ru_RU/docs/data_types.html b/ru_RU/docs/data_types.html index 2f03a45a15f..b1586dfe2a1 100644 --- a/ru_RU/docs/data_types.html +++ b/ru_RU/docs/data_types.html @@ -32,8 +32,8 @@ - - + + @@ -165,7 +165,7 @@

    - + diff --git a/ru_RU/docs/dbresolver.html b/ru_RU/docs/dbresolver.html index 2e939ae441e..ea69f71a350 100644 --- a/ru_RU/docs/dbresolver.html +++ b/ru_RU/docs/dbresolver.html @@ -32,8 +32,8 @@ - - + + @@ -140,7 +140,7 @@

    Transaction

    When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

    But you can specifies which DB to use before starting a transaction, for example:

    -
    // Start transaction based on default replicas db
    tx := DB.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := DB.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
    +
    // Start transaction based on default replicas db
    tx := db.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := db.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

    Балансировка Нагрузки

    GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

    type Policy interface {
    Resolve([]gorm.ConnPool) gorm.ConnPool
    }
    @@ -159,7 +159,7 @@

    - + diff --git a/ru_RU/docs/delete.html b/ru_RU/docs/delete.html index 8c350e63974..66d3881cb75 100644 --- a/ru_RU/docs/delete.html +++ b/ru_RU/docs/delete.html @@ -32,8 +32,8 @@ - - + + @@ -178,7 +178,7 @@

    - + diff --git a/ru_RU/docs/error_handling.html b/ru_RU/docs/error_handling.html index ba153340da5..881adcabe3e 100644 --- a/ru_RU/docs/error_handling.html +++ b/ru_RU/docs/error_handling.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,21 +117,40 @@

    Обработка ошибок

    -

    В Go, очень важна обработка ошибок.

    -

    Вам рекомендуется проверять ошибки после Методов окончания

    -

    Обработка ошибок

    Обработка ошибок в GORM отличается от идиоматического Go кода из-за цепного API.

    -

    Если возникнет ошибка, GORM заполнит поле *gorm.DB Error, его необходимо проверять следующим образом:

    -
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // обработка ошибок ...
    }
    - -

    или

    -
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // обработка ошибок ...
    }
    - -

    ErrRecordNotFound

    GORM возвращает ErrRecordNotFound, когда не удалось найти данные при помощи First, Last, Take, если произошло несколько ошибок, вы можете проверить ErrRecordNotFound при помощи errors.Is, например:

    -
    // Проверка возвращается ли ошибка RecordNotFound
    err := db.First(&user, 100).Error
    errors.Is(err, gorm.ErrRecordNotFound)
    -

    Dialect Translated Errors

    Если вы хотите иметь возможность использовать ошибки перевода диалекта (например, ErrDuplicatedKey), то включите флаг TranslateError при открытии подключения к базе данных.

    +

    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.

    +

    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.

    +

    After a chain of methods, it’s crucial to check the Error field:

    +
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // Handle error...
    }
    + +

    Or alternatively:

    +
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // Handle error...
    }
    + +

    ErrRecordNotFound

    GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

    +
    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

    +
      +
    • Example: Handling MySQL Error Codes
    • +
    +
    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.

    db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
    -

    Ошибки

    Список ошибок

    +
      +
    • ErrDuplicatedKey
    • +
    +

    This error occurs when an insert operation violates a unique constraint:

    +
    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:

    +
    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.

    +

    Ошибки

    For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

    @@ -144,7 +163,7 @@

    - + @@ -258,7 +277,7 @@

    Gold Sponsors

    Содержимое -
    1. Обработка ошибок
    2. ErrRecordNotFound
    3. Dialect Translated Errors
    4. Ошибки
    +
    1. Basic Error Handling
    2. ErrRecordNotFound
    3. Handling Error Codes
    4. Dialect Translated Errors
    5. Ошибки
    diff --git a/ru_RU/docs/generic_interface.html b/ru_RU/docs/generic_interface.html index 0887383328c..559cfe2abcd 100644 --- a/ru_RU/docs/generic_interface.html +++ b/ru_RU/docs/generic_interface.html @@ -32,8 +32,8 @@ - - + + @@ -136,7 +136,7 @@

    - + diff --git a/ru_RU/docs/gorm_config.html b/ru_RU/docs/gorm_config.html index 1d1720c57d0..59ee7b97be2 100644 --- a/ru_RU/docs/gorm_config.html +++ b/ru_RU/docs/gorm_config.html @@ -32,8 +32,8 @@ - - + + @@ -158,7 +158,7 @@

    diff --git a/ru_RU/docs/has_many.html b/ru_RU/docs/has_many.html index 008c8ad28ca..39ff2c502c5 100644 --- a/ru_RU/docs/has_many.html +++ b/ru_RU/docs/has_many.html @@ -32,8 +32,8 @@ - - + + @@ -159,7 +159,7 @@

    - + diff --git a/ru_RU/docs/has_one.html b/ru_RU/docs/has_one.html index ba187130194..9f427150bd7 100644 --- a/ru_RU/docs/has_one.html +++ b/ru_RU/docs/has_one.html @@ -32,8 +32,8 @@ - - + + @@ -159,7 +159,7 @@

    - + diff --git a/ru_RU/docs/hints.html b/ru_RU/docs/hints.html index 55e5c180cf4..601201b8847 100644 --- a/ru_RU/docs/hints.html +++ b/ru_RU/docs/hints.html @@ -32,8 +32,8 @@ - - + + @@ -136,7 +136,7 @@

    diff --git a/ru_RU/docs/hooks.html b/ru_RU/docs/hooks.html index 1d7a20ebbb5..c82c01b1bc9 100644 --- a/ru_RU/docs/hooks.html +++ b/ru_RU/docs/hooks.html @@ -32,8 +32,8 @@ - - + + @@ -162,7 +162,7 @@

    - + diff --git a/ru_RU/docs/index.html b/ru_RU/docs/index.html index 1bdb32ad709..73b02c5e813 100644 --- a/ru_RU/docs/index.html +++ b/ru_RU/docs/index.html @@ -32,8 +32,8 @@ - - + + @@ -149,7 +149,7 @@

    - + diff --git a/ru_RU/docs/indexes.html b/ru_RU/docs/indexes.html index b55b8d7d01c..7c3d75f8b35 100644 --- a/ru_RU/docs/indexes.html +++ b/ru_RU/docs/indexes.html @@ -32,8 +32,8 @@ - - + + @@ -155,7 +155,7 @@

    - + diff --git a/ru_RU/docs/logger.html b/ru_RU/docs/logger.html index 73d205df6cd..a8b8d779c92 100644 --- a/ru_RU/docs/logger.html +++ b/ru_RU/docs/logger.html @@ -32,8 +32,8 @@ - - + + @@ -142,7 +142,7 @@

    - + diff --git a/ru_RU/docs/many_to_many.html b/ru_RU/docs/many_to_many.html index f47fb336ed1..790d19c5765 100644 --- a/ru_RU/docs/many_to_many.html +++ b/ru_RU/docs/many_to_many.html @@ -32,8 +32,8 @@ - - + + @@ -144,7 +144,7 @@

    NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

    -
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addressses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // Change model Person's field Addresses' join table to PersonAddress
    // PersonAddress must defined all required foreign keys or it will raise error
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
    +
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addresses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // Change model Person's field Addresses' join table to PersonAddress
    // PersonAddress must defined all required foreign keys or it will raise error
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

    Ограничения внешних ключей

    You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

    type User struct {
    gorm.Model
    Languages []Language `gorm:"many2many:user_speaks;"`
    }

    type Language struct {
    Code string `gorm:"primarykey"`
    Name string
    }

    // CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
    @@ -167,7 +167,7 @@

    - + diff --git a/ru_RU/docs/method_chaining.html b/ru_RU/docs/method_chaining.html index 8f66afb9954..f12215cf102 100644 --- a/ru_RU/docs/method_chaining.html +++ b/ru_RU/docs/method_chaining.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,33 +117,63 @@

    Цепочки методов

    -

    GORM позволяет делать цепочки методов, так что вы можете написать код следующим образом:

    +

    GORM’s method chaining feature allows for a smooth and fluent style of coding. Here’s an example:

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
    -

    There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

    -

    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:

    -
    queryDB := DB.Where("name = ?", "jinzhu")

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // 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:

    -
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 20
    - -

    Метод цепочки

    Chain methods are methods to modify or add Clauses to current Statement, like:

    -

    Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

    -

    Here is the full lists, also check out the SQL Builder 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 here.

    -

    New Session Method

    GORM defined Session, WithContext, Debug methods as New Session Method, refer Session 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:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new 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:
    // 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:
    // 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:
    // SELECT * FROM users;
    - -

    (Bad) Example 2:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is safe to 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

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    - -

    Example 3:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is 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

    // good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // good case
    tx.Where("age = ?", 28).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
    +

    Method Categories

    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. Also, the SQL Builder 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.

    +

    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 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

    queryDB := DB.Where("name = ?", "jinzhu")

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query with unintended compounded condition
    queryDB.Where("age > ?", 20).First(&user2)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
    + +

    Example of Safe Reuse

    To safely reuse a *gorm.DB instance, use a New Session Method:

    +
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query, safely isolated
    queryDB.Where("age > ?", 20).First(&user2)
    // 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.

    +

    Examples for Clarity

    Let’s clarify with a few examples:

    +
      +
    • Example 1: Safe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
    // 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)
    // 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)
    // Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
    // SELECT * FROM users;
    + +

    In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

    +
      +
    • (Bad) Example 2: Unsafe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized *gorm.DB, safe for initial reuse.

    tx := db.Where("name = ?", "jinzhu")
    // `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // Reuses 'tx' correctly for a single logical operation, executing:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // Bad case
    tx.Where("age = ?", 28).Find(&users)
    // Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    + +

    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
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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.

    @@ -156,7 +186,7 @@

    - + @@ -270,7 +300,7 @@

    Gold Sponsors

    Содержимое -
    1. Метод цепочки
    2. Finisher Method
    3. New Session Method
    +
    1. Method Categories
      1. Chain Methods
      2. Finisher Methods
      3. New Session Methods
    2. Reusability and Safety
      1. Example of Unsafe Reuse
      2. Example of Safe Reuse
    3. Examples for Clarity
    diff --git a/ru_RU/docs/migration.html b/ru_RU/docs/migration.html index 84ca59251e5..cf4b3444033 100644 --- a/ru_RU/docs/migration.html +++ b/ru_RU/docs/migration.html @@ -32,8 +32,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/ru_RU/docs/models.html b/ru_RU/docs/models.html index 973ac0e1c40..9018c642832 100644 --- a/ru_RU/docs/models.html +++ b/ru_RU/docs/models.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,16 +117,45 @@

    Объявление моделей
    -

    Объявление моделей

    Модели являются обычными структурами с основными типами Go, указателями/псевдонимами или пользовательскими типами, реализующими интерфейсы Scanner и Valuer

    -

    Например:

    -
    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
    }
    - -

    Преобразования

    GORM предпочитает преобразование конфигурации. По умолчанию GORM использует ID в качестве первичного ключа, преобразует имя структуры во множественное число в snake_cases в качестве имени таблицы, snake_case в качестве имени столбца и использует СreatedAt, UpdatedAt для отслеживания времени создания/обновления записи

    -

    Если вы будете следовать соглашениям, принятым GORM, вам нужно будет написать очень мало конфигурации / кода. Если соглашение не соответствует вашим требованиям, GORM позволяет вам настраивать их

    -

    gorm.Model

    GORM определяет структуру gorm.Model, которая включает в себя поля ID, CreatedAt, UpdatedAt, DeletedAt

    +

    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 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 and Valuer interfaces from the database/sql package

    +

    Consider the following example of a User model:

    +
    type User struct {
    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 for a detailed explanation

    +

    Преобразования

      +
    1. Primary Key: GORM uses a field named ID as the default primary key for each model.

      +
    2. +
    3. 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.

      +
    4. +
    5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

      +
    6. +
    7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

      +
    8. +
    +

    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.

    +

    gorm.Model

    GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

    // объявление gorm.Model
    type Model struct {
    ID uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    }
    -

    Вы можете встроить gorm.Model в свою структуру. Чтобы включить эти поля, смотрите Встроенные структуры

    +
      +
    • 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

      +
    • +
    • 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).
      • +
      +
    • +

    Дополнительно

    Разрешение для каждого поля

    Экспортированные поля имеют все разрешения при выполнении CRUD с помощью GORM, и GORM позволяет вам изменять разрешение на уровне поля с помощью тегов, поэтому вы можете сделать поле доступным только для чтения, записи, создания, обновления или игнорируемым

    Игнорируемые поля не будут созданы при использовании GORM Migrator для создания таблицы

    @@ -262,7 +291,7 @@

    - + @@ -376,7 +405,7 @@

    Gold Sponsors

    Содержимое -
    1. Объявление моделей
    2. Преобразования
    3. gorm.Model
    4. Дополнительно
      1. Разрешение для каждого поля
      2. Создание/обновление Time/Unix (Milli/Nano) секунд отслеживания
      3. Вложенные структуры
      4. Теги полей
      5. Взаимосвязи
    +
    1. Объявление моделей
      1. Преобразования
      2. gorm.Model
    2. Дополнительно
      1. Разрешение для каждого поля
      2. Создание/обновление Time/Unix (Milli/Nano) секунд отслеживания
      3. Вложенные структуры
      4. Теги полей
      5. Взаимосвязи
    diff --git a/ru_RU/docs/performance.html b/ru_RU/docs/performance.html index 834e5bc35f5..6a9782dc6c7 100644 --- a/ru_RU/docs/performance.html +++ b/ru_RU/docs/performance.html @@ -32,8 +32,8 @@ - - + + @@ -154,7 +154,7 @@

    - + diff --git a/ru_RU/docs/preload.html b/ru_RU/docs/preload.html index bf8f080105a..ed0b4b15a35 100644 --- a/ru_RU/docs/preload.html +++ b/ru_RU/docs/preload.html @@ -32,8 +32,8 @@ - - + + @@ -167,7 +167,7 @@

    - + diff --git a/ru_RU/docs/prometheus.html b/ru_RU/docs/prometheus.html index 81b33f9fc6a..6385ee8ccc4 100644 --- a/ru_RU/docs/prometheus.html +++ b/ru_RU/docs/prometheus.html @@ -32,8 +32,8 @@ - - + + @@ -138,7 +138,7 @@

    - + diff --git a/ru_RU/docs/query.html b/ru_RU/docs/query.html index 709318d8521..8875bed08e2 100644 --- a/ru_RU/docs/query.html +++ b/ru_RU/docs/query.html @@ -32,8 +32,8 @@ - - + + @@ -219,7 +219,7 @@

    - + diff --git a/ru_RU/docs/scopes.html b/ru_RU/docs/scopes.html index 15510d61581..5b7eac88bf7 100644 --- a/ru_RU/docs/scopes.html +++ b/ru_RU/docs/scopes.html @@ -32,8 +32,8 @@ - - + + @@ -140,7 +140,7 @@

    - + diff --git a/ru_RU/docs/security.html b/ru_RU/docs/security.html index ec703622a31..e1454145cd7 100644 --- a/ru_RU/docs/security.html +++ b/ru_RU/docs/security.html @@ -32,8 +32,8 @@ - - + + @@ -127,7 +127,7 @@

    Строчные условия

    // will be escaped
    db.First(&user, "name = ?", userInput)

    // SQL injection
    db.First(&user, fmt.Sprintf("name = %v", userInput))

    When retrieving objects with number primary key by user’s input, you should check the type of variable.

    -
    userInputID := "1=1;drop table users;"
    // safe, return error
    id,err := strconv.Atoi(userInputID)
    if err != nil {
    return error
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;
    +
    userInputID := "1=1;drop table users;"
    // safe, return error
    id, err := strconv.Atoi(userInputID)
    if err != nil {
    return err
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;

    Методы SQL инъекции

    To support some features, some inputs are not escaped, be careful when using user’s input with those methods

    db.Select("name; drop table users;").First(&user)
    db.Distinct("name; drop table users;").First(&user)

    db.Model(&user).Pluck("name; drop table users;", &names)

    db.Group("name; drop table users;").First(&user)

    db.Group("name").Having("1 = 1;drop table users;").First(&user)

    db.Raw("select name from users; drop table users;").First(&user)

    db.Exec("select name from users; drop table users;")

    db.Order("name; drop table users;").First(&user)
    @@ -145,7 +145,7 @@

    - + diff --git a/ru_RU/docs/serializer.html b/ru_RU/docs/serializer.html index e2ea38a8831..b34fa667ee5 100644 --- a/ru_RU/docs/serializer.html +++ b/ru_RU/docs/serializer.html @@ -56,8 +56,8 @@ - - + + @@ -171,7 +171,7 @@

    - + diff --git a/ru_RU/docs/session.html b/ru_RU/docs/session.html index e10f37fa718..45e4183f2ff 100644 --- a/ru_RU/docs/session.html +++ b/ru_RU/docs/session.html @@ -32,8 +32,8 @@ - - + + @@ -180,7 +180,7 @@

    diff --git a/ru_RU/docs/settings.html b/ru_RU/docs/settings.html index 400cd0c55e5..92a3df977da 100644 --- a/ru_RU/docs/settings.html +++ b/ru_RU/docs/settings.html @@ -32,8 +32,8 @@ - - + + @@ -139,7 +139,7 @@

    - + diff --git a/ru_RU/docs/sharding.html b/ru_RU/docs/sharding.html index 293a225b35a..c33f60a5f4b 100644 --- a/ru_RU/docs/sharding.html +++ b/ru_RU/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    - + diff --git a/ru_RU/docs/sql_builder.html b/ru_RU/docs/sql_builder.html index 18fa6ad9eff..6cfad83c8bc 100644 --- a/ru_RU/docs/sql_builder.html +++ b/ru_RU/docs/sql_builder.html @@ -32,8 +32,8 @@ - - + + @@ -183,7 +183,7 @@

    diff --git a/ru_RU/docs/transactions.html b/ru_RU/docs/transactions.html index 09971f2678c..c9db9f3086d 100644 --- a/ru_RU/docs/transactions.html +++ b/ru_RU/docs/transactions.html @@ -32,8 +32,8 @@ - - + + @@ -124,7 +124,7 @@

    db.Transaction(func(tx *gorm.DB) error {
    // выполняем операции с БД в транзакции (используйте 'tx' вмсето 'db')
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // возврат любой ошибки, откатит транзакцию
    return err
    }

    if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
    }

    // возврат nil сделает фиксацию всей транзакции
    return nil
    })

    Вложенные транзакции

    GORM поддерживает вложенные транзакции, вы можете откатить подмножество операций, выполняемых в рамках более крупной транзакции, например:

    -
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3
    +
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx3 *gorm.DB) error {
    tx3.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3

    Control the transaction manually

    Gorm supports calling transaction control functions (commit / rollback) directly, for example:

    // начало транзакции
    tx := db.Begin()

    // выполнить операции с БД в транзакции (используйте 'tx' вместо 'db')
    tx.Create(...)

    // ...

    // откатить транзакцию в случае ошибки
    tx.Rollback()

    // фиксация транзакции
    tx.Commit()
    @@ -145,7 +145,7 @@

    - + diff --git a/ru_RU/docs/update.html b/ru_RU/docs/update.html index 61d709fa8ae..a59644469ae 100644 --- a/ru_RU/docs/update.html +++ b/ru_RU/docs/update.html @@ -32,8 +32,8 @@ - - + + @@ -184,7 +184,7 @@

    - + diff --git a/ru_RU/docs/v2_release_note.html b/ru_RU/docs/v2_release_note.html index 24586b3869d..f2df7c9d0a3 100644 --- a/ru_RU/docs/v2_release_note.html +++ b/ru_RU/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

    - + diff --git a/ru_RU/docs/write_driver.html b/ru_RU/docs/write_driver.html index f9943788198..5d2ee270f96 100644 --- a/ru_RU/docs/write_driver.html +++ b/ru_RU/docs/write_driver.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,12 +117,45 @@

    Написание нового дра
    -

    Написание нового драйвера

    GORM предоставляет официальную поддержку sqlite, mysql, postgres, sqlserver.

    -

    Некоторые базы данных могут быть совместимы с mysql или postgres диалектами, в этом случае можно просто использовать диалект для этих баз данных.

    -

    Для других вы можете создать новый драйвер, он должен реализовывать интерфейс диалекта.

    -
    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
    }
    - -

    Смотрите Драйвер MySQL в качестве примера

    +

    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.

    +

    Compatibility with MySQL or Postgres Dialects

    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.

    +

    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:

    +
    type Dialector interface {
    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.

    +
    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
    }
    + +

    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:

    +
    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:

    +
    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:

    +
    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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

    @@ -135,7 +168,7 @@

    Написание нового дра
    - +
    @@ -249,7 +282,7 @@

    Gold Sponsors

    Содержимое -
    1. Написание нового драйвера
    +
    1. Compatibility with MySQL or Postgres Dialects
    2. Implementing the Dialector
      1. Nested Transaction Support
      2. Custom Clause Builders
    diff --git a/ru_RU/docs/write_plugins.html b/ru_RU/docs/write_plugins.html index 76cbd764330..d8f4af7e653 100644 --- a/ru_RU/docs/write_plugins.html +++ b/ru_RU/docs/write_plugins.html @@ -25,15 +25,15 @@ - + - + - - + + @@ -117,28 +117,39 @@

    Написание плагинов
    -

    Callbacks

    Сам GORM основан на методах Callbacks, у которого есть callback функции для Create, Query, Update, Delete, Row, Raw, с помощью которых вы можете полностью настроить GORM по своему усмотрению

    -

    Вызовы callback регистрируются в глобальной *gorm.DB, а не на сессионном уровне, если вам требуется *gorm. B с другими функциями callback, вам нужно инициализировать другой *gorm.DB

    -

    Регистрация функций callback

    Регистрация функции callback в Callbacks

    -
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    db.Callback().Create().Register("crop_image", cropImage)
    // register a callback for Create process
    +

    Callbacks

    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 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.

    +

    Registering a Callback

    You can register a callback for specific operations. For example, to add a custom image cropping functionality:

    +
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    // Register the callback for the Create operation
    db.Callback().Create().Register("crop_image", cropImage)
    -

    Удаление функций callback

    Удаление функции callback из Callbacks

    -
    db.Callback().Create().Remove("gorm:create")
    // удалить callback `gorm:create` из обработчика Create
    +

    Deleting a Callback

    If a callback is no longer needed, it can be removed:

    +
    // Remove the 'gorm:create' callback from Create operations
    db.Callback().Create().Remove("gorm:create")
    -

    Замена функций callback

    Заменить callback с идентичным именем на новый

    -
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    // заменить callback `gorm:create` новой функцией `newCreateFunction` для обработчика Create
    +

    Replacing a Callback

    Callbacks with the same name can be replaced with a new function:

    +
    // Replace the 'gorm:create' callback with a new function
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    -

    Регистрация функций callback с порядком выполнения

    Регистрация функций callback с порядком выполнения

    -
    // before gorm:create
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // after gorm:create
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // after gorm:query
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // after gorm:delete
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // before gorm:update
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    +

    Ordering Callbacks

    Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

    +
    // Register to execute before the 'gorm:create' callback
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:create' callback
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:query' callback
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // Register to execute after the 'gorm:delete' callback
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // Register to execute before the 'gorm:update' callback
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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)

    // Register to execute before any other callbacks
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // Register to execute after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    -

    Существующие функции callback

    GORM предоставляет некоторые функции callback для поддержки текущих функций GORM, ознакомьтесь с ними перед началом написания своих плагинов

    -

    Плагин

    GORM предоставляет метод Use для регистрации плагинов, плагин должен реализовывать интерфейс Plugin

    +

    Predefined Callbacks

    GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

    +

    Plugins

    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:

    type Plugin interface {
    Name() string
    Initialize(*gorm.DB) error
    }
    -

    Метод Initialize будет вызван при первом регистрации плагина в GORM, и GORM сохранит его в зарегистрированные плагины, обращайтесь к ним таким образом:

    -
    db.Config.Plugins[pluginName]
    +
      +
    • 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:

    +
    // Example of registering a plugin
    db.Use(MyCustomPlugin{})
    -

    Смотрите Prometheus в качестве примера

    +

    Accessing Registered Plugins

    After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

    +
    // 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:

    +
    // Registering the Prometheus plugin
    db.Use(prometheus.New(prometheus.Config{
    // Configuration options here
    }))
    + +

    Prometheus plugin documentation provides detailed information on its implementation and usage.

    @@ -151,7 +162,7 @@

    - + @@ -265,7 +276,7 @@

    Gold Sponsors

    Содержимое -
    1. Callbacks
      1. Регистрация функций callback
      2. Удаление функций callback
      3. Замена функций callback
      4. Регистрация функций callback с порядком выполнения
      5. Существующие функции callback
    2. Плагин
    +
    1. Callbacks
      1. Registering a Callback
      2. Deleting a Callback
      3. Replacing a Callback
      4. Ordering Callbacks
      5. Predefined Callbacks
    2. Plugins
      1. The Plugin Interface
      2. Registering a Plugin
      3. Accessing Registered Plugins
      4. Practical Example
    diff --git a/ru_RU/gen.html b/ru_RU/gen.html index 2c1b1009777..bbdf866a0ad 100644 --- a/ru_RU/gen.html +++ b/ru_RU/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/ru_RU/gen/associations.html b/ru_RU/gen/associations.html index c4e812376bd..ff3886e667c 100644 --- a/ru_RU/gen/associations.html +++ b/ru_RU/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

    // specify model
    g.ApplyBasic(model.Customer{}, model.CreditCard{})

    // assoications will be detected and converted to code
    package query

    type customer struct {
    ...
    CreditCards customerHasManyCreditCards
    }

    type creditCard struct{
    ...
    }
    -

    Relate to table in database

    The association have to be speified by gen.FieldRelate

    -
    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"}},
    }),
    )

    g.ApplyBasic(card, custormer)
    +

    Relate to table in database

    The association have to be specified by gen.FieldRelate

    +
    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"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(card, custormer)

    GEN will generate models with associated field:

    -
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }
    +
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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;references:ID" json:"credit_cards"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }

    If associated model already exists, gen.FieldRelateModel can help you build associations between them.

    -
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
    }),
    )

    g.ApplyBasic(custormer)
    +
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(custormer)

    Relate Config

    type RelateConfig struct {
    // specify field's type
    RelatePointer bool // ex: CreditCard *CreditCard
    RelateSlice bool // ex: CreditCards []CreditCard
    RelateSlicePointer bool // ex: CreditCards []*CreditCard

    JSONTag string // related field's JSON tag
    GORMTag string // related field's GORM tag
    NewTag string // related field's new tag
    OverwriteTag string // related field's tag
    }

    Operation

    Skip Auto Create/Update

    user := model.User{
    Name: "modi",
    BillingAddress: Address{Address1: "Billing Address - Address 1"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
    Emails: []Email{
    {Email: "modi@example.com"},
    {Email: "modi-2@example.com"},
    },
    Languages: []Language{
    {Name: "ZH"},
    {Name: "EN"},
    },
    }

    u := query.Use(db).User

    u.WithContext(ctx).Select(u.Name).Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
    // Skip create BillingAddress when creating a user

    u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
    // Skip create BillingAddress.Address1 when creating a user

    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

    Find matched associations

    u := query.Use(db).User

    languages, err = u.Languages.Model(&user).Find()
    @@ -198,10 +198,10 @@

    Nested Preloading together, e.g:

    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:

    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.

    +

    Preload with select

    Specify selected columns with method Select. Foreign key must be selected.

    type User struct {
    gorm.Model
    CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
    }

    type CreditCard struct {
    gorm.Model
    Number string
    UserRefer uint
    }

    u := q.User
    cc := q.CreditCard

    // !!! Foregin key "cc.UserRefer" must be selected
    users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
    // SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
    // SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

    Preload with conditions

    GEN allows Preload associations with conditions, it works similar to Inline Conditions.

    @@ -209,14 +209,13 @@

    Nested Preloading

    GEN supports nested preloading, for example:

    db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

    // Customize Preload conditions for `Orders`
    // 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/ru_RU/gen/clause.html b/ru_RU/gen/clause.html index 9f3c71c9046..5f7523ab4ed 100644 --- a/ru_RU/gen/clause.html +++ b/ru_RU/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

    - + diff --git a/ru_RU/gen/create.html b/ru_RU/gen/create.html index b9d533d966e..04a20adc711 100644 --- a/ru_RU/gen/create.html +++ b/ru_RU/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

    - + diff --git a/ru_RU/gen/dao.html b/ru_RU/gen/dao.html index f3399a8fc7b..eb377e50c52 100644 --- a/ru_RU/gen/dao.html +++ b/ru_RU/gen/dao.html @@ -56,8 +56,8 @@ - - + + @@ -249,7 +249,7 @@

    - + diff --git a/ru_RU/gen/database_to_structs.html b/ru_RU/gen/database_to_structs.html index 1238b305f7f..c290a920312 100644 --- a/ru_RU/gen/database_to_structs.html +++ b/ru_RU/gen/database_to_structs.html @@ -56,8 +56,8 @@ - - + + @@ -170,7 +170,7 @@

    diff --git a/ru_RU/gen/delete.html b/ru_RU/gen/delete.html index 0111d5aa8ec..4f2fc5266a4 100644 --- a/ru_RU/gen/delete.html +++ b/ru_RU/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -174,7 +174,7 @@

    - + diff --git a/ru_RU/gen/dynamic_sql.html b/ru_RU/gen/dynamic_sql.html index ec0d5a0f00d..9bd89756d66 100644 --- a/ru_RU/gen/dynamic_sql.html +++ b/ru_RU/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/ru_RU/gen/gen_tool.html b/ru_RU/gen/gen_tool.html index 2c9fd54f209..7c399e57932 100644 --- a/ru_RU/gen/gen_tool.html +++ b/ru_RU/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

    - + diff --git a/ru_RU/gen/index.html b/ru_RU/gen/index.html index e5c68cac21f..47b0af1f3ac 100644 --- a/ru_RU/gen/index.html +++ b/ru_RU/gen/index.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/ru_RU/gen/query.html b/ru_RU/gen/query.html index 966318fd840..4bd8c603059 100644 --- a/ru_RU/gen/query.html +++ b/ru_RU/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

    int/uint/float Fields

    // int field
    f := field.NewInt("user", "id")
    // `user`.`id` = 123
    f.Eq(123)
    // `user`.`id` DESC
    f.Desc()
    // `user`.`id` AS `user_id`
    f.As("user_id")
    // COUNT(`user`.`id`)
    f.Count()
    // SUM(`user`.`id`)
    f.Sum()
    // SUM(`user`.`id`) > 123
    f.Sum().Gt(123)
    // ((`user`.`id`+1)*2)/3
    f.Add(1).Mul(2).Div(3),
    // `user`.`id` <<< 3
    f.LeftShift(3)
    -

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `uesr`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")
    +

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `user`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")

    Time Fields

    birth := field.NewString("user", "birth")
    // `user`.`birth` = ? (now)
    birth.Eq(time.Now())
    // DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
    birth.Add(time.Duration(time.Hour).Microseconds())
    // DATE_FORMAT(`user`.`birth`, "%W %M %Y")
    birth.DateFormat("%W %M %Y")

    Bool Fields

    active := field.NewBool("user", "active")
    // `user`.`active` = TRUE
    active.Is(true)
    // NOT `user`.`active`
    active.Not()
    // `user`.`active` AND TRUE
    active.And(true)

    SubQuery

    A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

    -
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
    // 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
    +
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

    From SubQuery

    GORM allows you using subquery in FROM clause with method Table, for example:

    u := query.User
    p := query.Pet

    users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
    // SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

    subQuery1 := u.WithContext(ctx).Select(u.Name)
    subQuery2 := p.WithContext(ctx).Select(p.Name)
    users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
    db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
    // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    @@ -312,7 +312,7 @@

    - + diff --git a/ru_RU/gen/rawsql_driver.html b/ru_RU/gen/rawsql_driver.html index 7e0af9b1a49..11ec4defeb5 100644 --- a/ru_RU/gen/rawsql_driver.html +++ b/ru_RU/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ru_RU/gen/sql_annotation.html b/ru_RU/gen/sql_annotation.html index aa9f1285e3f..f05278a22ae 100644 --- a/ru_RU/gen/sql_annotation.html +++ b/ru_RU/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -244,7 +244,7 @@

    query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
    // UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

    query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
    // UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

    query.User.Update(User{Age: 0}, 10)
    // UPDATE users SET is_adult=0 WHERE id=10

    for

    The for expression iterates over a slice to generate the SQL, let’s explain by example

    -
    // 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)
    +
    // SELECT * FROM @@table
    // {{where}}
    // {{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}}
    // {{end}}
    // {{end}}
    Filter(users []gen.T) ([]gen.T, error)

    Usage:

    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","%"))
    @@ -254,7 +254,7 @@

    - + diff --git a/ru_RU/gen/transaction.html b/ru_RU/gen/transaction.html index ef3ce25c295..95b08fa9b95 100644 --- a/ru_RU/gen/transaction.html +++ b/ru_RU/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/ru_RU/gen/update.html b/ru_RU/gen/update.html index 61c61919a91..72b3c0dcbb9 100644 --- a/ru_RU/gen/update.html +++ b/ru_RU/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/ru_RU/gorm.html b/ru_RU/gorm.html index 3c634865688..af3fcd6568b 100644 --- a/ru_RU/gorm.html +++ b/ru_RU/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - +
    diff --git a/ru_RU/gormx.html b/ru_RU/gormx.html index e8e20c47822..5b065018e58 100644 --- a/ru_RU/gormx.html +++ b/ru_RU/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ru_RU/hints.html b/ru_RU/hints.html index 26a82b97f2a..058d7e27424 100644 --- a/ru_RU/hints.html +++ b/ru_RU/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/ru_RU/index.html b/ru_RU/index.html index 8562bf87e97..43435330f2d 100644 --- a/ru_RU/index.html +++ b/ru_RU/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/ru_RU/rawsql.html b/ru_RU/rawsql.html index b7c335fab27..9dbd8a60b35 100644 --- a/ru_RU/rawsql.html +++ b/ru_RU/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ru_RU/rawsql_driver.html b/ru_RU/rawsql_driver.html index 8fc34640b66..6a12e789971 100644 --- a/ru_RU/rawsql_driver.html +++ b/ru_RU/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ru_RU/sharding.html b/ru_RU/sharding.html index 93cdd98e6a8..ab3fc8171e4 100644 --- a/ru_RU/sharding.html +++ b/ru_RU/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/ru_RU/stats.html b/ru_RU/stats.html index b1e1d5849aa..9cb7d2787c1 100644 --- a/ru_RU/stats.html +++ b/ru_RU/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/stats.html b/stats.html index befd5e7bacf..affd0673bc8 100644 --- a/stats.html +++ b/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/tr_TR/404.html b/tr_TR/404.html index 469759e6c3e..410a48148cd 100644 --- a/tr_TR/404.html +++ b/tr_TR/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/tr_TR/index.html b/tr_TR/index.html index 6387407312a..3b309ad64e1 100644 --- a/tr_TR/index.html +++ b/tr_TR/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/zh_CN/404.html b/zh_CN/404.html index a197f371397..c38a3fcf065 100644 --- a/zh_CN/404.html +++ b/zh_CN/404.html @@ -32,8 +32,8 @@ - - + + diff --git a/zh_CN/community.html b/zh_CN/community.html index 8421b61e681..2af42cf1b6c 100644 --- a/zh_CN/community.html +++ b/zh_CN/community.html @@ -56,8 +56,8 @@ - - + + @@ -184,7 +184,7 @@

    - + diff --git a/zh_CN/datatypes.html b/zh_CN/datatypes.html index 1d035d92e21..3941b65309a 100644 --- a/zh_CN/datatypes.html +++ b/zh_CN/datatypes.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/zh_CN/docs/advanced_query.html b/zh_CN/docs/advanced_query.html index a36393c9c50..55dd4df0d38 100644 --- a/zh_CN/docs/advanced_query.html +++ b/zh_CN/docs/advanced_query.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,80 +141,106 @@

    高级查询

    -

    智能选择字段

    GORM 允许通过 Select 方法选择特定的字段,如果您在应用程序中经常使用此功能,你也可以定义一个较小的结构体,以实现调用 API 时自动选择特定的字段,例如:

    -
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // 假设后面还有几百个字段...
    }

    type APIUser struct {
    ID uint
    Name string
    }

    // 查询时会自动选择 `id`, `name` 字段
    db.Model(&User{}).Limit(10).Find(&APIUser{})
    // SELECT `id`, `name` FROM `users` LIMIT 10
    +

    智能选择字段

    In GORM, you can efficiently select specific fields using the Select method. This is particularly useful when dealing with large models but requiring only a subset of fields, especially in API responses.

    +
    type User struct {
    ID uint
    Name string
    Age int
    Gender string
    // hundreds of fields
    }

    type APIUser struct {
    ID uint
    Name string
    }

    // GORM will automatically select `id`, `name` fields when querying
    db.Model(&User{}).Limit(10).Find(&APIUser{})
    // SQL: SELECT `id`, `name` FROM `users` LIMIT 10
    -

    注意 QueryFields 模式会根据当前 model 的所有字段名称进行 select。

    +

    NOTE In QueryFields mode, all model fields are selected by their names.

    -
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    db.Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users` // 带上这个选项

    // Session Mode
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    +
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    QueryFields: true,
    })

    // Default behavior with QueryFields set to true
    db.Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

    // Using Session Mode with QueryFields
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    -

    Locking (FOR UPDATE)

    GORM 支持多种类型的锁,例如:

    -
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SELECT * FROM `users` FOR UPDATE

    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SELECT * FROM `users` FOR SHARE OF `users`

    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SELECT * FROM `users` FOR UPDATE NOWAIT
    +

    Locking

    GORM 支持多种类型的锁,例如:

    +
    // Basic FOR UPDATE lock
    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE
    -

    查看 原生 SQL 及构造器 获取详情

    -

    子查询

    子查询可以嵌套在查询中,GORM 允许在使用 *gorm.DB 对象作为参数时生成子查询

    -
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    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%")
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "SHARE",
    Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR SHARE OF `users`
    -

    From 子查询

    GORM 允许您在 Table 方法中通过 FROM 子句使用子查询,例如:

    -
    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

    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
    +

    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.

    +
    db.Clauses(clause.Locking{
    Strength: "UPDATE",
    Options: "NOWAIT",
    }).Find(&users)
    // SQL: SELECT * FROM `users` FOR UPDATE NOWAIT
    -

    Group 条件

    使用 Group 条件可以更轻松的编写复杂 SQL

    -
    db.Where(
    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")
    +

    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.

    +

    子查询

    Subqueries are a powerful feature in SQL, allowing nested queries. GORM can generate subqueries automatically when using a *gorm.DB object as a parameter.

    +
    // Simple subquery
    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&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)
    // SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
    -

    带多个列的 In

    带多个列的 In 查询

    -
    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"));
    +

    From 子查询

    GORM allows the use of subqueries in the FROM clause, enabling complex queries and data organization.

    +
    // Using subquery in FROM clause
    db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    -

    命名参数

    GORM 支持 sql.NamedArgmap[string]interface{}{} 形式的命名参数,例如:

    -
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

    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
    +

    Group 条件

    Group Conditions in GORM provide a more readable and maintainable way to write complex SQL queries involving multiple conditions.

    +
    // Complex SQL query using Group Conditions
    db.Where(
    db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
    ).Or(
    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")
    -

    查看 原生 SQL 及构造器 获取详情

    -

    Find 至 map

    GORM 允许扫描结果至 map[string]interface{}[]map[string]interface{},此时别忘了指定 ModelTable,例如:

    -
    result := map[string]interface{}{}
    db.Model(&User{}).First(&result, "id = ?", 1)

    var results []map[string]interface{}
    db.Table("users").Find(&results)
    +

    带多个列的 In

    GORM supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query.

    +
    // Using IN with multiple columns
    db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
    // SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
    -

    FirstOrInit

    获取第一条匹配的记录,或者根据给定的条件初始化一个实例(仅支持 sturct 和 map 条件)

    -
    // 未找到 user,则根据给定的条件初始化一条记录
    db.FirstOrInit(&user, User{Name: "non_existing"})
    // user -> User{Name: "non_existing"}

    // 找到了 `name` = `jinzhu` 的 user
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}

    // 找到了 `name` = `jinzhu` 的 user
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
    +

    命名参数

    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 or map[string]interface{}{}, providing flexibility in how you structure your queries.

    +
    // Example using sql.NamedArg for named arguments
    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // 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)
    // SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
    -

    如果没有找到记录,可以使用包含更多的属性的结构体初始化 user,Attrs 不会被用于生成查询 SQL

    -
    // 未找到 user,则根据给定的条件以及 Attrs 初始化 user
    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,则根据给定的条件以及 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
    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}
    +

    For more examples and details, see Raw SQL and SQL Builder

    +

    Find 至 map

    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.

    +
    // 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`
    -

    不管是否找到记录,Assign 都会将属性赋值给 struct,但这些属性不会被用于生成查询 SQL,也不会被保存到数据库

    -
    // 未找到 user,根据条件和 Assign 属性初始化 struct
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // user -> User{Name: "non_existing", Age: 20}

    // 找到 `name` = `jinzhu` 的记录,依然会更新 Assign 相关的属性
    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}
    +

    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.

    +
    // 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"} if not found

    // Retrieving a user named "jinzhu"
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

    // Using a map to specify the search condition
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    FirstOrCreate

    获取匹配的第一条记录或者根据给定条件创建一条新纪录(仅 struct, map 条件有效),RowsAffected 返回创建、更新的记录数

    -
    // 未找到 User,根据给定条件创建一条新纪录
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1

    // 找到 `name` = `jinzhu` 的 User
    result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
    // user -> User{ID: 111, Name: "jinzhu", "Age": 18}
    // result.RowsAffected // => 0
    +

    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.

    +
    // If no User is found, initialize with given conditions and additional attributes
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // user -> User{Name: "non_existing", Age: 20} if not found

    // If a User named "Jinzhu" is found, `Attrs` are ignored
    db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found
    -

    如果没有找到记录,可以使用包含更多的属性的结构体创建记录,Attrs 不会被用于生成查询 SQL 。

    -
    // 未找到 user,根据条件和 Assign 属性创建记录
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // 找到了 `name` = `jinzhu` 的 user,则忽略 Attrs
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    +

    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.

    +
    // 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} if not found

    // If a User named "Jinzhu" is found, update the struct with Assign attributes
    db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found
    -

    不管是否找到记录,Assign 都会将属性赋值给 struct,并将结果写回数据库

    -
    // 未找到 user,根据条件和 Assign 属性创建记录
    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);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}

    // 找到了 `name` = `jinzhu` 的 user,依然会根据 Assign 更新记录
    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}
    +

    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

    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.

    +
    // Create a new record if not found
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // SQL: INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1 (record created)

    // 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)
    -

    优化器、索引提示

    优化器提示用于控制查询优化器选择某个查询执行计划,GORM 通过 gorm.io/hints 提供支持,例如:

    -
    import "gorm.io/hints"

    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    +

    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.

    +
    // Create a new record with additional attributes if not found
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // If the user is found, `Attrs` are ignored
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu';
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    -

    索引提示允许传递索引提示到数据库,以防查询计划器出现混乱。

    -
    import "gorm.io/hints"

    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // SELECT * FROM `users` USE INDEX (`idx_user_name`)

    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`)"
    +

    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.

    +
    // Initialize and save new record with `Assign` attributes if not found
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // 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}

    // Update found record with `Assign` attributes
    db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // 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 支持通过行进行迭代

    -
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    defer rows.Close()

    for rows.Next() {
    var user User
    // ScanRows 方法用于将一行记录扫描至结构体
    db.ScanRows(rows, &user)

    // 业务逻辑...
    }
    +

    优化器、索引提示

    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.

    +
    import "gorm.io/hints"

    // Using an optimizer hint to set a maximum execution time
    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    -

    FindInBatches

    用于批量查询并处理记录

    -
    // 每次批量处理 100 条
    result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
    for _, result := range results {
    // 批量处理找到的记录
    }

    tx.Save(&results)

    tx.RowsAffected // 本次批量操作影响的记录数

    batch // Batch 1, 2, 3

    // 如果返回错误会终止后续批量操作
    return nil
    })

    result.Error // returned error
    result.RowsAffected // 整个批量操作影响的记录数
    +

    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.

    +
    import "gorm.io/hints"

    // Suggesting the use of a specific index
    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // 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{})
    // SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)
    -

    查询钩子

    对于查询操作,GORM 支持 AfterFind 钩子,查询记录后会调用它,详情请参考 钩子

    -
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    if u.Role == "" {
    u.Role = "user"
    }
    return
    }
    +

    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 in the GORM documentation.

    +

    迭代

    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.

    +
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    defer rows.Close()

    for rows.Next() {
    var user User
    // ScanRows scans a row into a struct
    db.ScanRows(rows, &user)

    // Perform operations on each user
    }
    -

    Pluck

    Pluck 用于从数据库查询单个列,并将结果扫描到切片。如果您想要查询多列,您应该使用 SelectScan

    -
    var ages []int64
    db.Model(&users).Pluck("age", &ages)

    var names []string
    db.Model(&User{}).Pluck("name", &names)

    db.Table("deleted_users").Pluck("name", &names)

    // Distinct Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SELECT DISTINCT `name` FROM `users`

    // 超过一列的查询,应该使用 `Scan` 或者 `Find`,例如:
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    +

    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.

    +
    // 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 provides the count of records in the current batch
    // The variable 'batch' indicates the current batch number

    // Returning an error will stop further batch processing
    return nil
    })

    // result.Error contains any errors encountered during batch processing
    // result.RowsAffected provides the count of all processed records across batches
    -

    Scope

    Scopes 允许你指定常用的查询,可以在调用方法时引用这些查询

    -
    func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
    return db.Where("amount > ?", 1000)
    }

    func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
    return db.Where("pay_mode_sign = ?", "C")
    }

    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 {
    return db.Where("status IN (?)", status)
    }
    }

    db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
    // 查找所有金额大于 1000 的信用卡订单

    db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
    // 查找所有金额大于 1000 的货到付款订单

    db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
    // 查找所有金额大于 1000 且已付款或已发货的订单
    +

    FindInBatches is an effective tool for processing large volumes of data in manageable chunks, optimizing resource usage and performance.

    +

    查询钩子

    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 in the GORM documentation.

    +
    func (u *User) AfterFind(tx *gorm.DB) (err error) {
    // Custom logic after finding a user
    if u.Role == "" {
    u.Role = "user" // Set default role if not specified
    }
    return
    }

    // Usage of AfterFind hook happens automatically when a User is queried
    -

    查看 Scopes 获取详情

    -

    Count

    Count 用于获取匹配的记录数

    -
    var count int64
    db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

    db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
    // SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

    db.Table("deleted_users").Count(&count)
    // SELECT count(1) FROM deleted_users;

    // Count with Distinct
    db.Model(&User{}).Distinct("name").Count(&count)
    // SELECT COUNT(DISTINCT(`name`)) FROM `users`

    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SELECT count(distinct(name)) FROM deleted_users

    // Count with Group
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    count // => 3
    +

    Pluck

    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 or Find instead.

    +
    // Retrieving ages of all users
    var ages []int64
    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)

    // Using Distinct with Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SQL: SELECT DISTINCT `name` FROM `users`

    // Querying multiple columns
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    + +

    Scope

    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.

    +
    // 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")
    }

    // 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.

    +
    // 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)
    + +

    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 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.

    +
    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'

    // Counting records in a different table
    db.Table("deleted_users").Count(&count)
    // SQL: SELECT count(1) FROM deleted_users
    + +

    Count with Distinct and Group

    GORM also allows counting distinct values and grouping results.

    +
    // Counting distinct names
    db.Model(&User{}).Distinct("name").Count(&count)
    // SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

    // Counting distinct values with a custom select
    db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
    // SQL: SELECT count(distinct(name)) FROM deleted_users

    // Counting grouped records
    users := []User{
    {Name: "name1"},
    {Name: "name2"},
    {Name: "name3"},
    {Name: "name3"},
    }

    db.Model(&User{}).Group("name").Count(&count)
    // Count after grouping by name
    // count => 3
    @@ -227,7 +253,7 @@

    - + @@ -341,7 +367,7 @@

    Gold Sponsors

    内容 -
    1. 智能选择字段
    2. Locking (FOR UPDATE)
    3. 子查询
      1. From 子查询
    4. Group 条件
    5. 带多个列的 In
    6. 命名参数
    7. Find 至 map
    8. FirstOrInit
    9. FirstOrCreate
    10. 优化器、索引提示
    11. 迭代
    12. FindInBatches
    13. 查询钩子
    14. Pluck
    15. Scope
    16. Count
    +
    1. 智能选择字段
    2. Locking
    3. 子查询
      1. From 子查询
    4. Group 条件
    5. 带多个列的 In
    6. 命名参数
    7. Find 至 map
    8. FirstOrInit
      1. Using Attrs for Initialization
      2. Using Assign for Attributes
    9. FirstOrCreate
      1. Using Attrs with FirstOrCreate
      2. Using Assign with FirstOrCreate
    10. 优化器、索引提示
      1. Index Hints
    11. 迭代
    12. FindInBatches
    13. 查询钩子
    14. Pluck
    15. Scope
      1. Defining Scopes
      2. Applying Scopes in Queries
    16. Count
      1. Getting the Count of Matched Records
      2. Count with Distinct and Group
    diff --git a/zh_CN/docs/associations.html b/zh_CN/docs/associations.html index 5417d7f1b15..789e6f43d19 100644 --- a/zh_CN/docs/associations.html +++ b/zh_CN/docs/associations.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,62 +141,101 @@

    实体关联

    -

    自动创建、更新

    在创建、更新记录时,GORM 会通过 Upsert 自动保存关联及其引用记录。

    -
    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"},
    },
    }

    db.Create(&user)
    // BEGIN TRANSACTION;
    // 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 ('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 模式:

    -
    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);
    // ...
    - -

    跳过自动创建、更新

    若要在创建、更新时跳过自动保存,您可以使用 SelectOmit,例如:

    -
    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"},
    },
    }

    db.Select("Name").Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    db.Omit("BillingAddress").Create(&user)
    // Skip create BillingAddress when creating a user

    db.Omit(clause.Associations).Create(&user)
    // Skip all associations when creating a user
    - -

    NOTE: 对于 many2many 关联,GORM 在创建连接表引用之前,会先 upsert 关联。如果你想跳过关联的 upsert,你可以这样做:

    -
    db.Omit("Languages.*").Create(&user)
    - -

    下面的代码将跳过创建关联及其引用

    -
    db.Omit("Languages").Create(&user)
    - -

    Select/Omit 关联字段

    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
    }

    // 创建 user 及其 BillingAddress、ShippingAddress
    // 在创建 BillingAddress 时,仅使用其 address1、address2 字段,忽略其它字段
    db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)

    db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)
    - -

    关联模式

    关联模式包含一些在处理关系时有用的方法

    -
    // 开始关联模式
    var user User
    db.Model(&user).Association("Languages")
    // `user` 是源模型,它的主键不能为空
    // 关系的字段名是 `Languages`
    // 如果匹配了上面两个要求,会开始关联模式,否则会返回错误
    db.Model(&user).Association("Languages").Error
    - -

    查找关联

    查找所有匹配的关联记录

    -
    db.Model(&user).Association("Languages").Find(&languages)
    - -

    查找带条件的关联

    -
    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)
    - -

    添加关联

    many to manyhas many 添加新的关联;为 has one, belongs to 替换当前的关联

    -
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

    db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
    - -

    替换关联

    用一个新的关联替换当前的关联

    -
    db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

    db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
    - -

    删除关联

    如果存在,则删除源模型与参数之间的关系,只会删除引用,不会从数据库中删除这些对象。

    -
    db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
    db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
    - -

    清空关联

    删除源模型与关联之间的所有引用,但不会删除这些关联

    -
    db.Model(&user).Association("Languages").Clear()
    - -

    关联计数

    返回当前关联的计数

    -
    db.Model(&user).Association("Languages").Count()

    // 条件计数
    codes := []string{"zh-CN", "en-US", "ja-JP"}
    db.Model(&user).Where("code IN ?", codes).Association("Languages").Count()
    - -

    批量处理数据

    关联模式也支持批量处理,例如:

    -
    // 查询所有用户的所有角色
    db.Model(&users).Association("Role").Find(&roles)

    // 从所有 team 中删除 user A
    db.Model(&users).Association("Team").Delete(&userA)

    // 获取去重的用户所属 team 数量
    db.Model(&users).Association("Team").Count()

    // 对于批量数据的 `Append`、`Replace`,参数的长度必须与数据的长度相同,否则会返回 error
    var users = []User{user1, user2, user3}
    // 例如:现在有三个 user,Append userA 到 user1 的 team,Append userB 到 user2 的 team,Append userA、userB 和 userC 到 user3 的 team
    db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC})
    // 重置 user1 team 为 userA,重置 user2 的 team 为 userB,重置 user3 的 team 为 userA、 userB 和 userC
    db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC})
    - -

    删除关联记录

    默认情况下, gorm.Association <code> 中的 <code> Replace/Delete/Clear 操作只会删除关联引用,也就是将旧的关联的外键设置为null。

    -

    您可以使用 Unscoped 来删除这些对象(与 ManyToMany 无关)。

    -

    删除操作由 gorm.DB 决定。

    -
    // 软删除
    // UPDATE `languages` SET `deleted_at`= ...
    db.Model(&user).Association("Languages").Unscoped().Clear()

    // 永久删除
    // DELETE FROM `languages` WHERE ...
    db.Unscoped().Model(&item).Association("Languages").Unscoped().Clear()
    - -

    使用选择删除

    您可以在删除记录时通过 Select 来删除具有 has one、has many、many2many 关系的记录,例如:

    -
    // 删除 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)
    - -

    注意:只有在待删除记录的主键不为零时,关联关系才会被删除。GORM会将这些主键作为条件来删除选定的关联关系。

    -
    // 不会起作用
    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 也会被删除
    - -

    关联标签(Association Tags)

    +

    自动创建、更新

    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.

    +
    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"},
    },
    }

    // 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;
    // 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 ('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)
    + +

    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.

    +
    // Update a user and fully update all its associations
    db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
    // 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.

    +

    跳过自动创建、更新

    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.

    +
    user := User{
    // User and associated data
    }

    // Only include the 'Name' field when creating the user
    db.Select("Name").Create(&user)
    // 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.

    +
    // Skip creating the 'BillingAddress' when creating the user
    db.Omit("BillingAddress").Create(&user)

    // Skip all associations when creating the user
    db.Omit(clause.Associations).Create(&user)
    + +

    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 .*:

    +
    // Skip upserting 'Languages' associations
    db.Omit("Languages.*").Create(&user)
    + +

    To skip creating both the association and its references:

    +
    // Skip creating 'Languages' associations and their references
    db.Omit("Languages").Create(&user)
    + +

    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.

    +
    user := User{
    Name: "jinzhu",
    BillingAddress: Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
    }

    // 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.

    +
    // 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: 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.

    +
    // 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
    + +

    关联模式

    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.

    +
    var user User
    db.Model(&user).Association("Languages")
    // Check for errors
    error := db.Model(&user).Association("Languages").Error
    + +

    Finding Associations

    Retrieve associated records with or without additional conditions.

    +
    // Simple find
    db.Model(&user).Association("Languages").Find(&languages)

    // Find with conditions
    codes := []string{"zh-CN", "en-US", "ja-JP"}
    db.Model(&user).Where("code IN ?", codes).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.

    +
    // Append new languages
    db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

    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.

    +
    // 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.

    +
    // 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.

    +
    // Clear all languages
    db.Model(&user).Association("Languages").Clear()
    + +

    Counting Associations

    Get the count of current associations, with or without conditions.

    +
    // 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.
    • +
    +
    db.Model(&users).Association("Role").Find(&roles)
    + +
      +
    • Deleting Associations: Remove specific associations across multiple records.
    • +
    +
    db.Model(&users).Association("Team").Delete(&userA)
    + +
      +
    • Counting Associations: Get the count of associations for a batch of records.
    • +
    +
    db.Model(&users).Association("Team").Count()
    + +
      +
    • Appending/Replacing Associations: Manage associations for multiple records. Note the need for matching argument lengths with the data.
    • +
    +
    var users = []User{user1, user2, 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})

    // 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})
    + +

    删除关联记录

    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.

    +
      +
    • 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.
    • +
    +

    Modifying Deletion Behavior with Unscoped

    For scenarios requiring actual deletion of associated records, the Unscoped method alters this behavior.

    +
      +
    • Soft Delete: Marks associated records as deleted (sets deleted_at field) without removing them from the database.
    • +
    +
    db.Model(&user).Association("Languages").Unscoped().Clear()
    + +
      +
    • Permanent Delete: Physically deletes the association records from the database.
    • +
    +
    // db.Unscoped().Model(&user)
    db.Unscoped().Model(&user).Association("Languages").Unscoped().Clear()
    + +

    关联标签(Association Tags)

    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.

    + @@ -204,36 +243,36 @@

    - + @@ -362,7 +401,7 @@

    Gold Sponsors

    内容 -
    1. 自动创建、更新
    2. 跳过自动创建、更新
    3. Select/Omit 关联字段
    4. 关联模式
      1. 查找关联
      2. 添加关联
      3. 替换关联
      4. 删除关联
      5. 清空关联
      6. 关联计数
      7. 批量处理数据
    5. 删除关联记录
    6. 使用选择删除
    7. 关联标签(Association Tags)
    +
    1. 自动创建、更新
      1. Auto-Saving Associations on Create
      2. Updating Associations with FullSaveAssociations
    2. 跳过自动创建、更新
      1. Using Select to Include Specific Fields
      2. Using Omit to Exclude Fields or Associations
    3. Select/Omit 关联字段
    4. 删除关联
    5. 关联模式
      1. Finding Associations
      2. Appending Associations
      3. Replacing Associations
      4. Deleting Associations
      5. Clearing Associations
      6. Counting Associations
      7. Batch Data Handling
    6. 删除关联记录
      1. Modifying Deletion Behavior with Unscoped
    7. 关联标签(Association Tags)
    diff --git a/zh_CN/docs/belongs_to.html b/zh_CN/docs/belongs_to.html index 98f255a74c7..ce0ff34ff8a 100644 --- a/zh_CN/docs/belongs_to.html +++ b/zh_CN/docs/belongs_to.html @@ -56,8 +56,8 @@ - - + + @@ -177,7 +177,7 @@

    - + diff --git a/zh_CN/docs/changelog.html b/zh_CN/docs/changelog.html index 7580a3da65a..fe0ae8a9459 100644 --- a/zh_CN/docs/changelog.html +++ b/zh_CN/docs/changelog.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - + diff --git a/zh_CN/docs/composite_primary_key.html b/zh_CN/docs/composite_primary_key.html index 941f28fc572..f6ed56c324b 100644 --- a/zh_CN/docs/composite_primary_key.html +++ b/zh_CN/docs/composite_primary_key.html @@ -56,8 +56,8 @@ - - + + @@ -158,7 +158,7 @@

    复合主键

    diff --git a/zh_CN/docs/connecting_to_the_database.html b/zh_CN/docs/connecting_to_the_database.html index d1a334d5f0b..f2732a6a6d3 100644 --- a/zh_CN/docs/connecting_to_the_database.html +++ b/zh_CN/docs/connecting_to_the_database.html @@ -56,8 +56,8 @@ - - + + @@ -184,14 +184,14 @@

    标签
    import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    )

    type Product struct {
    ID uint `gorm:"primaryKey;default:auto_random()"`
    Code string
    Price uint
    }

    func main() {
    db, err := gorm.Open(mysql.Open("root:@tcp(127.0.0.1:4000)/test"), &gorm.Config{})
    if err != nil {
    panic("failed to connect database")
    }

    db.AutoMigrate(&Product{})

    insertProduct := &Product{Code: "D42", Price: 100}

    db.Create(insertProduct)
    fmt.Printf("insert ID: %d, Code: %s, Price: %d\n",
    insertProduct.ID, insertProduct.Code, insertProduct.Price)

    readProduct := &Product{}
    db.First(&readProduct, "code = ?", "D42") // find product with code D42

    fmt.Printf("read ID: %d, Code: %s, Price: %d\n",
    readProduct.ID, readProduct.Code, readProduct.Price)
    }

    Clickhouse

    https://github.com/go-gorm/clickhouse

    -
    import (
    "gorm.io/driver/clickhouse"
    "gorm.io/gorm"
    )

    func main() {
    dsn := "tcp://localhost:9000?database=gorm&username=gorm&password=gorm&read_timeout=10&write_timeout=20"
    db, err := gorm.Open(clickhouse.Open(dsn), &gorm.Config{})

    // 自动迁移 (这是GORM自动创建表的一种方式--译者注)
    db.AutoMigrate(&User{})
    // 设置表选项
    db.Set("gorm:table_options", "ENGINE=Distributed(cluster, default, hits)").AutoMigrate(&User{})

    // 插入
    db.Create(&user)

    // 查询
    db.Find(&user, "id = ?", 10)

    // 批量插入
    var users = []User{user1, user2, user3}
    db.Create(&users)
    // ...
    }
    +
    import (
    "gorm.io/driver/clickhouse"
    "gorm.io/gorm"
    )

    func main() {
    dsn := "clickhouse://gorm:gorm@localhost:9942/gorm?dial_timeout=10s&read_timeout=20s"
    db, err := gorm.Open(clickhouse.Open(dsn), &gorm.Config{})

    // 自动迁移 (这是GORM自动创建表的一种方式--译者注)
    db.AutoMigrate(&User{})
    // 设置表选项
    db.Set("gorm:table_options", "ENGINE=Distributed(cluster, default, hits)").AutoMigrate(&User{})

    // 插入
    db.Create(&user)

    // 查询
    db.Find(&user, "id = ?", 10)

    // 批量插入
    var users = []User{user1, user2, user3}
    db.Create(&users)
    // ...
    }

    连接池

    GORM 使用 database/sql 来维护连接池

    sqlDB, err := db.DB()

    // SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
    sqlDB.SetMaxIdleConns(10)

    // SetMaxOpenConns sets the maximum number of open connections to the database.
    sqlDB.SetMaxOpenConns(100)

    // SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
    sqlDB.SetConnMaxLifetime(time.Hour)

    查看 通用接口 获取详情。

    -

    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.

    -

    For others, you are encouraged to make a driver, pull request welcome!

    +

    还未支持的数据库

    有些数据库可能兼容 mysqlpostgres 的方言,在这种情况下,你可以直接使用这些数据库的方言。

    +

    对于其他还未支持的数据库驱动,我们鼓励开发者积极的提交更多类型的数据库驱动!

    @@ -204,7 +204,7 @@

    - + @@ -318,7 +318,7 @@

    Gold Sponsors

    内容 -
    1. MySQL
      1. 自定义驱动
      2. 现有的数据库连接
    2. PostgreSQL
      1. 自定义驱动
      2. 现有的数据库连接
    3. SQLite
    4. SQL Server
    5. TiDB
    6. Clickhouse
    7. 连接池
    8. Unsupported Databases
    +
    1. MySQL
      1. 自定义驱动
      2. 现有的数据库连接
    2. PostgreSQL
      1. 自定义驱动
      2. 现有的数据库连接
    3. SQLite
    4. SQL Server
    5. TiDB
    6. Clickhouse
    7. 连接池
    8. 还未支持的数据库
    diff --git a/zh_CN/docs/constraints.html b/zh_CN/docs/constraints.html index ab155e1a15c..ad91299757b 100644 --- a/zh_CN/docs/constraints.html +++ b/zh_CN/docs/constraints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - + diff --git a/zh_CN/docs/context.html b/zh_CN/docs/context.html index e8adac5280b..db9914aff40 100644 --- a/zh_CN/docs/context.html +++ b/zh_CN/docs/context.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,27 +141,25 @@

    Context

    -

    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.

    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.

    tx := db.WithContext(ctx)
    tx.First(&user, 1)
    tx.Model(&user).Update("role", "admin")
    -

    Context 超时

    对于长 Sql 查询,你可以传入一个带超时的 context 给 db.WithContext 来设置超时时间,例如:

    +

    Context Timeout

    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.

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    db.WithContext(ctx).Find(&users)
    -

    Hooks/Callbacks 中的 Context

    您可以从当前 Statement中访问 Context 对象,例如︰

    -
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ...
    return
    }
    +

    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.

    +
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    ctx := tx.Statement.Context
    // ... use context
    return
    }
    -

    Chi 中间件示例

    在处理 API 请求时持续会话模式会比较有用。例如,您可以在中间件中为 *gorm.DB 设置超时 Context,然后使用 *gorm.DB 处理所有请求

    -

    下面是一个 Chi 中间件的示例:

    -
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    db, ok := ctx.Value("DB").(*gorm.DB)

    var users []User
    db.Find(&users)

    // 你的其他 DB 操作...
    })

    r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
    db, ok := ctx.Value("DB").(*gorm.DB)

    var user User
    db.First(&user)

    // 你的其他 DB 操作...
    })
    +

    Integration with 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.

    +
    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))
    next.ServeHTTP(w, r.WithContext(ctx))
    })
    }

    // Router setup
    r := chi.NewRouter()
    r.Use(SetDBMiddleware)

    // Route handlers
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })

    r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
    db, ok := r.Context().Value("DB").(*gorm.DB)
    // ... db operations
    })
    -

    注意 通过 WithContext 设置的 Context 是线程安全的,参考会话获取详情

    -
    - -

    Logger

    Logger 也可以支持 Context,可用于日志追踪,查看 Logger 获取详情

    +

    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 in GORM.

    +

    Logger Integration

    GORM’s logger also accepts Context, which can be used for log tracking and integrating with existing logging infrastructures.

    +

    Refer to Logger documentation for more details.

    @@ -174,7 +172,7 @@

    - + @@ -288,7 +286,7 @@

    Gold Sponsors

    内容 -
    1. 单会话模式
    2. 持续会话模式
    3. Context 超时
    4. Hooks/Callbacks 中的 Context
    5. Chi 中间件示例
    6. Logger
    +
    1. 单会话模式
    2. Continuous Session Mode
    3. Context Timeout
    4. Hooks/Callbacks 中的 Context
    5. Integration with Chi Middleware
    6. Logger Integration
    diff --git a/zh_CN/docs/conventions.html b/zh_CN/docs/conventions.html index a2ef9d00572..33ca0dba61e 100644 --- a/zh_CN/docs/conventions.html +++ b/zh_CN/docs/conventions.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    // 根据 User 的字段创建 `deleted_users` 表
    db.Table("deleted_users").AutoMigrate(&User{})

    // 从另一张表查询数据
    var deletedUsers []User
    db.Table("deleted_users").Find(&deletedUsers)
    // SELECT * FROM deleted_users;

    db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
    // DELETE FROM deleted_users WHERE name = 'jinzhu';

    查看 from 子查询 了解如何在 FROM 子句中使用子查询

    -

    命名策略

    GORM 允许用户通过覆盖默认的命名策略更改默认的命名约定,命名策略被用于构建: TableNameColumnNameJoinTableNameRelationshipFKNameCheckerNameIndexName。查看 GORM 配置 获取详情

    +

    命名策略

    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 for details

    列名

    根据约定,数据表的列名使用的是 struct 字段名的 蛇形命名

    type User struct {
      ID        uint      // 列名是 `id`
      Name      string    // 列名是 `name`
      Birthday  time.Time // 列名是 `birthday`
      CreatedAt time.Time // 列名是 `created_at`
    }
    @@ -194,7 +194,7 @@

    - + diff --git a/zh_CN/docs/create.html b/zh_CN/docs/create.html index ddd9ae64ce7..fe54d3fa912 100644 --- a/zh_CN/docs/create.html +++ b/zh_CN/docs/create.html @@ -56,8 +56,8 @@ - - + + @@ -155,7 +155,7 @@

    db.Omit("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
    -

    批量插入

    要高效地插入大量记录,请将切片传递给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.

    var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
    db.Create(&users)

    for _, user := range users {
    user.ID // 1,2,3
    }

    You can specify batch size when creating with CreateInBatches, e.g:

    @@ -220,7 +220,7 @@

    diff --git a/zh_CN/docs/data_types.html b/zh_CN/docs/data_types.html index 827775bdb96..047aa5b46c5 100644 --- a/zh_CN/docs/data_types.html +++ b/zh_CN/docs/data_types.html @@ -56,8 +56,8 @@ - - + + @@ -189,7 +189,7 @@

    - + diff --git a/zh_CN/docs/dbresolver.html b/zh_CN/docs/dbresolver.html index 1a8a8774429..a54994dec18 100644 --- a/zh_CN/docs/dbresolver.html +++ b/zh_CN/docs/dbresolver.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    事务

    使用事务时,DBResolver 也会保持使用一个事务,且不会根据配置切换 sources/replicas 连接

    但您可以在事务开始之前指定使用哪个数据库,例如:

    -
    // 通过默认 replicas db 开始事务
    tx := DB.Clauses(dbresolver.Read).Begin()

    // 通过默认 sources db 开始事务
    tx := DB.Clauses(dbresolver.Write).Begin()

    // 通过 `secondary` 的 sources db 开始事务
    tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()
    +
    // Start transaction based on default replicas db
    tx := db.Clauses(dbresolver.Read).Begin()

    // Start transaction based on default sources db
    tx := db.Clauses(dbresolver.Write).Begin()

    // Start transaction based on `secondary`'s sources
    tx := db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

    负载均衡

    GORM 支持基于策略的 sources/replicas 负载均衡,自定义策略应该是一个实现了以下接口的 struct:

    type Policy interface {
    Resolve([]gorm.ConnPool) gorm.ConnPool
    }
    @@ -183,7 +183,7 @@

    - + diff --git a/zh_CN/docs/delete.html b/zh_CN/docs/delete.html index 7f037392c83..4d1dd74f575 100644 --- a/zh_CN/docs/delete.html +++ b/zh_CN/docs/delete.html @@ -56,8 +56,8 @@ - - + + @@ -202,7 +202,7 @@

    - + diff --git a/zh_CN/docs/error_handling.html b/zh_CN/docs/error_handling.html index 2dbe1da5859..31082ce9603 100644 --- a/zh_CN/docs/error_handling.html +++ b/zh_CN/docs/error_handling.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,21 +141,40 @@

    处理错误

    -

    在 Go 中,处理错误是很重要的。

    -

    我们鼓励您在调用任何 Finisher 方法 后,都进行错误检查

    -

    处理错误

    GORM 的错误处理与常见的 Go 代码不同,因为 GORM 提供的是链式 API。

    -

    如果遇到任何错误,GORM 会设置 *gorm.DBError 字段,您需要像这样检查它:

    -
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // 处理错误...
    }
    - -

    或者

    -
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // 处理错误...
    }
    - -

    ErrRecordNotFound

    FirstLastTake 方法找不到记录时,GORM 会返回 ErrRecordNotFound 错误。如果发生了多个错误,你可以通过 errors.Is 判断错误是否为 ErrRecordNotFound,例如:

    -
    // 检查错误是否为 RecordNotFound
    err := db.First(&user, 100).Error
    errors.Is(err, gorm.ErrRecordNotFound)
    -

    翻译方言错误

    如果您希望将数据库的方言错误转换为gorm的错误类型(例如将MySQL中的“Duplicate entry”转换为ErrDuplicatedKey),则在打开数据库连接时启用TranslateError标志。

    +

    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.

    +

    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.

    +

    After a chain of methods, it’s crucial to check the Error field:

    +
    if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // Handle error...
    }
    + +

    Or alternatively:

    +
    if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
    // Handle error...
    }
    + +

    ErrRecordNotFound

    GORM returns ErrRecordNotFound when no record is found using methods like First, Last, Take.

    +
    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

    +
      +
    • Example: Handling MySQL Error Codes
    • +
    +
    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.

    db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{TranslateError: true})
    -

    Errors

    Errors List

    +
      +
    • ErrDuplicatedKey
    • +
    +

    This error occurs when an insert operation violates a unique constraint:

    +
    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:

    +
    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

    For a complete list of errors that GORM can return, refer to the Errors List in GORM’s documentation.

    @@ -168,7 +187,7 @@

    - + @@ -282,7 +301,7 @@

    Gold Sponsors

    内容 -
    1. 处理错误
    2. ErrRecordNotFound
    3. 翻译方言错误
    4. Errors
    +
    1. Basic Error Handling
    2. ErrRecordNotFound
    3. Handling Error Codes
    4. Dialect Translated Errors
    5. Errors
    diff --git a/zh_CN/docs/generic_interface.html b/zh_CN/docs/generic_interface.html index b5e17f51bee..5c1852ebbe6 100644 --- a/zh_CN/docs/generic_interface.html +++ b/zh_CN/docs/generic_interface.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

    - + diff --git a/zh_CN/docs/gorm_config.html b/zh_CN/docs/gorm_config.html index 258c423c3a4..8c4882d6d78 100644 --- a/zh_CN/docs/gorm_config.html +++ b/zh_CN/docs/gorm_config.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    diff --git a/zh_CN/docs/has_many.html b/zh_CN/docs/has_many.html index 89c190754ad..3c70a7e7eda 100644 --- a/zh_CN/docs/has_many.html +++ b/zh_CN/docs/has_many.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

    - + diff --git a/zh_CN/docs/has_one.html b/zh_CN/docs/has_one.html index e740260eb4f..f4e77680f6f 100644 --- a/zh_CN/docs/has_one.html +++ b/zh_CN/docs/has_one.html @@ -56,8 +56,8 @@ - - + + @@ -183,7 +183,7 @@

    - + diff --git a/zh_CN/docs/hints.html b/zh_CN/docs/hints.html index 648f27d7fbf..7ed2f5610a6 100644 --- a/zh_CN/docs/hints.html +++ b/zh_CN/docs/hints.html @@ -56,8 +56,8 @@ - - + + @@ -160,7 +160,7 @@

    - + diff --git a/zh_CN/docs/hooks.html b/zh_CN/docs/hooks.html index 032d41b773e..0c2c39584e4 100644 --- a/zh_CN/docs/hooks.html +++ b/zh_CN/docs/hooks.html @@ -56,8 +56,8 @@ - - + + @@ -186,7 +186,7 @@

    - + diff --git a/zh_CN/docs/index.html b/zh_CN/docs/index.html index 3689e7345e6..38b00bf0f53 100644 --- a/zh_CN/docs/index.html +++ b/zh_CN/docs/index.html @@ -56,8 +56,8 @@ - - + + @@ -173,7 +173,7 @@

    - + diff --git a/zh_CN/docs/indexes.html b/zh_CN/docs/indexes.html index ac6fd0451ce..a36f23d7304 100644 --- a/zh_CN/docs/indexes.html +++ b/zh_CN/docs/indexes.html @@ -56,8 +56,8 @@ - - + + @@ -179,7 +179,7 @@

    - + diff --git a/zh_CN/docs/logger.html b/zh_CN/docs/logger.html index 52b3df637e9..cee62057a71 100644 --- a/zh_CN/docs/logger.html +++ b/zh_CN/docs/logger.html @@ -56,8 +56,8 @@ - - + + @@ -166,7 +166,7 @@

    diff --git a/zh_CN/docs/many_to_many.html b/zh_CN/docs/many_to_many.html index 08d8c5c86eb..060259361c0 100644 --- a/zh_CN/docs/many_to_many.html +++ b/zh_CN/docs/many_to_many.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    注意: 自定义连接表要求外键是复合主键或复合唯一索引

    -
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addressses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // 修改 Person 的 Addresses 字段的连接表为 PersonAddress
    // PersonAddress 必须定义好所需的外键,否则会报错
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
    +
    type Person struct {
    ID int
    Name string
    Addresses []Address `gorm:"many2many:person_addresses;"`
    }

    type Address struct {
    ID uint
    Name string
    }

    type PersonAddress struct {
    PersonID int `gorm:"primaryKey"`
    AddressID int `gorm:"primaryKey"`
    CreatedAt time.Time
    DeletedAt gorm.DeletedAt
    }

    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
    // ...
    }

    // 修改 Person 的 Addresses 字段的连接表为 PersonAddress
    // PersonAddress 必须定义好所需的外键,否则会报错
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

    外键约束

    你可以通过为标签 constraint 配置 OnUpdateOnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建,例如:

    type User struct {
    gorm.Model
    Languages []Language `gorm:"many2many:user_speaks;"`
    }

    type Language struct {
    Code string `gorm:"primarykey"`
    Name string
    }

    // CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);
    @@ -191,7 +191,7 @@

    - + diff --git a/zh_CN/docs/method_chaining.html b/zh_CN/docs/method_chaining.html index 783dbfb01b9..0e93b8d1de7 100644 --- a/zh_CN/docs/method_chaining.html +++ b/zh_CN/docs/method_chaining.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,33 +141,63 @@

    链式方法

    -

    GORM 允许进行链式操作,所以您可以像这样写代码:

    +

    GORM’s method chaining feature allows for a smooth and fluent style of coding. Here’s an example:

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
    -

    GORM 中有三种类型的方法: 链式方法终结方法新建会话方法

    -

    链式方法, 终结方法之后, GORM 返回一个初始化的 *gorm.DB 实例,实例不能安全地重复使用,并且新生成的 SQL 可能会被先前的条件污染,例如:

    -
    queryDB := DB.Where("name = ?", "jinzhu")

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
    - -

    为了重新使用初始化的 *gorm.DB 实例, 您可以使用 新建会话方法 创建一个可共享的 *gorm.DB, 例如:

    -
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    queryDB.Where("age > ?", 10).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    queryDB.Where("age > ?", 20).First(&user2)
    // SELECT * FROM users WHERE name = "jinzhu" AND age > 20
    - -

    链式方法

    链式方法是将 Clauses 修改或添加到当前 Statement 的方法,例如:

    -

    Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

    -

    这是 完整方法列表,也可以查看 SQL 构建器 获取更多关于 Clauses 的信息

    -

    终结方法

    终结(方法) 是会立即执行注册回调的方法,然后生成并执行 SQL,比如这些方法:

    -

    Create, First, Find, Take, Save, Update, Delete, Scan, Row, Rows

    -

    查看完整方法列表

    -

    新建会话方法

    GORM 定义了 SessionWithContextDebug 方法做为 新建会话方法,查看会话 获取详情.

    -

    链式方法, Finisher 方法之后, GORM 返回一个初始化的 *gorm.DB 实例,不能安全地再使用。您应该使用 新建会话方法 来标记 *gorm.DB 为可共享。

    -

    让我们用实例来解释它:

    -

    示例 1:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new 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:
    // 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:
    // 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:
    // SELECT * FROM users;
    - -

    (错误的) 示例2:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is safe to 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

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    - -

    示例 3:

    -
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // db is a new initialized *gorm.DB, which is 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

    // good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // good case
    tx.Where("age = ?", 28).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;
    +

    Method Categories

    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. Also, the SQL Builder 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.

    +

    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 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

    queryDB := DB.Where("name = ?", "jinzhu")

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query with unintended compounded condition
    queryDB.Where("age > ?", 20).First(&user2)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
    + +

    Example of Safe Reuse

    To safely reuse a *gorm.DB instance, use a New Session Method:

    +
    queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})

    // First query
    queryDB.Where("age > ?", 10).First(&user)
    // SQL: SELECT * FROM users WHERE name = "jinzhu" AND age > 10

    // Second query, safely isolated
    queryDB.Where("age > ?", 20).First(&user2)
    // 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.

    +

    Examples for Clarity

    Let’s clarify with a few examples:

    +
      +
    • Example 1: Safe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized `*gorm.DB`, which is safe to reuse.

    db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
    // 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)
    // 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)
    // Directly calling `Find(&users)` without any `Where` starts a new chain and executes:
    // SELECT * FROM users;
    + +

    In this example, each chain of method calls is independent, ensuring clean, non-polluted SQL queries.

    +
      +
    • (Bad) Example 2: Unsafe Instance Reuse
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // 'db' is a newly initialized *gorm.DB, safe for initial reuse.

    tx := db.Where("name = ?", "jinzhu")
    // `Where("name = ?", "jinzhu")` initializes a `*gorm.Statement` instance, which should not be reused across different logical operations.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // Reuses 'tx' correctly for a single logical operation, executing:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // Bad case
    tx.Where("age = ?", 28).Find(&users)
    // Incorrectly reuses 'tx', compounding conditions and leading to a polluted query:
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;
    + +

    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
    • +
    +
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    // '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` methods return a `*gorm.DB` instance marked as safe for reuse. They base a newly initialized `*gorm.Statement` on the current conditions.

    // Good case
    tx.Where("age = ?", 18).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

    // 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.

    @@ -180,7 +210,7 @@

    - + @@ -294,7 +324,7 @@

    Gold Sponsors

    内容 -
    1. 链式方法
    2. 终结方法
    3. 新建会话方法
    +
    1. Method Categories
      1. Chain Methods
      2. Finisher Methods
      3. New Session Methods
    2. Reusability and Safety
      1. Example of Unsafe Reuse
      2. Example of Safe Reuse
    3. Examples for Clarity
    diff --git a/zh_CN/docs/migration.html b/zh_CN/docs/migration.html index 4dd433e67df..661805cf659 100644 --- a/zh_CN/docs/migration.html +++ b/zh_CN/docs/migration.html @@ -56,8 +56,8 @@ - - + + @@ -206,7 +206,7 @@

    - + diff --git a/zh_CN/docs/models.html b/zh_CN/docs/models.html index cd3d9e36e52..d599ba8cb3f 100644 --- a/zh_CN/docs/models.html +++ b/zh_CN/docs/models.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,16 +141,45 @@

    模型定义

    -

    模型定义

    模型是标准的 struct,由 Go 的基本数据类型、实现了 ScannerValuer 接口的自定义类型及其指针或别名组成

    -

    例如:

    -
    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
    }
    - -

    约定

    GORM 倾向于约定优于配置 默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间

    -

    如果您遵循 GORM 的约定,您就可以少写的配置、代码。 如果约定不符合您的实际要求,GORM 允许你配置它们

    -

    gorm.Model

    GORM 定义一个 gorm.Model 结构体,其包括字段 IDCreatedAtUpdatedAtDeletedAt

    +

    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 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 and Valuer interfaces from the database/sql package

    +

    Consider the following example of a User model:

    +
    type User struct {
    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 for a detailed explanation

    +

    约定

      +
    1. Primary Key: GORM uses a field named ID as the default primary key for each model.

      +
    2. +
    3. 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.

      +
    4. +
    5. Column Names: GORM automatically converts struct field names to snake_case for column names in the database.

      +
    6. +
    7. Timestamp Fields: GORM uses fields named CreatedAt and UpdatedAt to automatically track the creation and update times of records.

      +
    8. +
    +

    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.

    +

    gorm.Model

    GORM provides a predefined struct named gorm.Model, which includes commonly used fields:

    // 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

      +
    • +
    • 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).
      • +
      +
    • +

    高级选项

    字段级权限控制

    可导出的字段在使用 GORM 进行 CRUD 时拥有全部的权限,此外,GORM 允许您用标签控制字段级别的权限。这样您就可以让一个字段的权限是只读、只写、只创建、只更新或者被忽略

    注意: 使用 GORM Migrator 创建表时,不会创建被忽略的字段

    @@ -286,7 +315,7 @@

    - + @@ -400,7 +429,7 @@

    Gold Sponsors

    内容 -
    1. 模型定义
    2. 约定
    3. gorm.Model
    4. 高级选项
      1. 字段级权限控制
      2. 创建/更新时间追踪(纳秒、毫秒、秒、Time)
      3. 嵌入结构体
      4. 字段标签
      5. 关联标签
    +
    1. 模型定义
      1. 约定
      2. gorm.Model
    2. 高级选项
      1. 字段级权限控制
      2. 创建/更新时间追踪(纳秒、毫秒、秒、Time)
      3. 嵌入结构体
      4. 字段标签
      5. 关联标签
    diff --git a/zh_CN/docs/performance.html b/zh_CN/docs/performance.html index 5e33290c930..0264fe725f1 100644 --- a/zh_CN/docs/performance.html +++ b/zh_CN/docs/performance.html @@ -56,8 +56,8 @@ - - + + @@ -178,7 +178,7 @@

    - + diff --git a/zh_CN/docs/preload.html b/zh_CN/docs/preload.html index 0e400dd5ffb..0a17381e9cd 100644 --- a/zh_CN/docs/preload.html +++ b/zh_CN/docs/preload.html @@ -56,8 +56,8 @@ - - + + @@ -191,7 +191,7 @@

    - + diff --git a/zh_CN/docs/prometheus.html b/zh_CN/docs/prometheus.html index 1e24afe7e94..b7b651d4a7f 100644 --- a/zh_CN/docs/prometheus.html +++ b/zh_CN/docs/prometheus.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - + diff --git a/zh_CN/docs/query.html b/zh_CN/docs/query.html index aed0d56e890..4b820082f69 100644 --- a/zh_CN/docs/query.html +++ b/zh_CN/docs/query.html @@ -56,8 +56,8 @@ - - + + @@ -243,7 +243,7 @@

    - + diff --git a/zh_CN/docs/scopes.html b/zh_CN/docs/scopes.html index cd55f972a80..56a4a3e0906 100644 --- a/zh_CN/docs/scopes.html +++ b/zh_CN/docs/scopes.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/zh_CN/docs/security.html b/zh_CN/docs/security.html index 3ae5ff3725f..eb76d31b6c5 100644 --- a/zh_CN/docs/security.html +++ b/zh_CN/docs/security.html @@ -56,8 +56,8 @@ - - + + @@ -151,7 +151,7 @@

    内联条件

    // 会被转义
    db.First(&user, "name = ?", userInput)

    // SQL 注入
    db.First(&user, fmt.Sprintf("name = %v", userInput))

    当通过用户输入的整形主键检索记录时,你应该对变量进行类型检查。

    -
    userInputID := "1=1;drop table users;"
    // 安全的,返回 err
    id,err := strconv.Atoi(userInputID)
    if err != nil {
    return error
    }
    db.First(&user, id)

    // SQL 注入
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;
    +
    userInputID := "1=1;drop table users;"
    // safe, return error
    id, err := strconv.Atoi(userInputID)
    if err != nil {
    return err
    }
    db.First(&user, id)

    // SQL injection
    db.First(&user, userInputID)
    // SELECT * FROM users WHERE 1=1;drop table users;

    SQL 注入方法

    为了支持某些功能,一些输入不会被转义,调用方法时要小心用户输入的参数。

    db.Select("name; drop table users;").First(&user)
    db.Distinct("name; drop table users;").First(&user)

    db.Model(&user).Pluck("name; drop table users;", &names)

    db.Group("name; drop table users;").First(&user)

    db.Group("name").Having("1 = 1;drop table users;").First(&user)

    db.Raw("select name from users; drop table users;").First(&user)

    db.Exec("select name from users; drop table users;")

    db.Order("name; drop table users;").First(&user)
    @@ -169,7 +169,7 @@

    diff --git a/zh_CN/docs/serializer.html b/zh_CN/docs/serializer.html index d48f25c287a..669d6685946 100644 --- a/zh_CN/docs/serializer.html +++ b/zh_CN/docs/serializer.html @@ -32,8 +32,8 @@ - - + + @@ -147,7 +147,7 @@

    - + diff --git a/zh_CN/docs/session.html b/zh_CN/docs/session.html index 784f29b9d3d..dc033b1afc0 100644 --- a/zh_CN/docs/session.html +++ b/zh_CN/docs/session.html @@ -56,8 +56,8 @@ - - + + @@ -204,7 +204,7 @@

    diff --git a/zh_CN/docs/settings.html b/zh_CN/docs/settings.html index cae14d09dbd..e92d1c834a0 100644 --- a/zh_CN/docs/settings.html +++ b/zh_CN/docs/settings.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - + diff --git a/zh_CN/docs/sharding.html b/zh_CN/docs/sharding.html index a82a622f272..fe0801c380f 100644 --- a/zh_CN/docs/sharding.html +++ b/zh_CN/docs/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -168,7 +168,7 @@

    - + diff --git a/zh_CN/docs/sql_builder.html b/zh_CN/docs/sql_builder.html index d40d51bd81d..84d26a3bf3c 100644 --- a/zh_CN/docs/sql_builder.html +++ b/zh_CN/docs/sql_builder.html @@ -56,8 +56,8 @@ - - + + @@ -207,7 +207,7 @@

    diff --git a/zh_CN/docs/transactions.html b/zh_CN/docs/transactions.html index c77e11b31f7..aebbf22f872 100644 --- a/zh_CN/docs/transactions.html +++ b/zh_CN/docs/transactions.html @@ -56,8 +56,8 @@ - - + + @@ -148,7 +148,7 @@

    db.Transaction(func(tx *gorm.DB) error {
    // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回任何错误都会回滚事务
    return err
    }

    if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
    }

    // 返回 nil 提交事务
    return nil
    })

    嵌套事务

    GORM 支持嵌套事务,您可以回滚较大事务内执行的一部分操作,例如:

    -
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3
    +
    db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user1)

    tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
    })

    tx.Transaction(func(tx3 *gorm.DB) error {
    tx3.Create(&user3)
    return nil
    })

    return nil
    })

    // Commit user1, user3

    手动事务

    Gorm 支持直接调用事务控制方法(commit、rollback),例如:

    // 开始事务
    tx := db.Begin()

    // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
    tx.Create(...)

    // ...

    // 遇到错误时回滚事务
    tx.Rollback()

    // 否则,提交事务
    tx.Commit()
    @@ -169,7 +169,7 @@

    - + diff --git a/zh_CN/docs/update.html b/zh_CN/docs/update.html index e29e439f2fc..8dac0753eef 100644 --- a/zh_CN/docs/update.html +++ b/zh_CN/docs/update.html @@ -56,8 +56,8 @@ - - + + @@ -144,25 +144,25 @@

    更新

    保存所有字段

    Save 会保存所有的字段,即使字段是零值

    db.First(&user)

    user.Name = "jinzhu 2"
    user.Age = 100
    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 (包含所有字段)。

    db.Save(&User{Name: "jinzhu", Age: 100})
    // INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")

    db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
    // UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1
    -

    NOTE Don’t use Save with Model, it’s an Undefined Behavior.

    +

    NOTE不要将 SaveModel一同使用, 这是 为定义的行为

    -

    更新单个列

    When updating a single column with Update, it needs to have any conditions or it will raise error ErrMissingWhereClause, checkout 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 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`:
    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
    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;
    +

    更新单个列

    当使用 Update 更新单列时,需要有一些条件,否则将会引起ErrMissingWhereClause 错误,查看 阻止全局更新 了解详情。 当使用 Model 方法,并且它有主键值时,主键将会被用于构建条件,例如:

    +
    // 根据条件更新
    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 的 ID 是 `111`
    db.Model(&user).Update("name", "hello")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

    // 根据条件和 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

    -
    // Update attributes with `struct`, will only update non-zero fields
    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`
    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;
    +

    更新多列

    Updates 方法支持 structmap[string]interface{} 参数。当使用 struct 更新时,默认情况下GORM 只会更新非零值的字段

    +
    // 根据 `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;

    // 根据 `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 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 声明字段来更新

    -

    更新选定字段

    If you want to update selected fields or ignore some fields when updating, you can use Select, Omit

    -
    // Select with Map
    // User's ID is `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)
    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)
    db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
    +

    更新选定字段

    如果您想要在更新时选择、忽略某些字段,您可以使用 SelectOmit

    +
    // 选择 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;

    // 选择 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;

    // 选择所有字段(选择包括零值字段的所有字段)
    db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

    // 选择除 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 for details

    +

    更新 Hook

    GORM 支持的 hook 包括:BeforeSave, BeforeUpdate, AfterSave, AfterUpdate. 更新记录时将调用这些方法,查看 Hooks 获取详细信息

    func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
    return errors.New("admin user not allowed to update")
    }
    return
    }

    批量更新

    If we haven’t specified a record having a primary key value with Model, GORM will perform a batch update

    @@ -208,7 +208,7 @@

    - + diff --git a/zh_CN/docs/v2_release_note.html b/zh_CN/docs/v2_release_note.html index c61957a340a..106589d8ae9 100644 --- a/zh_CN/docs/v2_release_note.html +++ b/zh_CN/docs/v2_release_note.html @@ -56,8 +56,8 @@ - - + + @@ -355,7 +355,7 @@

    - + diff --git a/zh_CN/docs/write_driver.html b/zh_CN/docs/write_driver.html index 97c4dbcabb4..1133e8649da 100644 --- a/zh_CN/docs/write_driver.html +++ b/zh_CN/docs/write_driver.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,12 +141,45 @@

    编写驱动

    -

    编写新驱动

    GORM 官方支持 sqlitemysqlpostgressqlserver

    -

    有些数据库可能兼容 mysqlpostgres 的方言,在这种情况下,你可以直接使用这些数据库的方言。

    -

    对于其它不兼容的情况,您可以自行编写一个新驱动,这需要实现 方言接口

    -
    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
    }
    - -

    查看 MySQL 驱动 的例子

    +

    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.

    +

    Compatibility with MySQL or Postgres Dialects

    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.

    +

    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:

    +
    type Dialector interface {
    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.

    +
    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
    }
    + +

    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:

    +
    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:

    +
    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:

    +
    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 can be helpful. This driver demonstrates how the Dialector interface is implemented to suit the specific needs of the MySQL database.

    @@ -159,7 +192,7 @@

    编写驱动

    @@ -273,7 +306,7 @@

    Gold Sponsors

    内容 -
    1. 编写新驱动
    +
    1. Compatibility with MySQL or Postgres Dialects
    2. Implementing the Dialector
      1. Nested Transaction Support
      2. Custom Clause Builders
    diff --git a/zh_CN/docs/write_plugins.html b/zh_CN/docs/write_plugins.html index 38e0591e160..160787d9e32 100644 --- a/zh_CN/docs/write_plugins.html +++ b/zh_CN/docs/write_plugins.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,28 +141,39 @@

    编写插件

    -

    Callbacks

    GORM 自身也是基于 Callbacks 的,包括 CreateQueryUpdateDeleteRowRaw。此外,您也完全可以根据自己的意愿自定义 GORM

    -

    回调会注册到全局 *gorm.DB,而不是会话级别。如果您想要 *gorm.DB 具有不同的回调,您需要初始化另一个 *gorm.DB

    -

    注册 Callback

    注册 callback 至 callbacks

    -
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    db.Callback().Create().Register("crop_image", cropImage)
    // register a callback for Create process
    +

    Callbacks

    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 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.

    +

    Registering a Callback

    You can register a callback for specific operations. For example, to add a custom image cropping functionality:

    +
    func cropImage(db *gorm.DB) {
    if db.Statement.Schema != nil {
    // crop image fields and upload them to CDN, dummy code
    for _, field := range db.Statement.Schema.Fields {
    switch db.Statement.ReflectValue.Kind() {
    case reflect.Slice, reflect.Array:
    for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i)); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }
    }
    case reflect.Struct:
    // Get value from field
    if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
    if crop, ok := fieldValue.(CropInterface); ok {
    crop.Crop()
    }
    }

    // Set value to field
    err := field.Set(db.Statement.Context, db.Statement.ReflectValue, "newValue")
    }
    }

    // All fields for current model
    db.Statement.Schema.Fields

    // All primary key fields for current model
    db.Statement.Schema.PrimaryFields

    // Prioritized primary key field: field with DB name `id` or the first defined primary key
    db.Statement.Schema.PrioritizedPrimaryField

    // All relationships for current model
    db.Statement.Schema.Relationships

    // Find field with field name or db name
    field := db.Statement.Schema.LookUpField("Name")

    // processing
    }
    }

    // Register the callback for the Create operation
    db.Callback().Create().Register("crop_image", cropImage)
    -

    删除 Callback

    从 callbacks 中删除回调

    -
    db.Callback().Create().Remove("gorm:create")
    // 从 Create 的 callbacks 中删除 `gorm:create`
    +

    Deleting a Callback

    If a callback is no longer needed, it can be removed:

    +
    // Remove the 'gorm:create' callback from Create operations
    db.Callback().Create().Remove("gorm:create")
    -

    替换 Callback

    用一个新的回调替换已有的同名回调

    -
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    // 用新函数 `newCreateFunction` 替换 Create 流程目前的 `gorm:create`
    +

    Replacing a Callback

    Callbacks with the same name can be replaced with a new function:

    +
    // Replace the 'gorm:create' callback with a new function
    db.Callback().Create().Replace("gorm:create", newCreateFunction)
    -

    注册带顺序的 Callback

    注册带顺序的 Callback

    -
    // gorm:create 之前
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // gorm:create 之后
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // gorm:query 之后
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // gorm:delete 之后
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // gorm:update 之前
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 位于 gorm:before_create 之后 gorm:create 之前
    db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate)

    // 所有其它 callback 之前
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // 所有其它 callback 之后
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    +

    Ordering Callbacks

    Callbacks can be registered with specific orders to ensure they execute at the right time in the operation lifecycle.

    +
    // Register to execute before the 'gorm:create' callback
    db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:create' callback
    db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)

    // Register to execute after the 'gorm:query' callback
    db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)

    // Register to execute after the 'gorm:delete' callback
    db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)

    // Register to execute before the 'gorm:update' callback
    db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)

    // 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)

    // Register to execute before any other callbacks
    db.Callback().Create().Before("*").Register("update_created_at", updateCreated)

    // Register to execute after any other callbacks
    db.Callback().Create().After("*").Register("update_created_at", updateCreated)
    -

    预定义 Callback

    GORM 已经定义了 一些 callback 来支持当前的 GORM 功能,在启动您的插件之前可以先看看这些 callback

    -

    插件

    GORM 提供了 Use 方法来注册插件,插件需要实现 Plugin 接口

    +

    Predefined Callbacks

    GORM comes with a set of predefined callbacks that drive its standard features. It’s recommended to review these defined callbacks before creating custom plugins or additional callback functions.

    +

    Plugins

    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:

    type Plugin interface {
    Name() string
    Initialize(*gorm.DB) error
    }
    -

    当插件首次注册到 GORM 时将调用 Initialize 方法,且 GORM 会保存已注册的插件,你可以这样访问访问:

    -
    db.Config.Plugins[pluginName]
    +
      +
    • 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:

    +
    // Example of registering a plugin
    db.Use(MyCustomPlugin{})
    -

    查看 Prometheus 的例子

    +

    Accessing Registered Plugins

    After a plugin is registered, it is stored in GORM’s configuration. You can access registered plugins via the Plugins map:

    +
    // 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:

    +
    // Registering the Prometheus plugin
    db.Use(prometheus.New(prometheus.Config{
    // Configuration options here
    }))
    + +

    Prometheus plugin documentation provides detailed information on its implementation and usage.

    @@ -175,7 +186,7 @@

    - + @@ -289,7 +300,7 @@

    Gold Sponsors

    内容 -
    1. Callbacks
      1. 注册 Callback
      2. 删除 Callback
      3. 替换 Callback
      4. 注册带顺序的 Callback
      5. 预定义 Callback
    2. 插件
    +
    1. Callbacks
      1. Registering a Callback
      2. Deleting a Callback
      3. Replacing a Callback
      4. Ordering Callbacks
      5. Predefined Callbacks
    2. Plugins
      1. The Plugin Interface
      2. Registering a Plugin
      3. Accessing Registered Plugins
      4. Practical Example
    diff --git a/zh_CN/gen.html b/zh_CN/gen.html index b233333a511..a15213a1e94 100644 --- a/zh_CN/gen.html +++ b/zh_CN/gen.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/zh_CN/gen/associations.html b/zh_CN/gen/associations.html index 1e551fcbe19..d2ad998cbe3 100644 --- a/zh_CN/gen/associations.html +++ b/zh_CN/gen/associations.html @@ -56,8 +56,8 @@ - - + + @@ -150,20 +150,20 @@

    // specify model
    g.ApplyBasic(model.Customer{}, model.CreditCard{})

    // assoications will be detected and converted to code
    package query

    type customer struct {
    ...
    CreditCards customerHasManyCreditCards
    }

    type creditCard struct{
    ...
    }
    -

    关联数据库中的表。

    关联必须由 gen.FieldRelate 指定。

    -
    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"}},
    }),
    )

    g.ApplyBasic(card, custormer)
    +

    关联数据库中的表。

    The association have to be specified by gen.FieldRelate

    +
    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"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(card, custormer)

    GEN 将生成带有关联字段的model:

    -
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }
    +
    // customers
    type Customer struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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;references:ID" json:"credit_cards"`
    }


    // credit_cards
    type CreditCard struct {
    ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    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"`
    CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
    }

    如果关联的mode已经存在, 可以通过gen.FieldRelateModel 设置关联。

    -
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"}},
    }),
    )

    g.ApplyBasic(custormer)
    +
    customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
    // RelateSlice: true,
    GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
    }),
    )

    g.ApplyBasic(custormer)

    关联配置

    type RelateConfig struct {
    // specify field's type
    RelatePointer bool // ex: CreditCard *CreditCard
    RelateSlice bool // ex: CreditCards []CreditCard
    RelateSlicePointer bool // ex: CreditCards []*CreditCard

    JSONTag string // related field's JSON tag
    GORMTag string // related field's GORM tag
    NewTag string // related field's new tag
    OverwriteTag string // related field's tag
    }

    操作

    跳过自动创建、更新

    user := model.User{
    Name: "modi",
    BillingAddress: Address{Address1: "Billing Address - Address 1"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
    Emails: []Email{
    {Email: "modi@example.com"},
    {Email: "modi-2@example.com"},
    },
    Languages: []Language{
    {Name: "ZH"},
    {Name: "EN"},
    },
    }

    u := query.Use(db).User

    u.WithContext(ctx).Select(u.Name).Create(&user)
    // INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

    u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
    // Skip create BillingAddress when creating a user

    u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
    // Skip create BillingAddress.Address1 when creating a user

    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

    查找关联

    查找匹配的关联

    u := query.Use(db).User

    languages, err = u.Languages.Model(&user).Find()
    @@ -176,7 +176,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.

    u := query.Use(db).User

    u.Languages.Model(&user).Delete(&languageZH, &languageEN)

    u.Languages.Model(&user).Delete([]*Language{&languageZH, &languageEN}...)

    清除关联

    删除源 & 关联之间的所有引用,不会删除这些关联

    @@ -185,23 +185,23 @@

    Count Associations

    返回当前关联的数量

    u.Languages.Model(&user).Count()
    -

    Delete with Select

    你可以在删除记录时通过 Select 来删除具有 has one、has many、many2many 关系的记录,例如:

    +

    Delete with Select

    You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

    u := query.Use(db).User

    // delete user's account when deleting user
    u.Select(u.Account).Delete(&user)

    // delete user's Orders, CreditCards relations when deleting user
    db.Select(u.Orders.Field(), u.CreditCards.Field()).Delete(&user)

    // delete user's has one/many/many2many relations when deleting user
    db.Select(field.AssociationFields).Delete(&user)

    Preloading

    支持关联已经存在的model,或者关联数据库中的表两种方式。

    Preload

    GEN允许使用 Preload通过多个SQL来直接加载关联表数据, 例如:

    type User struct {
    gorm.Model
    Username string
    Orders []Order
    }

    type Order struct {
    gorm.Model
    UserID uint
    Price float64
    }

    q := query.Use(db)
    u := q.User
    o := q.Order

    // Preload Orders when find users
    users, err := u.WithContext(ctx).Preload(u.Orders).Find()
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4);

    users, err := u.WithContext(ctx).Preload(u.Orders).Preload(u.Profile).Preload(u.Role).Find()
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
    // SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
    // SELECT * FROM roles WHERE id IN (4,5,6); // belongs to
    -

    Preload All

    与创建、更新时使用 Select 类似,clause.Associations 也可以和 Preload 一起使用,它可以用来 预加载 全部关联,例如:

    +

    Preload All

    clause.Associations can work with Preload similar like Select when creating/updating, you can use it to Preload all associations, for example:

    type User struct {
    gorm.Model
    Name string
    CompanyID uint
    Company Company
    Role Role
    Orders []Order
    }

    users, err := u.WithContext(ctx).Preload(field.Associations).Find()
    -

    clause.Associations不会预加载嵌套的关联关系,但是你可以将其与Nested Preloading一起使用, 例如:

    +

    clause.Associations won’t preload nested associations, but you can use it with Nested Preloading together, e.g:

    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:

    users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find()
    -

    Preload with select

    使用方法 Select 指定加载的列. 。 外键必须选择。

    +

    Preload with select

    使用方法 Select 指定加载的列. 。 Foreign key must be selected.

    type User struct {
    gorm.Model
    CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
    }

    type CreditCard struct {
    gorm.Model
    Number string
    UserRefer uint
    }

    u := q.User
    cc := q.CreditCard

    // !!! Foregin key "cc.UserRefer" must be selected
    users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
    // SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
    // SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

    Preload with conditions

    GEN 允许带条件的 Preload 关联,类似于内联条件.

    @@ -209,14 +209,13 @@

    Nested Preloading

    GEN 支持嵌套预加载,例如:

    db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

    // Customize Preload conditions for `Orders`
    // 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/zh_CN/gen/clause.html b/zh_CN/gen/clause.html index bbadbbd25ef..103a2b46026 100644 --- a/zh_CN/gen/clause.html +++ b/zh_CN/gen/clause.html @@ -56,8 +56,8 @@ - - + + @@ -156,7 +156,7 @@

    - + diff --git a/zh_CN/gen/create.html b/zh_CN/gen/create.html index 043c0318507..4d2b98b1987 100644 --- a/zh_CN/gen/create.html +++ b/zh_CN/gen/create.html @@ -56,8 +56,8 @@ - - + + @@ -167,7 +167,7 @@

    - + diff --git a/zh_CN/gen/dao.html b/zh_CN/gen/dao.html index f848d2d6adf..1c28952e07f 100644 --- a/zh_CN/gen/dao.html +++ b/zh_CN/gen/dao.html @@ -32,8 +32,8 @@ - - + + @@ -225,7 +225,7 @@

    - + diff --git a/zh_CN/gen/database_to_structs.html b/zh_CN/gen/database_to_structs.html index 288653a08c9..a59ca3678e3 100644 --- a/zh_CN/gen/database_to_structs.html +++ b/zh_CN/gen/database_to_structs.html @@ -2,7 +2,7 @@ - Database To Structs | GORM - The fantastic ORM library for Golang, aims to be developer friendly. + 从数据库生成结构 | GORM - The fantastic ORM library for Golang, aims to be developer friendly. @@ -51,13 +51,13 @@ - + - - + + @@ -135,42 +135,42 @@

    -

    Database To Structs

    +

    从数据库生成结构

    Quick Start

    Gen 支持所有GORM Driver从数据库生成结构, 使用示例:

    -
    package main

    import "gorm.io/gen"

    func main() {
    g := gen.NewGenerator(gen.Config{
    OutPath: "../query",
    Mode: gen.WithoutContext|gen.WithDefaultQuery|gen.WithQueryInterface, // generate mode
    })

    // 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

    g.ApplyBasic(
    // Generate struct `User` based on table `users`
    g.GenerateModel("users"),

    // Generate struct `Employee` based on table `users`
    g.GenerateModelAs("users", "Employee"),


    // Generate struct `User` based on table `users` and generating options
    g.GenerateModel("users", gen.FieldIgnore("address"), gen.FieldType("id", "int64")),

    )
    g.ApplyBasic(
    // Generate structs from all tables of current database
    g.GenerateAllTable()...,
    )
    // Generate the code
    g.Execute()
    }

    +
    package main

    import "gorm.io/gen"

    func main() {
    g := gen.NewGenerator(gen.Config{
    OutPath: "../query",
    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

    // 按照约定为结构体 `model.User` 生成类型安全的 DAO API
    g.ApplyBasic(
    // 根据 `user` 表生成结构 `User`
    g.GenerateModel("users"),

    // 根据 `user` 表生成结构 `Employee`
    g.GenerateModelAs("users", "Employee"),

    // 根据 `user` 表和生成时选项生成结构 `User`
    g.GenerateModel("users", gen.FieldIgnore("address"), gen.FieldType("id", "int64")),
    )

    g.ApplyBasic(
    // 从当前数据库中生成所有表的结构
    g.GenerateAllTable()...,
    )

    // 生成代码
    g.Execute()
    }

    模板方法

    当从数据库生成结构时,您也可以通过面的方式,给生成的model添加模板方法,例如:

    -
    type CommonMethod struct {
    ID int32
    Name *string
    }

    func (m *CommonMethod) IsEmpty() bool {
    if m == nil {
    return true
    }
    return m.ID == 0
    }

    func (m *CommonMethod) GetName() string {
    if m == nil || m.Name == nil {
    return ""
    }
    return *m.Name
    }

    // Add IsEmpty method to the generated `People` struct
    g.GenerateModel("people", gen.WithMethod(CommonMethod{}.IsEmpty))

    // Add all methods defined on `CommonMethod` to the generated `User` struct
    g.GenerateModel("user", gen.WithMethod(CommonMethod{}))
    +
    type CommonMethod struct {
    ID int32
    Name *string
    }

    func (m *CommonMethod) IsEmpty() bool {
    if m == nil {
    return true
    }
    return m.ID == 0
    }

    func (m *CommonMethod) GetName() string {
    if m == nil || m.Name == nil {
    return ""
    }
    return *m.Name
    }

    // 为生成的 `People` 结构添加 `IsEmpty` 方法
    g.GenerateModel("people", gen.WithMethod(CommonMethod{}.IsEmpty))

    // 将 `CommonMethod` 上定义的所有方法添加到生成的 `User` 结构中
    g.GenerateModel("user", gen.WithMethod(CommonMethod{}))
    -

    The generated code would look like this:

    -
    // Generated Person struct
    type Person struct {
    // ...
    }

    func (m *Person) IsEmpty() bool {
    if m == nil {
    return true
    }
    return m.ID == 0
    }


    // Generated User struct
    type User struct {
    // ...
    }

    func (m *User) IsEmpty() bool {
    if m == nil {
    return true
    }
    return m.ID == 0
    }

    func (m *User) GetName() string {
    if m == nil || m.Name == nil {
    return ""
    }
    return *m.Name
    }
    +

    生成的代码看起来像这样:

    +
    // 生成的 Person 结构
    type Person struct {
    // ...
    }

    func (m *Person) IsEmpty() bool {
    if m == nil {
    return true
    }
    return m.ID == 0
    }


    // 生成的 User 结构
    type User struct {
    // ...
    }

    func (m *User) IsEmpty() bool {
    if m == nil {
    return true
    }
    return m.ID == 0
    }

    func (m *User) GetName() string {
    if m == nil || m.Name == nil {
    return ""
    }
    return *m.Name
    }

    自定义表名称

    当从数据库生成结构时,您也可以通过实现自己的TableName方法,例如:

    type CommonMethod struct {
    ID int32
    Name *string
    }

    // TableName
    func (m CommonMethod) TableName() string {
    return "@@table"
    }

    // TableName table name with gorm NamingStrategy
    func (m CommonMethod) TableName(namer schema.Namer) string {
    if namer == nil {
    return "@@table"
    }
    return namer.TableName("@@table")
    }

    // DIY TableName method for the generated `User` struct
    g.GenerateModel("user", gen.WithMethod(CommonMethod{}.TableName))

    // DIY TableName method for the generated all struct
    g.WithOpts(gen.WithMethod(CommonMethod{}.TableName))

    // Set Default DIY TableName method for the generated all struct
    g.WithOpts(gen.WithMethod(gen.DefaultMethodTableWithNamer))

    -

    Field Options

    Following are options that can be used during GenerateModel/GenerateModelAs

    -
    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
    +

    字段选项

    以下是调用 GenerateModel/GenerateModelAs 时可以使用的选项

    +
    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 // 指定与现有模型的关系

    全局生成选项

    Gen 有一些全局选项可以在 gen.Config中设置:

    -
    g := gen.NewGenerator(gen.Config{
    // if you want the nullable field generation property to be pointer type, set 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
    FieldCoverable: true,
    // if you want to generate field with unsigned integer type, set FieldSignable true
    FieldSignable: true,
    // if you want to generate index tags from database, set FieldWithIndexTag true
    FieldWithIndexTag: true,
    // if you want to generate type tags from database, set FieldWithTypeTag true
    FieldWithTypeTag: true,
    // if you need unit tests for query code, set WithUnitTest true
    WithUnitTest: true,
    })
    +
    g := gen.NewGenerator(gen.Config{
    // 如果你希望为可为null的字段生成属性为指针类型, 设置 FieldNullable 为 true
    FieldNullable: true,
    // 如果你希望在 `Create` API 中为字段分配默认值, 设置 FieldCoverable 为 true, 参考: https://gorm.io/docs/create.html#Default-Values
    FieldCoverable: true,
    // 如果你希望生成无符号整数类型字段, 设置 FieldSignable 为 true
    FieldSignable: true,
    // 如果你希望从数据库生成索引标记, 设置 FieldWithIndexTag 为 true
    FieldWithIndexTag: true,
    // 如果你希望从数据库生成类型标记, 设置 FieldWithTypeTag 为 true
    FieldWithTypeTag: true,
    // 如果你需要对查询代码进行单元测试, 设置 WithUnitTest 为 true
    WithUnitTest: true,
    })
    -
    // WithDbNameOpts set get database name function
    WithDbNameOpts(opts ...model.SchemaNameOpt)

    // WithTableNameStrategy specify table name naming strategy, only work when syncing table from db
    WithTableNameStrategy(ns func(tableName string) (targetTableName string))

    // WithModelNameStrategy specify model struct name naming strategy, only work when syncing table from db
    // 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(ns func(tableName string) (fileName string))

    // WithJSONTagNameStrategy specify json tag naming strategy
    WithJSONTagNameStrategy(ns func(columnName string) (tagContent string))

    // WithDataTypeMap specify data type mapping relationship, only work when syncing table from db
    WithDataTypeMap(newMap map[string]func(gorm.ColumnType) (dataType string))

    // WithImportPkgPath specify import package path
    WithImportPkgPath(paths ...string)

    // WithOpts specify global model options
    WithOpts(opts ...ModelOpt)
    +
    // WithDbNameOpts 设置获取数据库名称方法
    WithDbNameOpts(opts ...model.SchemaNameOpt)

    // WithTableNameStrategy 指定表名命名策略, 仅在从数据库同步表时工作
    WithTableNameStrategy(ns func(tableName string) (targetTableName string))

    // WithModelNameStrategy 指定 model 结构名命名策略, 仅在从数据库同步表时工作
    // If an empty string is returned, the table will be ignored
    WithModelNameStrategy(ns func(tableName string) (modelName string))

    // WithFileNameStrategy 指定文件名命名策略, 仅在从数据库同步表时工作
    WithFileNameStrategy(ns func(tableName string) (fileName string))

    // WithJSONTagNameStrategy 指定 json tag 命名策略
    WithJSONTagNameStrategy(ns func(columnName string) (tagContent string))

    // WithDataTypeMap 指定数据类型命名策略, 仅在从数据库同步表时工作
    WithDataTypeMap(newMap map[string]func(gorm.ColumnType) (dataType string))

    // WithImportPkgPath 指定导入包路径
    WithImportPkgPath(paths ...string)

    // WithOpts 指定全局 model 选项
    WithOpts(opts ...ModelOpt)

    数据类型映射

    指定model属性类型和 db 字段类型之间的映射关系。

    var dataMap = map[string]func(gorm.ColumnType) (dataType string){
    // int mapping
    "int": func(columnType gorm.ColumnType) (dataType string) {
    if n, ok := columnType.Nullable(); ok && n {
    return "*int32"
    }
    return "int32"
    },

    // bool mapping
    "tinyint": func(columnType gorm.ColumnType) (dataType string) {
    ct, _ := columnType.ColumnType()
    if strings.HasPrefix(ct, "tinyint(1)") {
    return "bool"
    }
    return "byte"
    },
    }

    g.WithDataTypeMap(dataMap)
    -

    Generate From Sql

    Gen supports generate structs from sql following GORM conventions, it can be used like:

    -
    package main

    import (
    "gorm.io/gorm"
    "gorm.io/rawsql"
    )

    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
    FilePath: []string{
    //"./sql/user.sql", // create table sql file
    "./test_sql", // create table sql file directory
    },
    }))
    g.UseDB(gormdb) // reuse your gorm db

    // Generate basic type-safe DAO API for struct `model.User` following conventions

    g.ApplyBasic(
    // Generate struct `User` based on table `users`
    g.GenerateModel("users"),

    // Generate struct `Employee` based on table `users`
    g.GenerateModelAs("users", "Employee"),

    )
    g.ApplyBasic(
    // Generate structs from all tables of current database
    g.GenerateAllTable()...,
    )
    // Generate the code
    g.Execute()
    }

    +

    Generate From Sql

    Gen 支持根据 GORM 约定从 SQL 生成 structs,其使用方式如下:

    +
    package main

    import (
    "gorm.io/gorm"
    "gorm.io/rawsql"
    )

    func main() {
    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", // 建表sql文件
    "./test_sql", // 建表sql目录
    },
    }))
    g.UseDB(gormdb) // 重新引用你的 gorm db

    // 按照约定为结构 `model.User` 生成基本类型安全的DAO API
    g.ApplyBasic(
    // 基于 `user` 表生成 `User` 结构
    g.GenerateModel("users"),

    // 基于 `user` 表生成 `Employee` 结构
    g.GenerateModelAs("users", "Employee"),
    )

    g.ApplyBasic(
    // 从当前数据库生成所有表结构
    g.GenerateAllTable()...,
    )

    // 生成代码
    g.Execute()
    }`

    @@ -284,7 +284,7 @@

    Gold Sponsors

    内容 -
    1. Quick Start
    2. 模板方法
      1. 自定义表名称
    3. Field Options
    4. 全局生成选项
      1. 数据类型映射
    5. Generate From Sql
    +
    1. Quick Start
    2. 模板方法
      1. 自定义表名称
    3. 字段选项
    4. 全局生成选项
      1. 数据类型映射
    5. Generate From Sql
    diff --git a/zh_CN/gen/delete.html b/zh_CN/gen/delete.html index e3a3d00e374..5f11ec7fca7 100644 --- a/zh_CN/gen/delete.html +++ b/zh_CN/gen/delete.html @@ -56,8 +56,8 @@ - - + + @@ -144,37 +144,37 @@

    Gen Delete

    删除记录

    当删除一条记录时,需要满足一些条件否则程序会抛出ErrMissingWhereClause异常,例如:

    e := query.Email

    // Email 的 ID 是 10
    e.WithContext(ctx).Where(e.ID.Eq(10)).Delete()
    // DELETE from emails where id = 10;

    // 有附加条件的删除
    e.WithContext(ctx).Where(e.ID.Eq(10), e.Name.Eq("modi")).Delete()
    // DELETE from emails where id = 10 AND name = "modi";

    result, err := e.WithContext(ctx).Where(e.ID.Eq(10), e.Name.Eq("modi")).Delete()

    result.RowsAffected // 受影响的行
    err // 错误
    -

    通过主键删除

    GEN allows to delete objects using primary key(s) with inline condition, it works with numbers.

    +

    通过主键删除

    GEN 允许使用带有内联条件的主键删除对象,它适用于数字

    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 将执行批量删除,删除所有匹配记录

    e := query.Email

    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!

    -

    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.

    -
    // 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;
    +

    软删除

    如果你的模型包含了 gorm.DeletedAt字段(在gorm.Model中),那么该模型将会自动获得软删除的能力!

    +

    当调用Delete时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt设置为当前时间,之后普通查询方法将无法查找到此条记录。

    +
    // 批量删除
    u.WithContext(ctx).Where(u.Age.Eq(20)).Delete()
    // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

    // 查询时将忽略被软删除的记录
    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,你也可以这样启用软删除特性:

    type User struct {
    ID int
    Deleted gorm.DeletedAt
    Name string
    }
    -

    Find soft deleted records

    You can find soft deleted records with Unscoped

    +

    查找被软删除的记录

    你可以使用 Unscoped 找到被软删除的记录

    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来永久删除匹配的记录

    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.

    +

    删除关联

    Remove the relationship between source & arguments if exists, only delete the reference, won’t delete those objects from DB.

    u := query.User

    u.Languages.Model(&user).Delete(&languageZH, &languageEN)

    u.Languages.Model(&user).Delete([]*Language{&languageZH, &languageEN}...)
    -

    Delete with Select

    You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for example:

    -
    u := query.User

    // delete user's account when deleting user
    u.Select(u.Account).Delete(&user)

    // delete user's Orders, CreditCards relations when deleting user
    db.Select(u.Orders.Field(), u.CreditCards.Field()).Delete(&user)

    // delete user's has one/many/many2many relations when deleting user
    db.Select(field.AssociationsFields).Delete(&user)
    +

    带 Select 的删除

    你可以在删除记录时通过 Select 来删除具有 has one、has many、many2many 关系的记录,例如:

    +
    u := query.User

    // 删除 user 时,也删除 user 关联的 account 数据
    u.Select(u.Account).Delete(&user)

    // 删除 user 时,也删除 user 关联的 Orders, CreditCards 数据
    db.Select(u.Orders.Field(), u.CreditCards.Field()).Delete(&user)

    // 删除 user 时,也删除 user 关联的 has one/many/many2many 关系数据
    db.Select(field.AssociationsFields).Delete(&user)
    @@ -288,7 +288,7 @@

    Gold Sponsors

    内容 -
    1. 删除记录
    2. 通过主键删除
    3. Batch Delete
    4. Soft Delete
    5. Find soft deleted records
    6. Delete permanently
      1. Delete Associations
      2. Delete with Select
    +
    1. 删除记录
    2. 通过主键删除
    3. 批量删除
    4. 软删除
    5. 查找被软删除的记录
    6. 永久删除
      1. 删除关联
      2. 带 Select 的删除
    diff --git a/zh_CN/gen/dynamic_sql.html b/zh_CN/gen/dynamic_sql.html index e8dd2342828..6832c30d0f5 100644 --- a/zh_CN/gen/dynamic_sql.html +++ b/zh_CN/gen/dynamic_sql.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/zh_CN/gen/gen_tool.html b/zh_CN/gen/gen_tool.html index 40051e36d00..ea03946aec2 100644 --- a/zh_CN/gen/gen_tool.html +++ b/zh_CN/gen/gen_tool.html @@ -56,8 +56,8 @@ - - + + @@ -175,7 +175,7 @@

    - + diff --git a/zh_CN/gen/index.html b/zh_CN/gen/index.html index 4af32c48814..c235c7680d6 100644 --- a/zh_CN/gen/index.html +++ b/zh_CN/gen/index.html @@ -49,15 +49,15 @@ - + - + - - + + @@ -141,7 +141,7 @@

    Gen Guides

    -

    GEN Guides

    GEN: 更友好 & 更安全 GORM 代码生成。

    +

    GEN 指南

    GEN: 更友好 & 更安全 GORM 代码生成。

    概览

    • Idiomatic & Reusable API from Dynamic Raw SQL
    • 100% Type-safe DAO API without interface{}
    • @@ -150,13 +150,13 @@

      安装

      go get -u gorm.io/gen
      -

      Quick start

      It is quite straightforward to use gen for your application, here is how it works:

      -

      1. Write the configuration in golang

      +

      快速入门

      在程序中使用 gen 非常简单,具体操作如下:

      +

      1. 在 Go 中写入配置

      package main

      import "gorm.io/gen"

      // Dynamic SQL
      type Querier interface {
      // SELECT * FROM @@table WHERE name = @name{{if role !=""}} AND role = @role{{end}}
      FilterWithNameAndRole(name, role string) ([]gen.T, error)
      }

      func main() {
      g := gen.NewGenerator(gen.Config{
      OutPath: "../query",
      Mode: gen.WithoutContext|gen.WithDefaultQuery|gen.WithQueryInterface, // generate mode
      })

      // 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
      g.ApplyBasic(model.User{})

      // Generate Type Safe API with Dynamic SQL defined on Querier interface for `model.User` and `model.Company`
      g.ApplyInterface(func(Querier){}, model.User{}, model.Company{})

      // Generate the code
      g.Execute()
      }
      -

      2. Generate Code

      +

      2. 生成代码

      go run main.go

      -

      3. Use the generated code in your project

      +

      3. 在您的项目中使用生成的代码

      import "your_project/query"

      func main() {
      // Basic DAO API
      user, err := query.User.Where(u.Name.Eq("modi")).First()

      // Dynamic SQL API
      users, err := query.User.FilterWithNameAndRole("modi", "admin")
      }
    @@ -164,7 +164,7 @@

    - + @@ -278,7 +278,7 @@

    Gold Sponsors

    内容 -
    1. GEN Guides
    2. 概览
    3. 安装
    4. Quick start
    +
    1. GEN 指南
    2. 概览
    3. 安装
    4. 快速入门
    diff --git a/zh_CN/gen/query.html b/zh_CN/gen/query.html index 8711a6ae930..6275749123d 100644 --- a/zh_CN/gen/query.html +++ b/zh_CN/gen/query.html @@ -56,8 +56,8 @@ - - + + @@ -266,14 +266,14 @@

    int/uint/float Fields

    // int field
    f := field.NewInt("user", "id")
    // `user`.`id` = 123
    f.Eq(123)
    // `user`.`id` DESC
    f.Desc()
    // `user`.`id` AS `user_id`
    f.As("user_id")
    // COUNT(`user`.`id`)
    f.Count()
    // SUM(`user`.`id`)
    f.Sum()
    // SUM(`user`.`id`) > 123
    f.Sum().Gt(123)
    // ((`user`.`id`+1)*2)/3
    f.Add(1).Mul(2).Div(3),
    // `user`.`id` <<< 3
    f.LeftShift(3)
    -

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `uesr`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")
    +

    String Fields

    name := field.NewString("user", "name")
    // `user`.`name` = "modi"
    name.Eq("modi")
    // `user`.`name` LIKE %modi%
    name.Like("%modi%")
    // `user`.`name` REGEXP .*
    name.Regexp(".*")
    // `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
    name.FindInSet("modi,jinzhu,zhangqiang")
    // `user`.`name` CONCAT("[",name,"]")
    name.Concat("[", "]")

    Time Fields

    birth := field.NewString("user", "birth")
    // `user`.`birth` = ? (now)
    birth.Eq(time.Now())
    // DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
    birth.Add(time.Duration(time.Hour).Microseconds())
    // DATE_FORMAT(`user`.`birth`, "%W %M %Y")
    birth.DateFormat("%W %M %Y")

    Bool Fields

    active := field.NewBool("user", "active")
    // `user`.`active` = TRUE
    active.Is(true)
    // NOT `user`.`active`
    active.Not()
    // `user`.`active` AND TRUE
    active.And(true)

    子查询

    子查询可以嵌套在查询中,GEN 可以在使用 Dao 对象作为参数时生成子查询

    -
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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()
    // 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
    +
    o := query.Order
    u := query.User

    orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

    subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
    users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "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).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

    From 子查询

    GORM 允许您在 Table 方法中使用子查询作为表查询,例如:

    u := query.User
    p := query.Pet

    users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
    // SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

    subQuery1 := u.WithContext(ctx).Select(u.Name)
    subQuery2 := p.WithContext(ctx).Select(p.Name)
    users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
    db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
    // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    @@ -312,7 +312,7 @@

    - + diff --git a/zh_CN/gen/rawsql_driver.html b/zh_CN/gen/rawsql_driver.html index 4936254af21..ce542805954 100644 --- a/zh_CN/gen/rawsql_driver.html +++ b/zh_CN/gen/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/zh_CN/gen/sql_annotation.html b/zh_CN/gen/sql_annotation.html index c9a9bfa561d..0f7a9ecd9a9 100644 --- a/zh_CN/gen/sql_annotation.html +++ b/zh_CN/gen/sql_annotation.html @@ -56,8 +56,8 @@ - - + + @@ -189,15 +189,15 @@

    @@table -escaped & quoted table name +被转义并引用的表名 @@<name> -escaped & quoted table/column name from params +被转义并引用为表或列名称的参数名 @<name> -SQL query params from params +用于SQL查询语句参数的参数名

    e.g:

    @@ -213,7 +213,7 @@

    set
  • for
  • -

    if/else

    The if/else expression allows to use golang syntax as condition, it can be written like:

    +

    if/else

    if/else表达式中可以使用golang句法的条件语句, 可以像这样写为:

    {{if cond1}}
    // do something here
    {{else if cond2}}
    // do something here
    {{else}}
    // do something here
    {{end}}

    例如:

    @@ -225,36 +225,36 @@

    如何使用它:

    query.User.QueryWith(&User{Name: "zhangqiang"})
    // SELECT * FROM users WHERE username="zhangqiang"
    -

    where

    The where expression make you write the WHERE clause for the SQL query easier, let take a simple case as example:

    +

    where

    where表达式可以让你更轻松的写出SQL查询语句中的WHERE子句, 下例为一个简单示例:

    type Querier interface {
    // SELECT * FROM @@table
    // {{where}}
    // id=@id
    // {{end}}
    Query(id int) gen.T
    }

    使用生成的代码,您可以使用它:

    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, ,

    type Querier interface {
    // SELECT * FROM @@table
    // {{where}}
    // {{if !start.IsZero()}}
    // created_time > @start
    // {{end}}
    // {{if !end.IsZero()}}
    // AND created_time < @end
    // {{end}}
    // {{end}}
    FilterWithTime(start, end time.Time) ([]gen.T, error)
    }
    -

    The generated code can be used like:

    +

    生成的代码可以像这样使用:

    var (
    since = time.Date(2022, 10, 1, 0, 0, 0, 0, time.UTC)
    end = time.Date(2022, 10, 10, 0, 0, 0, 0, time.UTC)
    zero = time.Time{}
    )

    query.User.FilterWithTime(since, end)
    // SELECT * FROM `users` WHERE created_time > "2022-10-01" AND created_time < "2022-10-10"

    query.User.FilterWithTime(since, zero)
    // SELECT * FROM `users` WHERE created_time > "2022-10-01"

    query.User.FilterWithTime(zero, end)
    // SELECT * FROM `users` WHERE created_time < "2022-10-10"

    query.User.FilterWithTime(zero, zero)
    // SELECT * FROM `users`
    -

    set

    The set expression used to generate the SET clause for the SQL query, it will trim uncessary , automatically, for example:

    +

    set

    set表达式用来生成SQL语句中的SET子句, 它会自动修剪不必要的,, 比如:

    // UPDATE @@table
    // {{set}}
    // {{if user.Name != ""}} username=@user.Name, {{end}}
    // {{if user.Age > 0}} age=@user.Age, {{end}}
    // {{if user.Age >= 18}} is_adult=1 {{else}} is_adult=0 {{end}}
    // {{end}}
    // WHERE id=@id
    Update(user gen.T, id int) (gen.RowsAffected, error)
    -

    The generated code can be used like:

    +

    生成的代码可以像这样使用:

    query.User.Update(User{Name: "jinzhu", Age: 18}, 10)
    // UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

    query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
    // UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

    query.User.Update(User{Age: 0}, 10)
    // UPDATE users SET is_adult=0 WHERE id=10
    -

    for

    The for expression iterates over a slice to generate the SQL, let’s explain by example

    -
    // 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)
    +

    for

    for表达式遍历切片生成SQL,用下例说明

    +

    -

    Usage:

    -
    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","%"))
    +

    用法:

    +

    diff --git a/zh_CN/gen/transaction.html b/zh_CN/gen/transaction.html index a1adce1d277..b3e58d6d017 100644 --- a/zh_CN/gen/transaction.html +++ b/zh_CN/gen/transaction.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - + diff --git a/zh_CN/gen/update.html b/zh_CN/gen/update.html index 1d262e2e887..3c65c3e87fa 100644 --- a/zh_CN/gen/update.html +++ b/zh_CN/gen/update.html @@ -56,8 +56,8 @@ - - + + @@ -164,7 +164,7 @@

    - + diff --git a/zh_CN/gorm.html b/zh_CN/gorm.html index bbdbd93119f..9b90939d5a9 100644 --- a/zh_CN/gorm.html +++ b/zh_CN/gorm.html @@ -56,8 +56,8 @@ - - + + @@ -161,7 +161,7 @@

    - +
    diff --git a/zh_CN/gormx.html b/zh_CN/gormx.html index 01a2d61262b..4ebd590c795 100644 --- a/zh_CN/gormx.html +++ b/zh_CN/gormx.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/zh_CN/hints.html b/zh_CN/hints.html index a51dccbf787..f60175a99c9 100644 --- a/zh_CN/hints.html +++ b/zh_CN/hints.html @@ -56,8 +56,8 @@ - - + + @@ -163,7 +163,7 @@

    - +
    diff --git a/zh_CN/index.html b/zh_CN/index.html index 4cd325aed47..efad74d2ccf 100644 --- a/zh_CN/index.html +++ b/zh_CN/index.html @@ -56,8 +56,8 @@ - - + + diff --git a/zh_CN/rawsql.html b/zh_CN/rawsql.html index f34d9f1b31e..4fff3640a07 100644 --- a/zh_CN/rawsql.html +++ b/zh_CN/rawsql.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/zh_CN/rawsql_driver.html b/zh_CN/rawsql_driver.html index 209f2b52231..84052a4dd22 100644 --- a/zh_CN/rawsql_driver.html +++ b/zh_CN/rawsql_driver.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/zh_CN/sharding.html b/zh_CN/sharding.html index a04ae358bfb..bb4d4af8762 100644 --- a/zh_CN/sharding.html +++ b/zh_CN/sharding.html @@ -56,8 +56,8 @@ - - + + @@ -162,7 +162,7 @@

    - +
    diff --git a/zh_CN/stats.html b/zh_CN/stats.html index 3aae3501ab1..5f9a3614d73 100644 --- a/zh_CN/stats.html +++ b/zh_CN/stats.html @@ -56,8 +56,8 @@ - - + + @@ -182,7 +182,7 @@

    - +