Module:UtilsCargo

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search

This module is a wrapper over mw.ext.cargo. It also provides utilities for constructing queries.

Using this module instead of using mw.ext.cargo directly allows us to see via Special:WhatLinksHere what pages are running Cargo queries.

This module exports the following functions.

mw.ext.cargo.query wrapper

query

query(tables, fields, [args])

Parameters

Returns

  • An array of the query results. Throws an error when query syntax is invalid.
  • Categories to add to the page.

Examples

#InputOutputResultStatus
1
query(
  "Games",
  "code, shortName",
  {
    orderBy = "releaseDate",
    where = "type='main'",
    limit = 3,
  }
)
{
  {
    shortName = "The Legend of Zelda",
    code = "TLoZ",
  },
  {
    shortName = "The Adventure of Link",
    code = "TAoL",
  },
  {
    shortName = "A Link to the Past",
    code = "ALttP",
  },
}
table
Green check.svg
"[[Category:Pages querying Cargo data]]"
Green check.svg


Query builders

allOf

allOf(...)

Parameters

Returns

  • A WHERE clause with ANDed conditions and escaped quotation marks.

Examples

#InputOutputStatus
2
allOf(
  {
    remakeNum = 2,
    game = "Link's Awakening",
  },
  "foo HOLDS 'bar'",
  "baz LIKE '%quux%'"
)
"(remakeNum='2') AND (game='Link\\'s Awakening') AND (foo HOLDS 'bar') AND (baz LIKE '%quux%')"
Green check.svg

anyOf

anyOf(...)

Parameters

Returns

  • A WHERE clause with ORed conditions and escaped quotation marks.

Examples

#InputOutputStatus
3
anyOf(
  {
    remakeNum = 2,
    game = "Link's Awakening",
  },
  "foo HOLDS 'bar'",
  "baz LIKE '%quux%'"
)
"remakeNum='2' OR game='Link\\'s Awakening' OR foo HOLDS 'bar' OR baz LIKE '%quux%'"
Green check.svg

escape

escape(str)

Escapes special characters (namely apostrophes) that are not supported in Cargo string values.

Returns

  • A string with escaped apostrophes.

Examples

#InputOutputStatus
4
escape("Dinraal's Claw")
"Dinraal\\'s Claw"
Green check.svg

holdsAll

holdsAll(field, values)

Parameters

Returns

  • A query string of and'ed HOLDS clauses.

Examples

#InputOutputStatus
5
holdsAll("game", {"Link's Awakening"})
"(game HOLDS 'Link\\'s Awakening')"
Green check.svg
As a workaround to a Cargo issue, multiple HOLDS statements are converted to an equivalent regex-based syntax.
6
holdsAll("game", {"OoT", "TP"})
"(game__full REGEXP '(^|\\\\|)OoT($|\\\\|)') AND (game__full REGEXP '(^|\\\\|)TP($|\\\\|)')"
Green check.svg

holdsAny

holdsAny(field, values)

Parameters

Returns

  • A query string of or'ed HOLDS clauses.

Examples

#InputOutputStatus
7
holdsAny("game", {"Link's Awakening"})
"game HOLDS 'Link\\'s Awakening'"
Green check.svg
8
holdsAny("game", {"OoT", "TP"})
"game HOLDS 'OoT' OR game HOLDS 'TP'"
Green check.svg

IN

IN(field, values)

Parameters

Returns

  • A where clause using the SQL IN keyword.

Examples

#InputOutputStatus
9
IN("_pageName", {"Link's Shadow", "Zelda", "Ganon"})
"_pageName IN ('Link\\'s Shadow', 'Zelda', 'Ganon')"
Green check.svg
10
IN("_pageName", {})
"_pageName IN ('')"
Green check.svg


Category strings

categoryQuerying

categoryQuerying()

Returns

Examples

#InputOutput
11
categoryQuerying()
"[[Category:Pages querying Cargo data]]"

categoryStoring

categoryStoring()

Returns

Examples

#InputOutput
12
categoryStoring()
"[[Category:Pages storing Cargo data]]"

local p = {}
local h = {}

local frame = mw.getCurrentFrame()

function p.query(tables, fields, args)
	return mw.ext.cargo.query(tables, fields, args), p.categoryQuerying()
end

