Fix SQL table `_id` filtering (#9030)
* Re-add support for filtering on _id using external SQL tables and fix filter key prefixes not working with _id field * Remove like operator from internal tables and only allow basic operators on SQL table _id column * Update data section filtering to respect new rules * Update automation section filtering to respect new rules * Update dynamic filter component to respect new rules
This commit is contained in:
parent
5913df0359
commit
c3b6afbb6f
|
@ -232,6 +232,7 @@
|
||||||
{filters}
|
{filters}
|
||||||
{bindings}
|
{bindings}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
|
datasource={{ type: "table", tableId }}
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
fillWidth
|
fillWidth
|
||||||
on:change={e => (tempFilters = e.detail)}
|
on:change={e => (tempFilters = e.detail)}
|
||||||
|
|
|
@ -190,6 +190,7 @@
|
||||||
{filters}
|
{filters}
|
||||||
on:change={onFilter}
|
on:change={onFilter}
|
||||||
disabled={!hasCols}
|
disabled={!hasCols}
|
||||||
|
tableId={id}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
export let schema
|
export let schema
|
||||||
export let filters
|
export let filters
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
export let tableId
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
allowBindings={false}
|
allowBindings={false}
|
||||||
{filters}
|
{filters}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
|
datasource={{ type: "table", tableId }}
|
||||||
on:change={e => (tempValue = e.detail)}
|
on:change={e => (tempValue = e.detail)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
export let panel = ClientBindingPanel
|
export let panel = ClientBindingPanel
|
||||||
export let allowBindings = true
|
export let allowBindings = true
|
||||||
export let fillWidth = false
|
export let fillWidth = false
|
||||||
export let tableId
|
export let datasource
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const { OperatorOptions } = Constants
|
const { OperatorOptions } = Constants
|
||||||
|
@ -41,11 +41,7 @@
|
||||||
|
|
||||||
$: parseFilters(filters)
|
$: parseFilters(filters)
|
||||||
$: dispatch("change", enrichFilters(rawFilters, matchAny))
|
$: dispatch("change", enrichFilters(rawFilters, matchAny))
|
||||||
$: enrichedSchemaFields = getFields(
|
$: enrichedSchemaFields = getFields(schemaFields || [], { allowLinks: true })
|
||||||
schemaFields || [],
|
|
||||||
{ allowLinks: true },
|
|
||||||
tableId
|
|
||||||
)
|
|
||||||
$: fieldOptions = enrichedSchemaFields.map(field => field.name) || []
|
$: fieldOptions = enrichedSchemaFields.map(field => field.name) || []
|
||||||
$: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"]
|
$: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"]
|
||||||
|
|
||||||
|
@ -119,7 +115,11 @@
|
||||||
|
|
||||||
const santizeOperator = filter => {
|
const santizeOperator = filter => {
|
||||||
// Ensure a valid operator is selected
|
// Ensure a valid operator is selected
|
||||||
const operators = getValidOperatorsForType(filter.type).map(x => x.value)
|
const operators = getValidOperatorsForType(
|
||||||
|
filter.type,
|
||||||
|
filter.field,
|
||||||
|
datasource
|
||||||
|
).map(x => x.value)
|
||||||
if (!operators.includes(filter.operator)) {
|
if (!operators.includes(filter.operator)) {
|
||||||
filter.operator = operators[0] ?? OperatorOptions.Equals.value
|
filter.operator = operators[0] ?? OperatorOptions.Equals.value
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,11 @@
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
disabled={!filter.field}
|
disabled={!filter.field}
|
||||||
options={getValidOperatorsForType(filter.type)}
|
options={getValidOperatorsForType(
|
||||||
|
filter.type,
|
||||||
|
filter.field,
|
||||||
|
datasource
|
||||||
|
)}
|
||||||
bind:value={filter.operator}
|
bind:value={filter.operator}
|
||||||
on:change={() => onOperatorChange(filter)}
|
on:change={() => onOperatorChange(filter)}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
let drawer
|
let drawer
|
||||||
|
|
||||||
$: tempValue = value
|
$: tempValue = value
|
||||||
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema
|
$: schema = getSchemaForDatasource($currentAsset, datasource)?.schema
|
||||||
$: schemaFields = Object.values(schema || {})
|
$: schemaFields = Object.values(schema || {})
|
||||||
|
|
||||||
async function saveFilter() {
|
async function saveFilter() {
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
filters={value}
|
filters={value}
|
||||||
{bindings}
|
{bindings}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
tableId={dataSource.tableId}
|
{datasource}
|
||||||
on:change={e => (tempValue = e.detail)}
|
on:change={e => (tempValue = e.detail)}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -16,11 +16,7 @@ export function getTableFields(linkField) {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFields(
|
export function getFields(fields, { allowLinks } = { allowLinks: true }) {
|
||||||
fields,
|
|
||||||
{ allowLinks } = { allowLinks: true },
|
|
||||||
tableId
|
|
||||||
) {
|
|
||||||
let filteredFields = fields.filter(
|
let filteredFields = fields.filter(
|
||||||
field => !BannedSearchTypes.includes(field.type)
|
field => !BannedSearchTypes.includes(field.type)
|
||||||
)
|
)
|
||||||
|
@ -34,9 +30,5 @@ export function getFields(
|
||||||
const staticFormulaFields = fields.filter(
|
const staticFormulaFields = fields.filter(
|
||||||
field => field.type === "formula" && field.formulaType === "static"
|
field => field.type === "formula" && field.formulaType === "static"
|
||||||
)
|
)
|
||||||
const table = get(tables).list.find(table => table._id === tableId)
|
|
||||||
if (table?.type === "external" && table?.sql) {
|
|
||||||
filteredFields = filteredFields.filter(field => field.name !== "_id")
|
|
||||||
}
|
|
||||||
return filteredFields.concat(staticFormulaFields)
|
return filteredFields.concat(staticFormulaFields)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
schema
|
schema
|
||||||
|
|
||||||
$: dataProviderId = dataProvider?.id
|
$: dataProviderId = dataProvider?.id
|
||||||
|
$: datasource = dataProvider?.datasource
|
||||||
$: addExtension = getAction(
|
$: addExtension = getAction(
|
||||||
dataProviderId,
|
dataProviderId,
|
||||||
ActionTypes.AddDataProviderQueryExtension
|
ActionTypes.AddDataProviderQueryExtension
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
dataProviderId,
|
dataProviderId,
|
||||||
ActionTypes.RemoveDataProviderQueryExtension
|
ActionTypes.RemoveDataProviderQueryExtension
|
||||||
)
|
)
|
||||||
$: fetchSchema(dataProvider || {})
|
$: fetchSchema(datasource)
|
||||||
$: schemaFields = getSchemaFields(schema, allowedFields)
|
$: schemaFields = getSchemaFields(schema, allowedFields)
|
||||||
|
|
||||||
// Add query extension to data provider
|
// Add query extension to data provider
|
||||||
|
@ -42,8 +43,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchSchema(dataProvider) {
|
async function fetchSchema(datasource) {
|
||||||
const datasource = dataProvider?.datasource
|
|
||||||
if (datasource) {
|
if (datasource) {
|
||||||
schema = await fetchDatasourceSchema(datasource, {
|
schema = await fetchDatasourceSchema(datasource, {
|
||||||
enrichRelationships: true,
|
enrichRelationships: true,
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<ModalContent title="Edit filters" size="XL" onConfirm={updateQuery}>
|
<ModalContent title="Edit filters" size="XL" onConfirm={updateQuery}>
|
||||||
<FilterModal bind:filters={tmpFilters} {schemaFields} />
|
<FilterModal bind:filters={tmpFilters} {schemaFields} {datasource} />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let filters = []
|
export let filters = []
|
||||||
|
export let datasource
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const BannedTypes = ["link", "attachment", "json"]
|
const BannedTypes = ["link", "attachment", "json"]
|
||||||
|
@ -59,7 +60,9 @@
|
||||||
|
|
||||||
// Ensure a valid operator is set
|
// Ensure a valid operator is set
|
||||||
const validOperators = LuceneUtils.getValidOperatorsForType(
|
const validOperators = LuceneUtils.getValidOperatorsForType(
|
||||||
expression.type
|
expression.type,
|
||||||
|
expression.field,
|
||||||
|
datasource
|
||||||
).map(x => x.value)
|
).map(x => x.value)
|
||||||
if (!validOperators.includes(expression.operator)) {
|
if (!validOperators.includes(expression.operator)) {
|
||||||
expression.operator =
|
expression.operator =
|
||||||
|
@ -118,7 +121,11 @@
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
disabled={!filter.field}
|
disabled={!filter.field}
|
||||||
options={LuceneUtils.getValidOperatorsForType(filter.type)}
|
options={LuceneUtils.getValidOperatorsForType(
|
||||||
|
filter.type,
|
||||||
|
filter.field,
|
||||||
|
datasource
|
||||||
|
)}
|
||||||
bind:value={filter.operator}
|
bind:value={filter.operator}
|
||||||
on:change={e => onOperatorChange(filter, e.detail)}
|
on:change={e => onOperatorChange(filter, e.detail)}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
|
|
|
@ -7,7 +7,7 @@ const HBS_REGEX = /{{([^{].*?)}}/g
|
||||||
* Returns the valid operator options for a certain data type
|
* Returns the valid operator options for a certain data type
|
||||||
* @param type the data type
|
* @param type the data type
|
||||||
*/
|
*/
|
||||||
export const getValidOperatorsForType = type => {
|
export const getValidOperatorsForType = (type, field, datasource) => {
|
||||||
const Op = OperatorOptions
|
const Op = OperatorOptions
|
||||||
const stringOps = [
|
const stringOps = [
|
||||||
Op.Equals,
|
Op.Equals,
|
||||||
|
@ -27,24 +27,37 @@ export const getValidOperatorsForType = type => {
|
||||||
Op.NotEmpty,
|
Op.NotEmpty,
|
||||||
Op.In,
|
Op.In,
|
||||||
]
|
]
|
||||||
|
let ops = []
|
||||||
if (type === "string") {
|
if (type === "string") {
|
||||||
return stringOps
|
ops = stringOps
|
||||||
} else if (type === "number") {
|
} else if (type === "number") {
|
||||||
return numOps
|
ops = numOps
|
||||||
} else if (type === "options") {
|
} else if (type === "options") {
|
||||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
|
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
|
||||||
} else if (type === "array") {
|
} else if (type === "array") {
|
||||||
return [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty, Op.ContainsAny]
|
ops = [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty, Op.ContainsAny]
|
||||||
} else if (type === "boolean") {
|
} else if (type === "boolean") {
|
||||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
} else if (type === "longform") {
|
} else if (type === "longform") {
|
||||||
return stringOps
|
ops = stringOps
|
||||||
} else if (type === "datetime") {
|
} else if (type === "datetime") {
|
||||||
return numOps
|
ops = numOps
|
||||||
} else if (type === "formula") {
|
} else if (type === "formula") {
|
||||||
return stringOps.concat([Op.MoreThan, Op.LessThan])
|
ops = stringOps.concat([Op.MoreThan, Op.LessThan])
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
|
// Filter out "like" for internal tables
|
||||||
|
const externalTable = datasource?.tableId?.includes("datasource_plus")
|
||||||
|
if (datasource?.type === "table" && !externalTable) {
|
||||||
|
ops = ops.filter(x => x !== Op.Like)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow equal/not equal for _id in SQL tables
|
||||||
|
if (field === "_id" && externalTable) {
|
||||||
|
ops = [Op.Equals, Op.NotEquals]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { processObjectSync } from "@budibase/string-templates"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { processFormulas, processDates } from "../../../utilities/rowProcessor"
|
import { processFormulas, processDates } from "../../../utilities/rowProcessor"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
|
import { removeKeyNumbering } from "./utils"
|
||||||
|
|
||||||
export interface ManyRelationship {
|
export interface ManyRelationship {
|
||||||
tableId?: string
|
tableId?: string
|
||||||
|
@ -55,15 +56,21 @@ function buildFilters(
|
||||||
let idCopy: undefined | string | any[] = cloneDeep(id)
|
let idCopy: undefined | string | any[] = cloneDeep(id)
|
||||||
if (filters) {
|
if (filters) {
|
||||||
// need to map over the filters and make sure the _id field isn't present
|
// need to map over the filters and make sure the _id field isn't present
|
||||||
for (let filter of Object.values(filters)) {
|
let prefix = 1
|
||||||
if (filter._id && primary) {
|
for (let operator of Object.values(filters)) {
|
||||||
const parts = breakRowIdField(filter._id)
|
for (let field of Object.keys(operator || {})) {
|
||||||
for (let field of primary) {
|
if (removeKeyNumbering(field) === "_id") {
|
||||||
filter[field] = parts.shift()
|
if (primary) {
|
||||||
|
const parts = breakRowIdField(operator[field])
|
||||||
|
for (let field of primary) {
|
||||||
|
operator[`${prefix}:${field}`] = parts.shift()
|
||||||
|
}
|
||||||
|
prefix++
|
||||||
|
}
|
||||||
|
// make sure this field doesn't exist on any filter
|
||||||
|
delete operator[field]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// make sure this field doesn't exist on any filter
|
|
||||||
delete filter._id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// there is no id, just use the user provided filters
|
// there is no id, just use the user provided filters
|
||||||
|
|
Loading…
Reference in New Issue