MSSQL SQL API pushdown omits joins from generated SELECT statements
- Dominant language
- Rust
- Stars
- 20.8k
- Forks
- 2.1k
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 203
Description
# MSSQL SQL API pushdown omits joins from generated SELECT statements
## Describe the bug
The MSSQL override of `statements.select` does not render the `joins` input. Cube can plan a join between grouped subqueries, but the generated SQL projects columns from the right-hand alias without emitting the JOIN that defines it.
SQL Server then rejects the generated query:
```text
The multi-part identifier "a.task_reference" could not be bound.
```
The base dialect already renders each join. Its MSSQL override is missing that loop.
## Observed SQL API query shape
The failure was reproduced with two semantic views exposing a shared task reference. Each source query succeeds independently. The grouped-CTE join fails:
```sql
WITH eligible_reports AS (
SELECT task_reference, company_code
FROM dynamic_reports
WHERE report_template_type = 'RPX'
GROUP BY task_reference, company_code
), affected_reports AS (
SELECT task_reference
FROM repair_defect_analysis
WHERE defect_identifier = 'DefRep_BoardsMissingPartially'
GROUP BY task_reference
)
SELECT r.company_code,
COUNT(*) AS eligible_reports,
COUNT(a.task_reference) AS affected_reports
FROM eligible_reports r
LEFT JOIN affected_reports a ON a.task_reference = r.task_reference
GROUP BY r.company_code;
```
There is deliberately no ORDER BY here. This isolates missing joins from the independent MSSQL null-ordering defect.
The full source model is not required to demonstrate the template omission. The following inspection works against the published package:
```javascript
const { MssqlQuery, BaseQuery } = require('@cubejs-backend/schema-compiler');
for (const Query of [BaseQuery, MssqlQuery]) {
const query = Object.create(Query.prototype);
const template = query.sqlTemplates().statements.select;
console.log(Query.name, template.includes('{% for join in joins %}'));
}
// BaseQuery true
// MssqlQuery false
```
## Generated SQL failure
This is a reduced representation of the observed generated SQL, with the expanded model replaced by a literal source:
```sql
SELECT r.company_code, a.task_reference
FROM (
SELECT 'A' AS company_code, 'task-1' AS task_reference
) AS r;
```
The projection still references `a`, but its join is absent. SQL Server reports that `a.task_reference` cannot be bound. This occurs before the presence or absence of matching data matters.
## Expected behavior and proposed fix
Render the supplied joins after either FROM branch and before WHERE, as BaseQuery does:
```jinja
{% for join in joins %}
{{ join }}{% endfor %}
```
Each supplied join retains its own type, source and ON condition. An empty join list renders nothing. The change does not alter pagination, sorting, semantic relationships or query rewriting.
The local patch adds this one line to `packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts`:
```typescript
'{% for join in joins %}\n{{ join }}{% endfor %}' +
```
## Versions
- Reproduced with `cubejs/cube:v1.7.33` and Azure SQL, compatibility level 150.
- Present in upstream commit `650e0b5814bd72c79bb8ed9649a851f74f7a3566`, package version 1.7.34.
Contributor guide
Research direction
Start in packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts and compare its statements.select template with BaseQuery. Inspect the published template check and reproduce the reduced SQL shape if useful. Done means supplied joins appear between the FROM branch and WHERE while empty joins remain absent.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- sql, typescript
- Domain
- backend-api-design, databases
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 92/100