sequel 5.11.0 → 5.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/doc/advanced_associations.rdoc +132 -14
  4. data/doc/postgresql.rdoc +14 -0
  5. data/doc/release_notes/5.12.0.txt +141 -0
  6. data/lib/sequel/adapters/ado/mssql.rb +1 -1
  7. data/lib/sequel/adapters/oracle.rb +5 -6
  8. data/lib/sequel/adapters/postgres.rb +18 -5
  9. data/lib/sequel/adapters/shared/mysql.rb +5 -5
  10. data/lib/sequel/adapters/sqlite.rb +0 -5
  11. data/lib/sequel/adapters/tinytds.rb +0 -5
  12. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +2 -5
  13. data/lib/sequel/core.rb +6 -1
  14. data/lib/sequel/dataset/graph.rb +25 -9
  15. data/lib/sequel/dataset/placeholder_literalizer.rb +47 -17
  16. data/lib/sequel/dataset/prepared_statements.rb +86 -18
  17. data/lib/sequel/dataset/sql.rb +5 -1
  18. data/lib/sequel/extensions/caller_logging.rb +79 -0
  19. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  20. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
  21. data/lib/sequel/model/associations.rb +56 -23
  22. data/lib/sequel/model/base.rb +3 -3
  23. data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
  24. data/lib/sequel/plugins/static_cache.rb +9 -8
  25. data/lib/sequel/plugins/tactical_eager_loading.rb +63 -1
  26. data/lib/sequel/version.rb +1 -1
  27. data/spec/adapters/oracle_spec.rb +44 -0
  28. data/spec/adapters/postgres_spec.rb +39 -0
  29. data/spec/core/dataset_spec.rb +23 -9
  30. data/spec/core/object_graph_spec.rb +314 -284
  31. data/spec/extensions/caller_logging_spec.rb +52 -0
  32. data/spec/extensions/eager_graph_eager_spec.rb +100 -0
  33. data/spec/extensions/finder_spec.rb +1 -1
  34. data/spec/extensions/prepared_statements_spec.rb +7 -12
  35. data/spec/extensions/static_cache_spec.rb +14 -0
  36. data/spec/extensions/tactical_eager_loading_spec.rb +262 -1
  37. data/spec/integration/associations_test.rb +72 -0
  38. data/spec/integration/dataset_test.rb +3 -3
  39. data/spec/model/eager_loading_spec.rb +90 -0
  40. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 935bcf2280ce67a62dfd6e705308c85324f9d82303f5144ccbede8883d0956e5
4
- data.tar.gz: 91dc87ca6afcaa82372233829919bab98227e2f7a7555bc0229811cfc829b482
3
+ metadata.gz: 5d316ca35fb36d4b3777021b4e3d6c108036f0bb1197ab7c9efc4529012858d1
4
+ data.tar.gz: 7f0e338f35027945f84cfeb9d6d997114e098f46d74b7b2fc3e32db34c4f5925
5
5
  SHA512:
