Skip to content

Commit 9bf0262

Browse files
authored
feat(event_tags): Add event_tags table and migration script (#349)
1 parent 3b5b1fc commit 9bf0262

File tree

3 files changed

+105
-13
lines changed

3 files changed

+105
-13
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
exports.up = async function (knex) {
2+
// Create the event_tags table
3+
await knex.schema.createTable('event_tags', function (table) {
4+
table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()'))
5+
table.binary('event_id').notNullable()
6+
table.text('tag_name').notNullable()
7+
table.text('tag_value').notNullable()
8+
})
9+
10+
// Add indexes
11+
await knex.schema.table('event_tags', function (table) {
12+
table.index('event_id')
13+
table.index(['tag_name', 'tag_value'])
14+
})
15+
16+
// Add triggers
17+
await knex.raw(
18+
`CREATE OR REPLACE FUNCTION process_event_tags() RETURNS TRIGGER AS $$
19+
DECLARE
20+
tag_element jsonb;
21+
tag_name text;
22+
tag_value text;
23+
BEGIN
24+
DELETE FROM event_tags WHERE event_id = OLD.event_id;
25+
26+
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
27+
FOR tag_element IN SELECT jsonb_array_elements(NEW.event_tags)
28+
LOOP
29+
tag_name := trim((tag_element->0)::text, '"');
30+
tag_value := trim((tag_element->1)::text, '"');
31+
IF length(tag_name) = 1 AND tag_value IS NOT NULL AND tag_value <> '' THEN
32+
INSERT INTO event_tags (event_id, tag_name, tag_value) VALUES (NEW.event_id, tag_name, tag_value);
33+
END IF;
34+
END LOOP;
35+
END IF;
36+
37+
RETURN NEW;
38+
END;
39+
$$ LANGUAGE plpgsql;
40+
41+
CREATE TRIGGER insert_event_tags
42+
AFTER INSERT OR UPDATE OR DELETE ON events
43+
FOR EACH ROW
44+
EXECUTE FUNCTION process_event_tags();
45+
`)
46+
47+
// Migrate jsonb event_tags to event_tags table
48+
const events = await knex.select('event_id', 'event_tags').from('events')
49+
const totalEvents = events.length
50+
let processedEvents = 0
51+
let lastPercentage = 0
52+
53+
for (const event of events) {
54+
const exists = await knex('event_tags').where('event_id', event.event_id).first()
55+
if (exists) {
56+
continue
57+
}
58+
59+
for (const tag of event.event_tags) {
60+
const [tag_name, tag_value] = tag
61+
if (tag_name.length === 1 && tag_value) {
62+
await knex('event_tags').insert({
63+
event_id: event.event_id,
64+
tag_name: tag_name,
65+
tag_value: tag_value,
66+
})
67+
}
68+
}
69+
70+
processedEvents++
71+
const currentPercentage = Math.floor(processedEvents / totalEvents * 100)
72+
if (currentPercentage > lastPercentage) {
73+
console.log(`${new Date().toLocaleString()} Migration progress: ${currentPercentage}% (${processedEvents}/${totalEvents})`)
74+
lastPercentage = currentPercentage
75+
}
76+
}
77+
}
78+
79+
exports.down = function (knex) {
80+
return knex.schema
81+
// Drop the trigger first
82+
.raw('DROP TRIGGER IF EXISTS insert_event_tags ON events')
83+
// Then drop the function
84+
.raw('DROP FUNCTION IF EXISTS process_event_tags')
85+
// Finally, drop the table
86+
.dropTable('event_tags')
87+
}

src/repositories/event-repository.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,26 +131,31 @@ export class EventRepository implements IEventRepository {
131131
const andWhereRaw = invoker(1, 'andWhereRaw')
132132
const orWhereRaw = invoker(2, 'orWhereRaw')
133133

134+
let isTagQuery = false
134135
pipe(
135136
toPairs,
136137
filter(pipe(nth(0) as () => string, isGenericTagQuery)) as any,
137138
forEach(([filterName, criteria]: [string, string[]]) => {
139+
isTagQuery = true
138140
builder.andWhere((bd) => {
139141
ifElse(
140142
isEmpty,
141143
() => andWhereRaw('1 = 0', bd),
142144
forEach((criterion: string) => void orWhereRaw(
143-
'"event_tags" @> ?',
144-
[
145-
JSON.stringify([[filterName[1], criterion]]) as any,
146-
],
145+
'event_tags.tag_name = ? AND event_tags.tag_value = ?',
146+
[filterName[1], criterion],
147147
bd,
148148
)),
149149
)(criteria)
150150
})
151151
}),
152152
)(currentFilter as any)
153153

154+
if (isTagQuery) {
155+
builder.leftJoin('event_tags', 'events.event_id', 'event_tags.event_id')
156+
.select('events.*')
157+
}
158+
154159
return builder
155160
})
156161

