sequel 2.7.1 → 2.8.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 (50) hide show
  1. data/CHANGELOG +56 -0
  2. data/README +1 -0
  3. data/Rakefile +1 -1
  4. data/lib/sequel_core.rb +9 -16
  5. data/lib/sequel_core/adapters/ado.rb +6 -15
  6. data/lib/sequel_core/adapters/db2.rb +8 -10
  7. data/lib/sequel_core/adapters/dbi.rb +6 -4
  8. data/lib/sequel_core/adapters/informix.rb +21 -22
  9. data/lib/sequel_core/adapters/jdbc.rb +69 -10
  10. data/lib/sequel_core/adapters/jdbc/postgresql.rb +1 -0
  11. data/lib/sequel_core/adapters/mysql.rb +81 -13
  12. data/lib/sequel_core/adapters/odbc.rb +32 -4
  13. data/lib/sequel_core/adapters/openbase.rb +6 -5
  14. data/lib/sequel_core/adapters/oracle.rb +23 -7
  15. data/lib/sequel_core/adapters/postgres.rb +42 -32
  16. data/lib/sequel_core/adapters/shared/mssql.rb +37 -62
  17. data/lib/sequel_core/adapters/shared/mysql.rb +22 -7
  18. data/lib/sequel_core/adapters/shared/oracle.rb +27 -48
  19. data/lib/sequel_core/adapters/shared/postgres.rb +64 -43
  20. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  21. data/lib/sequel_core/adapters/shared/sqlite.rb +15 -4
  22. data/lib/sequel_core/adapters/sqlite.rb +6 -14
  23. data/lib/sequel_core/connection_pool.rb +47 -13
  24. data/lib/sequel_core/database.rb +60 -35
  25. data/lib/sequel_core/database/schema.rb +4 -4
  26. data/lib/sequel_core/dataset.rb +12 -3
  27. data/lib/sequel_core/dataset/convenience.rb +4 -13
  28. data/lib/sequel_core/dataset/prepared_statements.rb +30 -28
  29. data/lib/sequel_core/dataset/sql.rb +144 -85
  30. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  31. data/lib/sequel_core/dataset/unsupported.rb +31 -0
  32. data/lib/sequel_core/exceptions.rb +6 -0
  33. data/lib/sequel_core/schema/generator.rb +4 -3
  34. data/lib/sequel_core/schema/sql.rb +41 -23
  35. data/lib/sequel_core/sql.rb +29 -1
  36. data/lib/sequel_model/associations.rb +1 -1
  37. data/lib/sequel_model/record.rb +31 -28
  38. data/spec/adapters/mysql_spec.rb +37 -4
  39. data/spec/adapters/oracle_spec.rb +26 -4
  40. data/spec/adapters/sqlite_spec.rb +7 -0
  41. data/spec/integration/prepared_statement_test.rb +24 -0
  42. data/spec/integration/schema_test.rb +1 -1
  43. data/spec/sequel_core/connection_pool_spec.rb +49 -2
  44. data/spec/sequel_core/core_sql_spec.rb +9 -2
  45. data/spec/sequel_core/database_spec.rb +64 -14
  46. data/spec/sequel_core/dataset_spec.rb +105 -7
  47. data/spec/sequel_core/schema_spec.rb +40 -12
  48. data/spec/sequel_core/spec_helper.rb +1 -0
  49. data/spec/sequel_model/spec_helper.rb +1 -0
  50. metadata +6 -3
@@ -14,6 +14,9 @@ module Sequel
14
14
  when 'mssql'
15
15
  require 'sequel_core/adapters/shared/mssql'
16
16
  extend Sequel::MSSQL::DatabaseMethods
17
+ when 'progress'
18
+ require 'sequel_core/adapters/shared/progress'
19
+ extend Sequel::Progress::DatabaseMethods
17
20
  end
18
21
  end
19
22
 
@@ -37,10 +40,6 @@ module Sequel
37
40
  conn
38
41
  end
39
42
 
40
- def disconnect
41
- @pool.disconnect {|c| c.disconnect}
42
- end
43
-
44
43
  def dataset(opts = nil)
45
44
  ODBC::Dataset.new(self, opts)
46
45
  end
@@ -63,6 +62,35 @@ module Sequel
63
62
  synchronize(opts[:server]){|conn| conn.do(sql)}
