sequel 3.39.0 → 3.40.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.
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