function p.allOf(...)
	local query
	for i, v in ipairs({...}) do
		if type(v) == "table" then
			for k, v in pairs(v) do
				v = p.escape(v)
				query = h.andClause(query, string.format("%s='%s'", k, v))
			end
		else
			query = h.andClause(query, v)
		end
	end
	return query
end

function p.anyOf(...)
	local query
	for i, v in ipairs({...}) do
		if type(v) == "table" then
			for k, v in pairs(v) do
				v = p.escape(v)
				query = h.orClause(query, string.format("%s='%s'", k, v))
			end
		else
			query = h.orClause(query, v)
		end
	end
	return query
end

function p.categoryQuerying()
	return "[[Category:Pages querying Cargo data]]"
end

function p.categoryStoring()
	local categories = "[[Category:Pages storing Cargo data]]"
	if mw.title.getCurrentTitle().nsText == "" then
		categories = categories.."[[Category:Articles storing Cargo data]]"
	end
	return categories
end

function p.escape(str)
	return string.gsub(str, "'", "\\'")
end

function p.holdsAll(field, values)
	local query
	for i, value in ipairs(values) do
		value = p.escape(value)
		if #values > 1 then
			-- Workaround for Cargo bug: https://phabricator.wikimedia.org/T267498
			-- __full lists all the categories as a string separated by pipes.
			-- Specific regex is needed to account for categories which are at the start or end of the string, which will be missing a pipe.
			-- (^|\\|) matches the beginning
			-- ($|\\|) matches the end
			query = h.andClause(query, field..[[__full REGEXP '(^|\\|)]]..value..[[($|\\|)']])
		else
			query = h.andClause(query, string.format("%s HOLDS '%s'", field, value))
		end
	end
	return query
end

function p.holdsAny(field, values)
	local query
	for i, value in ipairs(values) do
		value = p.escape(value)
		query = h.orClause(query, string.format("%s HOLDS '%s'", field, value))
	end
	return query
end

function p.IN(field, values)
	local inValues = {}
	for i, value in ipairs(values) do
		if type(value) == "string" then
			value = p.escape(value)
			value = string.format("'%s'", value)
		end
		inValues[i] = value
	end
	inValues = table.concat(inValues, ", ")
	if inValues == "" then
		inValues = "''"
	end
	return string.format("%s IN (%s)", field, inValues)
end

function h.andClause(query, clause)
	return h.addClause("AND", query, "("..clause..")")
end

function h.orClause(query, clause)
	return h.addClause("OR", query, clause)
end

function h.addClause(operator, query, clause)
	if not query or query == "" then
		return clause
	end
	return table.concat({query, operator, clause}, " ")
end

function p.Schemas()
	return {
		query = {
			tables = {
				type = "string",
				required = true,
			},
			fields = {
				type = "string",
				required = true,
			},
			args = {
				type = "record",
				properties = {
					{
						name = "where",
						type = "string",
					},	
					{
						name = "join",
						type = "string",
					},
					{
						name = "groupBy",
						type = "string",
					},
					{
						name = "having",
						type = "string",
					},
					{
						name = "orderBy",
						type = "string",
					},
					{
						name = "limit",
						type = "number",
						default = 100,
					},
					{
						name = "offset",
						type = "number",
						default = 0,
					},
				}
			},
		},
		allOf = {
			["..."] = {
				type = "array",
				required = true,
				items = {
					oneOf = {
						{
							type = "string",
						},
						{
							type = "map",
							keys = { type = "string" },
							values = { type = "string" },
						},
					},
				}
			},
		},
		anyOf = {
			["..."] = {
				type = "array",
				required = true,
				items = {
					oneOf = {
						{
							type = "string",
						},
						{
							type = "map",
							keys = { type = "string" },
							values = { type = "string" },
						},
					},
				}
			},
		},
		holdsAll = {
			field = {
				type = "string",
				required = true,
			},
			values = {
				type = "array",
				required = true,
				items = { type = "string" },
			},
		},
		holdsAny = {
			field = {
				type = "string",
				required = true,
			},
			values = {
				type = "array",
				required = true,
				items = { type = "string" },
			}
		},
		IN = {
			field = {
				type = "string",
				required = true,
			},
			values = {
				type = "array",
				required = true,
				items = { type = "string" },
			},
		}
	}
end

