@superhero/eventflow-db 4.0.0 → 4.0.2

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.
package/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  - Simplified table schema management for the Eventflow components.
8
8
  - Supports creating, reading, updating and deleting events and their associations.
9
9
  - Supports database interactions for scheduled and published events.
10
+ - Supports database interactions for certificate, hub and log.
10
11
  - Integrates with `@superhero/db` for file management of SQL queries, and `mysql2` as the database driver.
11
12
  - Error declarations with descriptive error messages and error codes.
12
13
 
@@ -110,6 +111,8 @@ await db.updateEventPublishedToSuccess(eventId)
110
111
 
111
112
  ### Logging
112
113
 
114
+ #### Persisting a log entry
115
+
113
116
  ```javascript
114
117
  const log =
115
118
  {
@@ -121,6 +124,15 @@ const log =
121
124
  await db.persistLog(log)
122
125
  ```
123
126
 
127
+ #### Archive logs
128
+
129
+ Archives logs by partitioning the log table by date.
130
+
131
+ ```javascript
132
+ const date = new Date()
133
+ await db.archiveLog(date)
134
+ ```
135
+
124
136
  ### Managing Hubs
125
137
 
126
138
  #### Persisting a Hub
@@ -145,10 +157,95 @@ const hubs = await db.readOnlineHubs()
145
157
  console.log('Online Hubs:', hubs)
146
158
  ```
147
159
 
160
+ ### Managing Certificate
161
+
162
+ #### Persisting a Certificate
163
+
164
+ ```javascript
165
+ const certificate =
166
+ {
167
+ id : 'unique_certificate_id',
168
+ validity : new Date('2025-12-31T23:59:59.000Z'),
169
+ cert : 'certificate_value',
170
+ key : Buffer.from('encryption_key'),
171
+ key_iv : Buffer.from('initialization_vector'),
172
+ key_salt : Buffer.from('key_salt'),
173
+ key_tag : Buffer.from('key_tag'),
174
+ pass : Buffer.from('encrypted_passphrase'),
175
+ pass_iv : Buffer.from('pass_iv'),
176
+ pass_salt : Buffer.from('pass_salt'),
177
+ pass_tag : Buffer.from('pass_tag'),
178
+ };
179
+
180
+ const isPersisted = await db.persistCertificate(certificate);
181
+ console.log('Persisted:', isPersisted);
182
+ ```
183
+
184
+ #### Reading a Certificate
185
+
186
+ ```javascript
187
+ try
188
+ {
189
+ const certificate = await db.readCertificate('unique_certificate_id');
190
+ console.log('Certificate:', certificate);
191
+ }
192
+ catch (error)
193
+ {
194
+ if (error.code === 'E_EVENTFLOW_DB_CERTIFICATE_NOT_FOUND')
195
+ {
196
+ console.error('Certificate not found');
197
+ }
198
+ else
199
+ {
200
+ console.error('Error reading certificate:', error);
201
+ }
202
+ }
203
+ ```
204
+
205
+ #### Revoking a Certificate
206
+
207
+ ```javascript
208
+ const revoked = await db.revokeCertificate('unique_certificate_id');
209
+ console.log('Certificate revoked:', revoked);
210
+ ```
211
+
148
212
  ## Table Schemas
149
213
 
150
214
  Below are the database schemas used in this component:
151
215
 
216
+ ### Certificate Table
217
+
218
+ Only expected to have 1 certificate for each ID active at the same time. Once a certificate is revoked, it will be be persisted with a version number greater than 0, hence partition it as `cold` data; archived.
219
+
220
+ ```sql
221
+ CREATE TABLE IF NOT EXISTS certificate
222
+ (
223
+ created DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
224
+ updated DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
225
+ version INT UNSIGNED NOT NULL DEFAULT 0,
226
+ id VARCHAR(64) NOT NULL,
227
+ cert TEXT NOT NULL,
228
+ `key` BLOB NOT NULL,
229
+ key_iv VARBINARY(16) NOT NULL,
230
+ key_salt VARBINARY(16) NOT NULL,
231
+ key_tag VARBINARY(16) NOT NULL,
232
+ pass BLOB NOT NULL,
233
+ pass_iv VARBINARY(16) NOT NULL,
234
+ pass_salt VARBINARY(16) NOT NULL,
235
+ pass_tag VARBINARY(16) NOT NULL,
236
+ validity DATETIME NOT NULL,
237
+ revoked DATETIME NULL,
238
+
239
+ PRIMARY KEY (version, id)
240
+ )
241
+ ENGINE=InnoDB
242
+ PARTITION BY RANGE (version)
243
+ (
244
+ PARTITION p_hot VALUES LESS THAN (1),
245
+ PARTITION p_cold VALUES LESS THAN MAXVALUE
246
+ )
247
+ ```
248
+
152
249
  ### Event Table
153
250
 
154
251
  ```sql
