chat_sdk-state-mysql 1.1.0 → 1.3.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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chat_sdk/state/mysql.rb +69 -19
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4113d277f2fa7cb1f2c0ae346240c6c589bfaf4b03449be422200645f1db310d
4
- data.tar.gz: 8c093057fa1677ef220cbacbbe96bdec2158447a886250fd273c0f64c86902da
3
+ metadata.gz: 83d39209e4718ceeac4fe28d88e7bb00b26649fc64169828e05d17ace894f2d3
4
+ data.tar.gz: 16bfde09613137f441d82a8e7720eb2515307569f875b7d542120e4144c2787b
5
5
  SHA512:
6
- metadata.gz: 77310f954281b2193b02141847c2edc0d458825e7ef6f1863c16b63c1c225c34d88a2c7ec9e174ee5b7823159b8b6323bd504417bd1ff2ed9cfa781c7fdf7aac
7
- data.tar.gz: a0bb0eacef3a7f175880fd1b108c0bcb84e3e5a4fac09e47e2dd2212301f0cc3a58e83c0d152fab24eaa4ebdac8cf207c7b126ad4660f07905a143804ad714c1
6
+ metadata.gz: 1f133d05b7ccee5344360ee84ff68efff1a44b89e3e31084d780d90451ab64de8e51a13559166394173715cfece2a41870fe4387479253c055083f586e08814f
7
+ data.tar.gz: 97699fbd0e4063ed56b1eff2c12e05d1825cf089ef8dcd23414345b414d9eb849c43391e6ed268915b3bb201e71ce2b937b8665950a1b5473bd19213032bdde4
@@ -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
- @conn.affected_rows > 0
82
+ ins_stmt.affected_rows > 0
56
83
  end
57
84
 
58
85
  def release_lock(key, owner:)
@@ -60,14 +87,14 @@ 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
- @conn.affected_rows > 0
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)
@@ -76,18 +103,18 @@ module ChatSDK
76
103
 
77
104
  def extend_lock(key, owner:, ttl:)
78
105
  stmt = @conn.prepare(
79
- "UPDATE chat_sdk_locks SET expires_at = DATE_ADD(NOW(), INTERVAL #{ttl.to_f} SECOND) " \
80
- "WHERE key_prefix = ? AND lock_key = ? AND owner = ? AND expires_at > NOW()"
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)"
81
108
  )
82
109
  stmt.execute(@key_prefix, key, owner)
83
- @conn.affected_rows > 0
110
+ stmt.affected_rows > 0
84
111
  end
85
112
 
86
113
  # Key-value store
87
114
 
88
115
  def get(key)
89
116
  stmt = @conn.prepare(
90
- "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))"
91
118
  )
92
119
  result = stmt.execute(@key_prefix, key)
93
120
  row = result.first
@@ -101,7 +128,7 @@ module ChatSDK
101
128
  stmt = if ttl
102
129
  @conn.prepare(
103
130
  "INSERT INTO chat_sdk_cache (key_prefix, cache_key, value, expires_at) " \
104
- "VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL #{ttl.to_f} SECOND)) " \
131
+ "VALUES (?, ?, ?, DATE_ADD(NOW(6), INTERVAL #{ttl.to_f} SECOND)) " \
105
132
  "ON DUPLICATE KEY UPDATE value = VALUES(value), expires_at = VALUES(expires_at)"
106
133
  )
107
134
  else
@@ -129,13 +156,13 @@ module ChatSDK
129
156
  def set_if_absent(key, value, ttl: nil)
130
157
  serialized = JSON.generate(value)
131
158
  del_stmt = @conn.prepare(
132
- "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)"
133
160
  )
134
161
  del_stmt.execute(@key_prefix, key)
135
162
  stmt = if ttl
136
163
  @conn.prepare(
137
164
  "INSERT IGNORE INTO chat_sdk_cache (key_prefix, cache_key, value, expires_at) " \
138
- "VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL #{ttl.to_f} SECOND))"
165
+ "VALUES (?, ?, ?, DATE_ADD(NOW(6), INTERVAL #{ttl.to_f} SECOND))"
139
166
  )
140
167
  else
141
168
  @conn.prepare(
@@ -144,7 +171,7 @@ module ChatSDK
144
171
  )
145
172
  end
146
173
  stmt.execute(@key_prefix, key, serialized)
147
- @conn.affected_rows > 0
174
+ stmt.affected_rows > 0
148
175
  end
149
176
 
150
177
  def enqueue(key, value, max_size:, drop: :drop_oldest)
@@ -208,11 +235,11 @@ module ChatSDK
208
235
 
209
236
  def cleanup_expired
210
237
  s1 = @conn.prepare(
211
- "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)"
212
239
  )
213
240
  s1.execute(@key_prefix)
214
241
  s2 = @conn.prepare(
215
- "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)"
216
243
  )
217
244
  s2.execute(@key_prefix)
218
245
  end
@@ -220,7 +247,7 @@ module ChatSDK
220
247
  private
221
248
 
222
249
  def connect(url)
223
- Mysql2::Client.new(url)
250
+ Mysql2::Client.new(self.class.parse_url(url))
224
251
  end
225
252
 
226
253
  def ensure_tables
@@ -237,7 +264,7 @@ module ChatSDK
237
264
  key_prefix VARCHAR(255) NOT NULL,
238
265
  lock_key VARCHAR(512) NOT NULL,
239
266
  owner VARCHAR(255) NOT NULL,
240
- expires_at TIMESTAMP NOT NULL,
267
+ expires_at TIMESTAMP(6) NOT NULL,
241
268
  PRIMARY KEY (key_prefix, lock_key)
242
269
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
243
270
  SQL
@@ -246,7 +273,7 @@ module ChatSDK
246
273
  key_prefix VARCHAR(255) NOT NULL,
247
274
  cache_key VARCHAR(512) NOT NULL,
248
275
  value JSON NOT NULL,
249
- expires_at TIMESTAMP NULL DEFAULT NULL,
276
+ expires_at TIMESTAMP(6) NULL DEFAULT NULL,
250
277
  PRIMARY KEY (key_prefix, cache_key)
251
278
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
252
279
  SQL
@@ -258,9 +285,32 @@ module ChatSDK
258
285
  value JSON NOT NULL,
259
286
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
260
287
  PRIMARY KEY (id),
261
- INDEX chat_sdk_queues_lookup (key_prefix, queue_key, id)
288
+ INDEX chat_sdk_queues_lookup (key_prefix, queue_key)
262
289
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
263
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
264
314
  end
265
315
  end
266
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.1.0
4
+ version: 1.3.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: '0.1'
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: '0.1'
25
+ version: '1.2'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: mysql2
28
28
  requirement: !ruby/object:Gem::Requirement