6
- metadata.gz: f0540143323390f812a543e8a28fa2ec3184faad7ad5508e9d64cf49bf6642f09dd636f0356d8bd2a4ad632a21e5a4358eda096d86b08ddaae0f62cdf8bfc9fa
7
- data.tar.gz: f85a4c4880b34f737b00affaab544473e02d70052e70ddc438597455974b411a65093f2aa511af84558a15121f3032192df191677bc7ea4b1c0b779cda1714ae
6
+ metadata.gz: 8d5f3c64cf5e6361ced90c90c0cfd8e3f35cfb50e2e5acfd7b25b79728dbf207bf673e03662ab72e9208cd283675e278f126d27d656a7e2f7ec0baec7330a0b4
7
+ data.tar.gz: c741e9fd139c5e8b13c0c434f66836bbecba900f03eecddf559f1992d4155bfcb2af1e846425d9f495fc442f09e79c82e1933dc3a362f81caced379ef608e248
data/CHANGELOG CHANGED
@@ -1,3 +1,35 @@
1
+ === 5.12.0 (2018-08-31)
2
+
3
+ * Make constraint_validations extension respect Database#constraint_validations_table setting (jeremyevans)
4
+
5
+ * Make Sequel.extension load files from gems (jeremyevans)
6
+
7
+ * Map clob prepared statement argument type to OCI8::CLOB in the oracle adapter (pipistrellka) (#1534)
8
+
9
+ * Make Model.load_cache public in the static_cache plugin (AlexWayfer) (#1533)
10
+
11
+ * Enable support for NOWAIT on MariaDB 10.3+ (jeremyevans)
12
+
13
+ * Enable support for INTERSECT and EXCEPT on MariaDB 10.3+ (jeremyevans)
14
+
15
+ * Make tactical_eager_loading plugin handle automatic eager loading for associated objects created by eager_graph (jeremyevans)
16
+
17
+ * Cache eager_graph loader to speed up subsequent loads from the same dataset (jeremyevans)
18
+
19
+ * Add caller_logging database extension to log callers before queries, useful during development (jeremyevans)
20
+
21
+ * Add Database#call_procedure in the postgres adapter for calling PostgreSQL 11+ procedures (jeremyevans)
22
+
23
+ * Add eager_graph_eager plugin for chaining eager association loads after eager_graph association loads (jeremyevans)
24
+
25
+ * Support using Dataset#eager_graph in eager load callback for associations using join tables (jeremyevans)
26
+
27
+ * Make Dataset#graph handle existing selections without determinable aliases by forcing a subselect (jeremyevans)
28
+
29
+ * Freeze prepared statement arguments before returning the prepared statement (jeremyevans)
30
+
31
+ * Refactor emulated prepared statement internals to use a placeholder literalizer (jeremyevans)
32
+
1
33
  === 5.11.0 (2018-08-01)
2
34
 
3
35
  * Fix using the jdbc/sqlserver adapter on JRuby 9.2+ (jeremyevans)
@@ -1,29 +1,149 @@
1
1
  = Advanced Associations
2
2
 
3
- Sequel::Model's association support is very powerful and flexible, but using that power and flexibility
4
- often results in complexity.
3
+ Sequel::Model's association support is powerful and flexible, but it can be difficult for
4
+ new users to understand what the support enables. This guide shows off some of the more
5
+ advanced Sequel::Model association features.
5
6
 
6
7
  You should probably review the {Model Associations Basics and Options guide}[rdoc-ref:doc/association_basics.rdoc]
7
8
  before reviewing this guide.
8
9
 
9
- == Background: Sequel::Model association options
10
+ == Sequel::Model Eager Loading
11
+
12
+ Sequel::Model offers two different ways to perform eager loading, +eager+ and
13
+ +eager_graph+. +eager+ uses an SQL query per association, +eager_graph+ uses a single
14
+ SQL query containing JOINs.
15
+
16
+ Assuming the following associations:
17
+
18
+ Artist.one_to_many :albums
19
+ Album.one_to_many :tracks
20
+ Tracks.many_to_one :lyric
21
+
22
+ Let's say you wanted to load all artists and eagerly load the related albums, tracks, and lyrics.
23
+
24
+ Artist.eager(albums: {tracks: :lyric})
25
+ # 4 Queries:
26
+ # SELECT * FROM artists;
27
+ # SELECT * FROM albums WHERE (artist_id IN (...));
28
+ # SELECT * FROM tracks WHERE (album_id IN (...));
29
+ # SELECT * FROM lyrics WHERE (id IN (...));
30
+
31
+ Artist.eager_graph(albums: {tracks: :lyric})
32
+ # 1 Query:
33
+ # SELECT artists.id, artists.name, ...
34
+ # albums.id AS albums_id, albums.name AS albums_name, ...
35
+ # tracks.id AS tracks_id, tracks.name AS tracks_name, ...
36
+ # lyric.id AS lyric_id, ...
37
+ # FROM artists
38
+ # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
39
+ # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
40
+ # LEFT OUTER JOIN lyrics AS lyric ON (lyric.id = tracks.lyric_id);
41
+
42
+ In general, the recommendation is to use +eager+ unless you have a reason to use +eager_graph+.
43
+ +eager_graph+ is needed when you want to reference columns in an associated table. For example,
44
+ if you want to order the loading of returned artists based on the names of the albums, you cannot
45
+ do:
46
+
47
+ Artist.eager(albums: {tracks: :lyric}).order{albums[:name]}
48
+
49
+ because the initial query Sequel will use would be:
50
+
51
+ # SELECT * FROM artists ORDER BY albums.name;
52
+
53
+ and +albums+ is not a valid qualifier in such a query. In this situation, you must use +eager_graph+:
54
+
55
+ Artist.eager_graph(albums: {tracks: :lyric}).order{albums[:name]}
56
+
57
+ Whether +eager+ or +eager_graph+ performs better is association and database dependent. If
58
+ you are concerned about performance, you should try benchmarking both cases with appropriate
59
+ data to see which performs better.
60
+
61
+ === Mixing eager and eager_graph
62
+
63
+ Sequel offers the ability to mix +eager+ and +eager_graph+ when loading results. This can
64
+ be done at the main level by calling both +eager+ and +eager_graph+ on the same dataset:
65
+
66
+ Album.eager(:artist).eager_graph(:tracks)
67
+ # 2 Queries:
68
+ # SELECT albums.id, albums.name, ...
69
+ # artist.id AS artist_id, artist.name AS artist_name, ...
70
+ # FROM albums
71
+ # LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id);
72
+ # SELECT * FROM artists WHERE (id IN (...));
73
+
74
+ You can also use +eager+ to load initial associations, and +eager_graph+ to load
75
+ remaining associations, by using +eager_graph+ in an eager load callback:
76
+
77
+ Artist.eager(albums: {tracks: proc{|ds| ds.eager_graph(:lyric)}})
78
+ # 3 Queries:
79
+ # SELECT * FROM artists;
80
+ # SELECT * FROM albums WHERE (artist_id IN (...));
81
+ # SELECT tracks.id, tracks.name, ...
82
+ # lyric.id AS lyric_id, ...
83
+ # FROM tracks
84
+ # LEFT OUTER JOIN lyrics AS lyric ON (lyric.id = tracks.lyric_id)
85
+ # WHERE (tracks.album_id IN (...));
86
+
87
+ Using the +eager_graph_eager+ plugin, you can use +eager_graph+ to load the
88
+ initial associations, and +eager+ to load the remaining associations. When
89
+ you call +eager_graph_eager+, you must specify the dependency chain at
90
+ which to start the eager loading via +eager+:
91
+
92
+ Artist.plugin :eager_graph_eager
93
+ Artist.eager_graph(albums: :tracks).eager_graph_eager([:albums, :tracks], :lyric)
94
+ # 2 Queries:
95
+ # SELECT artists.id, artists.name, ...
96
+ # albums.id AS albums_id, albums.name AS albums_name, ...
97
+ # tracks.id AS tracks_id, tracks.name AS tracks_name, ...
98
+ # FROM artists
99
+ # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
100
+ # LEFT OUTER JOIN tracks ON (tracks.album_id= albums.id);
101
+ # SELECT * FROM lyrics WHERE (id IN (...));
102
+
103
+ These two approaches can also be nested, with +eager+ -> +eager_graph+ -> +eager+:
104
+
105
+ Album.plugin :eager_graph_eager
106
+ Artist.eager(albums: proc{|ds| ds.eager_graph(:tracks).eager_graph_eager([:tracks], :lyric)})
107
+ # 3 Queries:
108
+ # SELECT * FROM artists;
109
+ # SELECT albums.id, albums.name, ...
110
+ # tracks.id AS tracks_id, tracks.name AS tracks_name, ...
111
+ # FROM albums
112
+ # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
113
+ # WHERE (albums.artist_id IN (...));
114
+ # SELECT * FROM lyrics WHERE (id IN (...));
115
+
116
+ Or with 2 separate +eager_graph+ queries:
117
+
118
+ Artist.eager_graph(:albums).eager_graph_eager([:albums], :tracks=>proc{|ds| ds.eager_graph(:lyric)})
119
+ # 2 Queries:
120
+ # SELECT artists.id, artists.name, ...
121
+ # albums.id AS albums_id, albums.name AS albums_name, ...
122
+ # FROM artists
123
+ # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id);
124
+ # SELECT tracks.id, tracks.name, ...
125
+ # lyric.id AS lyric_id, ...
126
+ # FROM tracks
127
+ # LEFT OUTER JOIN lyrics AS lyric ON (lyric.id = tracks.lyric_id)
128
+ # WHERE (tracks.album_id IN (...));
129
+
130
+ == Sequel::Model Association Loading Options
10
131
 
11
132
  There are a bunch of advanced association options that are available to
12
133
  handle more complex cases. First we'll go over some of the simpler ones:
13
134
 
14
135
  All associations take a block that can be used to further filter/modify the
15
- default dataset. There's also an :eager_block option if you want to use
16
- a different block when eager loading via <tt>Dataset#eager</tt>. Association blocks are
17
- useful for things like:
136
+ default dataset:
18
137
 
19
138
  Artist.one_to_many :gold_albums, class: :Album do |ds|
20
139
  ds.where{copies_sold > 500000}
21
140
  end
22
141
 
23
- There are a whole bunch of options for changing how the association is eagerly
24
- loaded via <tt>Dataset#eager_graph</tt>: <tt>:graph_block</tt>, <tt>:graph_conditions</tt>,
25
- <tt>:graph_only_conditions</tt>, <tt>:graph_join_type</tt> (and <tt>:graph_join_table_*</tt> ones for
26
- JOINing to the join table in a many_to_many association).
142
+ There's also an :eager_block option if you want to use a different block when
143
+ eager loading via <tt>Dataset#eager</tt>.
144
+
145
+ There are many options for changing how the association is eagerly
146
+ loaded via <tt>Dataset#eager_graph</tt>:
27
147
 
28
148
  :graph_join_type :: The type of join to do (<tt>:inner</tt>, <tt>:left</tt>, <tt>:right</tt>)
29
149
  :graph_conditions :: Additional conditions to put on join (needs to be a
@@ -62,10 +182,6 @@ These can be used like this:
62
182
  # Handles the case where both key columns have the name artist_name, and you want to use
63
183
  # a JOIN USING
64
184
  Artist.one_to_many :albums, key: :artist_name, graph_only_conditions: [:artist_name]
65
-
66
- Remember, using +eager_graph+ is generally only necessary when you need to
67
- filter/order based on columns in an associated table, it is recommended to
68
- use +eager+ for eager loading if possible.
69
185
 
70
186
  One advantage of using +eager_graph+ is that you can easily filter/order
71
187
  on columns in an associated table on a per-query basis, using regular
@@ -83,6 +199,8 @@ For lazy loading (e.g. Model[1].association), the <tt>:dataset</tt> option can b
83
199
  to specify an arbitrary dataset (one that uses different keys, multiple keys,
84
200
  joins to other tables, etc.).
85
201
 
202
+ == Custom Eager Loaders
203
+
86
204
  For eager loading via +eager+, the <tt>:eager_loader</tt> option can be used to specify
87
205
  how to eagerly load a complex association. This is an extremely powerful
88
206
  option. Though it can often be verbose (compared to other things in Sequel),
@@ -337,6 +337,20 @@ rows that are distinct on just those columns:
337
337
  DB[:table].distinct(:id).all
338
338
  # SELECT DISTINCT ON ("id") * FROM "table"
339
339
 
340
+ === Calling PostgreSQL 11+ Procedures <tt>postgres only</tt>
341
+
342
+ PostgreSQL 11+ added support for procedures, which are different from the user defined
343
+ functions that PostgreSQL has historically supported. These procedures are
344
+ called via a special +CALL+ syntax, and Sequel supports them via
345
+ <tt>Database#call_procedure</tt>:
346
+
347
+ DB.call_procedure(:foo, 1, "bar")
348
+ # CALL foo(1, 'bar')
349
+
350
+ <tt>Database#call_procedure</tt> will return a hash of return values if
351
+ the procedure returns a result, or +nil+ if the procedure does not return
352
+ a result.
353
+
340
354
  === Using a Cursor to Process Large Datasets <tt>postgres only</tt>
341
355
 
342
356
  The postgres adapter offers a <tt>Dataset#use_cursor</tt> method to process large result sets
@@ -0,0 +1,141 @@
1
+ = New Features
2
+
3
+ * An eager_graph_eager plugin has been added, which allows you to
4
+ chain eager loads using separate queries to an existing dataset that
5
+ uses eager_graph. Given the following model associations:
6
+
7
+ Band.one_to_many :albums
8
+ Album.one_to_many :tracks
9
+
10
+ Let's say you wanted to return bands ordered by album name, and
11
+ eagerly load those albums, you can do that using:
12
+
13
+ Band.eager_graph(:albums).order{albums[:name]}
14
+
15
+ Let's say you also wanted to eagerly load the tracks for each album.
16
+ You could just add them to the eager_graph call:
17
+
18
+ Band.eager_graph(albums: :tracks).order{albums[:name]}
19
+
20
+ However, the bloats the result set, and you aren't ordering by the
21
+ track information, so a join is not required. The eager_graph_eager
22
+ plugin allows you to specify that the tracks be eagerly loaded in a
23
+ separate query after the eager_graph load of albums:
24
+
25
+ Band.eager_graph(:albums).
26
+ eager_graph_eager([:albums], :tracks).
27
+ order{albums[:name]}
28
+
29
+ eager_graph_eager's first argument is a dependency chain, specified
30
+ as an array of symbols. This specifies the point at which to
31
+ perform the eager load. The remaining arguments are arguments that
32
+ could be passed to Dataset#eager to specify what dependent
33
+ associations should be loaded at that point.
34
+
35
+ * A caller_logging Database extension has been added, which logs
36
+ caller information before queries, filtering out the internal
37
+ Sequel callers. Example:
38
+
39
+ DB.extension :caller_logging
40
+ DB[:table].first
41
+ # Logger:
42
+ # (0.000041s) (source: /path/to/app/foo/t.rb:12 in `get_first`)
43
+ # SELECT * FROM table LIMIT 1
44
+
45
+ You can further filter the caller lines by setting
46
+ Database#caller_logging_ignore to a regexp of additional caller
47
+ lines to ignore. This is useful if you have specific methods or
48
+ internal extensions/plugins that you would also like to ignore as
49
+ they obscure the code actually making the request.
50
+
51
+ DB.caller_logging_ignore = %r{/path/to/app/lib/plugins}
52
+
53
+ You can also format the caller before it is placed in the logger,
54
+ using caller_logging_formatter:
55
+
56
+ DB.caller_logging_formatter = lambda do |caller|
57
+ "(#{caller.sub(/\A\/path\/to\/app\//, '')})"
58
+ end
59
+ DB[:table].first
60
+ # Logger:
61
+ # (0.000041s) (foo/t.rb:12 in `get_first`) SELECT * FROM table LIMIT 1
62
+
63
+ * Database#call_procedure has been added to the postgres adapter, and
64
+ is usable on PostgreSQL 11+ for calling procedures created with
65
+ CREATE PROCEDURE.
66
+
67
+ DB.call_procedure(:foo, 1, "bar")
68
+ # CALL foo(1, 'bar')
69
+
70
+ This method will return a hash of results if the procedure returns
71
+ a result, or nil if it does not return a result.
72
+
73
+ = Other Improvements
74
+
75
+ * It is now possible to use Dataset#eager_graph in an eager load
76
+ callback for associations that use join tables. This allows you
77
+ to eager load some associations using separate queries and other
78
+ associations using joins. For example:
79
+
80
+ Band.eager(:albums=>proc{|ds| ds.eager_graph(:tracks)})
81
+
82
+ Will load the bands in one query, and load the albums and tracks
83
+ in a separate query using a join. Previously, this construction
84
+ worked only for associations that did not use join tables. It now
85
+ works for associations that use join tables, as long as existing
86
+ selected columns are not removed inside the callback.
87
+
88
+ * The tactical_eager_loading plugin now handles automatic eager
89
+ loading for associated objects that were created during the
90
+ load of dataset that uses eager_graph. When using the plugin,
91
+ the following code will now only execute 2 queries, instead of
92
+ issuing a separate query for each album to get the tracks for
93
+ the album.
94
+
95
+ artists = Artist.eager_graph(:albums).all
96
+ artists.each do |artist|
97
+ artist.albums.each do |album|
98
+ album.tracks
99
+ end
100
+ end
101
+
102
+ * Calling Dataset#graph with a dataset with existing selections where
103
+ the column aliases cannot be determined automatically now works
104
+ correctly by using a subselect. Previously, attempting to do this
105
+ would raise an exception. This allows the following code to work:
106
+
107
+ DB[:table].select_all(:table).select_append(expr).graph(...)
108
+
109
+ * Datasets now cache the EagerGraphLoader object that is generated to
110
+ convert arrays of hashes into an object graph, so that subsequent
111
+ eager loads on the same dataset do not need to recompute the same
112
+ information. Most EagerGraphLoader internal state is now frozen to
113
+ prevent unintentional modification.
114
+
115
+ * Sequel.extension now loads files from gems. Previously, it used
116
+ Kernel.require, which does not load files from gems.
117
+
118
+ * Adapters that emulate prepared statements using literalization now
119
+ use a placeholder literalizer and should execute significantly
120
+ faster. More prepared statement internal metadata is now frozen
121
+ to prevent unintentional modification.
122
+
123
+ * Dataset#intersect, #except, and #nowait are now supported on MariaDB
124
+ 10.3+.
125
+
126
+ * The constraint_validations extension now respects the
127
+ constraint_validations_table setting when adding metadata for the
128
+ constraint validations.
129
+
130
+ * In the oracle adapter, the clob prepared statement argument type is
131
+ now mapped to the OCI8::CLOB class, allowing the use of Oracle
132
+ procedures with clob output parameters.
133
+
134
+ * The Model.load_cache method in the static_cache plugin is now public.
135
+
136
+ = Backwards Compatibility
137
+
138
+ * The private Dataset#prepared_arg? method has been removed. It is no
139
+ longer necessary after the refactoring to the prepared statement
140
+ code. External adapters that currently call the method should be
141
+ updated to no longer call the method.
@@ -50,7 +50,7 @@ module Sequel
50
50
  # is necessary as ADO's default :provider uses a separate native
51
51
  # connection for each query.
52
52
  def insert(*values)
53
- return super if @opts[:sql] || @opts[:returning]
53
+ return super if (@opts[:sql] && !@opts[:prepared_sql]) || @opts[:returning]
54
54
  with_sql("SET NOCOUNT ON; #{insert_sql(*values)}; SELECT CAST(SCOPE_IDENTITY() AS INTEGER)").single_value
55
55
  end
56
56
 
@@ -115,7 +115,7 @@ module Sequel
115
115
 
116
116
  PS_TYPES = {'string'=>String, 'integer'=>Integer, 'float'=>Float,
117
117
  'decimal'=>Float, 'date'=>Time, 'datetime'=>Time,
118
- 'time'=>Time, 'boolean'=>String, 'blob'=>OCI8::BLOB}.freeze
118
+ 'time'=>Time, 'boolean'=>String, 'blob'=>OCI8::BLOB, 'clob'=>OCI8::CLOB}.freeze
119
119
  def cursor_bind_params(conn, cursor, args)
120
120
  i = 0
121
121
  args.map do |arg, type|
@@ -129,6 +129,10 @@ module Sequel
129
129
  arg = arg.to_f
130
130
  when ::Sequel::SQL::Blob
131
131
  arg = ::OCI8::BLOB.new(conn, arg)
132
+ when String
133
+ if type == 'clob'
134
+ arg = ::OCI8::CLOB.new(conn, arg)
135
+ end
132
136
  end
133
137
  cursor.bind_param(i, arg, PS_TYPES[type] || arg.class)
134
138
  arg
@@ -347,11 +351,6 @@ module Sequel
347
351
  i = prepared_args.length
348
352
  LiteralString.new(":#{i}")
349
353
  end
350
-
351
- # Always assume a prepared argument.
352
- def prepared_arg?(k)
353
- true
354
- end
355
354
  end
356
355
 
357
356
  BindArgumentMethods = prepared_statements_module(:bind, ArgumentMapper)
@@ -173,6 +173,15 @@ module Sequel
173
173
  end
174
174
  end
175
175
 
176
+ # Call a procedure with the given name and arguments. Returns a hash if the procedure
177
+ # returns a value, and nil otherwise. Example:
178
+ #
179
+ # DB.call_procedure(:foo, 1, 2)
180
+ # # CALL foo(1, 2)
181
+ def call_procedure(name, *args)
182
+ dataset.send(:call_procedure, name, args)
183
+ end
184
+
176
185
  # Connects to the database. In addition to the standard database
177
186
  # options, using the :encoding or :charset option changes the
178
187
  # client encoding for the connection, :connect_timeout is a
@@ -672,11 +681,6 @@ module Sequel
672
681
  end
673
682
  LiteralString.new("#{prepared_arg_placeholder}#{i}")
674
683
  end
675
-
676
- # Always assume a prepared argument.
677
- def prepared_arg?(k)
678
- true
679
- end
680
684
  end
681
685
 
682
686
  BindArgumentMethods = prepared_statements_module(:bind, [ArgumentMapper], %w'execute execute_dui')
@@ -701,6 +705,15 @@ module Sequel
701
705
 
702
706
  private
703
707
 
708
+ # Generate and execute a procedure call.
709
+ def call_procedure(name, args)
710
+ sql = String.new
711
+ sql << "CALL "
712
+ identifier_append(sql, name)
713
+ literal_append(sql, args)
714
+ with_sql_first(sql)
715
+ end
716
+
704
717
  # Use a cursor to fetch groups of records at a time, yielding them to the block.
705
718
  def cursor_fetch_rows(sql)
706
719
  server_opts = {:server=>@opts[:server] || :read_only}