@@ -283,6 +380,8 @@ ENGINE=InnoDB
283
380
 
284
381
  ### Log Table
285
382
 
383
+ Partition on timestamp to make it possible to archive logs.
384
+
286
385
  ```sql
287
386
  CREATE TABLE IF NOT EXISTS log
288
387
  (
@@ -324,72 +423,84 @@ npm test
324
423
  ▶ @superhero/eventflow-db
325
424
  ▶ Setup table schemas
326
425
  ▶ Persist a hub
327
- ✔ Read online hubs (5.587373ms)
426
+ ✔ Read online hubs (8.808208ms)
328
427
 
329
428
  ▶ Persisting an event should generate an ID if not provided
330
- ✔ Read an event by id should return the same data as when persisted the event (4.480859ms)
429
+ ✔ Read an event by id should return the same data as when persisted the event (9.057985ms)
331
430
 
332
431
  ▶ Schedule a persisted event
333
- ✔ Read all scheduled events (5.224091ms)
334
- ✔ Update scheduled event as executed (5.74482ms)
335
- ✔ Update scheduled event as success (5.297828ms)
336
- ✔ Update scheduled event as failed (6.276729ms)
337
- ✔ Schedule a persisted event (29.417902ms)
432
+ ✔ Read all scheduled events (5.28951ms)
433
+ ✔ Update scheduled event as executed (11.686935ms)
434
+ ✔ Update scheduled event as success (10.393756ms)
435
+ ✔ Update scheduled event as failed (9.877872ms)
436
+ ✔ Schedule a persisted event (50.493271ms)
338
437
 
339
438
  ▶ Publish a persisted event
340
- ✔ Update published event to consumed by hub (7.16767ms)
341
- ✔ Update published event to consumed by spoke (6.096988ms)
342
- ✔ Update published event to success (6.491936ms)
343
- ✔ Update published event to failed (7.925911ms)
344
- ✔ Update published event to orphan (4.379269ms)
345
- ✔ Publish a persisted event (38.916677ms)
439
+ ✔ Update published event to consumed by hub (8.985592ms)
440
+ ✔ Update published event to consumed by spoke (10.847144ms)
441
+ ✔ Update published event to success (8.945934ms)
442
+ ✔ Update published event to failed (12.743366ms)
443
+ ✔ Update published event to orphan (12.484305ms)
444
+ ✔ Publish a persisted event (69.042999ms)
346
445
 
347
446
  ▶ Persist event cpid association
348
- ✔ Read events by domain and cpid (3.532181ms)
349
- ✔ Read associated cpid by event id (4.202583ms)
350
- ✔ Delete associated cpid by event id (7.538617ms)
351
- ✔ Read deleted associated cpid by event id returns empty (2.465462ms)
352
- ✔ Persist event cpid association (25.886055ms)
447
+ ✔ Read events by domain and cpid (6.375096ms)
448
+ ✔ Read associated cpid by event id (6.539711ms)
449
+ ✔ Delete associated cpid by event id (35.785772ms)
450
+ ✔ Read deleted associated cpid by event id returns empty (3.44164ms)
451
+ ✔ Persist event cpid association (63.474382ms)
353
452
 
354
453
  ▶ Persist event eid association
355
- ✔ Read events by eid (3.706348ms)
356
- ✔ Read events by domain and eid (4.862519ms)
357
- ✔ Read associated eid by event id (3.864714ms)
358
- ✔ Delete associated eid by event id (3.806146ms)
359
- ✔ Read deleted associated eid by event id returns empty (3.217061ms)
360
- ✔ Persist event eid association (24.547606ms)
454
+ ✔ Read events by eid (3.186193ms)
455
+ ✔ Read events by domain and eid (3.211094ms)
456
+ ✔ Read associated eid by event id (5.22712ms)
457
+ ✔ Delete associated eid by event id (4.791241ms)
458
+ ✔ Read deleted associated eid by event id returns empty (2.82022ms)
459
+ ✔ Persist event eid association (25.839658ms)
361
460
 
362
461
  ▶ Delete event
363
- ✔ Reading a deleted event rejects (2.195439ms)
364
- ✔ Delete event (9.437439ms)
462
+ ✔ Reading a deleted event rejects (2.870914ms)
463
+ ✔ Delete event (12.001935ms)
365
464
 
366
465
  ▶ By domain and pid
367
- ✔ Read event by domain and pid (3.467037ms)
368
- ✔ Delete event by domain and pid (5.735279ms)
369
- ✔ Read empty eventlog by domain and pid (2.678678ms)
370
- ✔ By domain and pid (17.088958ms)
371
- ✔ Persisting an event should generate an ID if not provided (158.031614ms)
372
-
373
- ✔ Persist log (5.5136ms)
374
- ✔ Update hub to quit (7.367751ms)
375
- ✔ Persist a hub (182.853588ms)
376
- Reading a non existing event should reject with an error (2.51415ms)
377
- Setup table schemas (250.260079ms)
378
- @superhero/eventflow-db (251.7368ms)
379
-
380
- tests 36
466
+ ✔ Read event by domain and pid (4.653075ms)
467
+ ✔ Delete event by domain and pid (6.649683ms)
468
+ ✔ Read empty eventlog by domain and pid (2.870288ms)
469
+ ✔ By domain and pid (19.564013ms)
470
+ ✔ Persisting an event should generate an ID if not provided (257.977724ms)
471
+
472
+ ✔ Persist log (8.236839ms)
473
+ ✔ Update hub to quit (11.86896ms)
474
+
475
+ Certificate management
476
+ Persist a certificate (9.410534ms)
477
+ Persisting a duplicate certificate should return false (3.669139ms)
478
+ ✔ Read a persisted certificate by id (9.817305ms)
479
+ Reading a non-existing certificate should reject with an error (3.705119ms)
480
+
481
+ ▶ Revoke a persisted certificate
482
+ ✔ Reading a revoked certificate should reject with an error (3.894204ms)
483
+ ✔ Revoke a persisted certificate (14.21005ms)
484
+ ✔ Certificate management (44.130336ms)
485
+ ✔ Persist a hub (339.721847ms)
486
+
487
+ ✔ Reading a non existing event should reject with an error (6.280731ms)
488
+ ✔ Setup table schemas (431.031157ms)
489
+ ✔ @superhero/eventflow-db (436.106715ms)
490
+
491
+ tests 43
381
492
  suites 1
382
- pass 36
493
+ pass 43
383
494
 
384
- ----------------------------------------------------------------
495
+ -------------------------------------------------------------------------------------------------------------------------
385
496
  file | line % | branch % | funcs % | uncovered lines
386
- ----------------------------------------------------------------
497
+ -------------------------------------------------------------------------------------------------------------------------
387
498
  config.js | 100.00 | 100.00 | 100.00 |
388
- index.js | 100.00 | 96.51 | 100.00 |
499
+ index.js | 69.41 | 56.25 | 97.92 | 43-48 58-62 73-77 88-92 103-107 118-122 133-137 148-153 186-191 203-207…
389
500
  index.test.js | 100.00 | 100.00 | 100.00 |
390
- ----------------------------------------------------------------
391
- all files | 100.00 | 97.62 | 100.00 |
392
- ----------------------------------------------------------------
501
+ -------------------------------------------------------------------------------------------------------------------------
502
+ all files | 79.42 | 70.83 | 98.92 |
503
+ -------------------------------------------------------------------------------------------------------------------------
393
504
  ```
