Class: Sequel::Dataset::PlaceholderLiteralizer

Inherits:
Object
  • Object
show all
Defined in:
lib/sequel/dataset/placeholder_literalizer.rb

Overview

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.

Defined Under Namespace

Classes: Argument, Recorder

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dataset, fragments, final_sql, arity) ⇒ PlaceholderLiteralizer

Save the dataset, array of SQL fragments, and ending SQL string.



154
155
156
157
158
159
160
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 154

def initialize(dataset, fragments, final_sql, arity)
  @dataset = dataset
  @fragments = fragments
  @final_sql = final_sql
  @arity = arity
  freeze
end

Class Method Details

.loader(dataset, &block) ⇒ Object

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.



149
150
151
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 149

def self.loader(dataset, &block)
  Recorder.new.loader(dataset, &block)
end

Instance Method Details

#all(*args, &block) ⇒ Object

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.



181
182
183
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 181

def all(*args, &block)
  @dataset.with_sql_all(sql(*args), &block)
end

#each(*args, &block) ⇒ Object

Run the SQL query for the given arguments, yielding each returned row to the block.



186
187
188
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 186

def each(*args, &block)
  @dataset.with_sql_each(sql(*args), &block)
end

#first(*args) ⇒ Object

Run the SQL query for the given arguments, returning the first row.



191
192
193
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 191

def first(*args)
  @dataset.with_sql_first(sql(*args))
end

#freezeObject

Freeze the fragments and final SQL when freezing the literalizer.



163
164
165
166
167
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 163

def freeze
  @fragments.freeze
  @final_sql.freeze
  super
end

#get(*args) ⇒ Object

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).



197
198
199
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 197

def get(*args)
  @dataset.with_sql_single_value(sql(*args))
end

#sql(*args) ⇒ Object

Return the SQL query to use for the given arguments.

Raises:



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 202

def sql(*args)
  raise Error, "wrong number of arguments (#{args.length} for #{@arity})" unless args.length == @arity
  s = String.new
  ds = @dataset
  @fragments.each do |sql, i, transformer|
    s << sql
    if i.is_a?(Integer)
      v = args.fetch(i)
      v = transformer.call(v) if transformer
    else
      v = i.call
    end
    ds.literal_append(s, v)
  end
  if sql = @final_sql
    s << sql
  end
  s
end

#with_datasetObject

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.



172
173
174
175
176
177
# File 'lib/sequel/dataset/placeholder_literalizer.rb', line 172

def with_dataset
  dataset = yield @dataset
  other = dup
  other.instance_variable_set(:@dataset, dataset)
  other.freeze
end