chat_sdk-state-mysql 1.0.1 → 1.2.0
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.
- checksums.yaml +4 -4
- data/lib/chat_sdk/state/mysql.rb +133 -15
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3defa38d43dccd1b7baeb73f8e0000a4ef0db31748c967a85b5d327b1c73cf3
|
|
4
|
+
data.tar.gz: 16bfde09613137f441d82a8e7720eb2515307569f875b7d542120e4144c2787b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ba996e06d497b68d5aa63254fb66c16f61c1b663b609952ce89e36276b4ef8ac909a070e13b433181b288be372985cda0faa58a47381d211d8223bf997cd98c
|
|
7
|
+
data.tar.gz: 97699fbd0e4063ed56b1eff2c12e05d1825cf089ef8dcd23414345b414d9eb849c43391e6ed268915b3bb201e71ce2b937b8665950a1b5473bd19213032bdde4
|
data/lib/chat_sdk/state/mysql.rb
CHANGED
|
@@ -3,10 +3,37 @@
|
|
|
3
3
|
require "chat_sdk"
|
|
4
4
|
require "mysql2"
|
|
5
5
|
require "json"
|
|
6
|
+
require "uri"
|
|
6
7
|
|
|
7
8
|
module ChatSDK
|
|
8
9
|
module State
|
|
9
10
|
class Mysql < Base
|
|
11
|
+
def self.parse_url(url)
|
|
12
|
+
uri = URI.parse(url)
|
|
13
|
+
options = {}
|
|
14
|
+
options[:host] = uri.host if uri.host
|
|
15
|
+
options[:port] = uri.port if uri.port
|
|
16
|
+
options[:username] = URI::DEFAULT_PARSER.unescape(uri.user) if uri.user
|
|
17
|
+
options[:password] = URI::DEFAULT_PARSER.unescape(uri.password) if uri.password
|
|
18
|
+
database = uri.path.delete_prefix("/")
|
|
19
|
+
options[:database] = URI::DEFAULT_PARSER.unescape(database) unless database.empty?
|
|
20
|
+
query_options = URI.decode_www_form(uri.query.to_s).to_h
|
|
21
|
+
query_options.transform_values! do |value|
|
|
22
|
+
case value
|
|
23
|
+
when /\A\d+\z/
|
|
24
|
+
value.to_i
|
|
25
|
+
when "true"
|
|
26
|
+
true
|
|
27
|
+
when "false"
|
|
28
|
+
false
|
|
29
|
+
else
|
|
30
|
+
value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
options.merge!(query_options.transform_keys(&:to_sym))
|
|
34
|
+
options
|
|
35
|
+
end
|
|
36
|
+
|
|
10
37
|
def initialize(url: nil, connection: nil, key_prefix: "chat_sdk", auto_migrate: true)
|
|
11
38
|
@conn = connection || connect(url || ENV["MYSQL_URL"] || ENV["DATABASE_URL"] || "mysql2://root@localhost/chat_sdk_dev")
|
|
12
39
|
@key_prefix = key_prefix
|
|
@@ -44,15 +71,15 @@ module ChatSDK
|
|
|
44
71
|
def acquire_lock(key, owner:, ttl:)
|
|
45
72
|
ttl_seconds = ttl.to_f
|
|
46
73
|
del_stmt = @conn.prepare(
|
|
47
|
-
"DELETE FROM chat_sdk_locks WHERE key_prefix = ? AND lock_key = ? AND expires_at < NOW()"
|
|
74
|
+
"DELETE FROM chat_sdk_locks WHERE key_prefix = ? AND lock_key = ? AND expires_at < NOW(6)"
|
|
48
75
|
)
|
|
49
76
|
del_stmt.execute(@key_prefix, key)
|
|
50
77
|
ins_stmt = @conn.prepare(
|
|
51
78
|
"INSERT IGNORE INTO chat_sdk_locks (key_prefix, lock_key, owner, expires_at) " \
|
|
52
|
-
"VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL #{ttl_seconds} SECOND))"
|
|
79
|
+
"VALUES (?, ?, ?, DATE_ADD(NOW(6), INTERVAL #{ttl_seconds} SECOND))"
|
|
53
80
|
)
|
|
54
81
|
ins_stmt.execute(@key_prefix, key, owner)
|
|
55
|
-
|
|
82
|
+
ins_stmt.affected_rows > 0
|
|
56
83
|
end
|
|
57
84
|
|
|
58
85
|
def release_lock(key, owner:)
|
|
@@ -60,25 +87,34 @@ module ChatSDK
|
|
|
60
87
|
"DELETE FROM chat_sdk_locks WHERE key_prefix = ? AND lock_key = ? AND owner = ?"
|
|
61
88
|
)
|
|
62
89
|
stmt.execute(@key_prefix, key, owner)
|
|
63
|
-
|
|
90
|
+
stmt.affected_rows > 0
|
|
64
91
|
end
|
|
65
92
|
|
|
66
93
|
def force_lock(key, owner:, ttl:)
|
|
67
94
|
ttl_seconds = ttl.to_f
|
|
68
95
|
stmt = @conn.prepare(
|
|
69
96
|
"INSERT INTO chat_sdk_locks (key_prefix, lock_key, owner, expires_at) " \
|
|
70
|
-
"VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL #{ttl_seconds} SECOND)) " \
|
|
97
|
+
"VALUES (?, ?, ?, DATE_ADD(NOW(6), INTERVAL #{ttl_seconds} SECOND)) " \
|
|
71
98
|
"ON DUPLICATE KEY UPDATE owner = VALUES(owner), expires_at = VALUES(expires_at)"
|
|
72
99
|
)
|
|
73
100
|
stmt.execute(@key_prefix, key, owner)
|
|
74
101
|
true
|
|
75
102
|
end
|
|
76
103
|
|
|
104
|
+
def extend_lock(key, owner:, ttl:)
|
|
105
|
+
stmt = @conn.prepare(
|
|
106
|
+
"UPDATE chat_sdk_locks SET expires_at = DATE_ADD(NOW(6), INTERVAL #{ttl.to_f} SECOND) " \
|
|
107
|
+
"WHERE key_prefix = ? AND lock_key = ? AND owner = ? AND expires_at > NOW(6)"
|
|
108
|
+
)
|
|
109
|
+
stmt.execute(@key_prefix, key, owner)
|
|
110
|
+
stmt.affected_rows > 0
|
|
111
|
+
end
|
|
112
|
+
|
|
77
113
|
# Key-value store
|
|
78
114
|
|
|
79
115
|
def get(key)
|
|
80
116
|
stmt = @conn.prepare(
|
|
81
|
-
"SELECT value FROM chat_sdk_cache WHERE key_prefix = ? AND cache_key = ? AND (expires_at IS NULL OR expires_at > NOW())"
|
|
117
|
+
"SELECT value FROM chat_sdk_cache WHERE key_prefix = ? AND cache_key = ? AND (expires_at IS NULL OR expires_at > NOW(6))"
|
|
82
118
|
)
|
|
83
119
|
result = stmt.execute(@key_prefix, key)
|
|
84
120
|
row = result.first
|
|
@@ -92,7 +128,7 @@ module ChatSDK
|
|
|
92
128
|
stmt = if ttl
|
|
93
129
|
@conn.prepare(
|
|
94
130
|
"INSERT INTO chat_sdk_cache (key_prefix, cache_key, value, expires_at) " \
|
|
95
|
-
"VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL #{ttl.to_f} SECOND)) " \
|
|
131
|
+
"VALUES (?, ?, ?, DATE_ADD(NOW(6), INTERVAL #{ttl.to_f} SECOND)) " \
|
|
96
132
|
"ON DUPLICATE KEY UPDATE value = VALUES(value), expires_at = VALUES(expires_at)"
|
|
97
133
|
)
|
|
98
134
|
else
|
|
@@ -120,13 +156,13 @@ module ChatSDK
|
|
|
120
156
|
def set_if_absent(key, value, ttl: nil)
|
|
121
157
|
serialized = JSON.generate(value)
|
|
122
158
|
del_stmt = @conn.prepare(
|
|
123
|
-
"DELETE FROM chat_sdk_cache WHERE key_prefix = ? AND cache_key = ? AND expires_at IS NOT NULL AND expires_at < NOW()"
|
|
159
|
+
"DELETE FROM chat_sdk_cache WHERE key_prefix = ? AND cache_key = ? AND expires_at IS NOT NULL AND expires_at < NOW(6)"
|
|
124
160
|
)
|
|
125
161
|
del_stmt.execute(@key_prefix, key)
|
|
126
162
|
stmt = if ttl
|
|
127
163
|
@conn.prepare(
|
|
128
164
|
"INSERT IGNORE INTO chat_sdk_cache (key_prefix, cache_key, value, expires_at) " \
|
|
129
|
-
"VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL #{ttl.to_f} SECOND))"
|
|
165
|
+
"VALUES (?, ?, ?, DATE_ADD(NOW(6), INTERVAL #{ttl.to_f} SECOND))"
|
|
130
166
|
)
|
|
131
167
|
else
|
|
132
168
|
@conn.prepare(
|
|
@@ -135,7 +171,53 @@ module ChatSDK
|
|
|
135
171
|
)
|
|
136
172
|
end
|
|
137
173
|
stmt.execute(@key_prefix, key, serialized)
|
|
138
|
-
|
|
174
|
+
stmt.affected_rows > 0
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def enqueue(key, value, max_size:, drop: :drop_oldest)
|
|
178
|
+
@conn.query("START TRANSACTION")
|
|
179
|
+
count_stmt = @conn.prepare(
|
|
180
|
+
"SELECT id FROM chat_sdk_queues WHERE key_prefix = ? AND queue_key = ? ORDER BY id FOR UPDATE"
|
|
181
|
+
)
|
|
182
|
+
ids = count_stmt.execute(@key_prefix, key).map { |row| row["id"] }
|
|
183
|
+
if ids.length >= max_size && drop.to_sym == :drop_newest
|
|
184
|
+
@conn.query("COMMIT")
|
|
185
|
+
return ids.length
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
insert = @conn.prepare("INSERT INTO chat_sdk_queues (key_prefix, queue_key, value) VALUES (?, ?, ?)")
|
|
189
|
+
insert.execute(@key_prefix, key, JSON.generate(value))
|
|
190
|
+
overflow = ids.length + 1 - max_size
|
|
191
|
+
if overflow.positive?
|
|
192
|
+
placeholders = Array.new(overflow, "?").join(", ")
|
|
193
|
+
remove = @conn.prepare("DELETE FROM chat_sdk_queues WHERE id IN (#{placeholders})")
|
|
194
|
+
remove.execute(*ids.first(overflow))
|
|
195
|
+
end
|
|
196
|
+
@conn.query("COMMIT")
|
|
197
|
+
[ids.length + 1, max_size].min
|
|
198
|
+
rescue
|
|
199
|
+
@conn.query("ROLLBACK")
|
|
200
|
+
raise
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def drain_queue(key)
|
|
204
|
+
@conn.query("START TRANSACTION")
|
|
205
|
+
select = @conn.prepare(
|
|
206
|
+
"SELECT id, value FROM chat_sdk_queues WHERE key_prefix = ? AND queue_key = ? ORDER BY id FOR UPDATE"
|
|
207
|
+
)
|
|
208
|
+
rows = select.execute(@key_prefix, key).to_a
|
|
209
|
+
delete = @conn.prepare("DELETE FROM chat_sdk_queues WHERE key_prefix = ? AND queue_key = ?")
|
|
210
|
+
delete.execute(@key_prefix, key)
|
|
211
|
+
@conn.query("COMMIT")
|
|
212
|
+
rows.map { |row| JSON.parse(row["value"]) }
|
|
213
|
+
rescue
|
|
214
|
+
@conn.query("ROLLBACK")
|
|
215
|
+
raise
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def queue_depth(key)
|
|
219
|
+
stmt = @conn.prepare("SELECT COUNT(*) AS count FROM chat_sdk_queues WHERE key_prefix = ? AND queue_key = ?")
|
|
220
|
+
stmt.execute(@key_prefix, key).first["count"].to_i
|
|
139
221
|
end
|
|
140
222
|
|
|
141
223
|
# Cleanup
|
|
@@ -147,15 +229,17 @@ module ChatSDK
|
|
|
147
229
|
s2.execute(@key_prefix)
|
|
148
230
|
s3 = @conn.prepare("DELETE FROM chat_sdk_cache WHERE key_prefix = ?")
|
|
149
231
|
s3.execute(@key_prefix)
|
|
232
|
+
s4 = @conn.prepare("DELETE FROM chat_sdk_queues WHERE key_prefix = ?")
|
|
233
|
+
s4.execute(@key_prefix)
|
|
150
234
|
end
|
|
151
235
|
|
|
152
236
|
def cleanup_expired
|
|
153
237
|
s1 = @conn.prepare(
|
|
154
|
-
"DELETE FROM chat_sdk_cache WHERE key_prefix = ? AND expires_at IS NOT NULL AND expires_at < NOW()"
|
|
238
|
+
"DELETE FROM chat_sdk_cache WHERE key_prefix = ? AND expires_at IS NOT NULL AND expires_at < NOW(6)"
|
|
155
239
|
)
|
|
156
240
|
s1.execute(@key_prefix)
|
|
157
241
|
s2 = @conn.prepare(
|
|
158
|
-
"DELETE FROM chat_sdk_locks WHERE key_prefix = ? AND expires_at < NOW()"
|
|
242
|
+
"DELETE FROM chat_sdk_locks WHERE key_prefix = ? AND expires_at < NOW(6)"
|
|
159
243
|
)
|
|
160
244
|
s2.execute(@key_prefix)
|
|
161
245
|
end
|
|
@@ -163,7 +247,7 @@ module ChatSDK
|
|
|
163
247
|
private
|
|
164
248
|
|
|
165
249
|
def connect(url)
|
|
166
|
-
Mysql2::Client.new(url)
|
|
250
|
+
Mysql2::Client.new(self.class.parse_url(url))
|
|
167
251
|
end
|
|
168
252
|
|
|
169
253
|
def ensure_tables
|
|
@@ -180,7 +264,7 @@ module ChatSDK
|
|
|
180
264
|
key_prefix VARCHAR(255) NOT NULL,
|
|
181
265
|
lock_key VARCHAR(512) NOT NULL,
|
|
182
266
|
owner VARCHAR(255) NOT NULL,
|
|
183
|
-
expires_at TIMESTAMP NOT NULL,
|
|
267
|
+
expires_at TIMESTAMP(6) NOT NULL,
|
|
184
268
|
PRIMARY KEY (key_prefix, lock_key)
|
|
185
269
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
|
186
270
|
SQL
|
|
@@ -189,10 +273,44 @@ module ChatSDK
|
|
|
189
273
|
key_prefix VARCHAR(255) NOT NULL,
|
|
190
274
|
cache_key VARCHAR(512) NOT NULL,
|
|
191
275
|
value JSON NOT NULL,
|
|
192
|
-
expires_at TIMESTAMP NULL DEFAULT NULL,
|
|
276
|
+
expires_at TIMESTAMP(6) NULL DEFAULT NULL,
|
|
193
277
|
PRIMARY KEY (key_prefix, cache_key)
|
|
194
278
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
|
195
279
|
SQL
|
|
280
|
+
@conn.query(<<~SQL)
|
|
281
|
+
CREATE TABLE IF NOT EXISTS chat_sdk_queues (
|
|
282
|
+
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
283
|
+
key_prefix VARCHAR(255) NOT NULL,
|
|
284
|
+
queue_key VARCHAR(512) NOT NULL,
|
|
285
|
+
value JSON NOT NULL,
|
|
286
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
287
|
+
PRIMARY KEY (id),
|
|
288
|
+
INDEX chat_sdk_queues_lookup (key_prefix, queue_key)
|
|
289
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
|
290
|
+
SQL
|
|
291
|
+
ensure_expiration_precision
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def ensure_expiration_precision
|
|
295
|
+
columns = @conn.query(<<~SQL).each_with_object({}) do |row, result|
|
|
296
|
+
SELECT TABLE_NAME, DATETIME_PRECISION
|
|
297
|
+
FROM information_schema.columns
|
|
298
|
+
WHERE table_schema = DATABASE()
|
|
299
|
+
AND table_name IN ('chat_sdk_locks', 'chat_sdk_cache')
|
|
300
|
+
AND column_name = 'expires_at'
|
|
301
|
+
SQL
|
|
302
|
+
result[row["TABLE_NAME"]] = row["DATETIME_PRECISION"].to_i
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
definitions = {
|
|
306
|
+
"chat_sdk_locks" => "TIMESTAMP(6) NOT NULL",
|
|
307
|
+
"chat_sdk_cache" => "TIMESTAMP(6) NULL DEFAULT NULL"
|
|
308
|
+
}
|
|
309
|
+
definitions.each do |table_name, definition|
|
|
310
|
+
next if columns[table_name] == 6
|
|
311
|
+
|
|
312
|
+
@conn.query("ALTER TABLE #{table_name} MODIFY expires_at #{definition}")
|
|
313
|
+
end
|
|
196
314
|
end
|
|
197
315
|
end
|
|
198
316
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chat_sdk-state-mysql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Quentin Rousseau
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '1.2'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '1.2'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: mysql2
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|