394
505
 
395
506
  ## License
package/index.js CHANGED
@@ -33,16 +33,31 @@ export default class DB
33
33
  await this.gateway.close()
34
34
  }
35
35
 
36
- async createTableEventPublished()
36
+ async createTableCertificate()
37
37
  {
38
38
  try
39
39
  {
40
- await this.gateway.query('event_published/schema')
40
+ await this.gateway.query('certificate/schema')
41
41
  }
42
42
  catch(reason)
43
43
  {
44
- const error = new Error('could not create table event_published')
45
- error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_EVENT_PUBLISHED'
44
+ const error = new Error('could not create table certificate')
45
+ error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_CERTIFICATE'
46
+ error.cause = reason
47
+ throw error
48
+ }
49
+ }
50
+
51
+ async createTableEvent()
52
+ {
53
+ try
54
+ {
55
+ await this.gateway.query('event/schema')
56
+ }
57
+ catch(reason)
58
+ {
59
+ const error = new Error('could not create table event')
60
+ error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_EVENT'
46
61
  error.cause = reason
47
62
  throw error
48
63
  }
@@ -78,68 +93,69 @@ export default class DB
78
93
  }
79
94
  }
80
95
 
81
- async createTableEventScheduled()
96
+ async createTableEventPublished()
82
97
  {
83
98
  try
84
99
  {
85
- await this.gateway.query('event_scheduled/schema')
100
+ await this.gateway.query('event_published/schema')
86
101
  }
87
102
  catch(reason)
88
103
  {
89
- const error = new Error('could not create table event_schedule')
90
- error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_EVENT_SCHEDULED'
104
+ const error = new Error('could not create table event_published')
105
+ error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_EVENT_PUBLISHED'
91
106
  error.cause = reason
92
107
  throw error
93
108
  }
94
109
  }
