[checked revision] | [checked revision] |
([ST] uh dont get rid of important data....) |
([ST] log full list) |
||
Line 107: | Line 107: | ||
for i, field in ipairs(listOfFields) do |
for i, field in ipairs(listOfFields) do |
||
local partiallyParsedField = field:match('(.-) *%[(%w+)%]$') |
local partiallyParsedField = field:match('(.-) *%[(%w+)%]$') |
||
⚫ | |||
if partiallyParsedField then |
if partiallyParsedField then |
||
listOfFields[i] = partiallyParsedField |
listOfFields[i] = partiallyParsedField |
||
end |
end |
||
end |
end |
||
⚫ | |||
end |
end |
||
end |
end |
Revision as of 21:43, 10 August 2020
Edit the documentation or categories for this module.
local util_args = require('Module:ArgsUtil')
local util_map = require('Module:MapUtil')
local util_sort = require('Module:SortUtil')
local util_table = require('Module:TableUtil')
local util_text = require('Module:TextUtil')
local util_vars = require('Module:VarsUtil')
local cargowiki = require('Module:CargoUtil/Wiki')
local bool_false = { ['false'] = true, ['0'] = true, ['no'] = true, [''] = true }
local argPrefix = 'q?'
local lang = mw.getLanguage('en')
local bool_to_str = { [true] = 'Yes', [false] = 'No' }
local p = {}
local h = {}
-- oneToMany structure:
--[[
{
groupBy = { set of things to group by here },
fields = {
key1_plural = singleFieldSingular,
key2_plural = { table, of, fields },
}
}
]]
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.oneToMany)
end
function h.getFinalizedCopyQuery(query)
local copyQuery = mw.clone(query)
copyQuery.tables = util_table.concatIfTable(query.tables)
copyQuery.fields, copyQuery.types = h.parseAndConcatFieldNames(query.fields, query.oneToMany)
if query.oneToMany then
h.cleanupOneToManyFields(query.oneToMany.fields)
end
copyQuery.join = util_table.concatIfTable(query.join)
copyQuery.limit = query.limit or 9999
util_table.merge(copyQuery.types, query.types)
copyQuery.complexTypes = query.complexTypes or {}
h.lowercaseifyTypes(copyQuery)
h.setObjectTypes(copyQuery)
-- default to the where being an AND of its params if it's a table
if type(query.where) == 'table' then
copyQuery.where = p.concatWhere(query.where)
end
return copyQuery
end
function h.parseAndConcatFieldNames(fields, oneToMany)
if not oneToMany then oneToMany = {} end
oneToMany.allFields = h.getListOfAllOneToManyFields(oneToMany.fields)
if type(fields) == 'string' then
fields = util_text.split(fields)
end
util_table.mergeArrays(fields, oneToMany.allFields)
local parsedFields = {}
local types = {}
for _, field in ipairs(fields) do
local partiallyParsedField, parsedType
if field:find('%[') then
-- partially parsed field still needs to be parsed the rest of the way
-- can include for example both a table name and an alias
partiallyParsedField, parsedType = field:match('(.-) *%[(%w+)%]$')
else
partiallyParsedField = field
end
local parsedField = h.parseOneFieldName(partiallyParsedField)
parsedFields[#parsedFields+1] = parsedField
types[h.getFieldAlias(parsedField)] = parsedType
end
local finalParsedFields = util_table.concat(parsedFields, ', ')
return finalParsedFields, types
end
function h.getListOfAllOneToManyFields(fields)
if not fields then return nil end
local allFields = {}
for k, v in pairs(fields) do
util_table.merge(allFields, util_table.guaranteeTable(v))
end
return allFields
end
function h.parseOneFieldName(str)
if not str:find('%.') then
return str
elseif str:find('=') then
return str
end
local name = str:match('%.(.+)')
return ('%s=%s'):format(str, name)
end
function h.cleanupOneToManyFields(fields)
-- we need to get rid of any types that are here, like we did above for the full fields list
for k, listOfFields in pairs(fields) do
for i, field in ipairs(listOfFields) do
local partiallyParsedField = field:match('(.-) *%[(%w+)%]$')
if partiallyParsedField then
listOfFields[i] = partiallyParsedField
end
end
util_vars.log(listOfFields)
end
end
function h.lowercaseifyTypes(copyQuery)
for k, v in pairs(copyQuery.types) do
copyQuery.types[k] = lang:lc(v)
end
for _, v in pairs(copyQuery.complexTypes) do
v.type = lang:lc(v.type)
end
end
function h.setObjectTypes(copyQuery)
-- fields that cannot be nil because they must be an object with an is_nil value
copyQuery.objectTypes = {}
for k, v_type in pairs(copyQuery.types) do
if cargowiki.objectTypes[v_type] then
copyQuery.objectTypes[k] = v_type
end
end
end
-- post query
function h.cast(result, copyQuery)
for i, row in ipairs(result) do
row.index = i
for k, v in pairs(row) do
row[k] = h.castField(v, copyQuery.types[k])
end
for k, v in pairs(copyQuery.complexTypes) do
row[k] = cargowiki.castComplexTypes(row, v)
end
for k, v_type in pairs(copyQuery.objectTypes) do
if row[k] == nil then
row[k] = cargowiki.castField(nil, v_type)
end
end
end
end
function h.castField(v, v_type)
if v == '' then return nil end
if not v_type then return v end
if v_type == 'boolean' then
return p.strToBool(v)
elseif v_type == 'number' then
return tonumber(v)
elseif v_type == 'namespace' then
return mw.site.namespaces[tonumber(v)].name
elseif v_type == 'unicode_lowercase' then
return mw.ustring.lower(v)
end
return cargowiki.castField(v, v_type)
end
function h.groupOneToManyFields(result, oneToMany)
if not oneToMany then return result end
local currentKey
-- fields is a blob
local fieldsBlob = h.parseFieldSetsForKeys(oneToMany.fields)
local groupedResult = {}
for _, row in ipairs(result) do
local newKey = h.getNewKey(row, util_table.guaranteeTable(oneToMany.groupBy))
if newKey == currentKey then
h.addRowToExistingGroup(groupedResult[#groupedResult], row, fieldsBlob)
else
h.addRowToNewGroup(groupedResult, row, fieldsBlob)
currentKey = newKey
end
end
return groupedResult
end
function h.parseFieldSetsForKeys(fields)
-- fields is a blob, and we return a blob
return util_map.safe(fields, h.parseFieldsForKeys)
end
function h.parseFieldsForKeys(fieldSet)
return util_map.inPlace(
util_map.inPlace(
util_table.guaranteeTable(fieldSet),
h.parseOneFieldName
),
h.getFieldAlias
)
end
function h.getNewKey(row, groupBy)
local toConcat = {}
for _, v in ipairs(groupBy) do
toConcat[#toConcat+1] = row[v]
end
return table.concat(toConcat)
end
function h.getFieldAlias(str)
if not str:find('=') then return str end
-- in case we have a CASE statement, we need to make sure we're splitting on
-- the right = here to retrieve the field name
return str:match('=([^=]+)$')
end
function h.addRowToExistingGroup(groupedRow, row, fieldsBlob)
for k, fieldSet in pairs(fieldsBlob) do
local curRowInGroup = groupedRow[k]
util_table.push(curRowInGroup, h.extractFieldsetFromDataRow(row, fieldSet, #curRowInGroup+1))
end
end
function h.extractFieldsetFromDataRow(row, fieldSet, indexInGroup)
local ret = {
index = indexInGroup,
}
for _, field in ipairs(fieldSet) do
ret[field] = row[field]
end
return ret
end
function h.addRowToNewGroup(groupedResult, row, fieldsBlob)
for k, fieldSet in pairs(fieldsBlob) do
row[k] = { h.extractFieldsetFromDataRow(row, fieldSet, 1) }
end
groupedResult[#groupedResult+1] = row
end
function p.getOneResult(query, field)
local result = p.queryAndCast(query)
if result[1] then
return result[1][field or h.getOneFieldName(query.fields)]
end
return nil
end
function h.getOneFieldName(field)
if type(field) == 'table' then field = field[1] end
return h.getFieldAlias(h.parseOneFieldName(field))
end
function p.getOneRow(query)
local result = p.queryAndCast(query)
return result[1] or {}
end
function p.getOneField(query, field)
local result = p.queryAndCast(query)
local tbl = {}
for i, row in ipairs(result) do
tbl[#tbl+1] = row[field]
end
return tbl
end
function p.strToBool(v)
if not v then
return false
elseif bool_false[lang:lc(v)] then
return false
end
return true
end
function p.getConstDict(query, key, value)
return p.makeConstDict(p.queryAndCast(query), key, value)
end
function p.makeConstDict(result, key, value)
local tbl = {}
for _, row in ipairs(result) do
if row[key] then
tbl[row[key]] = row[value]
end
end
return tbl
end
function p.getRowDict(query, key)
local result = p.queryAndCast(query)
local ret = {}
for _, row in ipairs(result) do
if row[key] then
ret[row[key]] = row
end
end
return ret
end
function p.getOrderedDict(query, key, value)
return h.makeOrderedDict(p.queryAndCast(query), key, value)
end
function h.makeOrderedDict(result, key, value)
local tbl = {}
for _, row in ipairs(result) do
if row[key] then
tbl[#tbl+1] = row[key]
tbl[row[key]] = row[value]
end
end
return tbl
end
function p.getOrderedList(query, key)
local result = p.queryAndCast(query)
return h.makeOrderedList(result, key or query.fields)
end
function h.makeOrderedList(result, key)
local tbl = {}
for k, row in ipairs(result) do
tbl[#tbl+1] = row[key]
end
return tbl
end
function p.groupResultOrdered(result, key, f)
local data = {}
local this
local thisvalue
local thistab
local i = 1
for _, row in ipairs(result) do
if not row[key] then row[key] = 'Uncategorized' end
if row[key] ~= thisvalue then
data[#data+1] = { name = row[key], index = i }
i = i + 1
thistab = data[#data] or {}
thisvalue = row[key]
end
thistab[#thistab+1] = f and f(row) or row
end
return data
end
function p.groupResultByValue(result, key, f)
local data = {}
local this
local thisvalue
local i = 1
for _, row in ipairs(result) do
if row[key] ~= thisvalue then
thisvalue = row[key]
data[thisvalue] = { name = row[key] }
i = i + 1
thistab = data[thisvalue]
end
thistab[#thistab+1] = f and f(row) or row
end
return data
end
function p.queryFromArgs(args, defaults)
-- sometimes we want to specify query args in the template
-- this function parses them into args that cargo will understand
-- change argPrefix above to change the prefix for query params
local query = mw.clone(defaults or {})
for k, v in pairs(args) do
if string.sub(k, 0, 2) == argPrefix then
query[string.sub(k,3)] = v
end
end
return query
end
function p.store(tbl)
if CARGO_NAMESPACE and mw.title.getCurrentTitle().nsText ~= CARGO_NAMESPACE then
return
end
if not tbl then return end
local tbl2 = { '' }
for k, v in pairs(tbl) do
if type(v) == 'boolean' then
tbl2[k] = bool_to_str[v]
elseif type(v) == 'table' then
-- Lua Class System
tbl2[k] = tostring(v)
else
tbl2[k] = v
end
end
mw.getCurrentFrame():callParserFunction{
name = '#cargo_store',
args = tbl2
}
return
end
function p.setStoreNamespace(ns)
CARGO_NAMESPACE = ns
end
function p.doWeStoreCargo(nocargo, desiredNamespace,title)
local argOkay = not util_args.castAsBool(nocargo)
if not desiredNamespace then
return argOkay
end
if not title then
title = mw.title.getCurrentTitle()
end
return argOkay and title.nsText == desiredNamespace
end
function p.whereFromArg(str, ...)
-- if an arg is defined, formats a string with the arg to be included in a where table
-- if it's not defined, returns false and NOT nil so the table can be used
-- with util_table.concat
if #{...} == 0 then
return false
else
return str:format(...)
end
end
function p.whereFromArgList(str, argTbl, sep, f)
if not sep then sep = '%s*,%s*' end
if not argTbl then return nil end
argTbl = util_table.guaranteeTable(argTbl)
if #argTbl == 0 then return end
local splitArgs = {}
for _, arg in ipairs(argTbl) do
splitArgs[#splitArgs+1] = util_map.split(arg, sep, f)
end
local argsForFormat = {}
for lineIndex, v in ipairs(splitArgs[1]) do
argsForFormat[lineIndex] = {}
for i, arg in ipairs(splitArgs) do
argsForFormat[lineIndex][i] = arg[lineIndex]
end
end
local where = {}
for _, condition in ipairs(argsForFormat) do
where[#where+1] = p.whereFromArg(str, unpack(condition))
end
return ('(%s)'):format(p.concatWhereOr(where))
end
function p.whereFromCompoundEntity(str, argTbl)
if not argTbl then return nil end
if argTbl.is_nil then return nil end
local where = {}
for _, v in ipairs(argTbl) do
where[#where+1] = str:format(v:get())
end
return ('(%s)'):format(p.concatWhereOr(where))
end
function p.concatWhere(tbl)
local arr = {}
-- pairs because maybe some entries are nil, and since it's an AND, order doesn't matter
for _, v in pairs(tbl) do
if v then
arr[#arr+1] = ('(%s)'):format(v)
end
end
if #arr == 0 then return nil end
return '(' .. util_table.concat(arr, ' AND ') .. ')'
end
function p.concatWhereOr(tbl)
local arr = {}
-- pairs because maybe some entries are nil, and since it's an AND, order doesn't matter
for _, v in pairs(tbl) do
if v then
arr[#arr+1] = ('(%s)'):format(v)
end
end
return '(' .. util_table.concat(arr, ' OR ') .. ')'
end
function p.fakeHolds(field, str, sep)
if str == nil then return false end
sep = sep or ','
str = h.escape(str)
return ('%s__full RLIKE ".*(^|%s)%s($|%s).*"'):format(field, sep, str, sep)
end
function h.escape(str)
local tbl = { '%(', '%)' }
for _, v in ipairs(tbl) do
str = str:gsub(v, '.')
end
return str
end
function p.fakeHoldsVariable(field, str, sep)
sep = sep or ','
return ('%s__full RLIKE CONCAT(".*(^|%s)",%s,"($|%s).*")'):format(field, sep, str, sep)
end
function p.makeMinMaxQuery(query, field, orderby, order)
-- modifies a pre-existing query to add an extra set of conditions to get the max/min value of some field
-- order will be either MIN or MAX, and orderby is usually going to be a date/datetime
-- example: c.makeMinMaxQuery(query, 'SP.Champion','SP.Time','MAX')
--to get the most-recent played champions
local query2 = mw.clone(query)
query2.fields = ("%s(%s)=value, %s=field"):format(order or 'MAX', orderby, field)
local result = p.queryAndCast(query2)
util_map.inPlace(result, function(row)
return row.value and ('(%s="%s" AND %s="%s")'):format(field, row.field, orderby, row.value)
end)
local newwhere = {
next(result) and ("(%s)"):format(p.concatWhereOr(result)),
query.where and ("(%s)"):format(query.where)
}
return p.concatWhere(newwhere)
end
function p.getUniqueLine(...)
local args = {...}
for k, v in ipairs(args) do
if type(v) == 'string' then
args[k] = util_vars.getGlobalIndex(v) or v
end
end
table.insert(args, 1, mw.title.getCurrentTitle().text)
return util_table.concat(args, '_')
end
function p.concatQueriesAnd(original, new)
-- combine tables, fields, and join
-- "and" the wheres together
-- overwrite everything else with new
for _, v in ipairs({ 'tables', 'fields', 'join' }) do
original[v] = util_text.splitIfString(original[v])
util_table.mergeArrays(original[v], util_text.splitIfString(new[v]))
new[v] = nil
end
new.where = h.concatQueriesWhereAnd(original.where, new.where)
util_table.merge(new.types, original.types)
util_table.merge(original, new)
return original
end
function h.concatQueriesWhereAnd(original, new)
if not original then return new end
if not new then return original end
local tbl = { original, new }
return p.concatWhere(tbl)
end
function p.wikitextQuery(query)
local copyQuery = h.getFinalizedCopyQuery(query)
local text = ([[{{#cargo_query:tables=%s
|join on=%s
|fields=%s
|where=%s
|order by=%s
|group by=%s
}}]]):format(
copyQuery.tables or '',
copyQuery.join or '',
copyQuery.fields or '',
copyQuery.where or '',
copyQuery.orderBy or '',
copyQuery.groupBy or ''
)
return mw.text.nowiki(text)
end
function p.logQuery(query)
util_vars.log(p.wikitextQuery(query))
end
local kv_sep = ':@:'
local arg_sep = ';@;'
function p.concatArgsForStore(args)
local argArray = {}
for k, v in pairs(args) do
argArray[#argArray+1] = ('%s%s%s'):format(k, kv_sep, v)
end
return table.concat(argArray, arg_sep)
end
function p.extractArgs(argString)
local args = util_text.split(argString, arg_sep)
local newArgs = {}
for _, arg in ipairs(args) do
local k, v = arg:match(('(.-)%s(.*)'):format(kv_sep))
if not k then
error(("Can't properly parse arg %s"):format(arg))
end
newArgs[tonumber(k) or k] = v
end
return newArgs
end
return p