test/unit/repositories/event-repository.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -274,23 +274,23 @@ describe('EventRepository', () => {
274274

275275
const query = repository.findByFilters(filters).toString()
276276

277-
expect(query).to.equal('select * from "events" where (1 = 0) order by "event_created_at" asc limit 500')
277+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (1 = 0) order by "event_created_at" asc limit 500')
278278
})
279279

280280
it('selects events by one #e tag', () => {
281281
const filters = [{ '#e': ['aaaaaa'] }]
282282

283283
const query = repository.findByFilters(filters).toString()
284284

285-
expect(query).to.equal('select * from "events" where ("event_tags" @> \'[["e","aaaaaa"]]\') order by "event_created_at" asc limit 500')
285+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (event_tags.tag_name = \'e\' AND event_tags.tag_value = \'aaaaaa\') order by "event_created_at" asc limit 500')
286286
})
287287

288288
it('selects events by two #e tag', () => {
289289
const filters = [{ '#e': ['aaaaaa', 'bbbbbb'] }]
290290

291291
const query = repository.findByFilters(filters).toString()
292292

293-
expect(query).to.equal('select * from "events" where ("event_tags" @> \'[["e","aaaaaa"]]\' or "event_tags" @> \'[["e","bbbbbb"]]\') order by "event_created_at" asc limit 500')
293+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (event_tags.tag_name = \'e\' AND event_tags.tag_value = \'aaaaaa\' or event_tags.tag_name = \'e\' AND event_tags.tag_value = \'bbbbbb\') order by "event_created_at" asc limit 500')
294294
})
295295
})
296296

@@ -300,23 +300,23 @@ describe('EventRepository', () => {
300300

301301
const query = repository.findByFilters(filters).toString()
302302

303-
expect(query).to.equal('select * from "events" where (1 = 0) order by "event_created_at" asc limit 500')
303+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (1 = 0) order by "event_created_at" asc limit 500')
304304
})
305305

306306
it('selects events by one #p tag', () => {
307307
const filters = [{ '#p': ['aaaaaa'] }]
308308

309309
const query = repository.findByFilters(filters).toString()
310310

311-
expect(query).to.equal('select * from "events" where ("event_tags" @> \'[["p","aaaaaa"]]\') order by "event_created_at" asc limit 500')
311+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (event_tags.tag_name = \'p\' AND event_tags.tag_value = \'aaaaaa\') order by "event_created_at" asc limit 500')
312312
})
313313

314314
it('selects events by two #p tag', () => {
315315
const filters = [{ '#p': ['aaaaaa', 'bbbbbb'] }]
316316

317317
const query = repository.findByFilters(filters).toString()
318318

319-
expect(query).to.equal('select * from "events" where ("event_tags" @> \'[["p","aaaaaa"]]\' or "event_tags" @> \'[["p","bbbbbb"]]\') order by "event_created_at" asc limit 500')
319+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (event_tags.tag_name = \'p\' AND event_tags.tag_value = \'aaaaaa\' or event_tags.tag_name = \'p\' AND event_tags.tag_value = \'bbbbbb\') order by "event_created_at" asc limit 500')
320320
})
321321
})
322322

@@ -326,23 +326,23 @@ describe('EventRepository', () => {
326326

327327
const query = repository.findByFilters(filters).toString()
328328

329-
expect(query).to.equal('select * from "events" where (1 = 0) order by "event_created_at" asc limit 500')
329+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (1 = 0) order by "event_created_at" asc limit 500')
330330
})
331331

332332
it('selects events by one #r tag', () => {
333333
const filters = [{ '#r': ['aaaaaa'] }]
334334

335335
const query = repository.findByFilters(filters).toString()
336336

337-
expect(query).to.equal('select * from "events" where ("event_tags" @> \'[["r","aaaaaa"]]\') order by "event_created_at" asc limit 500')
337+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (event_tags.tag_name = \'r\' AND event_tags.tag_value = \'aaaaaa\') order by "event_created_at" asc limit 500')
338338
})
339339

340340
it('selects events by two #r tag', () => {
341341
const filters = [{ '#r': ['aaaaaa', 'bbbbbb'] }]
342342

343343
const query = repository.findByFilters(filters).toString()
344344

345-
expect(query).to.equal('select * from "events" where ("event_tags" @> \'[["r","aaaaaa"]]\' or "event_tags" @> \'[["r","bbbbbb"]]\') order by "event_created_at" asc limit 500')
345+
expect(query).to.equal('select "events".* from "events" left join "event_tags" on "events"."event_id" = "event_tags"."event_id" where (event_tags.tag_name = \'r\' AND event_tags.tag_value = \'aaaaaa\' or event_tags.tag_name = \'r\' AND event_tags.tag_value = \'bbbbbb\') order by "event_created_at" asc limit 500')
346346
})
347347
})
348348
})

0 commit comments

Comments
 (0)