/usr/lib/ruby/vendor_ruby/sequel/dataset/placeholder_literalizer.rb is in ruby-sequel 4.15.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | module Sequel
class Dataset
# 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 normal 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(: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(: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.
class PlaceholderLiteralizer
# A placeholder argument used by the PlaceholderLiteralizer. This records the offset
# that the argument should be used in the resulting SQL.
class Argument
# Set the recorder, the argument position, and any transforming block to use
# for this placeholder.
def initialize(recorder, pos, transformer=nil)
@recorder = recorder
@pos = pos
@transformer = transformer
end
# Record the SQL query offset, argument position, and transforming block where the
# argument should be literalized.
def sql_literal_append(ds, sql)
if ds.opts[:placeholder_literal_null]
ds.send(:literal_append, sql, nil)
else
@recorder.use(sql, @pos, @transformer)
end
end
# Return a new Argument object for the same recorder and argument position, but with a
# different transformer block.
def transform(&block)
Argument.new(@recorder, @pos, block)
end
end
# Records the offsets at which the placeholder arguments are used in
# the SQL query.
class Recorder
# Yields the receiver and the dataset to the block, which should
# call #arg on the receiver for each placeholder argument, and
# return the dataset that you want to load.
def loader(dataset)
@argn = -1
@args = []
ds = yield self, dataset
sql = ds.clone(:placeholder_literalizer=>self).sql
last_offset = 0
fragments = @args.map do |used_sql, offset, arg, t|
raise Error, "placeholder literalizer argument literalized into different string than dataset returned" unless used_sql.equal?(sql)
a = [sql[last_offset...offset], arg, t]
last_offset = offset
a
end
final_sql = sql[last_offset..-1]
arity = @argn+1
PlaceholderLiteralizer.new(ds.clone, fragments, final_sql, arity)
end
# Return an Argument with the specified position, or the next position. In
# general you shouldn't mix calls with an argument and calls without an
# argument for the same receiver.
def arg(v=(no_arg_given = true; @argn+=1))
unless no_arg_given
@argn = v if @argn < v
end
Argument.new(self, v)
end
# Record the offset at which the argument is used in the SQL query, and any
# transforming
def use(sql, arg, transformer)
@args << [sql, sql.length, arg, transformer]
end
end
# 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.
def self.loader(dataset, &block)
Recorder.new.loader(dataset, &block)
end
# Save the dataset, array of SQL fragments, and ending SQL string.
def initialize(dataset, fragments, final_sql, arity)
@dataset = dataset
@fragments = fragments
@final_sql = final_sql
@arity = arity
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.
def with_dataset
dup.instance_exec{@dataset = yield @dataset; self}
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.
def all(*args, &block)
@dataset.with_sql_all(sql(*args), &block)
end
# Run the SQL query for the given arguments, yielding each returned row to the block.
def each(*args, &block)
@dataset.with_sql_each(sql(*args), &block)
end
# Run the SQL query for the given arguments, returning the first row.
def first(*args)
@dataset.with_sql_first(sql(*args))
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).
def get(*args)
@dataset.with_sql_single_value(sql(*args))
end
# Return the SQL query to use for the given arguments.
def sql(*args)
raise Error, "wrong number of arguments (#{args.length} for #{@arity})" unless args.length == @arity
s = ''
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
end
end
end
|