95
110
 
96
- async createTableEvent()
111
+ async createTableEventScheduled()
97
112
  {
98
113
  try
99
114
  {
100
- await this.gateway.query('event/schema')
115
+ await this.gateway.query('event_scheduled/schema')
101
116
  }
102
117
  catch(reason)
103
118
  {
104
- const error = new Error('could not create table event')
105
- error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_EVENT'
119
+ const error = new Error('could not create table event_schedule')
120
+ error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_EVENT_SCHEDULED'
106
121
  error.cause = reason
107
122
  throw error
108
123
  }
109
124
  }
110
125
 
111
- async createTableLog()
126
+ async createTableHub()
112
127
  {
113
128
  try
114
129
  {
115
- await this.gateway.query('log/schema')
130
+ await this.gateway.query('hub/schema')
116
131
  }
117
132
  catch(reason)
118
133
  {
119
- const error = new Error('could not create table log')
120
- error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_LOG'
134
+ const error = new Error('could not create table hub')
135
+ error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_HUB'
121
136
  error.cause = reason
122
137
  throw error
123
138
  }
124
139
  }
125
140
 
126
- async createTableHub()
141
+ async createTableLog()
127
142
  {
128
143
  try
129
144
  {
130
- await this.gateway.query('hub/schema')
145
+ await this.gateway.query('log/schema')
131
146
  }
132
147
  catch(reason)
133
148
  {
134
- const error = new Error('could not create table hub')
135
- error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_HUB'
149
+ const error = new Error('could not create table log')
150
+ error.code = 'E_EVENTFLOW_DB_CREATE_TABLE_LOG'
136
151
  error.cause = reason
137
152
  throw error
138
- }
153
+ }
139
154
  }
140
155
 
141
156
  async setupTableSchemas()
142
157
  {
158
+ await this.createTableCertificate()
143
159
  await this.createTableHub()
144
160
  await this.createTableEvent()
145
161
  await this.createTableEventCpid()
@@ -659,4 +675,68 @@ export default class DB
659
675
  throw error
660
676
  }
661
677
  }
