Documentation for this module may be created at Module:UtilsCargo/doc
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