sequel 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +1551 -4
- data/README +306 -19
- data/Rakefile +84 -56
- data/bin/sequel +106 -0
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +182 -0
- data/lib/sequel_core.rb +136 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
- data/lib/sequel_core/adapters/ado.rb +80 -0
- data/lib/sequel_core/adapters/db2.rb +148 -0
- data/lib/sequel_core/adapters/dbi.rb +117 -0
- data/lib/sequel_core/adapters/informix.rb +78 -0
- data/lib/sequel_core/adapters/jdbc.rb +186 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
- data/lib/sequel_core/adapters/mysql.rb +231 -0
- data/lib/sequel_core/adapters/odbc.rb +155 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
- data/lib/sequel_core/adapters/openbase.rb +64 -0
- data/lib/sequel_core/adapters/oracle.rb +170 -0
- data/lib/sequel_core/adapters/postgres.rb +199 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
- data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
- data/lib/sequel_core/adapters/sqlite.rb +138 -0
- data/lib/sequel_core/connection_pool.rb +194 -0
- data/lib/sequel_core/core_ext.rb +203 -0
- data/lib/sequel_core/core_sql.rb +184 -0
- data/lib/sequel_core/database.rb +471 -0
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/dataset.rb +457 -0
- data/lib/sequel_core/dataset/callback.rb +13 -0
- data/lib/sequel_core/dataset/convenience.rb +245 -0
- data/lib/sequel_core/dataset/pagination.rb +96 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sql.rb +889 -0
- data/lib/sequel_core/deprecated.rb +26 -0
- data/lib/sequel_core/exceptions.rb +42 -0
- data/lib/sequel_core/migration.rb +187 -0
- data/lib/sequel_core/object_graph.rb +216 -0
- data/lib/sequel_core/pretty_table.rb +71 -0
- data/lib/sequel_core/schema.rb +2 -0
- data/lib/sequel_core/schema/generator.rb +239 -0
- data/lib/sequel_core/schema/sql.rb +325 -0
- data/lib/sequel_core/sql.rb +812 -0
- data/lib/sequel_model.rb +5 -1
- data/lib/sequel_model/association_reflection.rb +3 -8
- data/lib/sequel_model/base.rb +15 -10
- data/lib/sequel_model/inflector.rb +3 -5
- data/lib/sequel_model/plugins.rb +1 -1
- data/lib/sequel_model/record.rb +11 -3
- data/lib/sequel_model/schema.rb +4 -4
- data/lib/sequel_model/validations.rb +6 -1
- data/spec/adapters/ado_spec.rb +17 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +764 -0
- data/spec/adapters/oracle_spec.rb +222 -0
- data/spec/adapters/postgres_spec.rb +441 -0
- data/spec/adapters/spec_helper.rb +7 -0
- data/spec/adapters/sqlite_spec.rb +400 -0
- data/spec/integration/dataset_test.rb +51 -0
- data/spec/integration/eager_loader_test.rb +702 -0
- data/spec/integration/schema_test.rb +102 -0
- data/spec/integration/spec_helper.rb +44 -0
- data/spec/integration/type_test.rb +43 -0
- data/spec/rcov.opts +2 -0
- data/spec/sequel_core/connection_pool_spec.rb +363 -0
- data/spec/sequel_core/core_ext_spec.rb +156 -0
- data/spec/sequel_core/core_sql_spec.rb +427 -0
- data/spec/sequel_core/database_spec.rb +964 -0
- data/spec/sequel_core/dataset_spec.rb +2977 -0
- data/spec/sequel_core/expression_filters_spec.rb +346 -0
- data/spec/sequel_core/migration_spec.rb +261 -0
- data/spec/sequel_core/object_graph_spec.rb +234 -0
- data/spec/sequel_core/pretty_table_spec.rb +58 -0
- data/spec/sequel_core/schema_generator_spec.rb +122 -0
- data/spec/sequel_core/schema_spec.rb +497 -0
- data/spec/sequel_core/spec_helper.rb +51 -0
- data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
- data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
- data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
- data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
- data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
- data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
- data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
- data/spec/sequel_model/inflector_spec.rb +119 -0
- data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
- data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
- data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
- data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
- data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
- data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
- data/spec/spec_config.rb +9 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +110 -37
- data/spec/inflector_spec.rb +0 -34
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
require 'sequel_core/adapters/shared/postgres'
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'pg'
|
|
5
|
+
rescue LoadError => e
|
|
6
|
+
begin
|
|
7
|
+
require 'postgres'
|
|
8
|
+
class PGconn
|
|
9
|
+
unless method_defined?(:escape_string)
|
|
10
|
+
if self.respond_to?(:escape)
|
|
11
|
+
def escape_string(str)
|
|
12
|
+
self.class.escape(str)
|
|
13
|
+
end
|
|
14
|
+
else
|
|
15
|
+
def escape_string(obj)
|
|
16
|
+
raise Sequel::Error, "string escaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
unless method_defined?(:escape_bytea)
|
|
21
|
+
if self.respond_to?(:escape_bytea)
|
|
22
|
+
def escape_bytea(obj)
|
|
23
|
+
self.class.escape_bytea(obj)
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
begin
|
|
27
|
+
require 'postgres-pr/typeconv/conv'
|
|
28
|
+
require 'postgres-pr/typeconv/bytea'
|
|
29
|
+
extend Postgres::Conversion
|
|
30
|
+
def escape_bytea(obj)
|
|
31
|
+
self.class.encode_bytea(obj)
|
|
32
|
+
end
|
|
33
|
+
metaalias :unescape_bytea, :decode_bytea
|
|
34
|
+
rescue
|
|
35
|
+
def escape_bytea(obj)
|
|
36
|
+
raise Sequel::Error, "bytea escaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
|
|
37
|
+
end
|
|
38
|
+
def self.unescape_bytea(obj)
|
|
39
|
+
raise Sequel::Error, "bytea unescaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
alias_method :finish, :close unless method_defined?(:finish)
|
|
45
|
+
end
|
|
46
|
+
class PGresult
|
|
47
|
+
alias_method :nfields, :num_fields unless method_defined?(:nfields)
|
|
48
|
+
alias_method :ntuples, :num_tuples unless method_defined?(:ntuples)
|
|
49
|
+
alias_method :ftype, :type unless method_defined?(:ftype)
|
|
50
|
+
alias_method :fname, :fieldname unless method_defined?(:fname)
|
|
51
|
+
alias_method :cmd_tuples, :cmdtuples unless method_defined?(:cmd_tuples)
|
|
52
|
+
end
|
|
53
|
+
rescue LoadError
|
|
54
|
+
raise e
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module Sequel
|
|
59
|
+
module Postgres
|
|
60
|
+
CONVERTED_EXCEPTIONS << PGError
|
|
61
|
+
PG_TYPES = {
|
|
62
|
+
16 => lambda{ |s| Postgres.string_to_bool(s) }, # boolean
|
|
63
|
+
17 => lambda{ |s| Adapter.unescape_bytea(s).to_blob }, # bytea
|
|
64
|
+
20 => lambda{ |s| s.to_i }, # int8
|
|
65
|
+
21 => lambda{ |s| s.to_i }, # int2
|
|
66
|
+
22 => lambda{ |s| s.to_i }, # int2vector
|
|
67
|
+
23 => lambda{ |s| s.to_i }, # int4
|
|
68
|
+
26 => lambda{ |s| s.to_i }, # oid
|
|
69
|
+
700 => lambda{ |s| s.to_f }, # float4
|
|
70
|
+
701 => lambda{ |s| s.to_f }, # float8
|
|
71
|
+
790 => lambda{ |s| s.to_d }, # money
|
|
72
|
+
1082 => lambda{ |s| s.to_date }, # date
|
|
73
|
+
1083 => lambda{ |s| s.to_time }, # time without time zone
|
|
74
|
+
1114 => lambda{ |s| s.to_sequel_time }, # timestamp without time zone
|
|
75
|
+
1184 => lambda{ |s| s.to_sequel_time }, # timestamp with time zone
|
|
76
|
+
1186 => lambda{ |s| s.to_i }, # interval
|
|
77
|
+
1266 => lambda{ |s| s.to_time }, # time with time zone
|
|
78
|
+
1700 => lambda{ |s| s.to_d }, # numeric
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def self.string_to_bool(s)
|
|
82
|
+
if(s.blank?)
|
|
83
|
+
nil
|
|
84
|
+
elsif(s.downcase == 't' || s.downcase == 'true')
|
|
85
|
+
true
|
|
86
|
+
else
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class Adapter < ::PGconn
|
|
92
|
+
include Sequel::Postgres::AdapterMethods
|
|
93
|
+
self.translate_results = false if respond_to?(:translate_results=)
|
|
94
|
+
|
|
95
|
+
def connected?
|
|
96
|
+
status == Adapter::CONNECTION_OK
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def execute(sql, &block)
|
|
100
|
+
q = nil
|
|
101
|
+
begin
|
|
102
|
+
q = exec(sql)
|
|
103
|
+
rescue PGError => e
|
|
104
|
+
unless connected?
|
|
105
|
+
reset
|
|
106
|
+
q = exec(sql)
|
|
107
|
+
else
|
|
108
|
+
raise e
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
begin
|
|
112
|
+
block ? block[q] : q.cmd_tuples
|
|
113
|
+
ensure
|
|
114
|
+
q.clear
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def result_set_values(r, *vals)
|
|
119
|
+
return if r.nil? || (r.ntuples == 0)
|
|
120
|
+
case vals.length
|
|
121
|
+
when 1
|
|
122
|
+
r.getvalue(0, vals.first)
|
|
123
|
+
else
|
|
124
|
+
vals.collect{|col| r.getvalue(0, col)}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
class Database < Sequel::Database
|
|
130
|
+
include Sequel::Postgres::DatabaseMethods
|
|
131
|
+
|
|
132
|
+
set_adapter_scheme :postgres
|
|
133
|
+
|
|
134
|
+
def connect
|
|
135
|
+
conn = Adapter.connect(
|
|
136
|
+
@opts[:host] || 'localhost',
|
|
137
|
+
@opts[:port] || 5432,
|
|
138
|
+
'', '',
|
|
139
|
+
@opts[:database],
|
|
140
|
+
@opts[:user],
|
|
141
|
+
@opts[:password]
|
|
142
|
+
)
|
|
143
|
+
if encoding = @opts[:encoding] || @opts[:charset]
|
|
144
|
+
conn.set_client_encoding(encoding)
|
|
145
|
+
end
|
|
146
|
+
conn
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def dataset(opts = nil)
|
|
150
|
+
Postgres::Dataset.new(self, opts)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def disconnect
|
|
154
|
+
@pool.disconnect {|c| c.finish}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def execute(sql, &block)
|
|
158
|
+
begin
|
|
159
|
+
log_info(sql)
|
|
160
|
+
@pool.hold {|conn| conn.execute(sql, &block)}
|
|
161
|
+
rescue => e
|
|
162
|
+
log_info(e.message)
|
|
163
|
+
raise convert_pgerror(e)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
# PostgreSQL doesn't need the pool to convert exceptions, either.
|
|
170
|
+
def connection_pool_default_options
|
|
171
|
+
super.merge(:pool_convert_exceptions=>false)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
class Dataset < Sequel::Dataset
|
|
176
|
+
include Sequel::Postgres::DatasetMethods
|
|
177
|
+
|
|
178
|
+
def fetch_rows(sql, &block)
|
|
179
|
+
@columns = []
|
|
180
|
+
@db.execute(sql) do |res|
|
|
181
|
+
(0...res.ntuples).each do |recnum|
|
|
182
|
+
converted_rec = {}
|
|
183
|
+
(0...res.nfields).each do |fieldnum|
|
|
184
|
+
fieldsym = res.fname(fieldnum).to_sym
|
|
185
|
+
@columns << fieldsym
|
|
186
|
+
converted_rec[fieldsym] = if value = res.getvalue(recnum,fieldnum)
|
|
187
|
+
(PG_TYPES[res.ftype(fieldnum)] || lambda{|s| s.to_s}).call(value)
|
|
188
|
+
else
|
|
189
|
+
value
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
yield converted_rec
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module MySQL
|
|
3
|
+
module DatabaseMethods
|
|
4
|
+
AUTO_INCREMENT = 'AUTO_INCREMENT'.freeze
|
|
5
|
+
NOT_NULL = Sequel::Schema::SQL::NOT_NULL
|
|
6
|
+
NULL = Sequel::Schema::SQL::NULL
|
|
7
|
+
PRIMARY_KEY = Sequel::Schema::SQL::PRIMARY_KEY
|
|
8
|
+
SQL_BEGIN = Sequel::Database::SQL_BEGIN
|
|
9
|
+
SQL_COMMIT = Sequel::Database::SQL_COMMIT
|
|
10
|
+
SQL_ROLLBACK = Sequel::Database::SQL_ROLLBACK
|
|
11
|
+
TYPES = Sequel::Schema::SQL::TYPES
|
|
12
|
+
UNIQUE = Sequel::Schema::SQL::UNIQUE
|
|
13
|
+
UNSIGNED = Sequel::Schema::SQL::UNSIGNED
|
|
14
|
+
|
|
15
|
+
def alter_table_sql(table, op)
|
|
16
|
+
type = type_literal(op[:type])
|
|
17
|
+
type << '(255)' if type == 'varchar'
|
|
18
|
+
case op[:op]
|
|
19
|
+
when :rename_column
|
|
20
|
+
"ALTER TABLE #{table} CHANGE COLUMN #{literal(op[:name])} #{literal(op[:new_name])} #{type}"
|
|
21
|
+
when :set_column_type
|
|
22
|
+
"ALTER TABLE #{table} CHANGE COLUMN #{literal(op[:name])} #{literal(op[:name])} #{type}"
|
|
23
|
+
when :drop_index
|
|
24
|
+
"DROP INDEX #{default_index_name(table, op[:columns])} ON #{table}"
|
|
25
|
+
else
|
|
26
|
+
super(table, op)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def auto_increment_sql
|
|
31
|
+
AUTO_INCREMENT
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def column_definition_sql(column)
|
|
35
|
+
if column[:type] == :check
|
|
36
|
+
return constraint_definition_sql(column)
|
|
37
|
+
end
|
|
38
|
+
sql = "#{literal(column[:name].to_sym)} #{TYPES[column[:type]]}"
|
|
39
|
+
column[:size] ||= 255 if column[:type] == :varchar
|
|
40
|
+
elements = column[:size] || column[:elements]
|
|
41
|
+
sql << literal(Array(elements)) if elements
|
|
42
|
+
sql << UNSIGNED if column[:unsigned]
|
|
43
|
+
sql << UNIQUE if column[:unique]
|
|
44
|
+
sql << NOT_NULL if column[:null] == false
|
|
45
|
+
sql << NULL if column[:null] == true
|
|
46
|
+
sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
|
|
47
|
+
sql << PRIMARY_KEY if column[:primary_key]
|
|
48
|
+
sql << " #{auto_increment_sql}" if column[:auto_increment]
|
|
49
|
+
if column[:table]
|
|
50
|
+
sql << ", FOREIGN KEY (#{literal(column[:name].to_sym)}) REFERENCES #{column[:table]}"
|
|
51
|
+
sql << literal(Array(column[:key])) if column[:key]
|
|
52
|
+
sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
|
|
53
|
+
end
|
|
54
|
+
sql
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def index_definition_sql(table_name, index)
|
|
58
|
+
index_name = index[:name] || default_index_name(table_name, index[:columns])
|
|
59
|
+
unique = "UNIQUE " if index[:unique]
|
|
60
|
+
case index[:type]
|
|
61
|
+
when :full_text
|
|
62
|
+
"CREATE FULLTEXT INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
|
|
63
|
+
when :spatial
|
|
64
|
+
"CREATE SPATIAL INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
|
|
65
|
+
when nil
|
|
66
|
+
"CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
|
|
67
|
+
else
|
|
68
|
+
"CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])} USING #{index[:type]}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def serial_primary_key_options
|
|
73
|
+
{:primary_key => true, :type => :integer, :auto_increment => true}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def server_version
|
|
77
|
+
m = /(\d+)\.(\d+)\.(\d+)/.match(get(:version[]))
|
|
78
|
+
@server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Changes the database in use by issuing a USE statement.
|
|
82
|
+
def use(db_name)
|
|
83
|
+
disconnect
|
|
84
|
+
@opts[:database] = db_name if self << "USE #{db_name}"
|
|
85
|
+
@schemas = nil
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def schema_ds_dataset
|
|
92
|
+
ds = schema_utility_dataset.clone
|
|
93
|
+
ds.quote_identifiers = true
|
|
94
|
+
ds
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def schema_ds_filter(table_name, opts)
|
|
98
|
+
filt = super
|
|
99
|
+
# Restrict it to the given or current database, unless specifically requesting :database = nil
|
|
100
|
+
filt = SQL::BooleanExpression.new(:AND, filt, {:c__table_schema=>opts[:database] || database_name}) if opts[:database] || !opts.include?(:database)
|
|
101
|
+
filt
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def schema_ds_join(table_name, opts)
|
|
105
|
+
[:information_schema__columns, {:table_schema => :table_schema, :table_name => :table_name}, :c]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
module DatasetMethods
|
|
110
|
+
BOOL_TRUE = '1'.freeze
|
|
111
|
+
BOOL_FALSE = '0'.freeze
|
|
112
|
+
COMMA_SEPARATOR = ', '.freeze
|
|
113
|
+
|
|
114
|
+
def complex_expression_sql(op, args)
|
|
115
|
+
case op
|
|
116
|
+
when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
117
|
+
"(#{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))})"
|
|
118
|
+
when :'||'
|
|
119
|
+
if args.length > 1
|
|
120
|
+
"CONCAT(#{args.collect{|a| literal(a)}.join(', ')})"
|
|
121
|
+
else
|
|
122
|
+
literal(args.at(0))
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
super(op, args)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# MySQL supports ORDER and LIMIT clauses in DELETE statements.
|
|
130
|
+
def delete_sql(opts = nil)
|
|
131
|
+
sql = super
|
|
132
|
+
opts = opts ? @opts.merge(opts) : @opts
|
|
133
|
+
|
|
134
|
+
if order = opts[:order]
|
|
135
|
+
sql << " ORDER BY #{expression_list(order)}"
|
|
136
|
+
end
|
|
137
|
+
if limit = opts[:limit]
|
|
138
|
+
sql << " LIMIT #{limit}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
sql
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def full_text_search(cols, terms, opts = {})
|
|
145
|
+
mode = opts[:boolean] ? " IN BOOLEAN MODE" : ""
|
|
146
|
+
s = if Array === terms
|
|
147
|
+
if mode.blank?
|
|
148
|
+
"MATCH #{literal(Array(cols))} AGAINST #{literal(terms)}"
|
|
149
|
+
else
|
|
150
|
+
"MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)[1...-1]}#{mode})"
|
|
151
|
+
end
|
|
152
|
+
else
|
|
153
|
+
"MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)}#{mode})"
|
|
154
|
+
end
|
|
155
|
+
filter(s)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# MySQL allows HAVING clause on ungrouped datasets.
|
|
159
|
+
def having(*cond, &block)
|
|
160
|
+
@opts[:having] = {}
|
|
161
|
+
x = filter(*cond, &block)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def insert_default_values_sql
|
|
165
|
+
"INSERT INTO #{source_list(@opts[:from])} () VALUES ()"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns a join clause based on the specified join type
|
|
169
|
+
# and condition. MySQL's NATURAL join is 'semantically
|
|
170
|
+
# equivalent to a JOIN with a USING clause that names all
|
|
171
|
+
# columns that exist in both tables. The constraint
|
|
172
|
+
# expression may be nil, so join expression can accept two
|
|
173
|
+
# arguments.
|
|
174
|
+
#
|
|
175
|
+
# === Note
|
|
176
|
+
# Full outer joins (:full_outer) are not implemented in
|
|
177
|
+
# MySQL (as of v6.0), nor is there currently a work around
|
|
178
|
+
# implementation in Sequel. Straight joins with 'ON
|
|
179
|
+
# <condition>' are not yet implemented.
|
|
180
|
+
#
|
|
181
|
+
# === Example
|
|
182
|
+
# @ds = MYSQL_DB[:nodes]
|
|
183
|
+
# @ds.join_table(:natural_left_outer, :nodes)
|
|
184
|
+
# # join SQL is 'NATURAL LEFT OUTER JOIN nodes'
|
|
185
|
+
def join_table(type, table, expr=nil, table_alias=nil)
|
|
186
|
+
type = :inner if (type == :cross) && !expr.nil?
|
|
187
|
+
raise(Sequel::Error::InvalidJoinType, "MySQL doesn't support FULL OUTER JOIN") if type == :full_outer
|
|
188
|
+
super(type, table, expr, table_alias)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def join_type_sql(join_type)
|
|
192
|
+
case join_type
|
|
193
|
+
when :straight then 'STRAIGHT_JOIN'
|
|
194
|
+
when :natural_inner then 'NATURAL LEFT JOIN'
|
|
195
|
+
else super
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def literal(v)
|
|
200
|
+
case v
|
|
201
|
+
when true
|
|
202
|
+
BOOL_TRUE
|
|
203
|
+
when false
|
|
204
|
+
BOOL_FALSE
|
|
205
|
+
else
|
|
206
|
+
super
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def multi_insert_sql(columns, values)
|
|
211
|
+
columns = column_list(columns)
|
|
212
|
+
values = values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)
|
|
213
|
+
["INSERT INTO #{source_list(@opts[:from])} (#{columns}) VALUES #{values}"]
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def quoted_identifier(c)
|
|
217
|
+
"`#{c}`"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def replace_sql(*values)
|
|
221
|
+
from = source_list(@opts[:from])
|
|
222
|
+
if values.empty?
|
|
223
|
+
"REPLACE INTO #{from} DEFAULT VALUES"
|
|
224
|
+
else
|
|
225
|
+
values = values[0] if values.size == 1
|
|
226
|
+
|
|
227
|
+
# if hash or array with keys we need to transform the values
|
|
228
|
+
if @transform && (values.is_a?(Hash) || (values.is_a?(Array) && values.keys))
|
|
229
|
+
values = transform_save(values)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
case values
|
|
233
|
+
when Array
|
|
234
|
+
if values.empty?
|
|
235
|
+
"REPLACE INTO #{from} DEFAULT VALUES"
|
|
236
|
+
else
|
|
237
|
+
"REPLACE INTO #{from} VALUES #{literal(values)}"
|
|
238
|
+
end
|
|
239
|
+
when Hash
|
|
240
|
+
if values.empty?
|
|
241
|
+
"REPLACE INTO #{from} DEFAULT VALUES"
|
|
242
|
+
else
|
|
243
|
+
fl, vl = [], []
|
|
244
|
+
values.each {|k, v| fl << literal(k.is_a?(String) ? k.to_sym : k); vl << literal(v)}
|
|
245
|
+
"REPLACE INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
|
|
246
|
+
end
|
|
247
|
+
when Dataset
|
|
248
|
+
"REPLACE INTO #{from} #{literal(values)}"
|
|
249
|
+
else
|
|
250
|
+
if values.respond_to?(:values)
|
|
251
|
+
replace_sql(values.values)
|
|
252
|
+
else
|
|
253
|
+
"REPLACE INTO #{from} VALUES (#{literal(values)})"
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# MySQL supports ORDER and LIMIT clauses in UPDATE statements.
|
|
260
|
+
def update_sql(values, opts = nil)
|
|
261
|
+
sql = super
|
|
262
|
+
opts = opts ? @opts.merge(opts) : @opts
|
|
263
|
+
|
|
264
|
+
if order = opts[:order]
|
|
265
|
+
sql << " ORDER BY #{expression_list(order)}"
|
|
266
|
+
end
|
|
267
|
+
if limit = opts[:limit]
|
|
268
|
+
sql << " LIMIT #{limit}"
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
sql
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|