678
+
679
+ async readCertificate(id)
680
+ {
681
+ let result
682
+
683
+ try
684
+ {
685
+ result = await this.gateway.query('certificate/read', [ id ])
686
+ }
687
+ catch(reason)
688
+ {
689
+ const error = new Error(`could not read certificate by id: ${id}`)
690
+ error.code = 'E_EVENTFLOW_DB_CERTIFICATE_READ'
691
+ error.cause = reason
692
+ throw error
693
+ }
694
+
695
+ if(0 === result.length)
696
+ {
697
+ const error = new Error(`certificate not found by id: ${id}`)
698
+ error.code = 'E_EVENTFLOW_DB_CERTIFICATE_NOT_FOUND'
699
+ throw error
700
+ }
701
+
702
+ return result[0]
703
+ }
704
+
705
+ async persistCertificate(certificate)
706
+ {
707
+ try
708
+ {
709
+ const result = await this.gateway.query('certificate/persist', [ certificate ])
710
+ return result.affectedRows > 0
711
+ }
712
+ catch(reason)
713
+ {
714
+ if('ER_DUP_ENTRY' === reason.code)
715
+ {
716
+ return false
717
+ }
718
+
719
+ const error = new Error(`could not persist certificate`)
720
+ error.code = 'E_EVENTFLOW_DB_CERTIFICATE_PERSIST'
721
+ error.cause = reason
722
+ error.certificate = certificate
723
+ throw error
724
+ }
725
+ }
726
+
727
+ async revokeCertificate(id)
728
+ {
729
+ try
730
+ {
731
+ const result = await this.gateway.query('certificate/revoke', [ id, id ])
732
+ return result.affectedRows > 0
733
+ }
734
+ catch(reason)
735
+ {
736
+ const error = new Error(`could not revoke certificate by id: ${id}`)
737
+ error.code = 'E_EVENTFLOW_DB_CERTIFICATE_REVOKE'
738
+ error.cause = reason
739
+ throw error
740
+ }
741
+ }
662
742
  }
package/index.test.js CHANGED
@@ -252,6 +252,71 @@ suite('@superhero/eventflow-db', async () =>
252
252
  const updated = await db.updateHubToQuit(hub.id)
253
253
  assert.ok(updated, 'Hub should be updated to quit')
254
254
  })
255
+
256
+ await sub.test('Certificate management', async (sub) =>
257
+ {
258
+ const
259
+ id = Date.now().toString(36),
260
+ crt =
261
+ {
262
+ id,
263
+ validity : new Date(new Date().toISOString().substring(0, 19)),
264
+ cert : 'test_certificate_value',
265
+ key : Buffer.from('test_key'),
266
+ key_iv : Buffer.from('test_key_iv'),
267
+ key_salt : Buffer.from('test_key_salt'),
268
+ key_tag : Buffer.from('test_key_tag'),
269
+ pass : Buffer.from('test_pass'),
270
+ pass_iv : Buffer.from('test_pass_iv'),
271
+ pass_salt : Buffer.from('test_pass_salt'),
272
+ pass_tag : Buffer.from('test_pass_tag')
273
+ }
274
+
275
+ await sub.test('Persist a certificate', async () =>
276
+ {
277
+ const persisted = await db.persistCertificate(crt)
278
+ assert.ok(persisted, 'Certificate should be persisted')
279
+ })
280
+
281
+ await sub.test('Persisting a duplicate certificate should return false', async () =>
282
+ {
283
+ const duplicatePersisted = await db.persistCertificate(crt)
284
+ assert.equal(duplicatePersisted, false, 'Duplicate certificate should not be persisted')
285
+ })
286
+
287
+ await sub.test('Read a persisted certificate by id', async () =>
288
+ {
289
+ const readCert = await db.readCertificate(id)
290
+
291
+ for(const key in crt)
292
+ {
293
+ assert.deepEqual(readCert[key], crt[key], 'Read certificate should match the persisted certificate')
294
+ }
295
+ })
296
+
297
+ await sub.test('Reading a non-existing certificate should reject with an error', async () =>
298
+ {
299
+ await assert.rejects(
300
+ db.readCertificate('ROOT', 'non_existing_id'),
301
+ { code: 'E_EVENTFLOW_DB_CERTIFICATE_NOT_FOUND' },
302
+ 'Should throw an error when certificate is not found'
303
+ )
304
+ })
305
+
306
+ await sub.test('Revoke a persisted certificate', async (sub) =>
307
+ {
308
+ const revoked = await db.revokeCertificate(id)
309
+ assert.ok(revoked, 'Certificate should be revoked')
310
+ await sub.test('Reading a revoked certificate should reject with an error', async () =>
311
+ {
312
+ await assert.rejects(
313
+ db.readCertificate(id),
314
+ { code: 'E_EVENTFLOW_DB_CERTIFICATE_NOT_FOUND' },
315
+ 'Revoked certificate should not be readable'
316
+ )
317
+ })
318
+ })
319
+ })
255
320
  })
