## Description
Currently type and return_type have rather strict restrictions:
* only one value per request
* they are combined using OR operator
https://www.mediawiki.org/wiki/Extension:WikiLambda/API#wikilambdasearch_labels
However, with persisted Function Calls we have more complex requirements for this API.
These are the use cases which we should be requiring different search patterns:
| use case | expect | allowed return types | what to do with result | query by | manually exclude |
| --- | --- | --- | --- | --- | --- |
| 1. function input or function output | anything that resolves to a Z4 (strict) | Z4, Z8, Z7 | Z4 and Z7: create reference, Z8: create function call | (type = Z4 OR return_type = Z4) | Z6884 |
| 2. function call function (Z7K1) parent type open | any function | Z8 | create reference | (type = Z8) | Z6884 (unless top level) |
| 3. function call function (Z7K1) parent type bound | any function that returns parentType (not strict) | Z8 | create reference | (type = Z8) AND (return_type = parentType OR return_type = Z1) | Z6884 (unless top level) |
| 4. type reference | anything that can be used as a Z4 | Z4, Z7 | create reference | (type = Z4) OR (return_type = Z4 AND type = Z7) | |
**Desired behavior/Acceptance criteria**
* [ ] The filter API is sufficiently flexible to adjust to all our use cases without needing multiple calls
* [ ] The UI requests objects according to the requirements, this includes:
* [ ] `TypeSelector` component
* [ ] `ZReference` component
===An example of current labels table===
| wlzl_zobject_zid | wlzl_type | wlzl_return_type | wlzl_label | etc... |
| --- | --- | --- | --- | --- |
| Z11 | Z4 | NULL | a normal type | |
| Z12 | Z4 | NULL | another normal type | |
| Z801 | Z8 | Z1 | a function that returns anything | |
| Z881 | Z8 | Z4 | a function that returns type | |
| Z866 | Z8 | Z40 | a function that returns boolean | |
| Z6884 | Z8 | Z4 | enum function, returns type | |
| Z10000 | Z7 | Z4 | one persisted enum | |
| Z10002 | Z7 | Z4 | another persisted enum | |
- use case 1: If I want to select a type for function input or output: **Z11**, **Z12**, **Z881**, **Z10000**, **Z10001**, Z6884*
- use case 2: If I want to select a function for a function call Z7K1, and parent type can be anything: **Z801**, **Z881**, **Z866**, Z6884*
- use case 3: If I want to select a function for a function call Z7K1, and parent type is bound to Z4: **Z801**, **Z881**, Z6884*
- use case 4: If I want to select a type for Z1K1 or for Z3K1: **Z11**, **Z12**, **Z10000**, **Z10002**
(zids marked with * will be manually excluded from results, but the query is expected to return them)
===Options===
====Option 1.a====
1. edit secondary table’s details, add return_type to non-function and on-functioncall objects
2. change type and return_type to be lists of strings
3. while building the query, aggregate type and return_type conditions with an AND operator
**use case 1:**
- type=null
- return_type=Z4
- build sql condition: `WHERE return_type=‘Z4’`
- matches objects: Z11, Z12, Z881, Z6884, Z10000, Z10002
**use case 2 (return type unbound):**
- type=Z8
- return_type=null
- build sql condition: `WHERE type='Z8'`
- matches objects: Z801, Z881, Z866, Z6884
**use case 3 (return type bound):**
- type=Z8
- return_type=[Z4, Z1]
- build sql condition: `WHERE type='Z8' AND return_type IN ['Z4', 'Z1']`
- matches objects: Z801, Z881, Z6884
**use case 4:**
- type=[Z4, Z7]
- return_type=Z4
- build sql condition: `WHERE type IN [ 'Z4', 'Z7' ] AND return_type='Z4' `
- matches objects: Z11, Z12, Z10000, Z10002
**Pros and cons:**
- ❌ We’d need to run secondary updates for all objects in production
- **Alternative:** We can build a script that just iterates through the labels table and: if return_type is NULL, set type value
- **Alternative:** We can avoid this by adding some exeption logic to the query building system: **Option 1.b**
- ✅ Pretty simple changes to API props:
- Make them lists of strings, to be able to do `type IN [ 'Z4', 'Z7' ]` or `return_type IN ['Z4', 'Z1']`
- We could remove strict property, which has always been a bit confusing
---
====Option 1.b====
1. aggregate type and return_type with an AND operator
2. aggregate values inside the array with an OR operator
3. **exception** if return_type is present but type is not, add `OR type = return_type` to the query:
- e.g. type=Z8 and return_type=Z4 would build `SELECT * WHERE type='Z8' AND return_type='Z4'` <-- all parameters present, aggregate conditions with AND
- e.g. **type=null** and return_type=Z4 would build `SELECT * WHERE return_type='Z4' OR type='Z4'` <-- type not present, duplicate the return_type and aggregate with OR
4. **exception** if type has values that are not Z8 or Z7 and return_type is present, we need to add `OR return_type IS NULL` to the return type condition
4. doesn't require any changes on the database content
**use case 1:**
- type=null
- return_type=Z4
- build sql condition: `WHERE return_type=‘Z4’ OR type='Z4'` <-- **exception when type is missing**
- matches objects: Z11, Z12, Z881, Z6884, Z10000, Z10002
**use case 2 (return type unbound):**
same as Option 1.a
**use case 3 (return type bound):**
same as Option 1.a
**use case 4:**
- type=[Z4, Z7]
- return_type=Z4
- build sql condition: `WHERE ( type='Z4' OR type='Z7' ) AND ( return_type='Z4' OR return_type IS NULL )`
- matches objects: Z11, Z12, Z10000, Z10002
**Pros and cons:**
- ✅ No database updates necessary
- ❌ The behavior when type is not there is a bit unpredictable, and needs good documentation
- ✅ Pretty simple changes to API props:
- Make them lists of strings
- We could remove strict property, which has always been a bit confusing
- ❌ There are a number of exceptions in the query building logic, which makes it more prone to bugs and confusion
- We need to add `OR return_type IS NULL` whenever any of the requested types is anything else than a Z7 or Z8, and requested return_type has values.
---
====Option 1.c====
1. change type and return_type to be lists of strings
2. add new parameter operator (defaults to AND)
3. when building the query, combine type and return_type conditions with the operator requested by the new parameter
**use case 1:**
- type=Z4
- return_type=Z4
- operator=OR
- build sql condition: `WHERE return_type=‘Z4’ OR type='Z4'`
- matches objects: Z11, Z12, Z881, Z6884, Z10000, Z10002
**use case 2 (return type unbound):**
same as Option 1.a
**use case 3 (return type bound):**
same as Option 1.a
**use case 4:**
- type=[ Z7, Z4 ]
- return_type=null
- operator=OR
- build sql condition: `WHERE type IN [ 'Z7', 'Z4' ]`
- matches objects: Z11, Z12, Z10000, Z10002
**Pros and cons:**
- ✅ No database updates necessary
- ✅ Pretty simple changes to API props:
- Make them lists of strings
- Add operator, which defaults to AND
- We could remove strict property, which has always been a bit confusing
- ❌ Depends on the assumption that a persisted Z7 will **ALWAYS** return a Z4
---
====Option 2====
1. Build an advanced filtering system with complex expressions passed as properties
2. Make no changes to the data represented in the secondary table
3. Each caller should know how to build the right filter_expression property
**use case 1: function input/output types**
```
filter_expression = {
"or": [
{ "type": "Z4" },
{ "return_type": "Z4" }
]
}
```
- builds sql condition: `WHERE type='Z4' OR return_type='Z4'`
- matches objects: Z11, Z12, Z881, Z6884, Z10000, Z10002
**use case 2: function call to function (return type unbound)**
```
filter_expression = { "type": "Z8" }
```
- builds sql condition: `WHERE type='Z8'`
- matches objects: Z801, Z881, Z866, Z6884
**use case 3: function call to function (return type bound)**
```
filter_expression = {
"and": [
{ "type": "Z8" },
{
"or": [
{ "return_type": "Z4" },
{ "return_type": "Z1" }
]
}
]
}
```
- builds sql condition: `WHERE type='Z8' AND ( return_type='Z4' OR return_type='Z1' )`
- matches objects: Z801, Z881, Z6884
**use case 4: persisted type or type expression**
```
filter_expression = {
"or": [
{ "type": "Z4" },
{
"and": [
{ "type": "Z7" },
{ "return_type": "Z4" }
]
}
]
}
```
- builds sql condition: `WHERE type='Z4' OR ( type='Z7' AND return_type='Z4' )`
- matches objects: Z11, Z12, Z10000, Z10002
**Pros and cons:**
- ❌ Not sure how ActionAPI can allow this kind of complex parameter
- Could be a JSON encoded as a string
- Could just be an advanced property, but keep type and return_type as they are for backwards compatibility.
- ❓would this be justified? this is an internal API only used by the frontend lookup components, so we can make changes to the signature if we wish
- ✅ This system is infinitely flexible
- ✅ Requires no changes to the database
- ❌ Complexity to use the API (and the ApiSandbox) increases significantly
====Option 3====
1. We stop using labels table return_type, and instead build this column dynamically by a JOIN with wikilambda_zobject_join
2. We need to add return type of persisted Z7s in the wikilambda_zobject_join table
3. But this join would fix the issues from Option 1.b. (NULL values), and we could use the parameters easily as described in Option 1.a.
**Pros and cons:**
- ❌ Much work
- ✅ We get rid of data duplication added by return_type column
- ✅ Requires little changes to the parameters, just changing from string to list of strings
---
====Option 3====
1. When persisting a Z7 which resolves to a Z4, instead of storing type=Z7 and return_type=Z4, store its labels as if it were a Z4, so:
* type=Z4 and return_type=NULL
**Pros and cons:**
- ✅ No changes needed in the API
- ❌ Function calls that resolve to a certain type are indistinguishable from objects of that literal type
- These are unsearchable in special list pages
- Might be more consequences that we can't anticipate now
---
## Completion checklist
* [ ] Before closing this task, review one by one the checklist available here: https://www.mediawiki.org/wiki/Abstract_Wikipedia_team/Definition_of_Done#Front-end_Task/Bug_Completion_Checklist