DEV: Support nullable column property modification (#32978)

By default, rails makes timestamp columns (`created_at` and
`updated_at`) non-nullable, we also have some required core and plugins
columns we wouldn't necessarily want to enforce in the intermediate DB
schema. It'll be better to set the default values for these during
import instead of enforcing these at the converter level.

This change adds support for globally modifying a column’s `nullable`
state, defaulting all `created_at` columns to be `nullable` while
allowing for table level overrides.

---------

Co-authored-by: Gerhard Schlager <gerhard.schlager@discourse.org>
This commit is contained in:
Selase Krakani
2025-06-01 22:39:18 +00:00
committed by GitHub
parent fc9946f595
commit c31035caf5
7 changed files with 47 additions and 7 deletions

View File

@ -41,6 +41,8 @@ schema:
- name: "id" - name: "id"
datatype: numeric datatype: numeric
rename_to: "original_id" rename_to: "original_id"
- name: "created_at"
nullable: true
- name_regex: ".*upload.*_id$" - name_regex: ".*upload.*_id$"
datatype: text datatype: text
- name_regex: ".*_id$" - name_regex: ".*_id$"

View File

@ -83,12 +83,26 @@
}, },
"datatype": { "datatype": {
"$ref": "#/$defs/datatypes" "$ref": "#/$defs/datatypes"
},
"nullable": {
"type": "boolean"
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"name", "name"
"datatype" ],
"anyOf": [
{
"required": [
"datatype"
]
},
{
"required": [
"nullable"
]
}
] ]
} }
}, },
@ -191,6 +205,9 @@
}, },
"rename_to": { "rename_to": {
"type": "string" "type": "string"
},
"nullable": {
"type": "boolean"
} }
}, },
"additionalProperties": false, "additionalProperties": false,
@ -204,6 +221,11 @@
"required": [ "required": [
"rename_to" "rename_to"
] ]
},
{
"required": [
"nullable"
]
} }
], ],
"oneOf": [ "oneOf": [

View File

@ -5,7 +5,7 @@
CREATE TABLE user_emails CREATE TABLE user_emails
( (
email TEXT NOT NULL PRIMARY KEY, email TEXT NOT NULL PRIMARY KEY,
created_at DATETIME NOT NULL, created_at DATETIME,
"primary" BOOLEAN, "primary" BOOLEAN,
user_id NUMERIC NOT NULL user_id NUMERIC NOT NULL
); );
@ -80,7 +80,7 @@ CREATE TABLE users
approved BOOLEAN, approved BOOLEAN,
approved_at DATETIME, approved_at DATETIME,
approved_by_id NUMERIC, approved_by_id NUMERIC,
created_at DATETIME NOT NULL, created_at DATETIME,
date_of_birth DATE, date_of_birth DATE,
first_seen_at DATETIME, first_seen_at DATETIME,
flair_group_id NUMERIC, flair_group_id NUMERIC,

View File

@ -48,7 +48,7 @@ module Migrations::Database::IntermediateDB
approved: nil, approved: nil,
approved_at: nil, approved_at: nil,
approved_by_id: nil, approved_by_id: nil,
created_at:, created_at: nil,
date_of_birth: nil, date_of_birth: nil,
first_seen_at: nil, first_seen_at: nil,
flair_group_id: nil, flair_group_id: nil,

View File

@ -18,7 +18,7 @@ module Migrations::Database::IntermediateDB
) )
SQL SQL
def self.create(email:, created_at:, primary: nil, user_id:) def self.create(email:, created_at: nil, primary: nil, user_id:)
::Migrations::Database::IntermediateDB.insert( ::Migrations::Database::IntermediateDB.insert(
SQL, SQL,
email, email,

View File

@ -27,6 +27,12 @@ module Migrations::Database::Schema
end end
end end
def modified_nullable(column_name)
if (modified_column = find_modified_column(column_name))
modified_column[:nullable]
end
end
private private
def find_modified_column(column_name) def find_modified_column(column_name)

View File

@ -45,7 +45,7 @@ module Migrations::Database::Schema
Column.new( Column.new(
name: name_for(column), name: name_for(column),
datatype: datatype_for(column), datatype: datatype_for(column),
nullable: column.null || column.default, nullable: nullable_for(column, config),
max_length: column.type == :text ? column.limit : nil, max_length: column.type == :text ? column.limit : nil,
is_primary_key: primary_key_column_names.include?(column.name), is_primary_key: primary_key_column_names.include?(column.name),
) )
@ -102,6 +102,16 @@ module Migrations::Database::Schema
end end
end end
def nullable_for(column, config)
modified_column = config.dig(:columns, :modify)&.find { |col| col[:name] == column.name }
return modified_column[:nullable] if modified_column&.key?(:nullable)
global_nullable = @global.modified_nullable(column.name)
return global_nullable unless global_nullable.nil?
column.null || column.default.present?
end
def indexes(config) def indexes(config)
config[:indexes]&.map do |index| config[:indexes]&.map do |index|
Index.new( Index.new(