64
63
  end
65
64
  alias_method :do, :execute_dui
65
+
66
+ # Support single level transactions on ODBC
67
+ def transaction(server=nil)
68
+ synchronize(server) do |conn|
69
+ return yield(conn) if @transactions.include?(Thread.current)
70
+ log_info(begin_transaction_sql)
71
+ conn.do(begin_transaction_sql)
72
+ begin
73
+ @transactions << Thread.current
74
+ yield(conn)
75
+ rescue ::Exception => e
76
+ log_info(rollback_transaction_sql)
77
+ conn.do(rollback_transaction_sql)
78
+ transaction_error(e)
79
+ ensure
80
+ unless e
81
+ log_info(commit_transaction_sql)
82
+ conn.do(commit_transaction_sql)
83
+ end
84
+ @transactions.delete(Thread.current)
85
+ end
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def disconnect_connection(c)
92
+ c.disconnect
93
+ end
66
94
  end
67
95
 
68
96
  class Dataset < Sequel::Dataset
@@ -15,11 +15,6 @@ module Sequel
15
15
  )
16
16
  end
17
17
 
18
- def disconnect
19
- # would this work?
20
- @pool.disconnect {|c| c.disconnect}
21
- end
22
-
23
18
  def dataset(opts = nil)
24
19
  OpenBase::Dataset.new(self, opts)
25
20
  end
@@ -33,6 +28,12 @@ module Sequel
33
28
  end
34
29
  end
35
30
  alias_method :do, :execute
31
+
32
+ private
33
+
34
+ def disconnect_connection(c)
35
+ c.disconnect
36
+ end
36
37
  end
37
38
 
38
39
  class Dataset < Sequel::Dataset
@@ -6,6 +6,12 @@ module Sequel
6
6
  class Database < Sequel::Database
7
7
  include DatabaseMethods
8
8
  set_adapter_scheme :oracle
9
+
10
+ # ORA-00028: your session has been killed
11
+ # ORA-01012: not logged on
12
+ # ORA-03113: end-of-file on communication channel
13
+ # ORA-03114: not connected to ORACLE
14
+ CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
9
15
 
10
16
  def connect(server)
11
17
  opts = server_opts(server)
@@ -21,10 +27,6 @@ module Sequel
21
27
  conn
22
28
  end
23
29
 
24
- def disconnect
25
- @pool.disconnect {|c| c.logoff}
26
- end
27
-
28
30
  def dataset(opts = nil)
29
31
  Oracle::Dataset.new(self, opts)
30
32
  end
@@ -32,9 +34,17 @@ module Sequel
32
34
  def execute(sql, opts={})
33
35
  log_info(sql)
34
36
  synchronize(opts[:server]) do |conn|
35
- r = conn.exec(sql)
36
- yield(r) if block_given?
37
- r
37
+ begin
38
+ r = conn.exec(sql)
39
+ yield(r) if block_given?
40
+ r
41
+ rescue OCIException => e
42
+ if CONNECTION_ERROR_CODES.include?(e.code)
43
+ raise(Sequel::DatabaseDisconnectError)
44
+ else
45
+ raise
46
+ end
47
+ end
38
48
  end
39
49
  end
40
50
  alias_method :do, :execute
@@ -57,6 +67,12 @@ module Sequel
57
67
  end
58
68
  end
59
69
  end
70
+
71
+ private
72
+
73
+ def disconnect_connection(c)
74
+ c.logoff
75
+ end
60
76
  end
61
77
 
62
78
  class Dataset < Sequel::Dataset
@@ -62,6 +62,9 @@ rescue LoadError => e
62
62
  def block(timeout=nil)
63
63
  end
64
64
  end
65
+ unless defined?(CONNECTION_OK)
66
+ CONNECTION_OK = -1
67
+ end
65
68
  end
66
69
  class PGresult
67
70
  alias_method :nfields, :num_fields unless method_defined?(:nfields)
@@ -97,7 +100,6 @@ module Sequel
97
100
  1083 => lambda{ |s| s.to_time }, # time without time zone
98
101
  1114 => lambda{ |s| s.to_sequel_time }, # timestamp without time zone
99
102
  1184 => lambda{ |s| s.to_sequel_time }, # timestamp with time zone
