/
mysql.rb
259 lines (231 loc) · 8.85 KB
/
mysql.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
module Sequel
module MySQL
# Methods shared by Database instances that connect to MySQL,
# currently supported by the native and JDBC adapters.
module DatabaseMethods
AUTO_INCREMENT = 'AUTO_INCREMENT'.freeze
NOT_NULL = Sequel::Schema::SQL::NOT_NULL
NULL = Sequel::Schema::SQL::NULL
PRIMARY_KEY = Sequel::Schema::SQL::PRIMARY_KEY
SQL_BEGIN = Sequel::Database::SQL_BEGIN
SQL_COMMIT = Sequel::Database::SQL_COMMIT
SQL_ROLLBACK = Sequel::Database::SQL_ROLLBACK
TYPES = Sequel::Schema::SQL::TYPES
UNIQUE = Sequel::Schema::SQL::UNIQUE
UNSIGNED = Sequel::Schema::SQL::UNSIGNED
# Use MySQL specific syntax for rename column, set column type, and
# drop index cases.
def alter_table_sql(table, op)
quoted_table = quote_identifier(table)
quoted_name = quote_identifier(op[:name]) if op[:name]
case op[:op]
when :rename_column
"ALTER TABLE #{quoted_table} CHANGE COLUMN #{quoted_name} #{quote_identifier(op[:new_name])} #{type_literal(op)}"
when :set_column_type
"ALTER TABLE #{quoted_table} CHANGE COLUMN #{quoted_name} #{quoted_name} #{type_literal(op)}"
when :drop_index
"#{drop_index_sql(table, op)} ON #{quoted_table}"
else
super(table, op)
end
end
# Use MySQL specific AUTO_INCREMENT text.
def auto_increment_sql
AUTO_INCREMENT
end
# Handle MySQL specific syntax for column references
def column_references_sql(column)
"#{", FOREIGN KEY (#{quote_identifier(column[:name])})" unless column[:type] == :check}#{super(column)}"
end
# Handle MySQL specific index SQL syntax
def index_definition_sql(table_name, index)
index_name = quote_identifier(index[:name] || default_index_name(table_name, index[:columns]))
index_type = case index[:type]
when :full_text
"FULLTEXT "
when :spatial
"SPATIAL "
else
using = " USING #{index[:type]}" unless index[:type] == nil
"UNIQUE " if index[:unique]
end
"CREATE #{index_type}INDEX #{index_name} ON #{quote_identifier(table_name)} #{literal(index[:columns])}#{using}"
end
# Get version of MySQL server, used for determined capabilities.
def server_version
m = /(\d+)\.(\d+)\.(\d+)/.match(get(:version[]))
@server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
end
# Return an array of symbols specifying table names in the current database.
def tables(server=nil)
self['SHOW TABLES'].server(server).map{|r| r.values.first.to_sym}
end
# Changes the database in use by issuing a USE statement. I would be
# very careful if I used this.
def use(db_name)
disconnect
@opts[:database] = db_name if self << "USE #{db_name}"
@schemas = nil
self
end
private
# Use the MySQL specific DESCRIBE syntax to get a table description.
def schema_parse_table(table_name, opts)
self["DESCRIBE ?", table_name].map do |row|
row.delete(:Extra)
row[:allow_null] = row.delete(:Null) == 'YES'
row[:default] = row.delete(:Default)
row[:primary_key] = row.delete(:Key) == 'PRI'
row[:default] = nil if row[:default].blank?
row[:db_type] = row.delete(:Type)
row[:type] = schema_column_type(row[:db_type])
[row.delete(:Field).to_sym, row]
end
end
end
# Dataset methods shared by datasets that use MySQL databases.
module DatasetMethods
BOOL_TRUE = '1'.freeze
BOOL_FALSE = '0'.freeze
COMMA_SEPARATOR = ', '.freeze
# MySQL specific syntax for LIKE/REGEXP searches, as well as
# string concatenation.
def complex_expression_sql(op, args)
case op
when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
"(#{literal(args.at(0))} #{'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)}#{[:~, :'!~', :'~*', :'!~*'].include?(op) ? 'REGEXP' : 'LIKE'} #{'BINARY ' if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)}#{literal(args.at(1))})"
when :'||'
if args.length > 1
"CONCAT(#{args.collect{|a| literal(a)}.join(', ')})"
else
literal(args.at(0))
end
else
super(op, args)
end
end
# MySQL supports ORDER and LIMIT clauses in DELETE statements.
def delete_sql(opts = nil)
sql = super
opts = opts ? @opts.merge(opts) : @opts
if order = opts[:order]
sql << " ORDER BY #{expression_list(order)}"
end
if limit = opts[:limit]
sql << " LIMIT #{limit}"
end
sql
end
# MySQL specific full text search syntax.
def full_text_search(cols, terms, opts = {})
mode = opts[:boolean] ? " IN BOOLEAN MODE" : ""
s = if Array === terms
if mode.blank?
"MATCH #{literal(Array(cols))} AGAINST #{literal(terms)}"
else
"MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)[1...-1]}#{mode})"
end
else
"MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)}#{mode})"
end
filter(s)
end
# MySQL allows HAVING clause on ungrouped datasets.
def having(*cond, &block)
@opts[:having] = {}
x = filter(*cond, &block)
end
# MySQL doesn't use the SQL standard DEFAULT VALUES.
def insert_default_values_sql
"INSERT INTO #{source_list(@opts[:from])} () VALUES ()"
end
# Transforms an CROSS JOIN to an INNER JOIN if the expr is not nil.
# Raises an error on use of :full_outer type, since MySQL doesn't support it.
def join_table(type, table, expr=nil, table_alias=nil)
type = :inner if (type == :cross) && !expr.nil?
raise(Sequel::Error, "MySQL doesn't support FULL OUTER JOIN") if type == :full_outer
super(type, table, expr, table_alias)
end
# Transforms :natural_inner to NATURAL LEFT JOIN and straight to
# STRAIGHT_JOIN.
def join_type_sql(join_type)
case join_type
when :straight then 'STRAIGHT_JOIN'
when :natural_inner then 'NATURAL LEFT JOIN'
else super
end
end
# Override the default boolean values.
def literal(v)
case v
when true
BOOL_TRUE
when false
BOOL_FALSE
else
super
end
end
# MySQL specific syntax for inserting multiple values at once.
def multi_insert_sql(columns, values)
columns = column_list(columns)
values = values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)
["INSERT INTO #{source_list(@opts[:from])} (#{columns}) VALUES #{values}"]
end
# MySQL uses the nonstandard ` (backtick) for quoting identifiers.
def quoted_identifier(c)
"`#{c}`"
end
# MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
# insert if it doesn't).
def replace_sql(*values)
from = source_list(@opts[:from])
if values.empty?
"REPLACE INTO #{from} DEFAULT VALUES"
else
values = values[0] if values.size == 1
# if hash or array with keys we need to transform the values
if @transform && (values.is_a?(Hash) || (values.is_a?(Array) && values.keys))
values = transform_save(values)
end
case values
when Array
if values.empty?
"REPLACE INTO #{from} DEFAULT VALUES"
else
"REPLACE INTO #{from} VALUES #{literal(values)}"
end
when Hash
if values.empty?
"REPLACE INTO #{from} DEFAULT VALUES"
else
fl, vl = [], []
values.each {|k, v| fl << literal(k.is_a?(String) ? k.to_sym : k); vl << literal(v)}
"REPLACE INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
end
when Dataset
"REPLACE INTO #{from} #{literal(values)}"
else
if values.respond_to?(:values)
replace_sql(values.values)
else
"REPLACE INTO #{from} VALUES (#{literal(values)})"
end
end
end
end
# MySQL supports ORDER and LIMIT clauses in UPDATE statements.
def update_sql(values, opts = nil)
sql = super
opts = opts ? @opts.merge(opts) : @opts
if order = opts[:order]
sql << " ORDER BY #{expression_list(order)}"
end
if limit = opts[:limit]
sql << " LIMIT #{limit}"
end
sql
end
end
end
end