Module:UtilsCargo
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
# | Input | Output | Result | Status |
---|---|---|---|---|
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 | |
"[[Category:Pages querying Cargo data]]"
|
Query builders
allOf
allOf(...)
Parameters
Returns
- A WHERE clause with ANDed conditions and escaped quotation marks.
Examples
# | Input | Output | Status |
---|---|---|---|
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%')"
|
anyOf
anyOf(...)
Parameters
Returns
- A WHERE clause with ORed conditions and escaped quotation marks.
Examples
# | Input | Output | Status |
---|---|---|---|
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%'"
|
escape
escape(str)
Escapes special characters (namely apostrophes) that are not supported in Cargo string values.
Returns
- A string with escaped apostrophes.
Examples
# | Input | Output | Status |
---|---|---|---|
4 | escape("Dinraal's Claw")
| "Dinraal\\'s Claw"
|
holdsAll
holdsAll(field, values)
Parameters
Returns
- A query string of and'ed HOLDS clauses.
Examples
# | Input | Output | Status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
5 | holdsAll("game", {"Link's Awakening"})
| "(game HOLDS 'Link\\'s Awakening')"
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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($|\\\\|)')"
|
holdsAny
holdsAny(field, values)
Parameters
Returns
- A query string of or'ed HOLDS clauses.
Examples
# | Input | Output | Status |
---|---|---|---|
7 | holdsAny("game", {"Link's Awakening"})
| "game HOLDS 'Link\\'s Awakening'"
| |
8 | holdsAny("game", {"OoT", "TP"})
| "game HOLDS 'OoT' OR game HOLDS 'TP'"
|
IN
IN(field, values)
Parameters
Returns
- A where clause using the SQL IN keyword.
Examples
# | Input | Output | Status |
---|---|---|---|
9 | IN("_pageName", {"Link's Shadow", "Zelda", "Ganon"})
| "_pageName IN ('Link\\'s Shadow', 'Zelda', 'Ganon')"
| |
10 | IN("_pageName", {})
| "_pageName IN ('')"
|
Category strings
categoryQuerying
categoryQuerying()
Returns
- The categories that should be added to pages querying Cargo data, as per Guidelines:Cargo#Best Practices.
Examples
# | Input | Output |
---|---|---|
11 | categoryQuerying()
| "[[Category:Pages querying Cargo data]]"
|
categoryStoring
categoryStoring()
Returns
- The categories that should be added to pages storing Cargo data, as per Guidelines:Cargo#Best Practices.
Examples
# | Input | Output |
---|---|---|
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