function p.Documentation()
	return {
		sections = {
			{
				heading = "<code>mw.ext.cargo.query</code> wrapper",
				section = {
					query = {
						params = {"tables", "fields", "args"},
						returns = {
							"An array of the query results. Throws an error when query syntax is invalid.",
							"Categories to add to the page.",
						},
						cases = {
							{
								args = {
									"Games",
									"code, shortName",
									{
										where = "type='main'",
										orderBy = "releaseDate",
										limit = 3,
									},
								},
								expect = {
									{
										{ code = "TLoZ", shortName = "The Legend of Zelda"},
										{ code = "TAoL", shortName = "The Adventure of Link"},
										{ code = "ALttP", shortName = "A Link to the Past"},
									},
									"[[Category:Pages querying Cargo data]]",
								},
							},
						}
					},
				}
			},
			{
				heading = "Query builders",
				section = {
					allOf = {
						params = {"..."},
						returns = "A WHERE clause with ANDed conditions and escaped quotation marks.",
						cases = {
							outputOnly = true,
							{
								args = {
									{
										game = "Link's Awakening",
										remakeNum = 2,
									},
									"foo HOLDS 'bar'",
									"baz LIKE '%quux%'",
								},
								expect = [[(remakeNum='2') AND (game='Link\'s Awakening') AND (foo HOLDS 'bar') AND (baz LIKE '%quux%')]]
							}
						},
					},
					anyOf = {
						params = {"..."},
						returns = "A WHERE clause with ORed conditions and escaped quotation marks.",
						cases = {
							outputOnly = true,
							{
								args = {
									{
										game = "Link's Awakening",
										remakeNum = 2,
									},
									"foo HOLDS 'bar'",
									"baz LIKE '%quux%'",
								},
								expect = [[remakeNum='2' OR game='Link\'s Awakening' OR foo HOLDS 'bar' OR baz LIKE '%quux%']]
							}
						},
					},
					escape = {
						desc = "Escapes special characters (namely apostrophes) that are not supported in Cargo string values.",
						params = {"str"},
						returns = "A string with escaped apostrophes.",
						cases = {
							outputOnly = true,
							{
								args = {"Dinraal's Claw"},
								expect = "Dinraal\\'s Claw",
							},
						},
					},
					holdsAll = {
						params = {"field", "values"},
						returns = "A query string of and'ed HOLDS clauses.",
						cases = {
							outputOnly = true,
							{
								args = {"game", {"Link's Awakening"}},
								expect = "(game HOLDS 'Link\\'s Awakening')",
							},
							{
								desc = "As a workaround to a [https://phabricator.wikimedia.org/T267498 Cargo issue], multiple HOLDS statements are converted to an equivalent regex-based syntax.",
								args = {"game", {"OoT", "TP"}},
								expect = "(game__full REGEXP '(^|\\\\|)OoT($|\\\\|)') AND (game__full REGEXP '(^|\\\\|)TP($|\\\\|)')",
							}
						},
					},
					holdsAny = {
						params = {"field", "values"},
						returns = "A query string of or'ed HOLDS clauses.",
						cases = {
							outputOnly = true,
							{
								args = {"game", {"Link's Awakening"}},
								expect = "game HOLDS 'Link\\'s Awakening'",
							},
							{
								args = {"game", {"OoT", "TP"}},
								expect = "game HOLDS 'OoT' OR game HOLDS 'TP'",
							}
						}
					},
					IN = {
						params = {"field", "values"},
						returns = "A where clause using the SQL IN keyword.",
						cases = {
							outputOnly = true,
							{
								args = {"_pageName", {"Link's Shadow", "Zelda", "Ganon"}},
								expect = [[_pageName IN ('Link\'s Shadow', 'Zelda', 'Ganon')]],
							},
							{
								args = {"_pageName", {}},
								expect = [[_pageName IN ('')]],
							}
						}
					},
				}
			},
			{
				heading = "Category strings",
				section = {
					categoryQuerying = {
						params = {},
						returns =  "The categories that should be added to pages querying Cargo data, as per [[Guidelines:Cargo#Best Practices]].",
						cases = {
							{
								args = {},
							}
						}
					},
					categoryStoring = {
						params = {},
						returns = "The categories that should be added to pages storing Cargo data, as per [[Guidelines:Cargo#Best Practices]].",
						cases = {
							{
								args = {},
							}
						}
					},
				},
			},
		},
	}
end

return p