sequel 4.5.0 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +14 -0
  3. data/README.rdoc +0 -1
  4. data/doc/mssql_stored_procedures.rdoc +43 -0
  5. data/doc/release_notes/3.18.0.txt +2 -3
  6. data/doc/release_notes/3.9.0.txt +1 -1
  7. data/doc/release_notes/4.6.0.txt +30 -0
  8. data/doc/security.rdoc +7 -0
  9. data/lib/sequel/adapters/jdbc/h2.rb +4 -4
  10. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -0
  11. data/lib/sequel/adapters/oracle.rb +1 -0
  12. data/lib/sequel/adapters/shared/mssql.rb +94 -1
  13. data/lib/sequel/adapters/shared/mysql.rb +4 -4
  14. data/lib/sequel/adapters/shared/postgres.rb +4 -4
  15. data/lib/sequel/adapters/tinytds.rb +22 -4
  16. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -2
  17. data/lib/sequel/database/dataset_defaults.rb +1 -1
  18. data/lib/sequel/model/associations.rb +1 -1
  19. data/lib/sequel/version.rb +1 -1
  20. data/spec/adapters/mssql_spec.rb +35 -0
  21. data/spec/adapters/oracle_spec.rb +4 -4
  22. data/spec/adapters/postgres_spec.rb +93 -93
  23. data/spec/adapters/spec_helper.rb +3 -1
  24. data/spec/bin_spec.rb +2 -0
  25. data/spec/core/database_spec.rb +22 -22
  26. data/spec/core/dataset_spec.rb +8 -8
  27. data/spec/core/expression_filters_spec.rb +1 -1
  28. data/spec/core/mock_adapter_spec.rb +2 -2
  29. data/spec/core/schema_generator_spec.rb +3 -3
  30. data/spec/core/spec_helper.rb +3 -1
  31. data/spec/core_extensions_spec.rb +3 -1
  32. data/spec/extensions/auto_validations_spec.rb +17 -17
  33. data/spec/extensions/caching_spec.rb +4 -4
  34. data/spec/extensions/error_splitter_spec.rb +1 -1
  35. data/spec/extensions/hook_class_methods_spec.rb +6 -6
  36. data/spec/extensions/migration_spec.rb +53 -53
  37. data/spec/extensions/pagination_spec.rb +9 -9
  38. data/spec/extensions/pg_array_associations_spec.rb +2 -2
  39. data/spec/extensions/pg_array_spec.rb +2 -2
  40. data/spec/extensions/pg_hstore_spec.rb +15 -15
  41. data/spec/extensions/pg_interval_spec.rb +3 -3
  42. data/spec/extensions/pg_range_spec.rb +20 -20
  43. data/spec/extensions/pg_row_spec.rb +1 -1
  44. data/spec/extensions/schema_caching_spec.rb +3 -3
  45. data/spec/extensions/spec_helper.rb +3 -1
  46. data/spec/extensions/static_cache_spec.rb +16 -16
  47. data/spec/extensions/tree_spec.rb +8 -8
  48. data/spec/extensions/validation_class_methods_spec.rb +10 -10
  49. data/spec/integration/database_test.rb +3 -3
  50. data/spec/integration/dataset_test.rb +6 -0
  51. data/spec/integration/migrator_test.rb +67 -67
  52. data/spec/integration/model_test.rb +2 -2
  53. data/spec/integration/schema_test.rb +1 -1
  54. data/spec/integration/spec_helper.rb +3 -1
  55. data/spec/integration/transaction_test.rb +2 -2
  56. data/spec/model/association_reflection_spec.rb +4 -4
  57. data/spec/model/associations_spec.rb +1 -1
  58. data/spec/model/class_dataset_methods_spec.rb +1 -1
  59. data/spec/model/eager_loading_spec.rb +7 -0
  60. data/spec/model/model_spec.rb +4 -4
  61. data/spec/model/record_spec.rb +20 -20
  62. data/spec/model/spec_helper.rb +2 -1
  63. data/spec/model/validations_spec.rb +1 -1
  64. data/spec/rspec_helper.rb +18 -0
  65. metadata +8 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab0c1f081253f4b43e1a46f2132e9b516e2a67ba