256
321
 
257
322
  await sub.test('Reading a non existing event should reject with an error', async () =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superhero/eventflow-db",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "Eventflow db is a set of common database logic in the eventflow ecosystem.",
5
5
  "keywords": [
6
6
  "eventflow"
@@ -20,7 +20,7 @@
20
20
  "@superhero/locator": "4.2.0"
21
21
  },
22
22
  "scripts": {
23
- "build": "docker run --rm --name eventflow-mysql -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=eventflow -p 3306:3306 -d mysql:latest",
23
+ "test-build": "docker ps -q -f name=eventflow-mysql | grep -q . && docker stop eventflow-mysql; docker run --rm --name eventflow-mysql -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=eventflow -p 3306:3306 --health-cmd=\"mysqladmin ping -h 127.0.0.1 -uroot -proot --silent || exit 1\" --health-interval=10s --health-timeout=5s --health-retries=5 -d mysql:latest; until [ \"$(docker inspect --format='{{.State.Health.Status}}' eventflow-mysql)\" == \"healthy\" ]; do sleep 1; done; npm test",
24
24
  "test": "node --trace-warnings --test --experimental-test-coverage"
25
25
  },
26
26
  "author": {
@@ -0,0 +1,2 @@
1
+ INSERT INTO certificate
2
+ SET ?
@@ -0,0 +1,5 @@
1
+ SELECT *
2
+ FROM certificate
3
+ WHERE id = ?
4
+ AND version = 0
5
+ AND revoked IS NULL
@@ -0,0 +1,13 @@
1
+ UPDATE certificate AS c
2
+ LEFT JOIN
3
+ (
4
+ SELECT id, COUNT(*) AS new_version
5
+ FROM certificate
6
+ WHERE id = ?
7
+ ) sub
8
+ ON c.id = sub.id
9
+ SET c.revoked = UTC_TIMESTAMP(),
10
+ c.version = sub.new_version
11
+ WHERE c.id = ?
12
+ AND c.version = 0
13
+ AND c.revoked IS NULL
@@ -0,0 +1,26 @@
1
+ CREATE TABLE IF NOT EXISTS certificate
2
+ (
3
+ created DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
4
+ updated DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
5
+ version INT UNSIGNED NOT NULL DEFAULT 0,
6
+ id VARCHAR(64) NOT NULL,
7
+ cert TEXT NOT NULL,
8
+ `key` BLOB NOT NULL,
9
+ key_iv VARBINARY(16) NOT NULL,
10
+ key_salt VARBINARY(16) NOT NULL,
11
+ key_tag VARBINARY(16) NOT NULL,
12
+ pass BLOB NOT NULL,
13
+ pass_iv VARBINARY(16) NOT NULL,
14
+ pass_salt VARBINARY(16) NOT NULL,
15
+ pass_tag VARBINARY(16) NOT NULL,
16
+ validity DATETIME NOT NULL,
17
+ revoked DATETIME NULL,
18
+
19
+ PRIMARY KEY (version, id)
20
+ )
21
+ ENGINE=InnoDB
22
+ PARTITION BY RANGE (version)
23
+ (
24
+ PARTITION p_hot VALUES LESS THAN (1),
25
+ PARTITION p_cold VALUES LESS THAN MAXVALUE
26
+ )