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.
Examples
# | Input | Output | Result |
---|---|---|---|
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",
},
}
|
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%'" |
holdsAll
holdsAll(field, values)
Parameters
Returns
- A query string of and'ed HOLDS clauses.
Examples
# | Input | Output | Status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3 | 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. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
4 | 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 |
---|---|---|---|
5 | holdsAny("game", {"Link's Awakening"})
| "game HOLDS 'Link\\'s Awakening'" | |
6 | 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 |
---|---|---|---|
7 | IN("_pageName", {"Link's Shadow", "Zelda", "Ganon"})
| "_pageName IN ('Link\\'s Shadow', 'Zelda', 'Ganon')" | |
8 | IN("_pageName", {})
| "_pageName IN ('')" |
local p = {}
local h = {}
local frame = mw.getCurrentFrame()
function p.query(tables, fields, args)
return mw.ext.cargo.query(tables, fields, args)
end
local function escape(str)
return string.gsub(str, "'", "\\'")
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 = 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.holdsAll(field, values)
local query
for i, value in ipairs(values) do
value = 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 = 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
value = escape(value)
value = string.format("'%s'", value)
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" },
},
},
}
},
},
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.",
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"},
},
},
}
},
}
},
{
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%']]
}
},
},
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 ('')]],
}
}
},
}
}
}
}
end
return p