Skip to content

Commit ec77a74

Browse files
authored
feat(core): Query builder (#1)
* WIP: query builder * restore changelog files, not sure why they were deleted... * restore changelog files without pre-commit hook * fix main changelog * refactor tests * add execute method to query builder * Implement limit + offset, refactor test to use expectsql * feat: implement remaining features * fix: prettier * fix: eslint * fix: remove js test file * fix: db2 * fix: db2 * fix: db2
1 parent c0a38df commit ec77a74

File tree

6 files changed

+1156
-0
lines changed

6 files changed

+1156
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
3+
# Runs tests for all dialects
4+
# Specify the test you want to run on .mocharc.jsonc on packages/core with the following content:
5+
# {
6+
# "file": "test/integration/query-builder/query-builder.test.js"
7+
# }
8+
# See https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#41-running-only-some-tests
9+
# Remember to run the `start.sh` scripts for the dialects you want to test from the dev folder.
10+
11+
DIALECT=sqlite3 yarn mocha && \
12+
DIALECT=mysql yarn mocha && \
13+
DIALECT=mariadb yarn mocha && \
14+
DIALECT=postgres yarn mocha && \
15+
# DIALECT=mssql yarn mocha && \
16+
# DIALECT=snowflake yarn mocha && \ ## Experimental
17+
# DIALECT=ibmi yarn mocha && \ ## Experimental
18+
# DIALECT=db2 yarn mocha && \ ## No matching manifest for arm64
19+
echo "Done"

packages/core/src/abstract-dialect/query-generator.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,8 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
15901590
topLevelInfo,
15911591
{ minifyAliases: options.minifyAliases },
15921592
);
1593+
} else if (include._isCustomJoin) {
1594+
joinQuery = this.generateCustomJoin(include, includeAs, topLevelInfo);
15931595
} else {
15941596
this._generateSubQueryFilter(include, includeAs, topLevelInfo);
15951597
joinQuery = this.generateJoin(include, topLevelInfo, options);
@@ -1710,6 +1712,93 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
17101712
);
17111713
}
17121714

1715+
generateCustomJoin(include, includeAs, topLevelInfo) {
1716+
const right = include.model;
1717+
const asRight = includeAs.internalAs;
1718+
let joinCondition;
1719+
let joinWhere;
1720+
1721+
if (!include.on) {
1722+
throw new Error('Custom joins require an "on" condition to be specified');
1723+
}
1724+
1725+
// Handle the custom join condition
1726+
joinCondition = this.whereItemsQuery(include.on, {
1727+
mainAlias: asRight,
1728+
model: include.model,
1729+
replacements: topLevelInfo.options?.replacements,
1730+
});
1731+
1732+
if (include.where) {
1733+
joinWhere = this.whereItemsQuery(include.where, {
1734+
mainAlias: asRight,
1735+
model: include.model,
1736+
replacements: topLevelInfo.options?.replacements,
1737+
});
1738+
if (joinWhere) {
1739+
if (include.or) {
1740+
joinCondition += ` OR ${joinWhere}`;
1741+
} else {
1742+
joinCondition += ` AND ${joinWhere}`;
1743+
}
1744+
}
1745+
}
1746+
1747+
// Handle alias minification like in generateJoin
1748+
if (topLevelInfo.options?.minifyAliases && asRight.length > 63) {
1749+
const alias = `%${topLevelInfo.options.includeAliases.size}`;
1750+
topLevelInfo.options.includeAliases.set(alias, asRight);
1751+
}
1752+
1753+
// Generate attributes for the joined table
1754+
const attributes = [];
1755+
const rightAttributes = right.modelDefinition.attributes;
1756+
1757+
// Process each attribute based on include.attributes or all attributes
1758+
const attributesToInclude =
1759+
include.attributes && include.attributes.length > 0
1760+
? include.attributes
1761+
: Array.from(rightAttributes.keys());
1762+
1763+
for (const attr of attributesToInclude) {
1764+
if (typeof attr === 'string') {
1765+
// Simple attribute name
1766+
const field = rightAttributes.get(attr)?.columnName || attr;
1767+
attributes.push(
1768+
`${this.quoteTable(asRight)}.${this.quoteIdentifier(field)} AS ${this.quoteIdentifier(`${asRight}.${attr}`)}`,
1769+
);
1770+
} else if (Array.isArray(attr)) {
1771+
// [field, alias] format
1772+
const [field, alias] = attr;
1773+
if (typeof field === 'string') {
1774+
const columnName = rightAttributes.get(field)?.columnName || field;
1775+
attributes.push(
1776+
`${this.quoteTable(asRight)}.${this.quoteIdentifier(columnName)} AS ${this.quoteIdentifier(`${asRight}.${alias}`)}`,
1777+
);
1778+
} else {
1779+
// Handle complex expressions
1780+
attributes.push(
1781+
`${this.formatSqlExpression(field)} AS ${this.quoteIdentifier(`${asRight}.${alias}`)}`,
1782+
);
1783+
}
1784+
}
1785+
}
1786+
1787+
return {
1788+
join: include.required
1789+
? 'INNER JOIN'
1790+
: include.right && this._dialect.supports['RIGHT JOIN']
1791+
? 'RIGHT OUTER JOIN'
1792+
: 'LEFT OUTER JOIN',
1793+
body: this.quoteTable(right, { ...topLevelInfo.options, ...include, alias: asRight }),
1794+
condition: joinCondition,
1795+
attributes: {
1796+
main: attributes,
1797+
subQuery: [],
1798+
},
1799+
};
1800+
}
1801+
17131802
generateJoin(include, topLevelInfo, options) {
17141803
const association = include.association;
17151804
const parent = include.parent;

0 commit comments

Comments
 (0)