Module: Queris

Included in:
Model
Defined in:
lib/queris.rb,
lib/queris/model.rb,
lib/queris/query.rb,
lib/queris/errors.rb,
lib/queris/indices.rb,
lib/queris/version.rb,
lib/queris/profiler.rb,
lib/queris/mixin/ohm.rb,
lib/queris/query/page.rb,
lib/queris/query/timer.rb,
lib/queris/query/trace.rb,
lib/queris/query_store.rb,
lib/queris/mixin/object.rb,
lib/rails/log_subscriber.rb,
lib/rails/request_timing.rb,
lib/queris/query/operations.rb,
lib/queris/mixin/queris_model.rb,
lib/queris/mixin/active_record.rb

Overview

Queris is a querying and indexing tool for various Ruby objects.

Defined Under Namespace

Modules: ActiveRecordMixin, ControllerRuntime, ObjectMixin, OhmMixin, QuerisModelMixin Classes: AccumulatorIndex, AccumulatorSearchIndex, ActiveRecordQuery, ClientError, CountIndex, DecayingAccumulatorIndex, DecayingAccumulatorSearchIndex, DummyProfiler, Error, ExpiringPresenceIndex, ForeignIndex, HashCache, Index, LogSubscriber, Model, NotImplemented, PresenceIndex, Profiler, QuerisModelQuery, Query, QueryProfiler, QueryProfilerBase, QueryProfilerLite, QueryStore, RangeIndex, RedisError, RedisStats, SchemaError, ScoredSearchIndex, SearchIndex, ServerError

Constant Summary collapse

VERSION =
"0.8.1"

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.debugObject

Returns the value of attribute debug.



22
23
24
# File 'lib/queris.rb', line 22

def debug
  @debug
end

.modelsObject

Returns the value of attribute models.



207
208
209
# File 'lib/queris.rb', line 207

def models
  @models
end

.redis_scriptsObject

Returns the value of attribute redis_scripts.



21
22
23
# File 'lib/queris.rb', line 21

def redis_scripts
  @redis_scripts
end

Class Method Details

.add_redis(redis, *roles) ⇒ Object



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
# File 'lib/queris.rb', line 72

def add_redis(redis, *roles)
  if !(Redis === redis) && (Redis === roles.first) # flipped aguments. that's okay, we accept those, too
    redis, roles = roles.first, [ redis ]
  end
  roles << :master if roles.empty?
  roles = [] if roles.length == 1 && !roles.first
  @redis_connections << redis
  roles.each do |role|
    role = role.to_sym
    @redis_by_role[role]||=[]
    @redis_by_role[role] << redis
  end
  
  #throw our lua scripts onto the server
  redis_scripts.each do |name, contents|
    load_lua_script redis, name, contents
  end
  
  def track_stats?
    @track_stats
  end
  def track_stats!
    @track_stats = true
  end
  def stats
    return false unless @track_stats
    puts RedisStats.summary
    return RedisStats
  end
  attr_accessor :log_stats_per_request
  def log_stats_per_request?
    @log_stats_per_request
  end
  def log_stats_per_request!
    track_stats!
    @log_stats_per_request = true
  end
  
  #bolt on our custom logger
  class << redis.client
    protected
    alias :_default_logging :logging
    if Object.const_defined?('ActiveSupport') && ActiveSupport.const_defined?("Notifications")
      #the following is one ugly monkey(patch).
      # we assume that, since we're in Railsworld, the Redis logger
      # is up for grabs. It would be cleaner to wrap the redis client in a class, 
      # but I'm coding dirty for brevity. 
      # THIS MUST BE ADDRESSED IN THE FUTURE
      def logging(commands)
        ActiveSupport::Notifications.instrument("command.queris") do
          start = Time.now.to_f
          ret = _default_logging(commands) { yield }
          Queris::RedisStats.record(self, Time.now.to_f - start) if Queris.track_stats?
          ret
        end
      end
    else
      def logging(commands)
        start = Time.now.to_f
        ret = _default_logging(commands) { yield }
        Queris::RedisStats.record(self, Time.now.to_f - start) if Queris.track_stats?
        ret
      end
    end
  end
  redis
end

.all_redisesObject



197
# File 'lib/queris.rb', line 197

def all_redises; @redis_connections; end

.build_missing_indices!Object



170
171
172
173
174
175
# File 'lib/queris.rb', line 170

def build_missing_indices!
  @models.each do |model|
    model.build_missing_redis_indices
  end
  self
end

.clear!Object



154
155
156
# File 'lib/queris.rb', line 154

def clear!
  clear_cache! + clear_queries!
end

.clear_cache!Object



140
141
142
143
144
# File 'lib/queris.rb', line 140

def clear_cache!
  cleared = 0
  @models.each { |model| cleared += model.clear_cache! }
  cleared
end

.clear_queries!Object



146
147
148
149
150
151
152
# File 'lib/queris.rb', line 146

def clear_queries!
  cleared = 0
  @models.each do |model|
    cleared += model.clear_queries! || 0
  end
  cleared
end

.debug?Boolean

Returns:

  • (Boolean)


26
# File 'lib/queris.rb', line 26

def debug?; @debug; end

.digest(val) ⇒ Object



23
24
25
# File 'lib/queris.rb', line 23

def digest(val)
  Digest::SHA1.hexdigest(val.to_s)
end

.disconnectObject



166
167
168
# File 'lib/queris.rb', line 166

def disconnect
  all_redises.each { |r| r.client.disconnect }
end

.duplicate_redis_client(redis, role = false) ⇒ Object

returns another connection to the same server

Raises:

  • (RedisException)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/queris.rb', line 34

def duplicate_redis_client(redis, role=false)
  raise RedisException, "No redis client to duplicate."  unless redis
  raise RedisException, "Not a redis client" unless Redis === redis
  cl = redis.client
  raise RedisException, "Redis client doesn't have connection info (Can't get client info while in a redis.multi block... for now...)" unless cl.host
  r = Redis.new({
                port:      cl.port,
                host:      cl.host,
                path:      cl.path,
                timeout:   cl.timeout,
                password:  cl.password,
                db:        cl.db })
  add_redis r, role
end

.from_redis_float(val) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/queris.rb', line 258

def from_redis_float(val)
  case val
  when "inf", "+inf"
    Float::INFINITY
  when "-inf"
    -Float::INFINITY
  when "nan"
    Float::NAN
  else
    val.to_f
  end
end

.import_lua_script(name, contents) ⇒ Object



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/queris.rb', line 295

def import_lua_script(name, contents)
  name=name.to_sym
  if redis_scripts[name]
    if redis_scripts[name]==contents
      raise Queris::Error, "Tried loading script #{name} more than once. this is disallowed."
    else
      raise Queris::Error, "A redis lua script names #{name} already exists."
    end
  else
    redis_scripts[name]=contents
  end
  all_redises.each do |r|
    binding.pry
    1+1.12
    load_lua_script r, name, contents
  end
end

.included(base) ⇒ Object



220
221
222
223
224
225
226
227
228
229
# File 'lib/queris.rb', line 220

def included(base)
  base.send :include, ObjectMixin
  if const_defined?('ActiveRecord') and base.superclass == ActiveRecord::Base then
    require "queris/mixin/active_record"
    base.send :include, ActiveRecordMixin
  elsif const_defined?('Ohm') and base.superclass == Ohm::Model
    require "queris/mixin/ohm"
    base.send :include, OhmMixin
  end
end

.infoObject



158
159
160
# File 'lib/queris.rb', line 158

def info
  models.each &:info
end

.load_lua_script(redis, name, contents) ⇒ Object

Raises:



62
63
64
65
66
67
68
69
# File 'lib/queris.rb', line 62

def load_lua_script(redis, name, contents)
  begin
    hash = redis.script 'load', contents
  rescue Redis::CommandError => e
    raise ClientError, "Error loading script #{name}: #{e}"
  end
  raise Error, "Failed loading script #{name} onto server: mismatched hash" unless script_hash(name) == hash
end

.model(model_name) ⇒ Object



216
217
218
# File 'lib/queris.rb', line 216

def model(model_name)
  @model_lookup[model_name.to_sym]
end

.rebuild!(clear = false) ⇒ Object

rebuild all known queris indices



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/queris.rb', line 178

def rebuild!(clear=false)
  start = Time.now
  if Object.const_defined? 'Rails'
    Dir.glob("#{Rails.root}/app/models/*.rb").sort.each { |file| require_dependency file } #load all models
  end
  @models.each do |model| 
    if clear
      delkeys = redis.keys "#{model.prefix}*"
      redis.multi do |r|
        delkeys.each { |k| redis.del k }
      end
      puts "Deleted #{delkeys.count} #{self.name} keys for #{model.name}."
    end
    model.build_redis_indices nil, false
  end
  printf "All redis indices rebuilt in %.2f sec.\r\n", Time.now-start
  self
