This page looks best with JavaScript enabled

Optimizing Cargo - query 1

 ·  ☕ 16 min read

When we migrated domains from gamepedia.com to fandom.com, there was a lot of unexpected activity due to Cargo. One particularly slow query on every player page caused some significant platform-wide slowdowns at this point, and I ended up rewriting the query to improve performance. In the process of rewriting I also added some interesting general-case application logic to my Cargo wrapper. I’ll present both the SQL optimization as well as the wrapper change here.

(Note: All code in this post was originally written by me for Leaguepedia and is licensed under CC BY-SA 3.0.)

The original query

Why are there code snippets from two modules here? Originally, I was subclassing Module:NewsCurrentStatusAbstract, which was able to filter through news & roster change lines to grab the current status of a player on a team, or a team of players, and print out the result. It was the way I was planning to print current team rosters, but that ended up falling through after I decided to rely on the Tenures table instead of tracking changes through NewsItems and RosterChanges.

I still may need to use this approach for rosters if I implement a feature that lets you show the historical state of a team by querying up until a specific date - however, in the process of refactoring this single query, I combined the two modules because the team subclass simply isn’t in use and it was too messy to bother with, and Module:NewsCurrentStatusAbstract is now deleted; I certainly won’t return to having PlayerCurrentTeam subclass something else, even if I have another module with a similar approach, as I felt it was too messy and hard to follow the logic.

Anyway, here’s the query.

In Module:NewsCurrentStatusAbstract:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function p:getQuery(subject)
	local query = {
		tables = self:getTables(),
		join = self:getJoin(),
		where = self:getWhere(subject),
		fields = self:getFields(),
		orderBy = 'News.Date_Sort ASC, News.N_LineInDate ASC, RC.N_LineInNews ASC',
		complexTypes = {
			Role = {
				type = 'RoleList',
				args = {
					'Roles',
					modifier = 'RoleModifier',
				}
			}
		}
	}
	util_cargo.logQuery(query)
	return query
end

function p:getTables() end
function p:getJoin() end
function p:getWhere() end

function p:getFields()
	local ret = {
		'RC.Date_Sort',
		'RC.Player',
		
		'RC.Team',
		'RC.Roles',
		'RC.RoleModifier',
		'RC.Direction',
		'RC.CurrentTeamPriority=Priority',
		'News.Date_Display',
		'News.Region',
		'News._pageName',
	}
	return ret
end

And in Module:PlayerCurrentTeam:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function PCT:getTables()
	local ret = {
		'NewsItems=News',
		'RosterChanges=RC',
		'PlayerRedirects=PR1',
		'Contracts',
		'PlayerRedirects=PR2',
		'TeamRedirects=TRed1', -- join from contracts to ST1
		'TeamRedirects=TRed2', -- join from news to ST2
		'SisterTeams=ST1', -- the sister team page (if one exists) of the contract team
		'SisterTeams=ST2', -- the sister team page (if one exists) of the news team
		
		'ResidencyChanges=ResC',
		'PlayerRedirects=PR3',
	}
	return ret
end

function PCT:getJoin()
	local ret = {
		'News.NewsId=RC.NewsId',
		'News.NewsId=Contracts.NewsId',
		'RC.Player=PR1.AllName',
		'Contracts.Player=PR2.AllName',
		
		-- contracts need to respect contracts from the same org
		'Contracts.Team=TRed1.AllName',
		'TRed1._pageName=ST1.Team',
		
		-- roster changes need to discover the org so we can pull contracts
		'RC.Team=TRed2.AllName',
		'TRed2._pageName=ST2.Team',
		
		-- residency changes are just the same news id that's it
		'News.NewsId=ResC.NewsId',
		'ResC.Player=PR3.AllName',
	}
	return ret
end

function PCT:getWhere(player)
	local where = {
		('PR1.OverviewPage="%s"'):format(player),
		('PR2.OverviewPage="%s"'):format(player),
		('PR3.OverviewPage="%s"'):format(player),
	}
	return util_cargo.concatWhereOr(where)
