mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-07 12:40:34 +08:00
Some checks failed
Tests / core system (push) Has been cancelled
Tests / plugins system (push) Has been cancelled
Licenses / run (push) Has been cancelled
Linting / run (push) Has been cancelled
Migration Tests / Tests (push) Has been cancelled
Publish Assets / publish-assets (push) Has been cancelled
Tests / core backend (push) Has been cancelled
Tests / plugins backend (push) Has been cancelled
Tests / core frontend (Chrome) (push) Has been cancelled
Tests / plugins frontend (push) Has been cancelled
Tests / themes frontend (push) Has been cancelled
Tests / themes system (push) Has been cancelled
Tests / core frontend (Firefox ESR) (push) Has been cancelled
Tests / core frontend (Firefox Evergreen) (push) Has been cancelled
Tests / chat system (push) Has been cancelled
Tests / merge (push) Has been cancelled
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)) ); ```
176 lines
2.6 KiB
Ruby
176 lines
2.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Migrations::Database
|
|
module Schema
|
|
Table =
|
|
Data.define(:name, :columns, :indexes, :primary_key_column_names, :constraints) do
|
|
def sorted_columns
|
|
columns.sort_by { |c| [c.is_primary_key ? 0 : 1, c.name] }
|
|
end
|
|
end
|
|
Column = Data.define(:name, :datatype, :nullable, :max_length, :is_primary_key)
|
|
Index = Data.define(:name, :column_names, :unique, :condition)
|
|
Constraint = Data.define(:name, :type, :condition)
|
|
|
|
class ConfigError < StandardError
|
|
end
|
|
|
|
SQLITE_KEYWORDS = %w[
|
|
abort
|
|
action
|
|
add
|
|
after
|
|
all
|
|
alter
|
|
always
|
|
analyze
|
|
and
|
|
as
|
|
asc
|
|
attach
|
|
autoincrement
|
|
before
|
|
begin
|
|
between
|
|
by
|
|
cascade
|
|
case
|
|
cast
|
|
check
|
|
collate
|
|
column
|
|
commit
|
|
conflict
|
|
constraint
|
|
create
|
|
cross
|
|
current
|
|
current_date
|
|
current_time
|
|
current_timestamp
|
|
database
|
|
default
|
|
deferrable
|
|
deferred
|
|
delete
|
|
desc
|
|
detach
|
|
distinct
|
|
do
|
|
drop
|
|
each
|
|
else
|
|
end
|
|
escape
|
|
except
|
|
exclude
|
|
exclusive
|
|
exists
|
|
explain
|
|
fail
|
|
filter
|
|
first
|
|
following
|
|
for
|
|
foreign
|
|
from
|
|
full
|
|
generated
|
|
glob
|
|
group
|
|
groups
|
|
having
|
|
if
|
|
ignore
|
|
immediate
|
|
in
|
|
index
|
|
indexed
|
|
initially
|
|
inner
|
|
insert
|
|
instead
|
|
intersect
|
|
into
|
|
is
|
|
isnull
|
|
join
|
|
key
|
|
last
|
|
left
|
|
like
|
|
limit
|
|
match
|
|
materialized
|
|
natural
|
|
no
|
|
not
|
|
nothing
|
|
notnull
|
|
null
|
|
nulls
|
|
of
|
|
offset
|
|
on
|
|
or
|
|
order
|
|
others
|
|
outer
|
|
over
|
|
partition
|
|
plan
|
|
pragma
|
|
preceding
|
|
primary
|
|
query
|
|
raise
|
|
range
|
|
recursive
|
|
references
|
|
regexp
|
|
reindex
|
|
release
|
|
rename
|
|
replace
|
|
restrict
|
|
returning
|
|
right
|
|
rollback
|
|
row
|
|
rows
|
|
savepoint
|
|
select
|
|
set
|
|
table
|
|
temp
|
|
temporary
|
|
then
|
|
ties
|
|
to
|
|
transaction
|
|
trigger
|
|
unbounded
|
|
union
|
|
unique
|
|
update
|
|
using
|
|
vacuum
|
|
values
|
|
view
|
|
virtual
|
|
when
|
|
where
|
|
window
|
|
with
|
|
without
|
|
]
|
|
|
|
def self.escape_identifier(identifier)
|
|
if SQLITE_KEYWORDS.include?(identifier)
|
|
%Q("#{identifier}")
|
|
else
|
|
identifier
|
|
end
|
|
end
|
|
end
|
|
end
|