Allow table-level check constraints in the Intermediate DB
In https://github.com/discourse/discourse/pull/34339, I’ve worked on
distinguishing between `user_custom_fields` tied to `user_fields` and
arbitrary `user_custom_fields` entries.
To support this, I’ve made both `field_id` and `name` nullable, which
requires a table-level constraint to ensure each entry has either a
`field_id` (referencing `user_fields`) or an arbitrary `name` for the
value.
This first pass supports only named table-level `CHECK` constraints.
## Usage
```yaml
user_custom_fields:
columns:
exclude:
- "id"
modify:
- name: "name"
nullable: true
add:
- name: "field_id"
datatype: numeric
- name: "is_multiselect_field"
datatype: boolean
indexes:
# ...
constraints:
- name: "require_field_id_or_name"
condition: "field_id IS NOT NULL OR name IS NOT NULL"
- name: "disallow_both_field_id_and_name"
type: check # default, only `check` supported for now
condition: "NOT (field_id IS NOT NULL AND name IS NOT NULL)"
```
```sql
CREATE TABLE user_custom_fields
(
created_at DATETIME,
field_id NUMERIC,
is_multiselect_field BOOLEAN,
name TEXT,
user_id NUMERIC NOT NULL,
value TEXT,
CONSTRAINT require_field_id_or_name CHECK (field_id IS NOT NULL OR name IS NOT NULL),
CONSTRAINT disallow_both_field_id_and_name CHECK (NOT (field_id IS NOT NULL AND name IS NOT NULL))
);
```
This adds converter(Discourse-only, for now) and importer steps for
`user_fields`
The change also includes additional steps for `user_field_options`
---------
Co-authored-by: Gerhard Schlager <gerhard.schlager@discourse.org>
Before:
```sql
CREATE TABLE muted_users
(
created_at DATETIME,
muted_user_id NUMERIC NOT NULL,
user_id NUMERIC NOT NULL
);
CREATE TABLE user_custom_fields
(
created_at DATETIME,
field_id NUMERIC,
is_multiselect_field BOOLEAN,
name TEXT NOT NULL,
user_id NUMERIC NOT NULL,
value TEXT
);
CREATE UNIQUE INDEX ucf_multiselect_by_field_id_index ON user_custom_fields (user_id, field_id, value) WHERE is_multiselect_field = TRUE AND field_id IS NOT NULL;
CREATE UNIQUE INDEX ucf_not_multiselect_by_field_id_index ON user_custom_fields (user_id, field_id) WHERE is_multiselect_field = FALSE AND field_id IS NOT NULL;
CREATE UNIQUE INDEX ucf_multiselect_by_name_index ON user_custom_fields (user_id, name, value) WHERE is_multiselect_field = TRUE;
CREATE UNIQUE INDEX ucf_not_multiselect_by_name_index ON user_custom_fields (user_id, name) WHERE is_multiselect_field = FALSE;
CREATE TABLE user_emails
(
email TEXT NOT NULL,
user_id NUMERIC NOT NULL,
created_at DATETIME,
"primary" BOOLEAN,
PRIMARY KEY (user_id, email)
);
```
After:
```sql
CREATE TABLE muted_users
(
created_at DATETIME,
muted_user_id NUMERIC NOT NULL,
user_id NUMERIC NOT NULL
);
CREATE TABLE user_custom_fields
(
created_at DATETIME,
field_id NUMERIC,
is_multiselect_field BOOLEAN,
name TEXT NOT NULL,
user_id NUMERIC NOT NULL,
value TEXT
);
CREATE UNIQUE INDEX ucf_multiselect_by_field_id_index ON user_custom_fields (user_id, field_id, value) WHERE is_multiselect_field = TRUE AND field_id IS NOT NULL;
CREATE UNIQUE INDEX ucf_not_multiselect_by_field_id_index ON user_custom_fields (user_id, field_id) WHERE is_multiselect_field = FALSE AND field_id IS NOT NULL;
CREATE UNIQUE INDEX ucf_multiselect_by_name_index ON user_custom_fields (user_id, name, value) WHERE is_multiselect_field = TRUE;
CREATE UNIQUE INDEX ucf_not_multiselect_by_name_index ON user_custom_fields (user_id, name) WHERE is_multiselect_field = FALSE;
CREATE TABLE user_emails
(
email TEXT NOT NULL,
user_id NUMERIC NOT NULL,
created_at DATETIME,
"primary" BOOLEAN,
PRIMARY KEY (user_id, email)
);
```
This commit responds to feedback in the Discourse Meta discussion
https://meta.discourse.org/t/monospace-font-in-the-markdown-only-editor/359936
This change introduces a user preference that allows users to choose
whether the Markdown editor uses a monospace font. The default setting
is `true` for new sites, but set to `false` for existing sites to avoid
disrupting current users' experiences.
Admins can change the `default_other_enable_markdown_monospace_font`
site setting to manage this for all users.
There are concrete implementations for a simple set, a key-value store,
and nested sets with 2 or 3 keys. The API stays the same for all
implementations and the performances is more or less the same as without
the wrapper (at least with YJIT enabled).
Update `intermedate_db.yml` config with newly promoted core plugins
names and tables to the intermediate DB config.
It also updates the existing `user_options` table with the newly
introduced `notification_level_when_assigned` column.
This also enhances the converter step, which stores the Gravatar if it is
set and used. It also fixes the import of uploads which didn't use the
correct user ID.
---------
Co-authored-by: Selase Krakani <849886+s3lase@users.noreply.github.com>
Currently, the first two rows returned by `DiscourseDB#query_array` are
silently dropped during the column size check in
`DiscourseDB#load_mapping`. This happens because the rows object, while
an enumerator, isn't fully compliant, it doesn't rewind during
introspection. As a result, calls like `#first`, `#peek`, or `#any?`
advance the iterator.
Ideally, we’d fix this by updating the `query_array` enumeration
implementation. However, customizing the enumerator to be fully
compliant would likely introduce unnecessary perf overhead for all use
cases. So, this fix works around that limitation by building the map a
little differently.
Currently, if a batch "copy" of an import step results in all rows being
skipped, the `after_commit_of_skipped_rows` callback is never triggered.
This happens because the callback is nested inside a block that only
runs when at least one row is inserted.
This change ensures the DB copy operation returns both inserted and
skipped rows, allowing the caller to respond appropriately in either
case.
---------
Co-authored-by: Gerhard Schlager <gerhard.schlager@discourse.org>