sequel 3.39.0 → 3.40.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +30 -0
- data/README.rdoc +4 -3
- data/doc/active_record.rdoc +1 -1
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/3.40.0.txt +73 -0
- data/lib/sequel/adapters/ado.rb +29 -3
- data/lib/sequel/adapters/ado/access.rb +334 -0
- data/lib/sequel/adapters/ado/mssql.rb +0 -6
- data/lib/sequel/adapters/cubrid.rb +143 -0
- data/lib/sequel/adapters/jdbc.rb +26 -18
- data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
- data/lib/sequel/adapters/jdbc/derby.rb +7 -7
- data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -4
- data/lib/sequel/adapters/mysql.rb +0 -3
- data/lib/sequel/adapters/mysql2.rb +0 -3
- data/lib/sequel/adapters/oracle.rb +4 -1
- data/lib/sequel/adapters/postgres.rb +4 -4
- data/lib/sequel/adapters/shared/access.rb +205 -3
- data/lib/sequel/adapters/shared/cubrid.rb +216 -0
- data/lib/sequel/adapters/shared/db2.rb +7 -2
- data/lib/sequel/adapters/shared/mssql.rb +3 -34
- data/lib/sequel/adapters/shared/mysql.rb +4 -33
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +11 -0
- data/lib/sequel/adapters/shared/oracle.rb +5 -0
- data/lib/sequel/adapters/shared/postgres.rb +2 -1
- data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/query.rb +30 -7
- data/lib/sequel/database/schema_methods.rb +7 -2
- data/lib/sequel/dataset/query.rb +9 -10
- data/lib/sequel/dataset/sql.rb +14 -26
- data/lib/sequel/extensions/pg_hstore.rb +19 -0
- data/lib/sequel/extensions/pg_row.rb +5 -5
- data/lib/sequel/plugins/association_pks.rb +121 -18
- data/lib/sequel/plugins/json_serializer.rb +19 -0
- data/lib/sequel/sql.rb +11 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +42 -0
- data/spec/core/database_spec.rb +17 -0
- data/spec/core/dataset_spec.rb +11 -0
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/extensions/association_pks_spec.rb +163 -3
- data/spec/extensions/pg_hstore_spec.rb +6 -0
- data/spec/extensions/pg_row_spec.rb +17 -0
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/dataset_test.rb +13 -13
- data/spec/integration/plugin_test.rb +232 -7
- data/spec/integration/schema_test.rb +8 -12
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/type_test.rb +6 -0
- metadata +9 -2
@@ -0,0 +1,216 @@
|
|
1
|
+
Sequel.require 'adapters/utils/split_alter_table'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Cubrid
|
5
|
+
module DatabaseMethods
|
6
|
+
include Sequel::Database::SplitAlterTable
|
7
|
+
|
8
|
+
AUTOINCREMENT = 'AUTO_INCREMENT'.freeze
|
9
|
+
COLUMN_DEFINITION_ORDER = [:auto_increment, :default, :null, :unique, :primary_key, :references]
|
10
|
+
|
11
|
+
def database_type
|
12
|
+
:cubrid
|
13
|
+
end
|
14
|
+
|
15
|
+
def indexes(table, opts={})
|
16
|
+
m = output_identifier_meth
|
17
|
+
m2 = input_identifier_meth
|
18
|
+
indexes = {}
|
19
|
+
metadata_dataset.
|
20
|
+
from(:db_index___i).
|
21
|
+
join(:db_index_key___k, :index_name=>:index_name, :class_name=>:class_name).
|
22
|
+
where(:i__class_name=>m2.call(table), :is_primary_key=>'NO').
|
23
|
+
order(:k__key_order).
|
24
|
+
select(:i__index_name, :k__key_attr_name___column, :is_unique).
|
25
|
+
each do |row|
|
26
|
+
index = indexes[m.call(row[:index_name])] ||= {:columns=>[], :unique=>row[:is_unique]=='YES'}
|
27
|
+
index[:columns] << m.call(row[:column])
|
28
|
+
end
|
29
|
+
indexes
|
30
|
+
end
|
31
|
+
|
32
|
+
def supports_savepoints?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def schema_parse_table(table_name, opts)
|
37
|
+
m = output_identifier_meth(opts[:dataset])
|
38
|
+
m2 = input_identifier_meth(opts[:dataset])
|
39
|
+
|
40
|
+
pks = metadata_dataset.
|
41
|
+
from(:db_index___i).
|
42
|
+
join(:db_index_key___k, :index_name=>:index_name, :class_name=>:class_name).
|
43
|
+
where(:i__class_name=>m2.call(table_name), :is_primary_key=>'YES').
|
44
|
+
order(:k__key_order).
|
45
|
+
select_map(:k__key_attr_name).
|
46
|
+
map{|c| m.call(c)}
|
47
|
+
|
48
|
+
metadata_dataset.
|
49
|
+
from(:db_attribute).
|
50
|
+
where(:class_name=>m2.call(table_name)).
|
51
|
+
order(:def_order).
|
52
|
+
select(:attr_name, :data_type___db_type, :default_value___default, :is_nullable___allow_null).
|
53
|
+
map do |row|
|
54
|
+
name = m.call(row.delete(:attr_name))
|
55
|
+
row[:allow_null] = row[:allow_null] == 'YES'
|
56
|
+
row[:primary_key] = pks.include?(name)
|
57
|
+
row[:type] = schema_column_type(row[:db_type])
|
58
|
+
[name, row]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def tables(opts={})
|
63
|
+
_tables('CLASS')
|
64
|
+
end
|
65
|
+
|
66
|
+
def views(opts={})
|
67
|
+
_tables('VCLASS')
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def _tables(type)
|
73
|
+
m = output_identifier_meth
|
74
|
+
metadata_dataset.
|
75
|
+
from(:db_class).
|
76
|
+
where(:is_system_class=>'NO', :class_type=>type).
|
77
|
+
select_map(:class_name).
|
78
|
+
map{|c| m.call(c)}
|
79
|
+
end
|
80
|
+
|
81
|
+
def alter_table_op_sql(table, op)
|
82
|
+
case op[:op]
|
83
|
+
when :rename_column
|
84
|
+
"RENAME COLUMN #{quote_identifier(op[:name])} AS #{quote_identifier(op[:new_name])}"
|
85
|
+
when :set_column_type, :set_column_null, :set_column_default
|
86
|
+
o = op[:op]
|
87
|
+
opts = schema(table).find{|x| x.first == op[:name]}
|
88
|
+
opts = opts ? opts.last.dup : {}
|
89
|
+
opts[:name] = o == :rename_column ? op[:new_name] : op[:name]
|
90
|
+
opts[:type] = o == :set_column_type ? op[:type] : opts[:db_type]
|
91
|
+
opts[:null] = o == :set_column_null ? op[:null] : opts[:allow_null]
|
92
|
+
opts[:default] = o == :set_column_default ? op[:default] : opts[:ruby_default]
|
93
|
+
opts.delete(:default) if opts[:default] == nil
|
94
|
+
"CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(opts))}"
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def alter_table_sql(table, op)
|
101
|
+
case op[:op]
|
102
|
+
when :drop_index
|
103
|
+
"ALTER TABLE #{quote_schema_table(table)} #{drop_index_sql(table, op)}"
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def auto_increment_sql
|
110
|
+
AUTOINCREMENT
|
111
|
+
end
|
112
|
+
|
113
|
+
# CUBRID requires auto increment before primary key
|
114
|
+
def column_definition_order
|
115
|
+
COLUMN_DEFINITION_ORDER
|
116
|
+
end
|
117
|
+
|
118
|
+
# CUBRID requires FOREIGN KEY keywords before a column reference
|
119
|
+
def column_references_sql(column)
|
120
|
+
sql = super
|
121
|
+
sql = " FOREIGN KEY#{sql}" unless column[:columns]
|
122
|
+
sql
|
123
|
+
end
|
124
|
+
|
125
|
+
def connection_execute_method
|
126
|
+
:query
|
127
|
+
end
|
128
|
+
|
129
|
+
# CUBRID is case insensitive, so don't modify identifiers
|
130
|
+
def identifier_input_method_default
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
|
134
|
+
# CUBRID is case insensitive, so don't modify identifiers
|
135
|
+
def identifier_output_method_default
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
139
|
+
# CUBRID doesn't support booleans, it recommends using smallint.
|
140
|
+
def type_literal_generic_trueclass(column)
|
141
|
+
:smallint
|
142
|
+
end
|
143
|
+
|
144
|
+
# CUBRID uses clob for text types.
|
145
|
+
def uses_clob_for_text?
|
146
|
+
true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
module DatasetMethods
|
151
|
+
SELECT_CLAUSE_METHODS = Sequel::Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
|
152
|
+
LIMIT = Sequel::Dataset::LIMIT
|
153
|
+
COMMA = Sequel::Dataset::COMMA
|
154
|
+
BOOL_FALSE = '0'.freeze
|
155
|
+
BOOL_TRUE = '1'.freeze
|
156
|
+
|
157
|
+
def complex_expression_sql_append(sql, op, args)
|
158
|
+
case op
|
159
|
+
when :ILIKE
|
160
|
+
super(sql, :LIKE, [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1))])
|
161
|
+
when :"NOT ILIKE"
|
162
|
+
super(sql, :"NOT LIKE", [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1))])
|
163
|
+
else
|
164
|
+
super
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def supports_join_using?
|
169
|
+
false
|
170
|
+
end
|
171
|
+
|
172
|
+
def supports_multiple_column_in?
|
173
|
+
false
|
174
|
+
end
|
175
|
+
|
176
|
+
def supports_timestamp_usecs?
|
177
|
+
false
|
178
|
+
end
|
179
|
+
|
180
|
+
# CUBRID supposedly supports TRUNCATE, but it appears not to work in my testing.
|
181
|
+
# Fallback to using DELETE.
|
182
|
+
def truncate
|
183
|
+
delete
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def literal_false
|
190
|
+
BOOL_FALSE
|
191
|
+
end
|
192
|
+
|
193
|
+
def literal_true
|
194
|
+
BOOL_TRUE
|
195
|
+
end
|
196
|
+
|
197
|
+
# CUBRID doesn't support CTEs or FOR UPDATE.
|
198
|
+
def select_clause_methods
|
199
|
+
SELECT_CLAUSE_METHODS
|
200
|
+
end
|
201
|
+
|
202
|
+
# CUBRID requires a limit to use an offset,
|
203
|
+
# and requires a FROM table if a limit is used.
|
204
|
+
def select_limit_sql(sql)
|
205
|
+
if @opts[:from] && (l = @opts[:limit])
|
206
|
+
sql << LIMIT
|
207
|
+
if o = @opts[:offset]
|
208
|
+
literal_append(sql, o)
|
209
|
+
sql << COMMA
|
210
|
+
end
|
211
|
+
literal_append(sql, l)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -123,10 +123,10 @@ module Sequel
|
|
123
123
|
end
|
124
124
|
|
125
125
|
# Supply columns with NOT NULL if they are part of a composite
|
126
|
-
# primary
|
126
|
+
# primary key or unique constraint
|
127
127
|
def column_list_sql(g)
|
128
128
|
ks = []
|
129
|
-
g.constraints.each{|c| ks = c[:columns] if [:primary_key, :
|
129
|
+
g.constraints.each{|c| ks = c[:columns] if [:primary_key, :unique].include?(c[:type])}
|
130
130
|
g.columns.each{|c| c[:null] = false if ks.include?(c[:name]) }
|
131
131
|
super
|
132
132
|
end
|
@@ -189,6 +189,11 @@ module Sequel
|
|
189
189
|
:smallint
|
190
190
|
end
|
191
191
|
alias type_literal_generic_falseclass type_literal_generic_trueclass
|
192
|
+
|
193
|
+
# DB2 uses clob for text types.
|
194
|
+
def uses_clob_for_text?
|
195
|
+
true
|
196
|
+
end
|
192
197
|
end
|
193
198
|
|
194
199
|
module DatasetMethods
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Sequel.require 'adapters/utils/
|
1
|
+
Sequel.require %w'emulate_offset_with_row_number split_alter_table', 'adapters/utils/'
|
2
2
|
|
3
3
|
module Sequel
|
4
4
|
Dataset::NON_SQL_OPTIONS << :disable_insert_output
|
@@ -13,6 +13,8 @@ module Sequel
|
|
13
13
|
SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
|
14
14
|
SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
|
15
15
|
MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
|
16
|
+
|
17
|
+
include Sequel::Database::SplitAlterTable
|
16
18
|
|
17
19
|
# Whether to use N'' to quote strings, which allows unicode characters inside the
|
18
20
|
# strings. True by default for compatibility, can be set to false for a possible
|
@@ -110,39 +112,6 @@ module Sequel
|
|
110
112
|
AUTO_INCREMENT
|
111
113
|
end
|
112
114
|
|
113
|
-
# Preprocess the array of operations. If it looks like some operations depend
|
114
|
-
# on results of earlier operations and may require reloading the schema to
|
115
|
-
# work correctly, split those operations into separate lists, and between each
|
116
|
-
# list, remove the cached schema so that the later operations deal with the
|
117
|
-
# then current table schema.
|
118
|
-
def apply_alter_table(name, ops)
|
119
|
-
modified_columns = []
|
120
|
-
op_groups = [[]]
|
121
|
-
ops.each do |op|
|
122
|
-
case op[:op]
|
123
|
-
when :add_column, :set_column_type, :set_column_null
|
124
|
-
if modified_columns.include?(op[:name])
|
125
|
-
op_groups << []
|
126
|
-
else
|
127
|
-
modified_columns << op[:name]
|
128
|
-
end
|
129
|
-
when :rename_column
|
130
|
-
if modified_columns.include?(op[:name]) || modified_columns.include?(op[:new_name])
|
131
|
-
op_groups << []
|
132
|
-
end
|
133
|
-
modified_columns << op[:name] unless modified_columns.include?(op[:name])
|
134
|
-
modified_columns << op[:new_name] unless modified_columns.include?(op[:new_name])
|
135
|
-
end
|
136
|
-
op_groups.last << op
|
137
|
-
end
|
138
|
-
|
139
|
-
op_groups.each do |ops|
|
140
|
-
next if ops.empty?
|
141
|
-
alter_table_sql_list(name, ops).each{|sql| execute_ddl(sql)}
|
142
|
-
remove_cached_schema(name)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
115
|
# MSSQL specific syntax for altering tables.
|
147
116
|
def alter_table_sql(table, op)
|
148
117
|
case op[:op]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
Sequel.require 'adapters/utils/split_alter_table'
|
2
|
+
|
1
3
|
module Sequel
|
2
4
|
Dataset::NON_SQL_OPTIONS << :insert_ignore
|
3
5
|
Dataset::NON_SQL_OPTIONS << :update_ignore
|
@@ -34,6 +36,8 @@ module Sequel
|
|
34
36
|
COLUMN_DEFINITION_ORDER = [:collate, :null, :default, :unique, :primary_key, :auto_increment, :references]
|
35
37
|
PRIMARY = 'PRIMARY'.freeze
|
36
38
|
MYSQL_TIMESTAMP_RE = /\ACURRENT_(?:DATE|TIMESTAMP)?\z/
|
39
|
+
|
40
|
+
include Sequel::Database::SplitAlterTable
|
37
41
|
|
38
42
|
# MySQL's cast rules are restrictive in that you can't just cast to any possible
|
39
43
|
# database type.
|
@@ -170,39 +174,6 @@ module Sequel
|
|
170
174
|
|
171
175
|
private
|
172
176
|
|
173
|
-
# Preprocess the array of operations. If it looks like some operations depend
|
174
|
-
# on results of earlier operations and may require reloading the schema to
|
175
|
-
# work correctly, split those operations into separate lists, and between each
|
176
|
-
# list, remove the cached schema so that the later operations deal with the
|
177
|
-
# then current table schema.
|
178
|
-
def apply_alter_table(name, ops)
|
179
|
-
modified_columns = []
|
180
|
-
op_groups = [[]]
|
181
|
-
ops.each do |op|
|
182
|
-
case op[:op]
|
183
|
-
when :add_column, :set_column_type, :set_column_null, :set_column_default
|
184
|
-
if modified_columns.include?(op[:name])
|
185
|
-
op_groups << []
|
186
|
-
else
|
187
|
-
modified_columns << op[:name]
|
188
|
-
end
|
189
|
-
when :rename_column
|
190
|
-
if modified_columns.include?(op[:name]) || modified_columns.include?(op[:new_name])
|
191
|
-
op_groups << []
|
192
|
-
end
|
193
|
-
modified_columns << op[:name] unless modified_columns.include?(op[:name])
|
194
|
-
modified_columns << op[:new_name] unless modified_columns.include?(op[:new_name])
|
195
|
-
end
|
196
|
-
op_groups.last << op
|
197
|
-
end
|
198
|
-
|
199
|
-
op_groups.each do |ops|
|
200
|
-
next if ops.empty?
|
201
|
-
alter_table_sql_list(name, ops).each{|sql| execute_ddl(sql)}
|
202
|
-
remove_cached_schema(name)
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
177
|
# Use MySQL specific syntax for some alter table operations.
|
207
178
|
def alter_table_op_sql(table, op)
|
208
179
|
case op[:op]
|
@@ -6,6 +6,17 @@ module Sequel
|
|
6
6
|
# prepared statements and stored procedures.
|
7
7
|
module PreparedStatements
|
8
8
|
module DatabaseMethods
|
9
|
+
disconnect_errors = <<-END.split("\n").map{|l| l.strip}
|
10
|
+
Commands out of sync; you can't run this command now
|
11
|
+
Can't connect to local MySQL server through socket
|
12
|
+
MySQL server has gone away
|
13
|
+
Lost connection to MySQL server during query
|
14
|
+
This connection is still waiting for a result, try again once you have the result
|
15
|
+
closed MySQL connection
|
16
|
+
END
|
17
|
+
# Error messages for mysql and mysql2 that indicate the current connection should be disconnected
|
18
|
+
MYSQL_DATABASE_DISCONNECT_ERRORS = /\A#{Regexp.union(disconnect_errors)}/o
|
19
|
+
|
9
20
|
# Support stored procedures on MySQL
|
10
21
|
def call_sproc(name, opts={}, &block)
|
11
22
|
args = opts[:args] || []
|
@@ -369,7 +369,7 @@ module Sequel
|
|
369
369
|
# :server :: The server to which to send the NOTIFY statement, if the sharding support
|
370
370
|
# is being used.
|
371
371
|
def notify(channel, opts={})
|
372
|
-
execute_ddl("NOTIFY #{channel}#{", #{literal(opts[:payload].to_s)}" if opts[:payload]}", opts)
|
372
|
+
execute_ddl("NOTIFY #{dataset.send(:table_ref, channel)}#{", #{literal(opts[:payload].to_s)}" if opts[:payload]}", opts)
|
373
373
|
end
|
374
374
|
|
375
375
|
# Return primary key for the given table.
|
@@ -735,6 +735,7 @@ module Sequel
|
|
735
735
|
def initialize_postgres_adapter
|
736
736
|
@primary_keys = {}
|
737
737
|
@primary_key_sequences = {}
|
738
|
+
@conversion_procs = PG_TYPES.dup
|
738
739
|
reset_conversion_procs
|
739
740
|
end
|
740
741
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Sequel::Database::SplitAlterTable
|
2
|
+
private
|
3
|
+
|
4
|
+
# Preprocess the array of operations. If it looks like some operations depend
|
5
|
+
# on results of earlier operations and may require reloading the schema to
|
6
|
+
# work correctly, split those operations into separate lists, and between each
|
7
|
+
# list, remove the cached schema so that the later operations deal with the
|
8
|
+
# then current table schema.
|
9
|
+
def apply_alter_table(name, ops)
|
10
|
+
modified_columns = []
|
11
|
+
op_groups = [[]]
|
12
|
+
ops.each do |op|
|
13
|
+
case op[:op]
|
14
|
+
when :add_column, :set_column_type, :set_column_null, :set_column_default
|
15
|
+
if modified_columns.include?(op[:name])
|
16
|
+
op_groups << []
|
17
|
+
else
|
18
|
+
modified_columns << op[:name]
|
19
|
+
end
|
20
|
+
when :rename_column
|
21
|
+
if modified_columns.include?(op[:name]) || modified_columns.include?(op[:new_name])
|
22
|
+
op_groups << []
|
23
|
+
end
|
24
|
+
modified_columns << op[:name] unless modified_columns.include?(op[:name])
|
25
|
+
modified_columns << op[:new_name] unless modified_columns.include?(op[:new_name])
|
26
|
+
end
|
27
|
+
op_groups.last << op
|
28
|
+
end
|
29
|
+
|
30
|
+
op_groups.each do |ops|
|
31
|
+
next if ops.empty?
|
32
|
+
alter_table_sql_list(name, ops).each{|sql| execute_ddl(sql)}
|
33
|
+
remove_cached_schema(name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -6,7 +6,7 @@ module Sequel
|
|
6
6
|
# ---------------------
|
7
7
|
|
8
8
|
# Array of supported database adapters
|
9
|
-
ADAPTERS = %w'ado amalgalite db2 dbi do firebird ibmdb informix jdbc mock mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
|
9
|
+
ADAPTERS = %w'ado amalgalite cubrid db2 dbi do firebird ibmdb informix jdbc mock mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
|
10
10
|
|
11
11
|
# Whether to use the single threaded connection pool by default
|
12
12
|
@@single_threaded = false
|