Skip to content

feat(gentool): add generated-column readonly detection with fieldReadonly option - #1455

Open
mentatxx wants to merge 1 commit into
go-gorm:masterfrom
mentatxx:feat/read-only-columns
Open

mentatxx wants to merge 1 commit into
go-gorm:masterfrom
mentatxx:feat/read-only-columns

Conversation

@mentatxx

@mentatxx mentatxx commented Apr 6, 2026 •

Copy link
Copy Markdown

Detect read-only fields (computed/generated) and mark them with GORM-tag

@propel-code-bot

Copy link
Copy Markdown

Add fieldReadonly support to detect generated DB columns and emit GORM readonly tags

This PR introduces a new generator capability to mark generated/computed database columns as readonly in generated models via a new fieldReadonly option. The change wires fieldReadonly through public generator config, internal model config, CLI flags, and YAML config, then applies dialect-specific schema inspection during table introspection to set column-level readonly metadata.

A new detection pipeline in internal/generate/readonly_columns.go supports mysql, postgres, sqlite, and clickhouse by querying system metadata sources, then updates Column.ReadOnly. Tag generation now appends GORM readonly marker -> (except for primary keys), with ordering handled in tag priority config. Documentation and tests were updated to cover behavior and new tool options.

Key Changes

• Added new config flag FieldReadonly to config.go and internal/model/config.go.
• Propagated FieldReadonly through generator model config creation in generator.go.
• Added fieldReadonly CLI flag and YAML support in tools/gentool/gentool.go and tools/gentool/gen.yml.
• Implemented readonly detection logic in new file internal/generate/readonly_columns.go with per-dialect handlers: getMySQLGeneratedColumns, getPostgresGeneratedColumns, getSQLiteGeneratedColumns, getClickHouseGeneratedColumns.
• Hooked detection into model metadata build flow in internal/generate/export.go via markReadOnlyColumns(...) when conf.FieldReadonly is enabled.
• Extended internal/model/tbl_column.go with Column.ReadOnly and gorm tag emission field.TagKeyGormReadonly when non-PK readonly columns are detected.
• Added readonly tag key constant TagKeyGormReadonly and ordering priority in field/tag.go.
• Added unit tests in internal/model/tbl_column_test.go validating readonly tag generation and PK exclusion.
• Updated tools/gentool/README.md and tools/gentool/README.ZH_CN.md with fieldReadonly usage and dialect details.

Affected Areas

• internal/generate table introspection/export pipeline
• internal/model column model and gorm tag construction
• field gorm tag key definitions and ordering
• gen.Config public API surface
• tools/gentool CLI flags, YAML config parsing, sample config, and docs
• Unit tests in internal/model

This summary was automatically generated by @propel-code-bot

@propel-code-bot propel-code-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR is solid overall with minor maintainability improvements needed around error handling and query clarity.

Status: Minor Suggestions | Risk: Low

Issues Identified & Suggestions
  • Preserve/log primary MySQL query errors before fallback retries: internal/generate/readonly_columns.go
  • Remove invalid PostgreSQL is_generated='YES' condition to reduce confusion: internal/generate/readonly_columns.go
Review Details

📁 12 files reviewed | 💬 2 comments

👍 / 👎 individual comments to help improve reviews for you

Comment on lines +59 to +70
err := db.Raw(
`SELECT COLUMN_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = ?
AND TABLE_NAME = ?
AND (
UPPER(COALESCE(GENERATION_TYPE, '')) IN ('VIRTUAL', 'STORED')
OR UPPER(COALESCE(EXTRA, '')) LIKE '%GENERATED%'
)`,
currentDB, tableName,
).Scan(&rows).Error
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended

[Maintainability] The MySQL fallback query silently swallows the original error from the first query attempt. If the primary query fails for a reason other than the GENERATION_TYPE column not existing (e.g., a permissions error, network timeout, or malformed DSN), the code retries with a weaker query and discards the root cause. This makes the failure mode invisible and hard to debug in production.

Consider logging the first error before retrying, or propagating it unless it matches a known "column not found" error condition. At minimum, the original error should not be silently dropped.

Context for Agents
The MySQL fallback query silently swallows the original error from the first query attempt. If the primary query fails for a reason other than the `GENERATION_TYPE` column not existing (e.g., a permissions error, network timeout, or malformed DSN), the code retries with a weaker query and discards the root cause. This makes the failure mode invisible and hard to debug in production.

Consider logging the first error before retrying, or propagating it unless it matches a known "column not found" error condition. At minimum, the original error should not be silently dropped.

File: internal/generate/readonly_columns.go
Line: 70

FROM information_schema.columns
WHERE table_schema = ?
AND table_name = ?
AND UPPER(COALESCE(is_generated, '')) IN ('ALWAYS', 'YES')`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended

[Maintainability] The PostgreSQL query filters on UPPER(COALESCE(is_generated, '')) IN ('ALWAYS', 'YES'). Per the SQL standard and PostgreSQL documentation, information_schema.columns.is_generated only takes the value 'ALWAYS' for generated columns — 'YES' is not a valid value for this column in any PostgreSQL version. Including 'YES' is either dead code or based on a misreading of the spec. Verify the intended behavior; if 'YES' is not needed, remove it to avoid confusion about what is being matched.

Context for Agents
The PostgreSQL query filters on `UPPER(COALESCE(is_generated, '')) IN ('ALWAYS', 'YES')`. Per the SQL standard and PostgreSQL documentation, `information_schema.columns.is_generated` only takes the value `'ALWAYS'` for generated columns — `'YES'` is not a valid value for this column in any PostgreSQL version. Including `'YES'` is either dead code or based on a misreading of the spec. Verify the intended behavior; if `'YES'` is not needed, remove it to avoid confusion about what is being matched.

File: internal/generate/readonly_columns.go
Line: 110

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant