Class: Tina4::QueryCache

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/cache.rb

Overview

In-memory TTL cache with tag-based invalidation.

Matches the Python / PHP / Node.js QueryCache API for cross-framework parity. Thread-safe via an internal Mutex.

Usage:

cache = Tina4::QueryCache.new(default_ttl: 60, max_size: 1000)
cache.set("key", "value", ttl: 30, tags: ["users"])
cache.get("key")  # => "value"
cache.clear_tag("users")

Defined Under Namespace

Classes: CacheEntry

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(default_ttl: 300, max_size: 1000) ⇒ QueryCache

Returns a new instance of QueryCache.

Parameters:

  • default_ttl (Integer) (defaults to: 300)

    default TTL in seconds (default: 300)

  • max_size (Integer) (defaults to: 1000)

    maximum number of cache entries (default: 1000)



18
19
20
21
22
23
# File 'lib/tina4/cache.rb', line 18

def initialize(default_ttl: 300, max_size: 1000)
  @default_ttl = default_ttl
  @max_size = max_size
  @store = {}
  @mutex = Mutex.new
end

Class Method Details

.query_key(sql, params = nil) ⇒ String

Generate a stable cache key from a SQL query and params. Mirrors SQLTranslator.query_key for direct use on QueryCache.

Parameters:

  • sql (String)
  • params (Array, nil) (defaults to: nil)

Returns:

  • (String)


149
150
151
152
# File 'lib/tina4/cache.rb', line 149

def self.query_key(sql, params = nil)
  raw = params ? "#{sql}|#{params.inspect}" : sql
  "query:#{Digest::SHA256.hexdigest(raw)}"
end

Instance Method Details

#clearObject

Clear all entries from the cache.



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

def clear
  @mutex.synchronize { @store.clear }
end

#clear_tag(tag) ⇒ Integer

Clear all entries with a given tag.

Parameters:

  • tag (String)

Returns:

  • (Integer)

    number of entries removed



101
102
103
104
105
106
107
# File 'lib/tina4/cache.rb', line 101

def clear_tag(tag)
  @mutex.synchronize do
    keys_to_remove = @store.select { |_k, v| v.tags.include?(tag) }.keys
    keys_to_remove.each { |k| @store.delete(k) }
    keys_to_remove.size
  end
end

#delete(key) ⇒ Boolean

Delete a key from the cache.

Parameters:

  • key (String)

Returns:

  • (Boolean)

    true if the key was present



86
87
88
89
90
# File 'lib/tina4/cache.rb', line 86

def delete(key)
  @mutex.synchronize do
    !@store.delete(key).nil?
  end
end

#get(key, default = nil) ⇒ Object?

Retrieve a cached value. Returns nil if expired or missing.

Parameters:

  • key (String)
  • default (Object) (defaults to: nil)

    value to return if key is missing

Returns:

  • (Object, nil)


50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/tina4/cache.rb', line 50

def get(key, default = nil)
  @mutex.synchronize do
    entry = @store[key]
    return default unless entry

    if Time.now.to_f > entry.expires_at
      @store.delete(key)
      return default
    end

    entry.value
  end
end

#has?(key) ⇒ Boolean

Check if a key exists and is not expired.

Parameters:

  • key (String)

Returns:

  • (Boolean)


68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/tina4/cache.rb', line 68

def has?(key)
  @mutex.synchronize do
    entry = @store[key]
    return false unless entry

    if Time.now.to_f > entry.expires_at
      @store.delete(key)
      return false
    end

    true
  end
end

#remember(key, ttl, &block) ⇒ Object

Fetch from cache, or compute and store.

Parameters:

  • key (String)
  • ttl (Integer)

    TTL in seconds

  • block (Proc)

    factory to compute the value if not cached

Returns:

  • (Object)


127
128
129
130
131
132
133
134
# File 'lib/tina4/cache.rb', line 127

def remember(key, ttl, &block)
  cached = get(key)
  return cached unless cached.nil?

  value = block.call
  set(key, value, ttl: ttl)
  value
end

#set(key, value, ttl: nil, tags: []) ⇒ Object

Store a value with optional TTL and tags.

Parameters:

  • key (String)
  • value (Object)
  • ttl (Integer, nil) (defaults to: nil)

    TTL in seconds (nil uses default)

  • tags (Array<String>) (defaults to: [])

    optional tags for grouped invalidation



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/tina4/cache.rb', line 31

def set(key, value, ttl: nil, tags: [])
  ttl ||= @default_ttl
  expires_at = Time.now.to_f + ttl

  @mutex.synchronize do
    # Evict oldest if at capacity
    if @store.size >= @max_size && !@store.key?(key)
      oldest_key = @store.keys.first
      @store.delete(oldest_key)
    end
    @store[key] = CacheEntry.new(value, expires_at, tags)
  end
end

#sizeInteger

Current number of entries in the cache.

Returns:

  • (Integer)


139
140
141
# File 'lib/tina4/cache.rb', line 139

def size
  @mutex.synchronize { @store.size }
end

#sweepInteger

Remove all expired entries.

Returns:

  • (Integer)

    number of entries removed



112
113
114
115
116
117
118
119
# File 'lib/tina4/cache.rb', line 112

def sweep
  @mutex.synchronize do
    now = Time.now.to_f
    keys_to_remove = @store.select { |_k, v| now > v.expires_at }.keys
    keys_to_remove.each { |k| @store.delete(k) }
    keys_to_remove.size
  end
end