/
schema_statements.rb
174 lines (147 loc) · 6.2 KB
/
schema_statements.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# frozen_string_literal: true
module ActiveRecord
module ConnectionAdapters
module SQLite3
module SchemaStatements # :nodoc:
# Returns an array of indexes for the given table.
def indexes(table_name)
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").filter_map do |row|
# Indexes SQLite creates implicitly for internal use start with "sqlite_".
# See https://www.sqlite.org/fileformat2.html#intschema
next if row["name"].start_with?("sqlite_")
index_sql = query_value(<<~SQL, "SCHEMA")
SELECT sql
FROM sqlite_master
WHERE name = #{quote(row['name'])} AND type = 'index'
UNION ALL
SELECT sql
FROM sqlite_temp_master
WHERE name = #{quote(row['name'])} AND type = 'index'
SQL
/\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?(?:\s*\/\*.*\*\/)?\z/i =~ index_sql
columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
col["name"]
end
orders = {}
if columns.any?(&:nil?) # index created with an expression
columns = expressions
else
# Add info on sort order for columns (only desc order is explicitly specified,
# asc is the default)
if index_sql # index_sql can be null in case of primary key indexes
index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
orders[order_column] = :desc
}
end
end
IndexDefinition.new(
table_name,
row["name"],
row["unique"] != 0,
columns,
where: where,
orders: orders
)
end
end
def add_foreign_key(from_table, to_table, **options)
alter_table(from_table) do |definition|
to_table = strip_table_name_prefix_and_suffix(to_table)
definition.foreign_key(to_table, **options)
end
end
def remove_foreign_key(from_table, to_table = nil, **options)
return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
to_table ||= options[:to_table]
options = options.except(:name, :to_table, :validate)
foreign_keys = foreign_keys(from_table)
fkey = foreign_keys.detect do |fk|
table = to_table || begin
table = options[:column].to_s.delete_suffix("_id")
Base.pluralize_table_names ? table.pluralize : table
end
table = strip_table_name_prefix_and_suffix(table)
fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
foreign_keys.delete(fkey)
alter_table(from_table, foreign_keys)
end
def check_constraints(table_name)
table_sql = query_value(<<-SQL, "SCHEMA")
SELECT sql
FROM sqlite_master
WHERE name = #{quote(table_name)} AND type = 'table'
UNION ALL
SELECT sql
FROM sqlite_temp_master
WHERE name = #{quote(table_name)} AND type = 'table'
SQL
table_sql.to_s.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
CheckConstraintDefinition.new(table_name, expression, name: name)
end
end
def add_check_constraint(table_name, expression, **options)
alter_table(table_name) do |definition|
definition.check_constraint(expression, **options)
end
end
def remove_check_constraint(table_name, expression = nil, **options)
return if options[:if_exists] && !check_constraint_exists?(table_name, **options)
check_constraints = check_constraints(table_name)
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
alter_table(table_name, foreign_keys(table_name), check_constraints)
end
def create_schema_dumper(options)
SQLite3::SchemaDumper.create(self, options)
end
def schema_creation # :nodoc
SQLite3::SchemaCreation.new(self)
end
private
def create_table_definition(name, **options)
SQLite3::TableDefinition.new(self, name, **options)
end
def validate_index_length!(table_name, new_name, internal = false)
super unless internal
end
def new_column_from_field(table_name, field)
default = field["dflt_value"]
type_metadata = fetch_type_metadata(field["type"])
default_value = extract_value_from_default(default)
default_function = extract_default_function(default_value, default)
Column.new(
field["name"],
default_value,
type_metadata,
field["notnull"].to_i == 0,
default_function,
collation: field["collation"]
)
end
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
scope[:type] ||= "'table','view'"
sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
sql << " AND name = #{scope[:name]}" if scope[:name]
sql << " AND type IN (#{scope[:type]})"
sql
end
def quoted_scope(name = nil, type: nil)
type = \
case type
when "BASE TABLE"
"'table'"
when "VIEW"
"'view'"
end
scope = {}
scope[:name] = quote(name) if name
scope[:type] = type if type
scope
end
end
end
end
end