sequel 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
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