This wiki uses the Cargo extension for storing and querying data, acting essentially as a database. This allows for automated methods of creating some pages (eg. Lists), reducing the need for manual maintenance across several pages. A list of all Cargo tables can be found at Special:CargoTables.
Without Cargo, there might be the same information across multiple pages maintained manually (eg. Hero stats are used in multiple places). Previously, these pages needed to all be updated manually any time information changed. If all these pages instead use Cargo to grab the information from the Cargo table, we simply only need to change the information within the Cargo table. Then, all the pages pulling data from the Cargo table would pull the new values and automatically update accordingly.
Understanding templates
Cargo tables are declared and stored into via templates, so understanding Cargo also requires a basic understanding of how templates work. See the tutorial section for templates.
General overview
A simplification of Cargo is as follows:
- Create/declare the Cargo table
- Store information into the Cargo table
- Query information from the Cargo table
The following sections will visit each topic more in-depth.
Declaring the Cargo table
To have a table to store information into, we must first create the table and also decide what kind of information we want stored in the table.
This is done inside the <noinclude></noinclude>
tags in the template. For more details on what those tags do, see the MediaWiki article on Partial Transclusion, but in short, if we don't put the table declaration in those tags, the declare statement will be called wherever the template is called. We only want it called once on the Template page itself.
The tag for declaring the Cargo table is #cargo_declare
. Inside the #cargo_declare
, you will then specify:
- Name of what you want to call the new table using
_table=
- Name and type (eg. Integer, String, Date, etc) of the columns you want to have in the table
- See Cargo's section for Declaring a table for a full list of types you can specify
Example
Here is the Cargo declaration from Template:Forging Bonds Infobox which stores information about Forging Bonds events.
{{#cargo_declare: _table=ForgingBonds |Name=String |Accessories=List (,) of Page |Number=Integer |StartTime=Start Datetime |EndTime=End Datetime }}
In this #cargo_declare
, the above:
- Names the table
ForgingBonds
- Creates five parameters/columns to be in the table
- Name - The name of an event.
- This is type
String
, which is essentially just text. Any text will generally be accepted if a column is typeString
. These columns are indexed, while columns with typeText
aren't.
- This is type
- Accessories - All the bonus Accessories in an event.
- This is type
List
. This means it can store multiple values of the type specified after. In this case,List of Page
(There can also beList of String
,List of Datetime
, etc.). The(,)
represents the character(s) which separate each item in the list which in this case is a comma. Sample list input in this case would look likeFeh Doll,Lance Røkkr Remnant,8-Bit Sharena
. If we wanted to use another symbol, like a semicolon, we could change this to beList (;) of Page
.
- This is type
- Number - A unique numerical index for each event.
- This is type
Integer
. It can store 32-bit signed integers; fractional values, as well as integers that are too large (such as Voting Gauntlet scores), should use the typeFloat
instead.
- This is type
- StartTime - The date and time an event starts on.
- It has type
Start Datetime
so that only Datetime inputs can be accepted (eg.FEH Wiki
won't be accepted as input to store into the table since it is not a datetime.)
- It has type
- EndTime - The date and time an event ends on.
- It has type
End Datetime
, but accepts the same datetime values asStart Datetime
. The datetime pair usually denotes the start and end times for a single row.
- It has type
- Name - The name of an event.
To create this table, you must first save the template and go to More > Recreate Data or add ?action=recreatedata
to the end of the template URL. This option is only available to admins, so if you do not have the permission to create tables, contact an administrator. Every time the cargo_declare
is modified, a table must be recreated for those changes to show up in the actual table.
After the table is created, go to Special:CargoTables to find the table. For this example, here is the ForgingBonds table with all the information we specified from the code snippet above.
Modifying the Cargo table
- Follow this guide.
- You can use Special:TagsReport to find all pages that query the table, and make sure that nothing is broken.
Storing information into the Cargo table
After you've created a new table, there won't be any rows of information yet in the table. To store information, we need to add #cargo_store
tags into the template. These will go inside the <includeonly></includeonly>
tags instead, so that information could get stored only when the template is transcluded in another page.
The #cargo_store
follows a similar format where you:
- Specify the table name to store info into
- List out all the parameters/columns you want to store info into and the information you want to store.
Example
Here is the example #cargo_store
for Template:Forging Bonds Infobox.
{{#cargo_store: _table=ForgingBonds |Name={{#if:{{{name|}}}|{{{name}}}|{{#titleparts:{{PAGENAME}}}}}} |Accessories={{{accessories|}}} |Number={{{number|}}} |StartTime={{{startTime|}}} |EndTime={{{endTime|}}} }}
As mentioned, inside the #cargo_store
we specify:
- The name of the table we want to store into
- In this case,
ForgingBonds
- In this case,
- All the parameters and what values we want to store into them.
This template stores the template parameters into each field. As a reminder, the triple curly bracket texts are template parameters passed in. The template parameters from the above code are:
{{{name|}}}
{{{accessories|}}}
{{{number|}}}
{{{startTime|}}}
{{{endTime|}}}
These are the parameters we specify when calling the template. The Forging Bonds infobox template actually has more than just these as template parameters such as characters
, but these are the parameters we want stored into the Cargo table. The following is what a Forging Bonds infobox template call would look like, using the Wikicode from Harmony amid Chaos as the example.
{{Forging Bonds Infobox |number=20 |characters=Ferdinand: Noblest of Nobles,Lysithea: Child Prodigy,Bernadetta: Eternal Loner,Annette: Overachiever |accessories=Nomad Bandana EX,Spy Mask EX,Bard Torse EX,Wing-Leader Icon EX,Cavalier Cap,Bear Clip,Flytrap Doll,Melody Clip |startTime=2020-03-06T07:00:00Z |endTime=2020-03-20T07:00:00Z }}
Cargo only cares about the parameters we told it to put into the table in the #cargo_store
. The following table represents what would be stored into the table:
Column name | Template parameter | Value passed to #cargo_store
|
---|---|---|
Name | {{#if:{{{name|}}}|{{{name}}}|{{#titleparts:{{PAGENAME}}}}}} |
Harmony amid Chaos
Notes:
|
Accessories | {{{accessories|}}} |
Nomad Bandana EX,Spy Mask EX,Bard Torse EX,Wing-Leader Icon EX,Cavalier Cap,Bear Clip,Flytrap Doll,Melody Clip
|
Number | {{{number|}}} |
20
|
StartTime | {{{startTime|}}} |
2020-03-06T07:00:00Z
|
EndTime | {{{endTime|}}} |
2020-03-20T07:00:00Z
|
If you look up this banner in the Forging Bonds Cargo table, you can see that this is the information stored as well. You may notice that all tables have a _pageName
column. This column is the page where a template call and the Cargo store happens. In this case, since we called the Forging Bonds infobox template from Harmony amid Chaos, the stored _pageName
is also Harmony amid Chaos
.
Preventing stores
In some cases, it is desirable to reuse a template in situations where Cargo tables must be kept intact, such as inside user pages. Some templates, such as {{Passive}}, allow this to be done using the no cargo parameter. (This is used on the Unused content page to display internal skills without affecting skill queries.) To add no cargo support to your template, the whole #cargo_store
section needs to be surrounded inside a conditional statement as follows:
{{#if:{{{no cargo|}}}|<!-- // do not store anything if "no cargo" is specified -->|<!-- // otherwise, proceed -->{{#cargo_store:_table=<!-- -->}}<!-- // other stores go here -->}}
Then, when a page transcludes the template while also supplying |no cargo=1
for the template arguments, no rows will be stored by the template.
Since only pages in the main namespace are expected to store rows into Cargo tables through templates, #cargo_store
s should also be protected with a namespace check using {{#ifeq:{{NAMESPACE}}|{{ns:0}}| ... }}
. This check is required for all Cargo table templates.
Other times, if a #cargo_store
creates fields that would violate the constraints specified in the cargo_declare
field descriptions, such as storing duplicate values on a unique
column, the store silently fails and the corresponding row is not created.
Querying information from the Cargo tables
The information stored can now be queried anywhere from the Wiki with a the #cargo_query
tag. There are also several other methods to query the Cargo tables (eg. #cargo_compound_query
, via Lua using mw.ext.cargo.query
), but this section will only cover doing a basic Cargo query. For more information on complex queries, refer to the Cargo documentation on querying data.
A basic #cargo_query
expects:
- The table name you want to query from
This is the only required information you need to specify to query the Cargo table, and it pulls every single row from the Cargo table. However, this isn't particularly useful. Other options you can include to narrow your search include:
- fields - The columns you want to specify.
- where - Condition you can specify to filter out results.
- Conditions follow MYSQL syntax.
- limit - The max number of results you want returned.
- Defaults to 100 if not specified.
- format - The format you want results to be returned in.
- See Cargo documentation for a full list of formats you can return Cargo queries in.
- order by - The order you want results to get returned in.
Although not documented on the MediaWiki Cargo page, REGEXP can also be used in place of = or LIKE.
Example
The following is an example #cargo_query
which gets all pages and bonus Accessories of Forging Bonds events which started in 2019, ordered by their StartTime in ascending order in table format.
{{#cargo_query: tables=ForgingBonds |fields=_pageName,Accessories |where=YEAR(StartTime)=2019 |format=table |order by=StartTime ASC }}
This code generates the following:
Styling Cargo query outputs
The above example table output is nice, but there are a few styling issues which could use some work. For example, the column names aren't exactly great header names for the table.
There are three main ways the FEH Wiki creates more complex formats to output in order of complexity. A few examples in the Wiki are included. To view the Cargo query code, view the source of those pages.
- Using
CONCAT()
in|fields
- e.g. Template:Weapon Type Unit Lists
- Works for simple tables and text displays
- May inline template and module calls by escaping curly brackets and pipes with formatting templates, e.g. Template:SkillPage
- May fail if the parameter is too long (Scribunto does not have this issue)
- Creating a template to use in the Cargo query via
|format=template
- e.g. Light Brand's chart and Template:Unit stat comparison chart
- Could inadvertently slow down page generation if the template performs its own Cargo queries
- Less flexible than above method
- Through Scribunto Lua modules
- e.g. Template:Hero List and Module:HeroList
- Lua runs much faster than Cargo queries, and a lot of complex data processing can be done in Lua while reducing the number of query calls
- May also use
CONCAT()
- Queries via Lua do not translate to Cargo display formats directly, so Lua code must also generate the visual layout
- Invoking the module without a wrapper template can roughly halve the post-expand include size
- Not recommended for small templates frequently used in plain wikitext as
#invoke
adds visual noise
Allowed MySQL functions
These are the currently allowed functions on the wiki ($wgCargoAllowedSQLFunctions
), non-default functions are bolded:
- AVG
- CEIL
- CONCAT
- COUNT
- DATE
- DATE_ADD
- DATE_FORMAT
- DATE_SUB
- DATEDIFF
- DAYOFMONTH
- FLOOR
- FORMAT
- GROUP_CONCAT
- IF
- IFNULL
- LCASE
- LN
- LOG
- LOWER
- MAX
- MIN
- MONTH
- NEAR
- NOW
- POWER
- ROUND
- SUBSTRING
- SUM
- TRIM
- UCASE
- UPPER
- YEAR
The following functions are not allowed, but their equivalent operators are available:
- MOD → %
- REGEXP_LIKE → REGEXP / RLIKE
Common fields on the wiki
- |WikiName=String
- This field is intended to uniquely identify a row where a page name is insufficient (because, for example, a page may store multiple distinct objects into the same table where all rows need to be distinguished in joins). These fields are often parsed through Template:MF for filename use for example. WikiName fields should not contain any symbols or Unicode characters to avoid technological issues. WikiName fields are for internal use only, and terminology guidelines should still be followed when actually displaying names. All WikiName fields should add the
mandatory
andunique
constraints.
- |TagID=String
- This field represents the internal string identifier of a row in the game assets. The main purpose of including them in the Cargo database is to allow external scripts to map game entities to wiki page names efficiently. They should not be used in queries for wiki content, but can be freely used in code that generates wikitext from game files, e,g, Module:ScenarioArchiveToWiki. All TagID fields should add the
unique
constraint, but notmandatory
since rows may be created after new information is released but before game files are available through updates.
- |Properties=List (,) of String
- This field represents an unordered set of properties that apply to a given row. If a property is called
abc
, the presence of a property can be checked with the following where-clauses:Properties HOLDS 'abc'
; Cargo internally translates this toTABLE__Properties._value='abc'
with a suitable join, and at most oneHOLDS
statement can be used on each table.Properties__full LIKE '%abc%'
; because of this, properties must not be substrings of other properties, to prevent false positives.
- The opposite can be checked with the following where-clauses:
Properties__full IS NULL OR Properties__full NOT LIKE '%abc%'
IFNULL(Properties__full,'') NOT LIKE '%abc%'
- The
HOLDS NOT
command is not a negation of theHOLDS
command; it returns true if the row contains any property that is not equal toabc
. TheHOLDS
commands cannot be used as predicates inside theIF()
function as they are purely Cargo extensions.
- |StartTime=Start Datetime, |EndTime=End Datetime
- These fields mark a UTC datetime range, and usually represent the time availability of an object. Both datetimes are inclusive; a time point
x
is included ifx BETWEEN StartTime AND EndTime
, orx>=StartTime AND x<=EndTime
. To simplify queries, it is permissible to store{{MinTime}}
and{{MaxTime}}
respectively into rows without explicit start or end times, but only if the null value does not have a special meaning.