100
- 1186 => lambda{ |s| s.to_i }, # interval
101
103
  1266 => lambda{ |s| s.to_time }, # time with time zone
102
104
  1700 => lambda{ |s| s.to_d }, # numeric
103
105
  }
@@ -119,7 +121,12 @@ module Sequel
119
121
  # the date style to ISO in order make Date object creation in ruby faster,
120
122
  # if Postgres.use_iso_date_format is true.
121
123
  def apply_connection_settings
122
- async_exec("SET DateStyle = 'ISO'") if Postgres.use_iso_date_format
124
+ super
125
+ if Postgres.use_iso_date_format
126
+ sql = "SET DateStyle = 'ISO'"
127
+ @db.log_info(sql)
128
+ execute(sql)
129
+ end
123
130
  end
124
131
 
125
132
  # Execute the given SQL with this connection. If a block is given,
@@ -128,12 +135,16 @@ module Sequel
128
135
  q = nil
129
136
  begin
130
137
  q = args ? async_exec(sql, args) : async_exec(sql)
131
- rescue PGError => e
132
- raise if status == Adapter::CONNECTION_OK
133
- reset
134
- q = args ? async_exec(sql, args) : async_exec(sql)
138
+ rescue PGError
139
+ begin
140
+ s = status
141
+ rescue PGError
142
+ raise(Sequel::DatabaseDisconnectError)
143
+ end
144
+ status_ok = (s == Adapter::CONNECTION_OK)
145
+ status_ok ? raise : raise(Sequel::DatabaseDisconnectError)
135
146
  ensure
136
- block
147
+ block if status_ok
137
148
  end
138
149
  begin
139
150
  block_given? ? yield(q) : q.cmd_tuples
@@ -200,8 +211,8 @@ module Sequel
200
211
  conn.async_exec("set client_encoding to '#{encoding}'")
201
212
  end
202
213
  end
203
- conn.apply_connection_settings
204
214
  conn.db = self
215
+ conn.apply_connection_settings
205
216
  conn
206
217
  end
207
218
 
@@ -210,11 +221,6 @@ module Sequel
210
221
  Postgres::Dataset.new(self, opts)
211
222
  end
212
223
 
213
- # Disconnect all active connections.
214
- def disconnect
215
- @pool.disconnect {|c| c.finish}
216
- end
217
-
218
224
  # Execute the given SQL with the given args on an available connection.
219
225
  def execute(sql, opts={}, &block)
220
226
  return execute_prepared_statement(sql, opts, &block) if Symbol === sql
@@ -250,6 +256,14 @@ module Sequel
250
256
  super.merge(:pool_convert_exceptions=>false)
251
257
  end
252
258
 
259
+ # Disconnect given connection
260
+ def disconnect_connection(conn)
261
+ begin
262
+ conn.finish
263
+ rescue PGError
264
+ end
265
+ end
266
+
253
267
  # Execute the prepared statement with the given name on an available
254
268
  # connection, using the given args. If the connection has not prepared
255
269
  # a statement with the given name yet, prepare it. If the connection
@@ -329,11 +343,9 @@ module Sequel
329
343
  # Return an array of strings for each of the hash values, inserting
330
344
  # them to the correct position in the array.
331
345
  def map_to_prepared_args(hash)
332
- array = []
333
- @prepared_args.each{|k,v| array[v] = hash[k].to_s}
334
- array
346
+ @prepared_args.map{|k| hash[k.to_sym].to_s}
335
347
  end
336
-
348
+
337
349
  private
338
350
 
339
351
  # PostgreSQL most of the time requires type information for each of
@@ -343,21 +355,16 @@ module Sequel
343
355
  # elminate ambiguity (and PostgreSQL from raising an exception).
344
356
  def prepared_arg(k)
345
357
  y, type = k.to_s.split("__")
346
- "#{prepared_arg_placeholder}#{@prepared_args[y.to_sym]}#{"::#{type}" if type}".lit
347
- end
348
-
349
- # If the named argument has already been used, return the position in
350
- # the output array that it is mapped to. Otherwise, map it to the
351
- # next position in the array.
352
- def prepared_args_hash
353
- max_prepared_arg = 0
354
- Hash.new do |h,k|
355
- h[k] = max_prepared_arg
356
- max_prepared_arg += 1
358
+ if i = @prepared_args.index(y)
359
+ i += 1
360
+ else
361
+ @prepared_args << y
362
+ i = @prepared_args.length
357
363
  end
