(key: string, computed: ComputedProjectionIrExpression)
| 398 | } |
| 399 | |
| 400 | const computedSelectExpr = (key: string, computed: ComputedProjectionIrExpression): string => { |
| 401 | const relationPath = dottedToJsonPath(computed.path) |
| 402 | const relationAlias = `_${computed.path}` |
| 403 | const relationFrom = dialect.jsonEachFrom(relationPath, relationAlias) |
| 404 | const toNumber = (expr: string) => |
| 405 | dialect.jsonColumnType === "JSON" ? `CAST(${expr} AS REAL)` : `(${expr})::numeric` |
| 406 | const compileExpr = (expression: ComputedProjectionMathIrExpression): string => { |
| 407 | switch (expression._tag) { |
| 408 | case "field": |
| 409 | return toNumber(dialect.jsonExtractElement(relationAlias, expression.field)) |
| 410 | case "mul": |
| 411 | return `(${compileExpr(expression.left)} * ${compileExpr(expression.right)})` |
| 412 | default: |
| 413 | return assertUnreachable(expression) |
| 414 | } |
| 415 | } |
| 416 | const factorCaseExpr = (unitExpr: string, toBase: string, factors: Readonly<Record<string, number>>) => { |
| 417 | const entries = Object.entries(factors).filter(([, factor]) => Number.isFinite(factor)) |
| 418 | const cases = entries.map(([unit, factor]) => ` WHEN ${sqlStringLiteral(unit)} THEN ${factor}`).join("") |
| 419 | return `CASE ${unitExpr} WHEN ${sqlStringLiteral(toBase)} THEN 1${cases} ELSE NULL END` |
| 420 | } |
| 421 | const filter = "filter" in computed ? computed.filter : [] |
| 422 | const whereClause = () => |
| 423 | filter.length > 0 |
| 424 | ? ` WHERE ${print(filter, computed.path, false)}` |
| 425 | : "" |
| 426 | const boolExpr = (sqlExpr: string) => |
| 427 | dialect.jsonColumnType === "JSON" |
| 428 | ? `CASE WHEN ${sqlExpr} THEN 'true' ELSE 'false' END AS "${key}"` |
| 429 | : `${sqlExpr} AS "${key}"` |
| 430 | switch (computed._tag) { |
| 431 | case "relation-count": |
| 432 | return `(SELECT COUNT(1) FROM ${relationFrom}${whereClause()}) AS "${key}"` |
| 433 | case "relation-any": |
| 434 | return boolExpr(`EXISTS(SELECT 1 FROM ${relationFrom}${whereClause()})`) |
| 435 | case "relation-every": |
| 436 | // ∀x.P(x) ≡ ¬∃x.¬P(x). When no filter, no element exists that violates ⊤ → true. |
| 437 | return boolExpr( |
| 438 | filter.length === 0 |
| 439 | ? `1=1` |
| 440 | : `NOT EXISTS(SELECT 1 FROM ${relationFrom} WHERE NOT (${print(filter, computed.path, false)}))` |
| 441 | ) |
| 442 | case "relation-distinct-count": { |
| 443 | const fieldExtract = dialect.jsonExtractElement(relationAlias, computed.field) |
| 444 | return `(SELECT COUNT(DISTINCT ${fieldExtract}) FROM ${relationFrom}${whereClause()}) AS "${key}"` |
| 445 | } |
| 446 | case "relation-sum": { |
| 447 | const fieldExtract = dialect.jsonExtractElement(relationAlias, computed.field) |
| 448 | return `(SELECT COALESCE(SUM(${toNumber(fieldExtract)}), 0) FROM ${relationFrom}${whereClause()}) AS "${key}"` |
| 449 | } |
| 450 | case "relation-sum-expr": { |
| 451 | const expression = compileExpr(computed.expression) |
| 452 | return `(SELECT COALESCE(SUM(${expression}), 0) FROM ${relationFrom}${whereClause()}) AS "${key}"` |
| 453 | } |
| 454 | case "relation-sum-expr-by": { |
| 455 | const expression = compileExpr(computed.expression) |
| 456 | const unitExpr = dialect.jsonExtractElement(relationAlias, computed.unit) |
| 457 | if (dialect.jsonColumnType === "JSON") { |
no test coverage detected
searching dependent graphs…