sequel 3.39.0 → 3.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/CHANGELOG +30 -0
  2. data/README.rdoc +4 -3
  3. data/doc/active_record.rdoc +1 -1
  4. data/doc/opening_databases.rdoc +7 -0
  5. data/doc/release_notes/3.40.0.txt +73 -0
  6. data/lib/sequel/adapters/ado.rb +29 -3
  7. data/lib/sequel/adapters/ado/access.rb +334 -0
  8. data/lib/sequel/adapters/ado/mssql.rb +0 -6
  9. data/lib/sequel/adapters/cubrid.rb +143 -0
  10. data/lib/sequel/adapters/jdbc.rb +26 -18
  11. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  12. data/lib/sequel/adapters/jdbc/derby.rb +7 -7
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +9 -4
  15. data/lib/sequel/adapters/mysql.rb +0 -3
  16. data/lib/sequel/adapters/mysql2.rb +0 -3
  17. data/lib/sequel/adapters/oracle.rb +4 -1
  18. data/lib/sequel/adapters/postgres.rb +4 -4
  19. data/lib/sequel/adapters/shared/access.rb +205 -3
  20. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  21. data/lib/sequel/adapters/shared/db2.rb +7 -2
  22. data/lib/sequel/adapters/shared/mssql.rb +3 -34
  23. data/lib/sequel/adapters/shared/mysql.rb +4 -33
  24. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +11 -0
  25. data/lib/sequel/adapters/shared/oracle.rb +5 -0
  26. data/lib/sequel/adapters/shared/postgres.rb +2 -1
  27. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  28. data/lib/sequel/database/connecting.rb +1 -1
  29. data/lib/sequel/database/query.rb +30 -7
  30. data/lib/sequel/database/schema_methods.rb +7 -2
  31. data/lib/sequel/dataset/query.rb +9 -10
  32. data/lib/sequel/dataset/sql.rb +14 -26
  33. data/lib/sequel/extensions/pg_hstore.rb +19 -0
  34. data/lib/sequel/extensions/pg_row.rb +5 -5
  35. data/lib/sequel/plugins/association_pks.rb +121 -18
  36. data/lib/sequel/plugins/json_serializer.rb +19 -0
  37. data/lib/sequel/sql.rb +11 -0
  38. data/lib/sequel/version.rb +1 -1
  39. data/spec/adapters/postgres_spec.rb +42 -0
  40. data/spec/core/database_spec.rb +17 -0
  41. data/spec/core/dataset_spec.rb +11 -0
  42. data/spec/core/expression_filters_spec.rb +13 -0
  43. data/spec/extensions/association_pks_spec.rb +163 -3
  44. data/spec/extensions/pg_hstore_spec.rb +6 -0
  45. data/spec/extensions/pg_row_spec.rb +17 -0
  46. data/spec/integration/associations_test.rb +1 -1
  47. data/spec/integration/dataset_test.rb +13 -13
  48. data/spec/integration/plugin_test.rb +232 -7
  49. data/spec/integration/schema_test.rb +8 -12
  50. data/spec/integration/spec_helper.rb +1 -1
  51. data/spec/integration/type_test.rb +6 -0
  52. 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/foreign key
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, :foreign_key].include? c[:type]}
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/emulate_offset_with_row_number'
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] || []
@@ -178,6 +178,11 @@ module Sequel
178
178
  def temporary_table_sql
179
179
  TEMPORARY
180
180
  end
181
+
182
+ # Oracle uses clob for text types.
183
+ def uses_clob_for_text?
184
+ true
185
+ end
181
186
  end
182
187
 
183
188
  module DatasetMethods
@@ -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