364
+ "#{prepared_arg_placeholder}#{i}#{"::#{type}" if type}".lit
358
365
  end
359
366
  end
360
-
367
+
361
368
  # Allow use of bind arguments for PostgreSQL using the pg driver.
362
369
  module BindArgumentMethods
363
370
  include ArgumentMapper
@@ -414,11 +421,14 @@ module Sequel
414
421
 
415
422
  # Prepare the given type of statement with the given name, and store
416
423
  # it in the database to be called later.
417
- def prepare(type, name, values=nil)
424
+ def prepare(type, name=nil, values=nil)
418
425
  ps = to_prepared_statement(type, values)
419
426
  ps.extend(PreparedStatementMethods)
420
- ps.prepared_statement_name = name
421
- db.prepared_statements[name] = ps
427
+ if name
428
+ ps.prepared_statement_name = name
429
+ db.prepared_statements[name] = ps
430
+ end
431
+ ps
422
432
  end
423
433
 
424
434
  private
@@ -35,6 +35,10 @@ module Sequel
35
35
  end
36
36
 
37
37
  module DatasetMethods
38
+ include Dataset::UnsupportedIntersectExcept
39
+
40
+ SELECT_CLAUSE_ORDER = %w'limit distinct columns from with join where group order having union'.freeze
41
+
38
42
  def complex_expression_sql(op, args)
39
43
  case op
40
44
  when :'||'
@@ -48,77 +52,48 @@ module Sequel
48
52
  filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
49
53
  end
50
54
 
51
- # Allows you to do .nolock on a query
52
- def nolock
53
- clone(:with => "(NOLOCK)")
54
- end
55
-
56
- # Formats a SELECT statement using the given options and the dataset
57
- # options.
58
- def select_sql(opts = nil)
59
- opts = opts ? @opts.merge(opts) : @opts
60
-
61
- if sql = opts[:sql]
62
- return sql
63
- end
64
-
65
- # ADD TOP to SELECT string for LIMITS
66
- if limit = opts[:limit]
67
- top = "TOP #{limit} "
68
- raise Error, "Offset not supported" if opts[:offset]
69
- end
70
-
71
- columns = opts[:select]
72
- # We had to reference const WILDCARD with its full path, because
73
- # the Ruby constant scope rules played against us (it was resolving it
74
- # as Sequel::Dataset::DatasetMethods::WILDCARD).
75
- select_columns = columns ? column_list(columns) : Sequel::Dataset::WILDCARD
76
-
77
- if distinct = opts[:distinct]
78
- distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{expression_list(distinct)})"
79
- sql = "SELECT #{top}#{distinct_clause} #{select_columns}"
55
+ def literal(v)
56
+ case v
57
+ when String
58
+ "N#{super}"
59
+ when Time
60
+ literal(v.iso8601)
61
+ when Date, DateTime
62
+ literal(v.to_s)
80
63
  else
81
- sql = "SELECT #{top}#{select_columns}"
82
- end
83
-
84
- if opts[:from]
85
- sql << " FROM #{source_list(opts[:from])}"
64
+ super
86
65
  end
66
+ end
87
67
 
88
- # ADD WITH to SELECT string for NOLOCK
89
- if with = opts[:with]
90
- sql << " WITH #{with}"
91
- end
68
+ def multi_insert_sql(columns, values)
69
+ values = values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")
70
+ ["INSERT INTO #{source_list(@opts[:from])} (#{identifier_list(columns)}) #{values}"]
71
+ end
92
72
 
93
- if join = opts[:join]
94
- join.each{|j| sql << literal(j)}
95
- end
73
+ # Allows you to do .nolock on a query
74
+ def nolock
75
+ clone(:with => "(NOLOCK)")
76
+ end
96
77
 
97
- if where = opts[:where]
98
- sql << " WHERE #{literal(where)}"
99
- end
78
+ def quoted_identifier(name)
79
+ "[#{name}]"
80
+ end
100
81
 
101
- if group = opts[:group]
102
- sql << " GROUP BY #{expression_list(group)}"
103
- end
82
+ private
104
83
 
105
- if order = opts[:order]
106
- sql << " ORDER BY #{expression_list(order)}"
107
- end
84
+ def select_clause_order
85
+ SELECT_CLAUSE_ORDER
86
+ end
108
87
 
