This commit is contained in:
Sam Rose 2024-10-01 11:48:14 +01:00
parent 3dbda132b8
commit 987a24fabc
No known key found for this signature in database
2 changed files with 88 additions and 38 deletions

View File

@ -71,18 +71,6 @@ function prioritisedArraySort(toSort: string[], priorities: string[]) {
}) })
} }
function getTableName(table?: Table): string | undefined {
// SQS uses the table ID rather than the table name
if (
table?.sourceType === TableSourceType.INTERNAL ||
table?.sourceId === INTERNAL_TABLE_SOURCE_ID
) {
return table?._id
} else {
return table?.name
}
}
function convertBooleans(query: SqlQuery | SqlQuery[]): SqlQuery | SqlQuery[] { function convertBooleans(query: SqlQuery | SqlQuery[]): SqlQuery | SqlQuery[] {
if (Array.isArray(query)) { if (Array.isArray(query)) {
return query.map((q: SqlQuery) => convertBooleans(q) as SqlQuery) return query.map((q: SqlQuery) => convertBooleans(q) as SqlQuery)
@ -180,15 +168,13 @@ class InternalBuilder {
} }
private generateSelectStatement(): (string | Knex.Raw)[] | "*" { private generateSelectStatement(): (string | Knex.Raw)[] | "*" {
const { meta, endpoint, resource, tableAliases } = this.query const { meta, endpoint, resource } = this.query
if (!resource || !resource.fields || resource.fields.length === 0) { if (!resource || !resource.fields || resource.fields.length === 0) {
return "*" return "*"
} }
const alias = tableAliases?.[endpoint.entityId] const alias = this.getTableName(endpoint.entityId)
? tableAliases?.[endpoint.entityId]
: endpoint.entityId
const schema = meta.table.schema const schema = meta.table.schema
if (!this.isFullSelectStatementRequired()) { if (!this.isFullSelectStatementRequired()) {
return [this.knex.raw(`${this.quote(alias)}.*`)] return [this.knex.raw(`${this.quote(alias)}.*`)]
@ -813,17 +799,39 @@ class InternalBuilder {
return query return query
} }
getTableName(t?: Table | string): string {
let table: Table
if (typeof t === "string") {
if (!this.query.meta.tables?.[t]) {
throw new Error(`Table ${t} not found`)
}
table = this.query.meta.tables[t]
} else if (t) {
table = t
} else {
table = this.table
}
let name = table.name
if (
(table.sourceType === TableSourceType.INTERNAL ||
table.sourceId === INTERNAL_TABLE_SOURCE_ID) &&
table._id
) {
// SQS uses the table ID rather than the table name
name = table._id
}
const aliases = this.query.tableAliases || {}
return aliases[name] ? aliases[name] : name
}
addDistinctCount(query: Knex.QueryBuilder): Knex.QueryBuilder { addDistinctCount(query: Knex.QueryBuilder): Knex.QueryBuilder {
const primary = this.table.primary if (!this.table.primary) {
const aliases = this.query.tableAliases
const aliased =
this.table.name && aliases?.[this.table.name]
? aliases[this.table.name]
: this.table.name
if (!primary) {
throw new Error("SQL counting requires primary key to be supplied") throw new Error("SQL counting requires primary key to be supplied")
} }
return query.countDistinct(`${aliased}.${primary[0]} as total`) return query.countDistinct(
`${this.getTableName(this.table)}.${this.table.primary[0]} as total`
)
} }
addAggregations( addAggregations(
@ -831,8 +839,9 @@ class InternalBuilder {
aggregations: Aggregation[] aggregations: Aggregation[]
): Knex.QueryBuilder { ): Knex.QueryBuilder {
const fields = this.query.resource?.fields || [] const fields = this.query.resource?.fields || []
const tableName = this.getTableName()
if (fields.length > 0) { if (fields.length > 0) {
query = query.groupBy(fields.map(field => `${this.table.name}.${field}`)) query = query.groupBy(fields.map(field => `${tableName}.${field}`))
} }
for (const aggregation of aggregations) { for (const aggregation of aggregations) {
const op = aggregation.calculationType const op = aggregation.calculationType
@ -861,10 +870,7 @@ class InternalBuilder {
addSorting(query: Knex.QueryBuilder): Knex.QueryBuilder { addSorting(query: Knex.QueryBuilder): Knex.QueryBuilder {
let { sort, resource } = this.query let { sort, resource } = this.query
const primaryKey = this.table.primary const primaryKey = this.table.primary
const tableName = getTableName(this.table) const aliased = this.getTableName()
const aliases = this.query.tableAliases
const aliased =
tableName && aliases?.[tableName] ? aliases[tableName] : this.table?.name
if (!Array.isArray(primaryKey)) { if (!Array.isArray(primaryKey)) {
throw new Error("Sorting requires primary key to be specified for table") throw new Error("Sorting requires primary key to be specified for table")
} }
@ -1508,18 +1514,35 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
return results.length ? results : [{ [operation.toLowerCase()]: true }] return results.length ? results : [{ [operation.toLowerCase()]: true }]
} }
private getTableName(
table: Table,
aliases?: Record<string, string>
): string | undefined {
let name = table.name
if (
table.sourceType === TableSourceType.INTERNAL ||
table.sourceId === INTERNAL_TABLE_SOURCE_ID
) {
if (!table._id) {
return
}
// SQS uses the table ID rather than the table name
name = table._id
}
return aliases?.[name] ? aliases[name] : name
}
convertJsonStringColumns<T extends Record<string, any>>( convertJsonStringColumns<T extends Record<string, any>>(
table: Table, table: Table,
results: T[], results: T[],
aliases?: Record<string, string> aliases?: Record<string, string>
): T[] { ): T[] {
const tableName = getTableName(table) const tableName = this.getTableName(table, aliases)
for (const [name, field] of Object.entries(table.schema)) { for (const [name, field] of Object.entries(table.schema)) {
if (!this._isJsonColumn(field)) { if (!this._isJsonColumn(field)) {
continue continue
} }
const aliasedTableName = (tableName && aliases?.[tableName]) || tableName const fullName = `${tableName}.${name}`
const fullName = `${aliasedTableName}.${name}`
for (let row of results) { for (let row of results) {
if (typeof row[fullName as keyof T] === "string") { if (typeof row[fullName as keyof T] === "string") {
row[fullName as keyof T] = JSON.parse(row[fullName]) row[fullName as keyof T] = JSON.parse(row[fullName])

View File

@ -39,13 +39,13 @@ import {
} from "@budibase/backend-core" } from "@budibase/backend-core"
describe.each([ describe.each([
["lucene", undefined], // ["lucene", undefined],
["sqs", undefined], // ["sqs", undefined],
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)], [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
[DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)], // [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
[DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)], // [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
[DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)], // [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
[DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)], // [DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)],
])("/v2/views (%s)", (name, dsProvider) => { ])("/v2/views (%s)", (name, dsProvider) => {
const config = setup.getConfig() const config = setup.getConfig()
const isSqs = name === "sqs" const isSqs = name === "sqs"
@ -2458,6 +2458,33 @@ describe.each([
expect("_id" in row).toBe(false) expect("_id" in row).toBe(false)
} }
}) })
it.only("should be able to group by a basic field", async () => {
const view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
schema: {
quantity: {
visible: true,
field: "quantity",
},
"Total Price": {
visible: true,
calculationType: CalculationType.SUM,
field: "price",
},
},
})
const response = await config.api.viewV2.search(view.id, {
query: {},
})
for (const row of response.rows) {
expect(row["quantity"]).toBeGreaterThan(0)
expect(row["Total Price"]).toBeGreaterThan(0)
}
})
}) })
}) })