4
- data.tar.gz: e6b2cd60d962bb23d4b6b03a5a1ef33f81762bd0
3
+ metadata.gz: 4da9d52fb246c1f0e83b88184dd82d4bf7b609cd
4
+ data.tar.gz: 041e624b4f998ebc9835898be941fadc12fda8c4
5
5
  SHA512:
6
- metadata.gz: d31e482c17d09830fc9895d61d3b9bc3f1bc527fe8939ea7856c98ce4b077ad842e73f8e7982708276898e6353b769fe281bdbb8becd82863b3f8e5e6c58fd7a
7
- data.tar.gz: 4c9791b19cab0084aead4cf4a57a5c822d937ad449208c5f10d4b64a8cc7f89c0effd799b1761f540fd1778fd4d8c399034d716dabd3141b6aae1551062df8ac
6
+ metadata.gz: d965c77370235e2ceee16aca1e1193b01d2eb259b179307a51f8218778dda40c4fb35cea667568ce7cfa4d5f95297396a2876ae744d3638d359b81dfa63a019a
7
+ data.tar.gz: 052c8d3db3bd66279b50cf455e6928eb9e753e49f273399f85d5d0e2fe238ecc33c97a1a95d1b56f5bca9cb670c1fedb9eeb95fdb68232ac032647eb370b7992
data/CHANGELOG CHANGED
@@ -1,3 +1,17 @@
1
+ === 4.6.0 (2014-01-02)
2
+
3
+ * Add Database#call_mssql_sproc on MSSQL for calling stored procedures and handling output parameters (jrgns, jeremyevans) (#748)
4
+
5
+ * Handle RuntimeErrors raised by oci8 in the oracle adapter (jeremyevans)
6
+
7
+ * Support OFFSET/FETCH on Microsoft SQL Server 2012 (jeremyevans)
8
+
9
+ * Support :server option for Database#{commit,rollback}_prepared_transaction on PostgreSQL, MySQL, and H2 (jeremyevans) (#743)
10
+
11
+ * Do not attempt to eager load and raise an exception when doing Model.eager(...).naked.all (jeremyevans)
12
+
13
+ * Recognize a couple additional disconnect errors in the jdbc/postgresql adapter (jeremyevans) (#742)
14
+
1
15
  === 4.5.0 (2013-12-02)
2
16
 
3
17
  * Support :on_commit=>(:drop|:delete_rows|:preserve_rows) options when creating temp tables on PostgreSQL (rosenfeld) (#737)
data/README.rdoc CHANGED
@@ -19,7 +19,6 @@ toolkit for Ruby.
19
19
  == Resources
20
20
 
21
21
  * {Website}[http://sequel.jeremyevans.net]
22
- * {Blog}[http://sequel.heroku.com]
23
22
  * {Source code}[http://github.com/jeremyevans/sequel]
24
23
  * {Bug tracking}[http://github.com/jeremyevans/sequel/issues]
25
24
  * {Google group}[http://groups.google.com/group/sequel-talk]
@@ -0,0 +1,43 @@
1
+ = Stored Procedures in MSSQL
2
+
3
+ This guide documents the workaround implemented to allow executing stored procedures
4
+ in MSSQL, as well as getting the value of output variables.
5
+
6
+ == Simple Execution
7
+
8
+ The following stored procedure is used as an example:
9
+
10
+ CREATE PROCEDURE dbo.SequelTest(
11
+ @Input varchar(25),
12
+ @Output int OUTPUT
13
+ )
14
+ AS
15
+ SET @Output = LEN(@Input)
16
+ RETURN 0
17
+
18
+ Execute it as follows:
19
+
20
+ DB.call_mssql_sproc(:SequelTest, {:args => ['Input String', :output]})
21
+
22
+ Use the +:output+ symbol to denote an output variable. The result will contain a
23
+ hash of the output variables, as well as the result code and number of affected rows:
24
+
25
+ {:result => 0, :numrows => 1, :var1 => "1"}
26
+
27
+ Output variables will be strings by default. To specify their type, include the
28
+ SQL type:
29
+
30
+ DB.call_mssql_sproc(:SequelTest, {:args => ['Input String', [:output, 'int']]})
31
+
32
+ Result:
33
+
34
+ {:result => 0, :numrows => 1, :var1 => 1}
35
+
36
+ Output variables will be named +var#{n}+ where n is their zero indexed position
37
+ in the parameter list. To name the output variable, include their name:
38
+
39
+ DB.call_mssql_sproc(:SequelTest, {:args => ['Input String', [:output, nil, 'Output']]})
40
+
41
+ Result:
42
+
43
+ {:result => 0, :numrows => 1, :output => "1"}
@@ -52,9 +52,8 @@
52
52
  the graphviz dot program in order to create visualizations
53
53
  of the dataset's abstract syntax tree. Examples:
54
54
 
55
- * http://sequel.heroku.com/images/to_dot_simple.gif
56
- * http://sequel.heroku.com/images/to_dot_complex.gif
57
- * http://imgpaste.com/i/lxngy.gif
55
+ * http://sequel.jeremyevans.net/images/to_dot_simple.gif
56
+ * http://sequel.jeremyevans.net/images/to_dot_complex.gif
58
57
 
59
58
  Both the to_dot extension and reversible migrations support
60
59
  were inspired by Aaron Patterson's recent work on ActiveRecord
@@ -230,4 +230,4 @@ Backwards Compatibility
230
230
  Other News
231
231
  ----------
232
232
 
233
- * Sequel now has an official blog at http://sequel.heroku.com.
233
+ * Sequel now has an official blog at http://sequel.jeremyevans.net/blog.html.
@@ -0,0 +1,30 @@
1
+ = New Features
2
+
3
+ * Database#call_mssql_sproc is now available for calling
4
+ stored procedures on Microsoft SQL Server, including the use
5
+ of output parameters.
6
+
7
+ * The Database#{commit,rollback}_prepared_transaction methods now
8
+ support a :server option for the server on which to operate.
9
+
10
+ = Other Improvements
11
+
12
+ * On Microsoft SQL Server 2012, the native OFFSET/FETCH support
13
+ is now used for offsets, instead of emulating support via the
14
+ ROW_NUMBER window function.
15
+
16
+ * Eager loading is now skipped when doing eager(...).naked.all on
17
+ a model dataset, instead of raising an error. This can fix issues
18
+ when the eager_each plugin is used.
19
+
20
+ * A couple additional disconnection errors are now detected in the
21
+ jdbc/postgresql adapter.
22
+
23
+ * The tinytds adapter now handles returning rows when the fields
24
+ are not immediately available.
25
+
26
+ * RuntimeErrors raised by oci8 are now handled correctly in the
27
+ oracle adapter.
28
+
29
+ * Sequel's specs now work with RSpec 3, while still running
30
+ correctly on RSpec 1.3 and 2.
data/doc/security.rdoc CHANGED
@@ -168,6 +168,13 @@ Instead, you should do:
168
168
  The Sequel::Dataset#lock_style method also treats an input string
169
169
  as SQL code. This method should not be called with user input.
170
170
 
171
+ ==== SQL Type Names
172
+
173
+ In general, most places where Sequel needs to use an SQL type that should
174
+ be specified by the user, it allows you to use a ruby string, and that
175
+ string is used verbatim as the SQL type. You should not use user input
176
+ for type strings.
177
+
171
178
  === SQL Identifier Injections
172
179
 
173
180
  Usually, Sequel treats ruby symbols as SQL identifiers, and ruby
@@ -9,8 +9,8 @@ module Sequel
9
9
 
10
10
  # Commit an existing prepared transaction with the given transaction
11
11
  # identifier string.
12
- def commit_prepared_transaction(transaction_id)
13
- run("COMMIT TRANSACTION #{transaction_id}")
12
+ def commit_prepared_transaction(transaction_id, opts=OPTS)
13
+ run("COMMIT TRANSACTION #{transaction_id}", opts)
14
14
  end
15
15
 
16
16
  # H2 uses the :h2 database type.
@@ -20,8 +20,8 @@ module Sequel
20
20
 
21
21
  # Rollback an existing prepared transaction with the given transaction
22
22
  # identifier string.
23
- def rollback_prepared_transaction(transaction_id)
24
- run("ROLLBACK TRANSACTION #{transaction_id}")
23
+ def rollback_prepared_transaction(transaction_id, opts=OPTS)
24
+ run("ROLLBACK TRANSACTION #{transaction_id}", opts)
25
25
  end
26
26
 
27
27
  # H2 uses an IDENTITY type
@@ -83,6 +83,10 @@ module Sequel
83
83
 
84
84
  private
85
85
 
86
+ def disconnect_error?(exception, opts)
87
+ super || exception.message =~ /\AThis connection has been closed\.\z|\AFATAL: terminating connection due to administrator command\z/
88
+ end
89
+
86
90
  # Use setNull for nil arguments as the default behavior of setString
87
91
  # with nil doesn't appear to work correctly on PostgreSQL.
88
92
  def set_ps_arg_nil(cps, i)
@@ -143,6 +143,7 @@ module Sequel
143
143
  end
144
144
 
145
145
  def database_specific_error_class(exception, opts)
146
+ return super unless exception.respond_to?(:code)
146
147
  case exception.code
147
148
  when 1400, 1407
148
149
  NotNullConstraintViolation
@@ -34,6 +34,51 @@ module Sequel
34
34
  # to :integer.
35
35
  DECIMAL_TYPE_RE = /number|numeric|decimal/io
36
36
 
37
+ # Execute the given stored procedure with the given name.
38
+ #
39
+ # Options:
40
+ # :args :: Array of arguments to stored procedure. Output parameters to
41
+ # the function are specified using :output. You can also name
42
+ # output parameters and provide a type by using an array containing
43
+ # :output, the type name, and the parameter name.
44
+ # :server :: The server/shard on which to execute the procedure.
45
+ #
46
+ # Examples:
47
+ #
48
+ # DB.call_mssql_sproc(:SequelTest, {:args => ['input arg', :output]})
49
+ # DB.call_mssql_sproc(:SequelTest, {:args => ['input arg', [:output, 'int', 'varname']]})
50
+ def call_mssql_sproc(name, opts=OPTS)
51
+ args = opts[:args] || []
52
+ names = ['@RC AS RESULT', '@@ROWCOUNT AS NUMROWS']
53
+ declarations = ['@RC int']
54
+ values = []
55
+
56
+ args.each_with_index do |v, i|
57
+ if v.is_a?(Array)
58
+ v, type, select = v
59
+ else
60
+ type = select = nil
61
+ end
62
+
63
+ if v == :output
64
+ type = "nvarchar(max)" unless type
65
+ varname = "var#{i}" unless varname
66
+ select ||= varname
67
+ names << "@#{varname} AS #{quote_identifier(select)}"
68
+ declarations << "@#{varname} #{type}"
69
+ values << "@#{varname} OUTPUT"
70
+ else
71
+ values << literal(v)
72
+ end
73
+ end
74
+
75
+ sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}"
76
+
77
+ ds = dataset.with_sql(sql)
78
+ ds = ds.server(opts[:server]) if opts[:server]
79
+ ds.first
80
+ end
81
+
37
82
  # Microsoft SQL Server uses the :mssql type.
38
83
  def database_type
39
84
  :mssql
@@ -457,6 +502,10 @@ module Sequel
457
502
  FORMAT_DATE = "'%Y%m%d'".freeze
458
503
  CROSS_APPLY = 'CROSS APPLY'.freeze
459
504
  OUTER_APPLY = 'OUTER APPLY'.freeze
505
+ OFFSET = " OFFSET ".freeze
506
+ ROWS = " ROWS".freeze
507
+ ROWS_ONLY = " ROWS ONLY".freeze
508
+ FETCH_NEXT = " FETCH NEXT ".freeze
460
509
 
461
510
  Sequel::Dataset.def_mutation_method(:disable_insert_output, :output, :module=>self)
462
511
 
@@ -607,6 +656,18 @@ module Sequel
607
656
  sql << BRACKET_OPEN << name.to_s.gsub(/\]/, DOUBLE_BRACKET_CLOSE) << BRACKET_CLOSE
608
657
  end
609
658
 
659
+ # On MSSQL 2012+ add a default order to the current dataset if an offset is used.
660
+ # The default offset emulation using a subquery would be used in the unordered
661
+ # case by default, and that also adds a default order, so it's better to just
662
+ # avoid the subquery.
663
+ def select_sql
664
+ if @opts[:offset] && !@opts[:order] && is_2012_or_later?
665
+ order(1).select_sql
666
+ else
667
+ super
668
+ end
669
+ end
670
+
610
671
  # The version of the database server.
611
672
  def server_version
612
673
  db.server_version(@opts[:server])
@@ -703,6 +764,11 @@ module Sequel
703
764
  server_version >= 10000000
704
765
  end
705
766
 
767
+ # Whether we are using SQL Server 2012 or later.
768
+ def is_2012_or_later?
769
+ server_version >= 11000000
770
+ end
771
+
706
772
  # Use strict ISO-8601 format with T between date and time,
707
773
  # since that is the format that is multilanguage and not
708
774
  # DATEFORMAT dependent.
@@ -730,6 +796,11 @@ module Sequel
730
796
  end
731
797
  end
732
798
  alias update_from_sql delete_from2_sql
799
+
800
+ # Microsoft SQL Server 2012 has native support for offsets, but only for ordered datasets.
801
+ def emulate_offset_with_row_number?
802
+ super && !(is_2012_or_later? && @opts[:order])
803
+ end
733
804
 
734
805
  # Return the first primary key for the current table. If this table has
735
806
  # multiple primary keys, this will only return one of them. Used by #_import.
@@ -800,7 +871,8 @@ module Sequel
800
871
  BOOL_TRUE
801
872
  end
802
873
 
803
- # MSSQL adds the limit before the columns
874
+ # MSSQL adds the limit before the columns, except on 2012+ when using
875
+ # offsets on ordered queries.
804
876
  def select_clause_methods
805
877
  SELECT_CLAUSE_METHODS
806
878
  end
@@ -816,6 +888,8 @@ module Sequel
816
888
  # to allow the limit to be a bound variable.
817
889
  def select_limit_sql(sql)
818
890
  if l = @opts[:limit]
891
+ return if is_2012_or_later? && @opts[:order] && @opts[:offset]
892
+
819
893
  if is_2005_or_later?
820
894
  sql << TOP_PAREN
821
895
  literal_append(sql, l)
@@ -840,6 +914,25 @@ module Sequel
840
914
  end
841
915
  end
842
916
 
917
+ # On 2012+ when there is an order with an offset, append the offset (and possible
918
+ # limit) at the end of the order clause.
919
+ def select_order_sql(sql)
920
+ super
921
+ if is_2012_or_later? && @opts[:order]
922
+ if o = @opts[:offset]
923
+ sql << OFFSET
924
+ literal_append(sql, o)
925
+ sql << ROWS
926
+
927
+ if l = @opts[:limit]
928
+ sql << FETCH_NEXT
929
+ literal_append(sql, l)
930
+ sql << ROWS_ONLY
931
+ end
932
+ end
933
+ end
934
+ end
935
+
843
936
  # SQL fragment for MSSQL's OUTPUT clause.
844
937
  def output_sql(sql)
845
938
  return unless supports_output_clause?
@@ -50,8 +50,8 @@ module Sequel
50
50
 
51
51
  # Commit an existing prepared transaction with the given transaction
52
52
  # identifier string.
53
- def commit_prepared_transaction(transaction_id)
54
- run("XA COMMIT #{literal(transaction_id)}")
53
+ def commit_prepared_transaction(transaction_id, opts=OPTS)
54
+ run("XA COMMIT #{literal(transaction_id)}", opts)
55
55
  end
56
56
 
57
57
  # MySQL uses the :mysql database type
@@ -112,8 +112,8 @@ module Sequel
112
112
 
113
113
  # Rollback an existing prepared transaction with the given transaction
114
114
  # identifier string.
115
- def rollback_prepared_transaction(transaction_id)
116
- run("XA ROLLBACK #{literal(transaction_id)}")
115
+ def rollback_prepared_transaction(transaction_id, opts=OPTS)
116
+ run("XA ROLLBACK #{literal(transaction_id)}", opts)
117
117
  end
118
118
 
119
119
  # Get version of MySQL server, used for determined capabilities.
@@ -156,8 +156,8 @@ module Sequel
156
156
 
157
157
  # Commit an existing prepared transaction with the given transaction
158
158
  # identifier string.
159
- def commit_prepared_transaction(transaction_id)
160
- run("COMMIT PREPARED #{literal(transaction_id)}")
159
+ def commit_prepared_transaction(transaction_id, opts=OPTS)
160
+ run("COMMIT PREPARED #{literal(transaction_id)}", opts)
161
161
  end
162
162
 
163
163
  # Creates the function in the database. Arguments:
@@ -426,8 +426,8 @@ module Sequel
426
426
 
427
427
  # Rollback an existing prepared transaction with the given transaction
428
428
  # identifier string.
429
- def rollback_prepared_transaction(transaction_id)
430
- run("ROLLBACK PREPARED #{literal(transaction_id)}")
429
+ def rollback_prepared_transaction(transaction_id, opts=OPTS)
430
+ run("ROLLBACK PREPARED #{literal(transaction_id)}", opts)
431
431
  end
432
432
 
433
433
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
@@ -230,11 +230,29 @@ module Sequel
230
230
  # various cases.
231
231
  def fetch_rows(sql)
232
232
  execute(sql) do |result|
233
- @columns = result.fields.map!{|c| output_identifier(c)}
234
- if db.timezone == :utc
235
- result.each(:timezone=>:utc){|r| yield r}
233
+ columns = result.fields.map!{|c| output_identifier(c)}
234
+ if columns.empty?
235
+ args = []
236
+ args << {:timezone=>:utc} if db.timezone == :utc
237
+ cols = nil
238
+ result.each(*args) do |r|
239
+ unless cols
240
+ cols = result.fields.map{|c| [c, output_identifier(c)]}
241
+ @columns = columns = cols.map{|c| c.last}
242
+ end
243
+ h = {}
244
+ cols.each do |s, sym|
245
+ h[sym] = r[s]
246
+ end
247
+ yield h
248
+ end
236
249
  else
237
- result.each{|r| yield r}
250
+ @columns = columns
251
+ if db.timezone == :utc
252
+ result.each(:timezone=>:utc){|r| yield r}
253
+ else
254
+ result.each{|r| yield r}
255
+ end
238
256
  end
239
257
  end
240
258
  self
@@ -9,8 +9,9 @@ module Sequel
9
9
  # If offset is used, an order must be provided, because the use of ROW_NUMBER
10
10
  # requires an order.
11
11
  def select_sql
12
- return super unless o = @opts[:offset]
12
+ return super unless emulate_offset_with_row_number?
13
13
 
14
+ offset = @opts[:offset]
14
15
  order = @opts[:order]
15
16
  if require_offset_order?
16
17
  order ||= default_offset_order
@@ -29,7 +30,7 @@ module Sequel
29
30
  from_self(:alias=>dsa1).
30
31
  select(*columns).
31
32
  limit(@opts[:limit]).
32
- where(SQL::Identifier.new(rn) > o).
33
+ where(SQL::Identifier.new(rn) > offset).
33
34
  order(rn))
34
35
  sql
35
36
  end
@@ -46,5 +47,10 @@ module Sequel
46
47
  def require_offset_order?
47
48
  true
48
49
  end
50
+
51
+ # Whether to use ROW_NUMBER to emulate offsets
52
+ def emulate_offset_with_row_number?
53
+ @opts[:offset]
54
+ end
49
55
  end
50
56
  end