Class | Sequel::Dataset::PlaceholderLiteralizer |
In: |
lib/sequel/dataset/placeholder_literalizer.rb
|
Parent: | Object |
PlaceholderLiteralizer allows you to record the application of arbitrary changes to a dataset with placeholder arguments, recording where those placeholder arguments are used in the query. When running the query, the literalization process is much faster as Sequel can skip most of the work it normally has to do when literalizing a dataset.
Basically, this enables optimizations that allow Sequel to cache the SQL produced for a given dataset, so that it doesn‘t need to recompute that information every time.
Example:
loader = Sequel::Dataset::PlaceholderLiteralizer.loader(DB[:items]) do |pl, ds| ds.where(id: pl.arg).exclude(name: pl.arg).limit(1) end loader.first(1, "foo") # SELECT * FROM items WHERE ((id = 1) AND (name != 'foo')) LIMIT 1 loader.first(2, "bar") # SELECT * FROM items WHERE ((id = 2) AND (name != 'bar')) LIMIT 1
Caveats:
Note that this method does not handle all possible cases. For example:
loader = Sequel::Dataset::PlaceholderLiteralizer.loader(DB[:items]) do |pl, ds| ds.join(pl.arg, item_id: :id) end loader.all(:cart_items)
Will not qualify the item_id column with cart_items. In this type of situation it‘s best to add a table alias when joining:
loader = Sequel::Dataset::PlaceholderLiteralizer.loader(DB[:items]) do |pl, ds| ds.join(Sequel.as(pl.arg, :t), item_id: :id) end loader.all(:cart_items)
There are other similar cases that are not handled, mainly when Sequel changes the SQL produced depending on the types of the arguments.
Create a PlaceholderLiteralizer by yielding a Recorder and dataset to the given block, recording the offsets at which the recorders arguments are used in the query.
# File lib/sequel/dataset/placeholder_literalizer.rb, line 119 119: def self.loader(dataset, &block) 120: Recorder.new.loader(dataset, &block) 121: end
Save the dataset, array of SQL fragments, and ending SQL string.
# File lib/sequel/dataset/placeholder_literalizer.rb, line 124 124: def initialize(dataset, fragments, final_sql, arity) 125: @dataset = dataset 126: @fragments = fragments 127: @final_sql = final_sql 128: @arity = arity 129: freeze 130: end
Return an array of all objects by running the SQL query for the given arguments. If a block is given, yields all objects to the block after loading them.
# File lib/sequel/dataset/placeholder_literalizer.rb, line 148 148: def all(*args, &block) 149: @dataset.with_sql_all(sql(*args), &block) 150: end
Run the SQL query for the given arguments, returning the first value. For this to make sense, the dataset should return a single row with a single value (or no rows).
# File lib/sequel/dataset/placeholder_literalizer.rb, line 164 164: def get(*args) 165: @dataset.with_sql_single_value(sql(*args)) 166: end
Return the SQL query to use for the given arguments.
# File lib/sequel/dataset/placeholder_literalizer.rb, line 169 169: def sql(*args) 170: raise Error, "wrong number of arguments (#{args.length} for #{@arity})" unless args.length == @arity 171: s = String.new 172: ds = @dataset 173: @fragments.each do |sql, i, transformer| 174: s << sql 175: if i.is_a?(Integer) 176: v = args.fetch(i) 177: v = transformer.call(v) if transformer 178: else 179: v = i.call 180: end 181: ds.literal_append(s, v) 182: end 183: if sql = @final_sql 184: s << sql 185: end 186: s 187: end
Return a new PlaceholderLiteralizer with a modified dataset. This yields the receiver‘s dataset to the block, and the block should return the new dataset to use.
# File lib/sequel/dataset/placeholder_literalizer.rb, line 142 142: def with_dataset 143: dup.instance_exec{@dataset = yield @dataset; self}.freeze 144: end