end

function PCT:getFields()
	local fields = self:super('getFields')
	fields[#fields+1] = 'Contracts.ContractEnd'
	fields[#fields+1] = 'COALESCE(ST1._pageName, Contracts.Team, ST2._pageName, RC.Team)=SisterTeamPage'
	fields[#fields+1] = 'COALESCE(PR1.OverviewPage, PR2.OverviewPage)=PlayerLink'
	fields[#fields+1] = 'Contracts.IsRemoval=IsContractRemoval [boolean]'
	fields[#fields+1] = 'ResC.ResidencyOld'
	fields[#fields+1] = 'RC.Preload'
	fields[#fields+1] = 'RC.Direction'
	return fields
end

Let’s specifically look at the tables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function PCT:getTables()
	local ret = {
		'NewsItems=News',
		'RosterChanges=RC',
		'PlayerRedirects=PR1',
		'Contracts',
		'PlayerRedirects=PR2',
		'TeamRedirects=TRed1', -- join from contracts to ST1
		'TeamRedirects=TRed2', -- join from news to ST2
		'SisterTeams=ST1', -- the sister team page (if one exists) of the contract team
		'SisterTeams=ST2', -- the sister team page (if one exists) of the news team
		
		'ResidencyChanges=ResC',
		'PlayerRedirects=PR3',
	}
	return ret
end

Yikes, that’s THREE entire copies of PlayerRedirects, a table that has 17,536 rows at the time of publication of this article. And in the where condition, I’m OR-ing them all.

The expected results sets are also completely disjoint. One copy of PlayerRedirects gets data about Contracts; another about ResidencyChanges; and finally, the last one about RosterChanges. It’s impossible that any single row will ever have non-null values from each of these tables at the same time. This is a huge performance sink for no reason.

The change to make

Because the three parts of the query being OR-ed together all represent disjoint rows in the join, I wanted to break up the query into three entirely disjoint parts and then UNION them all together to get my final result. No big deal; UNION is a common SQL operator.

There’s just one teeny tiny problem, which is that Cargo doesn’t support UNION at all. So even if I could write the SQL query I needed, I couldn’t write the Cargo query I needed. Instead I’d have to make three completely separate queries and then write the union part in Lua application logic.

Of course I wanted to do this UNION in the general case and put it in my Cargo wrapper, so I’d need to come up with some general-case syntax for it, add support to Module:CargoUtil, and then implement what I’d decided on in the PlayerCurrentTeam module.

Oh, also I was on a “literally asap” deadline. And I had a migraine, but who’s keeping score?

The change

Module:CargoUtil

Originally, p.queryAndCast looked like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function p.queryAndCast(query)
	local copyQuery = h.getFinalizedCopyQuery(query)
	local result = mw.ext.cargo.query(
		copyQuery.tables,
		copyQuery.fields,
		copyQuery
	)
	h.cast(result, copyQuery)
	return h.groupOneToManyFields(result, copyQuery)
end

After the update, it looked like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
function p.queryAndCast(query)
	if not query.union then
		return h.queryAndCastOne(query)
	end
	local result = util_map.inPlace(query.union, h.queryAndCastOne)
	result = util_table.mergeArrays(unpack(result))
	if query.sortKey then
		util_sort.tablesByKeys(result, query.sortKey, query.sortOrder)
	end
	return result
end

function h.queryAndCastOne(query)
	local copyQuery = h.getFinalizedCopyQuery(query)
	local result = mw.ext.cargo.query(
		copyQuery.tables,
		copyQuery.fields,
		copyQuery
	)
	h.cast(result, copyQuery)
	return h.groupOneToManyFields(result, copyQuery)
end

If there’s no union param defined, then I immediately return the same result as before. If there is, then I map the union to query each one individually, unpack the result, and then sort it according to its sortKey and sortOrder parameters.

I deliberately avoided reusing orderBy here because that parameter is recognized by Cargo; I wanted to avoid overloading this term.

Module:PlayerCurrentTeam

Now let’s look at the structure of the query this setup induces.

First, here’s the general form of the query, without any of the specifics:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
local QB = LCS.class()
local QBRosters = QB:extends()
local QBContracts = QB:extends()
local QBResidency = QB:extends()

function PCT:getQuery(subject)
	local rosBuilder = QBRosters()
	local contractsBuilder = QBContracts()
	local resBuilder = QBResidency()
	local query = {
		union = {
			{
				tables = rosBuilder:getTables(),
				join = rosBuilder:getJoin(),
				where = rosBuilder:getWhere(subject),
				fields = rosBuilder:getFields(),
				complexTypes = {
					Role = {
						type = 'RoleList',
						args = {
							'Roles',
							modifier = 'RoleModifier',
						}
					}
				},
			},
			{
				tables = contractsBuilder:getTables(),
				join = contractsBuilder:getJoin(),
				where = contractsBuilder:getWhere(subject),
				fields = contractsBuilder:getFields(),
			},
			{
				tables = resBuilder:getTables(),
				join = resBuilder:getJoin(),
				where = resBuilder:getWhere(subject),
				fields = resBuilder:getFields(),
			},
		},
		sortKey = { 'Date_Sort', 'N_LineInDate', 'N_LineInNews' },
		sortOrder = { true, true, true },
	}
	return query
end

As you can see, I’m nesting the union two levels deep instead of one. I’m actually a bit unhappy with this result, and in the future I’m going to adjust it so that only one additional level of nesting is needed; however, this is what I came up with at the time.

Other than the second layer of nesting, this is a pretty clean format. I have an OOP approach here to constructing each of the independent queries, using LuaClassSystem, though that’s not necessary; I could set up each of the parts of the query in any way I wanted. (The complexTypes thing is just an elaborate method for casting the Roles field as a RoleList with the additional parameter that modifier is given by the field RoleModifier. And by “elaborate” I mean, “as simple as I could make it without doing any string parsing, which is by necessity a very verbose syntax.”)

Here’s the rest of the query:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
function QB:getWhere(player)
	local where = {
		('PR.OverviewPage="%s"'):format(player),
	}
	return util_cargo.concatWhere(where)
end

function QB:getFields()
	local ret = {
		'News.Date_Display',
		'News.Region',
		'News._pageName',
		'News.Date_Sort',
		'News.N_LineInDate',
		'PR.OverviewPage=PlayerLink',
	}
	return ret
end

function QBRosters:getTables()
	local ret = {
		'NewsItems=News',
		'RosterChanges=RC',
		'PlayerRedirects=PR',
		'TeamRedirects=TRed',
		'SisterTeams=ST',
	}
	return ret
end

function QBRosters:getJoin()
	local ret = {
		'News.NewsId=RC.NewsId',
		'RC.Player=PR.AllName',
		
		-- roster changes need to discover the org so we can pull contracts
		'RC.Team=TRed.AllName',
		'TRed._pageName=ST.Team',
	}
	return ret
end

function QBRosters:getFields()
	local fields = self:super('getFields')
	local tbl = {
		'RC.Player',
		'RC.Team',
		'RC.Roles',
		'RC.RoleModifier',
		'RC.Direction',
		'RC.CurrentTeamPriority=Priority',
		'RC.Preload',
		'RC.Direction',
		'RC.N_LineInNews',
		'COALESCE(ST._pageName, RC.Team)=SisterTeamPage',
	}
	util_table.mergeArrays(fields, tbl)
	return fields
end

function QBContracts:getTables()
	local ret = {
		'NewsItems=News',
		'Contracts',
		'PlayerRedirects=PR',
		'TeamRedirects=TRed',
		'SisterTeams=ST',
	}
	return ret
end

function QBContracts:getJoin()
	local ret = {
		'News.NewsId=Contracts.NewsId',
		'Contracts.Player=PR.AllName',
		
		-- contracts need to respect contracts from the same org
		'Contracts.Team=TRed.AllName',
		'TRed._pageName=ST.Team',
	}
	return ret
end

function QBContracts:getFields()
	local fields = self:super('getFields')
	local tbl = {
		'COALESCE(ST._pageName, Contracts.Team)=SisterTeamPage',
		'Contracts.IsRemoval=IsContractRemoval [boolean]',
		'Contracts.ContractEnd',
	}
	util_table.mergeArrays(fields, tbl)
	return fields
end

function QBResidency:getTables()
	local ret = {
		'NewsItems=News',
		'ResidencyChanges=ResC',
		'PlayerRedirects=PR',
	}
	return ret
end

function QBResidency:getJoin()
	local ret = {
		-- residency changes are just the same news id that's it
		'News.NewsId=ResC.NewsId',
		'ResC.Player=PR.AllName',
	}
	return ret
end

function QBResidency:getFields()
	local fields = self:super('getFields')
	local tbl = {
		'ResC.ResidencyOld',
	}
	util_table.mergeArrays(fields, tbl)
	return fields
end

The use of LuaClassSystem lets me have the common fields:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function QB:getFields()
	local ret = {
		'News.Date_Display',
		'News.Region',
		'News._pageName',
		'News.Date_Sort',
		'News.N_LineInDate',
		'PR.OverviewPage=PlayerLink',
	}
	return ret
end

defined only a single time, and then appended to in each of the individual queries when I subclass. Originally I was also overwriting a parent method for the join, but the fact that join conditions need to be stated in order made that too cumbersome to deal with, so I didn’t bother in that case.

Conclusion

Normally, your goal is to create one Cargo query to fetch all your data at once. However, if you have a bunch of independent results fetched at the same time across a large result set with multiple JOINs and several ORs, it might be that you’re constructing a less efficient query than you could be, and you’d be better off with the UNION of disjoint queries, each with a single independent clause! In this case, I removed all critical performance issues by splitting up this query, and generated some permanent infrastructure that I was able to apply to other queries as well, which I’ll visit in future blog posts.

Full module code

Here’s the full code of Module:PlayerCurrentTeam:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
local util_args = require('Module:ArgsUtil')
local util_cargo = require("Module:CargoUtil")
local util_esports = require("Module:EsportsUtil")
local util_html = require("Module:HtmlUtil")
local util_map = require("Module:MapUtil")
local util_sort = require("Module:SortUtil")
local util_source = require("Module:SourceUtil")
local util_table = require("Module:TableUtil")
local util_text = require("Module:TextUtil")
local util_title = require("Module:TitleUtil")
local util_vars = require("Module:VarsUtil")
local i18n = require('Module:i18nUtil')

local LCS = require('Module:LuaClassSystem')

local OD = require('Module:OrderedDict')

local PCT = LCS.class()
local QB = LCS.class()

local CONTRACT_MAINTAINED_ON_LEAVE = {
	from_sister = true,
	to_sister = true,
	from_academy = true,
	to_academy = true,
	from_main = true,
	to_main = true,
	gcd_from_academy = true,
	gcd_from_main = true,
	gcd_to_academy = true,
	gcd_to_main = true,
}

local h = {}

local p = {}

-- for testing to be called from MW
function p.test(frame)
	local args = util_args.merge()
	local result = p.main(args[1])
	local output = mw.html.create('table')
		:addClass('wikitable')
	for _, row in ipairs(result) do
		output:tag('tr')
			:tag('td'):wikitext(row.team)
			:tag('td'):wikitext(tostring(row.role))
			:tag('td'):wikitext(row.role:images())
	end
	return output
end

-- intended to be called by Infobox/Player
-- so this does not apply any processing to the output
-- and instead just returns a data table
function p.main(player)
	return PCT:init(player)
end

function PCT:init(subject)
	self.CONTRACT_DATES = {}
	self.PREVIOUS_RESIDENCIES = {}
	local listOfChanges = self:queryForChanges(subject)
	self:processChangesRows(listOfChanges)
	if #listOfChanges == 0 then return self:defaultOutput() end
	local netStatuses = self:computeNetStatuses(listOfChanges)
	local listOfSubjects = self:getFinalListOfSubjects(netStatuses)
	return self:makeOutput(listOfSubjects, listOfChanges)
end

function PCT:defaultOutput()
	return { last = {}, contractDates = {} }
end

function PCT:queryForChanges(subject)
	return util_cargo.queryAndCast(self:getQuery(subject))
end

-- start cargo shit
local QBRosters = QB:extends()
local QBContracts = QB:extends()
local QBResidency = QB:extends()

function PCT:getQuery(subject)
	local rosBuilder = QBRosters()
	local contractsBuilder = QBContracts()
	local resBuilder = QBResidency()
	local query = {
		union = {
			{
				tables = rosBuilder:getTables(),
				join = rosBuilder:getJoin(),
				where = rosBuilder:getWhere(subject),
				fields = rosBuilder:getFields(),
				complexTypes = {
					Role = {
						type = 'RoleList',
						args = {
							'Roles',
							modifier = 'RoleModifier',
						}
					}
				},
			},
			{
				tables = contractsBuilder:getTables(),
				join = contractsBuilder:getJoin(),
				where = contractsBuilder:getWhere(subject),
				fields = contractsBuilder:getFields(),
			},
			{
				tables = resBuilder:getTables(),
				join = resBuilder:getJoin(),
				where = resBuilder:getWhere(subject),
				fields = resBuilder:getFields(),
			},
		},
		sortKey = { 'Date_Sort', 'N_LineInDate', 'N_LineInNews' },
		sortOrder = { true, true, true },
	}
	return query
end

function QB:getWhere(player)
	local where = {
		('PR.OverviewPage="%s"'):format(player),
	}
	return util_cargo.concatWhere(where)
end

function QB:getFields()
	local ret = {
		'News.Date_Display',
		'News.Region',
		'News._pageName',
		'News.Date_Sort',
		'News.N_LineInDate',
		'PR.OverviewPage=PlayerLink',
	}
	return ret
end

function QBRosters:getTables()
	local ret = {
		'NewsItems=News',
		'RosterChanges=RC',
		'PlayerRedirects=PR',
		'TeamRedirects=TRed',
		'SisterTeams=ST',
	}
	return ret
end

function QBRosters:getJoin()
	local ret = {
		'News.NewsId=RC.NewsId',
		'RC.Player=PR.AllName',
		
		-- roster changes need to discover the org so we can pull contracts
		'RC.Team=TRed.AllName',
		'TRed._pageName=ST.Team',
	}
	return ret
end

function QBRosters:getFields()
	local fields = self:super('getFields')
	local tbl = {
		'RC.Player',
		'RC.Team',
		'RC.Roles',
		'RC.RoleModifier',
		'RC.Direction',
		'RC.CurrentTeamPriority=Priority',
		'RC.Preload',
		'RC.Direction',
		'RC.N_LineInNews',
		'COALESCE(ST._pageName, RC.Team)=SisterTeamPage',
	}
	util_table.mergeArrays(fields, tbl)
	return fields
end

function QBContracts:getTables()
	local ret = {
		'NewsItems=News',
		'Contracts',
		'PlayerRedirects=PR',
		'TeamRedirects=TRed',
		'SisterTeams=ST',
	}
	return ret
end

function QBContracts:getJoin()
	local ret = {
		'News.NewsId=Contracts.NewsId',
		'Contracts.Player=PR.AllName',
		
		-- contracts need to respect contracts from the same org
		'Contracts.Team=TRed.AllName',
		'TRed._pageName=ST.Team',
	}
	return ret
end

function QBContracts:getFields()
	local fields = self:super('getFields')
	local tbl = {
		'COALESCE(ST._pageName, Contracts.Team)=SisterTeamPage',
		'Contracts.IsRemoval=IsContractRemoval [boolean]',
		'Contracts.ContractEnd',
	}
	util_table.mergeArrays(fields, tbl)
	return fields
end

function QBResidency:getTables()
	local ret = {
		'NewsItems=News',
		'ResidencyChanges=ResC',
		'PlayerRedirects=PR',
	}
	return ret
end

function QBResidency:getJoin()
	local ret = {
		-- residency changes are just the same news id that's it
		'News.NewsId=ResC.NewsId',
		'ResC.Player=PR.AllName',
	}
	return ret
end

function QBResidency:getFields()
	local fields = self:super('getFields')
	local tbl = {
		'ResC.ResidencyOld',
	}
	util_table.mergeArrays(fields, tbl)
	return fields
end

-- end cargo shit

function PCT:processChangesRows(listOfChanges)
	util_map.selfRowsInPlace(self, listOfChanges, self.processOneChangeRow)
end

function PCT:processOneChangeRow(row)
	row.Subject = row.Team
	row.team = row.Team
	row.role = row.Role
	if h.isContractRemoval(row) then
		self.CONTRACT_DATES[row.SisterTeamPage] = nil
	end
	if row.ContractEnd then
		self.CONTRACT_DATES[row.SisterTeamPage] = row.ContractEnd
	end
	
	-- this is plaintext so that we can cast it easily as a list when we're done
	self.PREVIOUS_RESIDENCIES[#self.PREVIOUS_RESIDENCIES+1] = row.ResidencyOld
end

function h.isContractRemoval(row)
	-- In contrast to "Module:PlayerTeamHistoryAbstract" we look at the preload to decide whether
	-- or not we have to remove contracts. PTHA instead does this weird reverse-lookup thingy
	-- working backwards through history in two phases. PTHA's approach is significantly more complex
	-- but maybe not unreasonable because it's doing a ton of other things at the same time. But
	-- this approach is hopefully sufficient for this smaller, less-complex use case here.
	if row.IsContractRemoval then return true end
	if row.Direction == 'Join' then return false end
	if not row.Preload then return false end
	return not CONTRACT_MAINTAINED_ON_LEAVE[row.Preload]
end

-- get dictionary keyed by subject
function PCT:computeNetStatuses(listOfChanges)
	local currentStatuses = {}
	for _, row in ipairs(listOfChanges) do
		self:updateEntryForThis(currentStatuses, row)
	end
	return currentStatuses
end

function PCT:updateEntryForThis(currentStatuses, row)
	if currentStatuses[row.Subject] then
		row.Priority = row.Priority or currentStatuses[row.Subject].Priority
	end
	if row.Subject then
		currentStatuses[row.Subject] = row
	end
end

-- get ordered list of subjects
function PCT:getFinalListOfSubjects(netStatuses)
	local subjects = {}
	for _, status in pairs(netStatuses) do
		self:addSubjectToOutputIfNeeded(subjects, status)
	end
	util_sort.tablesByKeys(subjects, 'Priority', true)
	return subjects
end

function PCT:addSubjectToOutputIfNeeded(subjects, status)
	if not self:doWeAddSubjectToOutput(status) then return end
	subjects[#subjects+1] = status
end

function PCT:doWeAddSubjectToOutput(status)
	return status.Direction == 'Join'
end

-- make output
function PCT:makeOutput(listOfSubjects, listOfChanges)
	listOfSubjects.last = h.getLastTeamList(listOfChanges)
	listOfSubjects.contractDates = self.CONTRACT_DATES
	listOfSubjects.resPrevList = self.PREVIOUS_RESIDENCIES
	return listOfSubjects
end

function h.getLastTeamList(listOfChanges)
	return h.getLastTeamInfo(listOfChanges[#listOfChanges])
end

function h.getLastTeamInfo(row)
	local ret = {
		team = row.Team,
		role = row.Role,
	}
	return ret
end

return p
Share on

river
WRITTEN BY
River
River is a developer most at home in MediaWiki and known for building Leaguepedia. She likes cats.

What's on this Page