109
- if having = opts[:having]
110
- sql << " HAVING #{literal(having)}"
111
- end
88
+ # MSSQL uses TOP for limit, with no offset support
89
+ def select_limit_sql(sql, opts)
90
+ raise(Error, "OFFSET not supported") if opts[:offset]
91
+ sql << " TOP #{opts[:limit]}" if opts[:limit]
92
+ end
112
93
 
113
- if union = opts[:union]
114
- sql << (opts[:union_all] ? \
115
- " UNION ALL #{union.sql}" : " UNION #{union.sql}")
116
- end
117
-
118
- raise Error, "Intersect not supported" if opts[:intersect]
119
- raise Error, "Except not supported" if opts[:except]
120
-
121
- sql
94
+ # MSSQL uses the WITH statement to lock tables
95
+ def select_with_sql(sql, opts)
96
+ sql << " WITH #{opts[:with]}" if opts[:with]
122
97
  end
123
98
  end
124
99
  end
@@ -14,15 +14,13 @@ module Sequel
14
14
  # Use MySQL specific syntax for rename column, set column type, and
15
15
  # drop index cases.
16
16
  def alter_table_sql(table, op)
17
- quoted_table = quote_identifier(table)
18
- quoted_name = quote_identifier(op[:name]) if op[:name]
19
17
  case op[:op]
20
18
  when :rename_column
21
- "ALTER TABLE #{quoted_table} CHANGE COLUMN #{quoted_name} #{quote_identifier(op[:new_name])} #{type_literal(op)}"
19
+ "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{quote_identifier(op[:new_name])} #{type_literal(op)}"
22
20
  when :set_column_type
23
- "ALTER TABLE #{quoted_table} CHANGE COLUMN #{quoted_name} #{quoted_name} #{type_literal(op)}"
21
+ "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{quote_identifier(op[:name])} #{type_literal(op)}"
24
22
  when :drop_index
25
- "#{drop_index_sql(table, op)} ON #{quoted_table}"
23
+ "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
26
24
  else
27
25
  super(table, op)
28
26
  end
@@ -50,7 +48,7 @@ module Sequel
50
48
  using = " USING #{index[:type]}" unless index[:type] == nil
51
49
  "UNIQUE " if index[:unique]
52
50
  end
53
- "CREATE #{index_type}INDEX #{index_name} ON #{quote_identifier(table_name)} #{literal(index[:columns])}#{using}"
51
+ "CREATE #{index_type}INDEX #{index_name} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}#{using}"
54
52
  end
55
53
 
56
54
  # Get version of MySQL server, used for determined capabilities.
@@ -75,9 +73,14 @@ module Sequel
75
73
 
76
74
  private
77
75
 
76
+ # MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers by default.
77
+ def upcase_identifiers_default
78
+ false
79
+ end
80
+
78
81
  # Use the MySQL specific DESCRIBE syntax to get a table description.
79
82
  def schema_parse_table(table_name, opts)
80
- self["DESCRIBE ?", table_name].map do |row|
83
+ self["DESCRIBE ?", SQL::Identifier.new(table_name)].map do |row|
81
84
  row.delete(:Extra)
82
85
  row[:allow_null] = row.delete(:Null) == 'YES'
83
86
  row[:default] = row.delete(:Default)
@@ -92,6 +95,8 @@ module Sequel
92
95
 
93
96
  # Dataset methods shared by datasets that use MySQL databases.
94
97
  module DatasetMethods
98
+ include Dataset::UnsupportedIntersectExcept
99
+
95
100
  BOOL_TRUE = '1'.freeze
96
101
  BOOL_FALSE = '0'.freeze
97
102
  COMMA_SEPARATOR = ', '.freeze
@@ -250,6 +255,16 @@ module Sequel
250
255
 
251
256
  sql
252
257
  end
258
+
259
+ private
260
+
261
+ # MySQL doesn't support DISTINCT ON
262
+ def select_distinct_sql(sql, opts)
263
+ if opts[:distinct]
264
+ raise(Error, "DISTINCT ON not supported by MySQL") unless opts[:distinct].empty?
265
+ sql << " DISTINCT"
266
+ end
267
+ end
253
268
  end
254
269
  end
255
270
  end