sqlite3_cache 0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +1 -0
- data/LICENSE +0 -0
- data/README.md +3 -0
- data/ROADMAP.md +1 -0
- data/lib/sqlite3_cache.rb +193 -0
- metadata +66 -0
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.08 - Removed dependency on Bundler and fixed spec temp files...
|
data/LICENSE
ADDED
File without changes
|
data/README.md
ADDED
data/ROADMAP.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Performance improvements?? Bug fixes?
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'logger'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module SQLite3
|
6
|
+
class Cache
|
7
|
+
# @param options [Hash] hash of options
|
8
|
+
# see method for options description...
|
9
|
+
# options are...
|
10
|
+
# :file - sqlite3 file to use as persistence
|
11
|
+
# :namespace - specify cache namespace (must be a string)
|
12
|
+
# :expiration - number of seconds until keys expire
|
13
|
+
# :timestamp_on_read - note the last time we touched a record...
|
14
|
+
def initialize(options)
|
15
|
+
@OPTIONS = options
|
16
|
+
raise "Must supply :file!" unless @OPTIONS[:file]
|
17
|
+
raise "Must supply :namespace!" unless @OPTIONS[:namespace]
|
18
|
+
raise "Must supply :expiration" unless @OPTIONS[:expiration]
|
19
|
+
@LOGGER = Logger.new(STDOUT)
|
20
|
+
if @OPTIONS[:debug]
|
21
|
+
@LOGGER.level = Logger::DEBUG
|
22
|
+
else
|
23
|
+
@LOGGER.level = Logger::WARN
|
24
|
+
end
|
25
|
+
|
26
|
+
unless @OPTIONS[:timestamp_on_read] == nil
|
27
|
+
@OPTIONS[:timestamp_on_read] = false
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
@MUTEX = Mutex.new
|
32
|
+
@SQLITE3 = SQLite3::Database.new(@OPTIONS[:file])
|
33
|
+
@SQLITE3.busy_timeout = 60000
|
34
|
+
rescue => e
|
35
|
+
raise "Unable to open #{@OPTIONS[:file]}: #{e.message}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param expiration [Integer] number of seconds before keys expire
|
40
|
+
# @return the value that was passed in
|
41
|
+
def expiration=(expiration)
|
42
|
+
@OPTIONS[:expiration] = expiration
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param key [String] key the key to delete
|
46
|
+
# deletes a key from the cache
|
47
|
+
def delete!(key)
|
48
|
+
@LOGGER.debug {"Deleting #{key} from namespace #{@OPTIONS[:namespace]}"}
|
49
|
+
@SQLITE3.execute("delete from #{@OPTIONS[:namespace]} where key = ?", key)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param key [String] key to save
|
53
|
+
# @param value [String] value to persist
|
54
|
+
# sets a value in the cache
|
55
|
+
def []=(key, value)
|
56
|
+
@LOGGER.debug {"Setting #{key} to #{value} for namespace: #{@OPTIONS[:namespace]}"}
|
57
|
+
# make sure that table exists...
|
58
|
+
table_init(@OPTIONS[:namespace])
|
59
|
+
# see if the key exists...
|
60
|
+
existing_key = nil
|
61
|
+
@MUTEX.synchronize do
|
62
|
+
@SQLITE3.transaction do
|
63
|
+
@SQLITE3.execute("select key from #{@OPTIONS[:namespace]} where key = ?", key) do |row|
|
64
|
+
existing_key = row[0]
|
65
|
+
end
|
66
|
+
begin
|
67
|
+
unless existing_key
|
68
|
+
@SQLITE3.execute("insert into #{@OPTIONS[:namespace]} (key, created, value) values (?, ?, ?)", key, Time.now.to_s, Marshal.dump(value))
|
69
|
+
else
|
70
|
+
@SQLITE3.execute("update #{@OPTIONS[:namespace]} set created = ?, value = ? where key = ?", Time.now.to_s, Marshal.dump(value), key)
|
71
|
+
end
|
72
|
+
rescue => e
|
73
|
+
raise e
|
74
|
+
end
|
75
|
+
return value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param key [String] key to test for existence
|
81
|
+
# @return true if exist, false if not
|
82
|
+
# checks to see if we have a given key...
|
83
|
+
def exist?(key)
|
84
|
+
@LOGGER.debug {"Checking for existence of key: #{key}"}
|
85
|
+
@MUTEX.synchronize do
|
86
|
+
@SQLITE3.transaction do
|
87
|
+
unless table_exists?(@OPTIONS[:namespace])
|
88
|
+
return nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
@SQLITE3.execute("SELECT * from #{@OPTIONS[:namespace]} where key = ?", key) do |row|
|
93
|
+
return true
|
94
|
+
end
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param key [String] key to retrieve
|
99
|
+
# @return value of the key or nil
|
100
|
+
# retrieves a value from the cache
|
101
|
+
def [](key)
|
102
|
+
@LOGGER.debug {"Retreiving key: #{key} for namespace: #{@OPTIONS[:namespace]}"}
|
103
|
+
value = nil
|
104
|
+
@MUTEX.synchronize do
|
105
|
+
@SQLITE3.transaction do
|
106
|
+
unless table_exists?(@OPTIONS[:namespace])
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
@SQLITE3.results_as_hash = true
|
110
|
+
value = nil
|
111
|
+
@SQLITE3.execute("SELECT * from #{@OPTIONS[:namespace]} where key = ?", key) do |row|
|
112
|
+
value = Marshal.load(row["value"])
|
113
|
+
if value
|
114
|
+
@LOGGER.debug {"Cache hit!"}
|
115
|
+
else
|
116
|
+
@LOGGER.debug {"Cache miss!"}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
if @OPTIONS[:timestamp_on_read]
|
121
|
+
# now update access time and count...
|
122
|
+
@SQLITE3.execute("UPDATE #{@OPTIONS[:namespace]} SET accessed = ? where key = ?", Time.now.to_s, key)
|
123
|
+
# retrieve count...
|
124
|
+
access_count = nil
|
125
|
+
@SQLITE3.execute("SELECT ACCESS_COUNT from #{@OPTIONS[:namespace]} where key = ?", key) do |row|
|
126
|
+
access_count = row[0]
|
127
|
+
end
|
128
|
+
if access_count
|
129
|
+
@SQLITE3.execute("UPDATE #{@OPTIONS[:namespace]} set access_count = ? where key = ?", access_count + 1, key)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
return value
|
134
|
+
end
|
135
|
+
|
136
|
+
# gets rid of ALL keys in this cache...
|
137
|
+
def purge!
|
138
|
+
@SQLITE3.execute("DROP TABLE #{@OPTIONS[:namespace]}") if table_exists?(@OPTIONS[:namespace])
|
139
|
+
end
|
140
|
+
|
141
|
+
# purge any expired keys...
|
142
|
+
def purge_expired!
|
143
|
+
# okay, so see if any objects are older than
|
144
|
+
# CURRENT_TIME - @OPTIONS[:expiration]
|
145
|
+
expire_time = (Time.now - @OPTIONS[:expiration]).to_s
|
146
|
+
if table_exists?(@OPTIONS[:namespace])
|
147
|
+
@SQLITE3.execute("DELETE FROM #{@OPTIONS[:namespace]} where created < ?", expire_time)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# iterate over each key and value in the namespace
|
152
|
+
def each_pair(&block)
|
153
|
+
@SQLITE3.results_as_hash = true
|
154
|
+
@SQLITE3.execute("SELECT * from #{@OPTIONS[:namespace]}") do |row|
|
155
|
+
key = row["key"]
|
156
|
+
value = Marshal.load(row["value"])
|
157
|
+
yield key, value
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# @return list of keys
|
162
|
+
def keys
|
163
|
+
@SQLITE3.results_as_hash = true
|
164
|
+
keys = []
|
165
|
+
@SQLITE3.execute("SELECT key from #{@OPTIONS[:namespace]}") do |row|
|
166
|
+
key = row["key"]
|
167
|
+
if block_given?
|
168
|
+
yield key
|
169
|
+
else
|
170
|
+
keys << key
|
171
|
+
end
|
172
|
+
end
|
173
|
+
return keys
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def table_exists?(table)
|
179
|
+
@SQLITE3.execute("SELECT CASE WHEN tbl_name = ? THEN 1 ELSE 0 END FROM sqlite_master WHERE tbl_name = ? AND type = 'table'", table, table) do |row|
|
180
|
+
return row[0] == 1 ? true : false
|
181
|
+
end
|
182
|
+
return false
|
183
|
+
end
|
184
|
+
|
185
|
+
def table_init(table)
|
186
|
+
query = "create table if not exists #{table}(key text primary key, created timestamp, accessed timestamp, access_count timestamp, value blob)"
|
187
|
+
@SQLITE3.execute(query)
|
188
|
+
query = "create index if not exists key_index on #{table} (key)"
|
189
|
+
@SQLITE3.execute(query)
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sqlite3_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.16'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joshua Harding
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sqlite3
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Cache module that uses SQLite3 as backend
|
31
|
+
email:
|
32
|
+
- josh@statewidesoftware.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- lib/sqlite3_cache.rb
|
38
|
+
- LICENSE
|
39
|
+
- README.md
|
40
|
+
- ROADMAP.md
|
41
|
+
- CHANGELOG.md
|
42
|
+
homepage: http://statewidesoftware.com
|
43
|
+
licenses: []
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 1.3.6
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.8.24
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Cache module that uses SQLite3 as backend
|
66
|
+
test_files: []
|