end

.reconnectObject

reconnect all redic clients



163
164
165
# File 'lib/queris.rb', line 163

def reconnect
  all_redises.each { |r| r.client.reconnect }
end

.redis(*redis_roles) ⇒ Object

retrieve redis connection matching given redis server role, in order or decreasing preference



29
30
31
# File 'lib/queris.rb', line 29

def redis(*redis_roles)
  redises(*redis_roles).sample || ($redis.kind_of?(Redis) ? $redis : nil) #for backwards compatibility with crappy old globals-using code.
end

.redis_prefix(app_name = nil) ⇒ Object



231
232
233
234
235
236
237
238
239
240
# File 'lib/queris.rb', line 231

def redis_prefix(app_name=nil)
#i'm using a simple string-concatenation key prefix scheme. I could have used something like Nest, but it seemed excessive.
  if Object.const_defined? 'Rails'
    "Rails:#{app_name || Rails.application.class.parent.to_s}:#{self.name}:"
  elsif app_name.nil?
    "#{self.name}:"
  else
    "#{app_name}:#{self.name}:"
  end
end

.redis_role(redis) ⇒ Object



198
199
200
201
202
203
204
205
206
# File 'lib/queris.rb', line 198

def redis_role(redis)
  @redis_by_role.each do |role, redises|
    if Redis::Client === redis
      return role if redises.map{|r| r.client}.member? redis
    else
      return role if redises.member? redis
    end
  end
end

.redises(*redis_roles) ⇒ Object

get all redis connections for given redis server role. when more than one role is passed, treat them in order of decreasing preference when no role is given, :master is assumed



52
53
54
55
56
57
58
59
60
# File 'lib/queris.rb', line 52

def redises(*redis_roles)
  redis_roles << :master if redis_roles.empty? #default
  redis_roles.each do |role|
    unless (redises=@redis_by_role[role.to_sym]).nil? || redises.empty?
      return redises
    end
  end
  []
end

.register_model(model) ⇒ Object



209
210
211
212
213
214
# File 'lib/queris.rb', line 209

def register_model(model)
  unless @models.member? model
    @models << model
    @model_lookup[model.name.to_sym]=model
  end
end

.run_script(script, redis, keys = [], args = []) ⇒ Object



287
288
289
290
291
292
293
# File 'lib/queris.rb', line 287

def run_script(script, redis, keys=[], args=[])
  begin
    redis.evalsha script_hash(script), keys, args
  rescue Redis::CommandError => e
    raise Redis::CommandError, e.to_s.gsub(/^ERR Error running script/, "ERR Error running script #{script}")
  end
end

.script(name) ⇒ Object



271
272
273
# File 'lib/queris.rb', line 271

def script(name)
  redis_scripts[name.to_sym]
end

.script_hash(name) ⇒ Object



275
276
277
278
279
280
281
282
283
284
285
# File 'lib/queris.rb', line 275

def script_hash(name)
  name = name.to_sym
  @script_hash||={}
  unless (hash=@script_hash[name])
    contents = script(name)
    raise  Error, "Unknown redis script #{name}." unless contents
    hash = Queris.digest contents
    @script_hash[name] = hash
  end
  hash
end

.to_redis_float(val) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/queris.rb', line 242

def to_redis_float(val)
  val=val.to_f
  case val
  when Float::INFINITY
    "inf"
  when -Float::INFINITY
    "-inf"
  else
    if val != val #NaN
      "nan"
    else
      val
    end
  end
end

Instance Method Details

#log_stats_per_request!Object



105
106
107
108
# File 'lib/queris.rb', line 105

def log_stats_per_request!
  track_stats!
  @log_stats_per_request = true
end

#log_stats_per_request?Boolean

Returns:

  • (Boolean)


102
103
104
# File 'lib/queris.rb', line 102

def log_stats_per_request?
  @log_stats_per_request
end

#statsObject



96
97
98
99
100
# File 'lib/queris.rb', line 96

def stats
  return false unless @track_stats
  puts RedisStats.summary
  return RedisStats
end

#track_stats!Object



93
94
95
# File 'lib/queris.rb', line 93

def track_stats!
  @track_stats = true
end

#track_stats?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/queris.rb', line 90

def track_stats?
  @track_stats
end