feat(core): add native UNION support via sequelize.union#18110
feat(core): add native UNION support via sequelize.union#18110agamennone-siae wants to merge 5 commits intosequelize:mainfrom
Conversation
…eries with support for `UNION ALL`, `ORDER BY`, `LIMIT`, and `OFFSET`.
📝 WalkthroughWalkthroughAdds UNION support: new UnionOptions type, AbstractQueryGenerator.unionQuery to build combined SQL, AbstractQueryInterface.union to execute it, and Sequelize.union to assemble per-model SELECTs and run the union. Includes integration tests for behavior (dedupe, UNION ALL, ORDER/LIMIT/OFFSET, attributes). Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant Sequelize as Sequelize
participant QueryInterface as QueryInterface
participant QueryGenerator as QueryGenerator
participant DB as Database
Client->>Sequelize: union(queries, options)
activate Sequelize
Sequelize->>Sequelize: validate & normalize queries
loop per query
Sequelize->>QueryGenerator: build SELECT SQL for model
QueryGenerator-->>Sequelize: SELECT SQL
end
Sequelize->>QueryInterface: union(sqls, options)
deactivate Sequelize
activate QueryInterface
QueryInterface->>QueryGenerator: unionQuery(sqls, options)
activate QueryGenerator
QueryGenerator->>QueryGenerator: join with UNION / UNION ALL
QueryGenerator->>QueryGenerator: append ORDER BY, LIMIT, OFFSET
QueryGenerator-->>QueryInterface: combined SQL
deactivate QueryGenerator
QueryInterface->>DB: execute combined SQL
DB-->>QueryInterface: results
QueryInterface-->>Client: results
deactivate QueryInterface
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 OpenGrep (1.16.0)packages/core/src/abstract-dialect/query-generator.js┌──────────────┐ �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m packages/core/src/abstract-dialect/query-interface.js┌──────────────┐ �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m packages/core/src/abstract-dialect/query-generator.types.ts┌──────────────┐ �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In `@packages/core/src/abstract-dialect/query-generator.js`:
- Around line 1495-1506: The ORDER BY direction from options.order is used
directly when building orderClauses; validate the direction against
VALID_ORDER_OPTIONS before interpolating to avoid unexpected SQL. In the order
mapping logic (where orders, orderClauses, and the tuple [col, dir] are
handled), normalize dir to a canonical form (e.g., toUpperCase()), check
inclusion in VALID_ORDER_OPTIONS, and if invalid either throw a clear error or
fall back to a safe default like 'ASC'; then use the validated value when
calling quoteIdentifier(col) and building the clause. Ensure VALID_ORDER_OPTIONS
is referenced and used for validation so behavior matches the rest of the
codebase.
- Around line 1509-1514: The conditional that decides whether to call
this.#internals.addLimitAndOffset uses a truthy check (if (options.limit ||
options.offset)) — change it to use explicit null/undefined checks to match the
file's pattern by testing options.limit != null || options.offset != null so
LIMIT/OFFSET with zero values are handled consistently; update the conditional
near the query assembly where query and limitOrder are used (the block calling
this.#internals.addLimitAndOffset with options.limit/options.offset).
In `@packages/core/src/abstract-dialect/query-interface.js`:
- Around line 585-588: Before calling this.sequelize.queryRaw in the union flow,
remove options.replacements the same way select() does to avoid double
replacements: obtain or clone the options passed to unionQuery (referencing
this.queryGenerator.unionQuery and the local variable options), delete the
replacements property (options.replacements) on that cloned options object, then
pass the sanitized options into this.sequelize.queryRaw(sql, sanitizedOptions).
Ensure you do not mutate the original options object if other callers rely on
it.
- Around line 583-584: The merge currently allows callers to override the query
type; in the async union(sqls, options) function ensure QueryTypes.SELECT is
applied after merging so callers cannot override it. Modify the options
assignment to first merge user options (e.g., options = { ...options }) and then
enforce options.type = QueryTypes.SELECT (or recreate options = { ...options,
type: QueryTypes.SELECT }) so the function always uses SELECT; update references
to options in union accordingly.
In `@packages/core/src/sequelize.js`:
- Around line 247-252: In union, add upfront validation to ensure the queries
array is not empty and each entry has a valid model: check that queries is an
array with length > 0 and iterate queries to verify q && q.model is present (and
optionally that q.options exists); throw a clear TypeError or return a rejected
Promise with a descriptive message if validation fails. Place this check at the
start of the async union(queries, options = {}) method before mapping queries so
that later uses of q.model and q.options (e.g., the mapping that references
q.model and q.options) are safe and produce deterministic errors.
- Line 287: Update the UnionOptions type so it includes transaction support by
extending QueryRawOptions instead of only AddLimitOffsetOptions: find the
UnionOptions type (used by the union method) and change its extends clause to
include QueryRawOptions (which includes Transactionable) so callers can pass a
transaction through the union() call that ultimately delegates to queryRaw;
ensure any existing properties (limit, offset, replacements) remain present and
compatible with QueryRawOptions.
In `@packages/core/test/integration/sequelize/union.test.ts`:
- Line 41: The tests cast sequelize to any because the custom union method is
missing from the Sequelize type definitions; add a TypeScript declaration for
the union method (its signature used in tests) and surface it via module
augmentation or by extending the Sequelize interface so the test instance has
the correct type instead of casting. Concretely, add a declaration file (or
update the package's exported types) that augments the 'sequelize' module /
Sequelize class with union(params: Array<{ model: typeof Model }>):
Promise<any[]> (match the real return type), export that types file from the
package (or include it in the package's types entry), then remove the (sequelize
as any) casts in union.test.ts so the test uses the updated typed
sequelize.union method.
- Around line 96-109: Test currently asserts that 'age' is absent but doesn't
assert whether the primary key was injected when attributes were specified;
update the test for sequelize.union to also assert presence (or absence, as
appropriate) of the injected primary key field (id) when calling (sequelize as
any).union with options { attributes: ['name'] } and raw mode not set, e.g.,
after creating vars.User and vars.Guest check results include the primary key
property 'id' (or verify deduplication behavior relies on id) so the behavior of
attribute injection in the union implementation (union, attributes, raw, id,
vars.User, vars.Guest) is covered.
🧹 Nitpick comments (4)
packages/core/src/abstract-dialect/query-generator.types.ts (1)
15-18: Consider adding JSDoc documentation for the new public interface.The
UnionOptionsinterface is part of the public API but lacks documentation. Adding JSDoc comments would improve developer experience.📝 Suggested documentation
+/** + * Options for union queries. + */ export interface UnionOptions extends AddLimitOffsetOptions { + /** If true, use UNION ALL (keeps duplicates). Defaults to false (UNION with deduplication). */ unionAll?: boolean; + /** Order clause for the combined result. Can be a column name, [column, direction] tuple, or array of either. */ order?: string | [string, string] | Array<string | [string, string]>; }packages/core/src/sequelize.d.ts (1)
611-617: Consider using a more specific type for thequeriesparameter.The
queriesparameter is typed asobject[], which is quite loose. Based on the implementation insequelize.js, each query object should havemodeland optionaloptionsproperties.💡 Suggested type improvement
+export interface UnionQueryEntry { + model: ModelStatic<any>; + options?: FindOptions<any>; +} + /** * Run a union query * * `@param` queries The queries to union. Can be a set of options or model instances. * `@param` options The options for the union query. */ -union(queries: object[], options?: UnionOptions): Promise<any>; +union(queries: UnionQueryEntry[], options?: UnionOptions): Promise<any>;packages/core/test/integration/sequelize/union.test.ts (1)
5-5: Add dialect teaser for test description.Per coding guidelines, use
Support.getTestDialectTeaser()for dialect-specific test descriptions.📝 Suggested fix
+import { sequelize, getTestDialectTeaser } from '../support'; -import { sequelize } from '../support'; -describe('Sequelize#union', () => { +describe(getTestDialectTeaser('Sequelize#union'), () => {packages/core/src/sequelize.js (1)
264-274: Add comments explaining the primary key injection logic.The condition for primary key injection is complex. Adding a comment would help future maintainers understand when and why the primary key is injected.
📝 Suggested comment
+ // Inject primary key into attributes if: + // - Custom attributes are specified (not selecting all columns) + // - Not a raw query (raw queries bypass model mapping) + // - Model has a primary key + // - Primary key is not already in the attributes + // - Either no grouping, or has multi-association (to ensure proper deduplication) if ( queryOptions.attributes && !queryOptions.raw && model.primaryKeyAttribute && !queryOptions.attributes.includes(model.primaryKeyAttribute) && (!queryOptions.group || !queryOptions.hasSingleAssociation || queryOptions.hasMultiAssociation) ) { queryOptions.attributes = [model.primaryKeyAttribute].concat(queryOptions.attributes); }
| await vars.User.create({ name: 'Alice', age: 20 }); | ||
| await vars.Guest.create({ name: 'Bob', age: 30 }); | ||
|
|
||
| const results = await (sequelize as any).union([{ model: vars.User }, { model: vars.Guest }]); |
There was a problem hiding this comment.
Type cast indicates missing type definition.
The (sequelize as any).union cast is used throughout the tests, indicating that the union method type isn't properly exported or the test's sequelize instance doesn't have the updated types. This should be addressed to ensure type safety in tests.
🤖 Prompt for AI Agents
In `@packages/core/test/integration/sequelize/union.test.ts` at line 41, The tests
cast sequelize to any because the custom union method is missing from the
Sequelize type definitions; add a TypeScript declaration for the union method
(its signature used in tests) and surface it via module augmentation or by
extending the Sequelize interface so the test instance has the correct type
instead of casting. Concretely, add a declaration file (or update the package's
exported types) that augments the 'sequelize' module / Sequelize class with
union(params: Array<{ model: typeof Model }>): Promise<any[]> (match the real
return type), export that types file from the package (or include it in the
package's types entry), then remove the (sequelize as any) casts in
union.test.ts so the test uses the updated typed sequelize.union method.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
packages/core/test/integration/sequelize/union.test.ts (2)
5-5: UseSupport.getTestDialectTeaser()for the describe block.Per coding guidelines, test descriptions should use the dialect teaser helper for dialect-specific context.
+import { sequelize } from '../support'; +import Support from '../support'; + -describe('Sequelize#union', () => { +describe(Support.getTestDialectTeaser('Sequelize#union'), () => {As per coding guidelines: "Use Support.getTestDialectTeaser() for dialect-specific test descriptions".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/test/integration/sequelize/union.test.ts` at line 5, Replace the hardcoded describe title with the dialect teaser helper: change the describe('Sequelize#union', ...) call to use Support.getTestDialectTeaser('Sequelize#union') so the test description is dialect-aware (locate the describe call in union.test.ts and update it to call Support.getTestDialectTeaser).
59-67: Deduplication test relies on auto-increment IDs being identical across tables.This test works because both
UserandGuesttables start auto-increment at 1, so the full row(1, 'Alice', 20)is identical in both sub-SELECTs. If the test setup or seeding order changes (e.g., a priorcreatecall advances one table's sequence), the IDs would differ and UNION would no longer deduplicate. Consider usingraw: trueor explicitattributesexcludingidto make the deduplication assertion independent of auto-increment state.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/test/integration/sequelize/union.test.ts` around lines 59 - 67, The deduplication assertion in the "supports UNION (deduplicates by default)" test is brittle because it relies on matching auto-increment IDs from vars.User and vars.Guest; update the test to make the UNION compare only business fields by calling sequelize.union with sub-selects that exclude the id (e.g., provide attributes: ['name','age'] for each entry) or pass raw: true so the result rows lack the id, then assert deduplication on name/age; locate the call to sequelize.union and the vars.User/vars.Guest creates in that test and change the union invocation to use attributes or raw as described.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/src/abstract-dialect/query-generator.js`:
- Around line 1487-1522: The unionQuery method should not use options.unionAll
unconditionally; update the join character selection in unionQuery to check the
dialect capability by using this.dialect.supports['UNION ALL'] &&
options.unionAll to decide between ' UNION ALL ' and ' UNION '; change the
existing joinChar assignment accordingly (referencing unionQuery,
options.unionAll, and this.dialect.supports['UNION ALL']) and ensure tests/usage
that passed options.unionAll on dialects without support still produce plain
UNION (or add a dialect-specific override for unionQuery where necessary, e.g.,
MSSQL).
- Around line 1514-1519: The code appends an extra leading space before the
LIMIT/OFFSET clause because addLimitAndOffset already returns a leading-space
string; in the block that computes limitOrder (the if checking
options.limit/options.offset) replace the template literal that prepends " "
(query += ` ${limitOrder}`) with a direct append (query += limitOrder) or trim
the leading space from limitOrder so the generated SQL has only a single space
before the clause; refer to the addLimitAndOffset call and the limitOrder
variable when making the change.
In `@packages/core/src/sequelize.js`:
- Around line 272-273: Remove the dead variable assignment: the tableNames
object (declared as tableNames and assigned tableNames[model.table] = true) is
never read later; delete the tableNames declaration and the assignment to
tableNames[model.table] so only the actual logic remains (this was likely copied
from a Model.findAll pattern and is unnecessary here), leaving all references to
model and its properties intact.
---
Duplicate comments:
In `@packages/core/src/abstract-dialect/query-generator.js`:
- Around line 1498-1509: The code silently normalizes an invalid order direction
to 'ASC' in the Array.isArray(t) branch; instead, validate the direction against
VALID_ORDER_OPTIONS and throw a descriptive error when it's not recognized. In
the Array tuple handling (where you destructure const [col, dir] and compute
normalizedDir), replace the fallback normalization with a check that if dir is a
string but not included in VALID_ORDER_OPTIONS you throw (e.g., Error or
TypeError) naming the invalid direction and listing VALID_ORDER_OPTIONS; if dir
is undefined use the default 'ASC' as before, and continue to use
this.quoteIdentifier(col) for the column in the returned string.
---
Nitpick comments:
In `@packages/core/test/integration/sequelize/union.test.ts`:
- Line 5: Replace the hardcoded describe title with the dialect teaser helper:
change the describe('Sequelize#union', ...) call to use
Support.getTestDialectTeaser('Sequelize#union') so the test description is
dialect-aware (locate the describe call in union.test.ts and update it to call
Support.getTestDialectTeaser).
- Around line 59-67: The deduplication assertion in the "supports UNION
(deduplicates by default)" test is brittle because it relies on matching
auto-increment IDs from vars.User and vars.Guest; update the test to make the
UNION compare only business fields by calling sequelize.union with sub-selects
that exclude the id (e.g., provide attributes: ['name','age'] for each entry) or
pass raw: true so the result rows lack the id, then assert deduplication on
name/age; locate the call to sequelize.union and the vars.User/vars.Guest
creates in that test and change the union invocation to use attributes or raw as
described.
| unionQuery(sqls, options) { | ||
| let query = ''; | ||
| const joinChar = options.unionAll ? ' UNION ALL ' : ' UNION '; | ||
|
|
||
| const cleanedSqls = sqls.map(sql => (sql.endsWith(';') ? sql.slice(0, -1) : sql)); | ||
|
|
||
| query += cleanedSqls.join(joinChar); | ||
|
|
||
| if (options.order) { | ||
| const orders = Array.isArray(options.order) ? options.order : [options.order]; | ||
| const orderClauses = orders.map(t => { | ||
| if (Array.isArray(t)) { | ||
| const [col, dir] = t; | ||
|
|
||
| let normalizedDir = typeof dir === 'string' ? dir.toUpperCase() : 'ASC'; | ||
| if (!VALID_ORDER_OPTIONS.includes(normalizedDir)) { | ||
| normalizedDir = 'ASC'; | ||
| } | ||
|
|
||
| return `${this.quoteIdentifier(col)} ${normalizedDir}`; | ||
| } | ||
|
|
||
| return this.quoteIdentifier(t); | ||
| }); | ||
| query += ` ORDER BY ${orderClauses.join(', ')}`; | ||
| } | ||
|
|
||
| if (options.limit != null || options.offset != null) { | ||
| const limitOrder = this.#internals.addLimitAndOffset(options); | ||
| if (limitOrder) { | ||
| query += ` ${limitOrder}`; | ||
| } | ||
| } | ||
|
|
||
| return `${query};`; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check which dialects extend the query generator
rg -n "class.*extends.*QueryGenerator" packages/ --type ts --type js -A 2
# Check if any dialect already has union-related support flags
rg -n "UNION" packages/core/src/abstract-dialect/dialect.ts -B 2 -A 2
rg -n "'UNION" packages/ --type ts --type js | head -20Repository: sequelize/sequelize
Length of output: 9762
🏁 Script executed:
# Search for unionQuery method in all query generators (both base and dialect-specific)
rg -n "unionQuery" packages/ --type ts --type js -B 2 -A 2Repository: sequelize/sequelize
Length of output: 1303
🏁 Script executed:
# Check dialect.ts files to see what support flags exist for each dialect
fd "dialect.ts" packages/ --type f | xargs cat | grep -E "(supports\.|UNION|union)"Repository: sequelize/sequelize
Length of output: 213
🏁 Script executed:
# Search for calls to unionQuery to see if unionAll is used and where
rg -n "unionQuery" packages/ --type ts --type js | grep -v "\.d\.ts"
# Check if tests call union with unionAll option
rg -n "\.union\(" packages/core/test --type ts --type js -A 3 | head -40
# Check if there are any union tests that might test SQLite
rg -n "union.*" packages/*/test --type ts --type js -B 2 | grep -i "sqlite" -A 2 -B 2 | head -30Repository: sequelize/sequelize
Length of output: 2303
🏁 Script executed:
# Check what addLimitAndOffset does and if it's dialect-specific
rg -n "addLimitAndOffset" packages/ --type ts --type js -B 2 -A 5 | head -60Repository: sequelize/sequelize
Length of output: 4628
🏁 Script executed:
# Verify the exact issue: check line 1299 mentioned to see how it properly checks the support flag
sed -n '1295,1305p' packages/core/src/abstract-dialect/query-generator.js
# Also check if there's any guard elsewhere in the union flow
grep -n "unionQuery\|supports\['UNION ALL'\]" packages/core/src/abstract-dialect/query-interface.js -B 2 -A 2Repository: sequelize/sequelize
Length of output: 675
Add validation that the dialect supports UNION ALL before using it in the query.
The unionQuery method at line 1489 uses options.unionAll directly without checking if the dialect supports it. SQLite explicitly declares 'UNION ALL': false in its dialect configuration, yet the method would generate invalid SQL if options.unionAll is true on SQLite.
This contradicts the pattern already present at line 1299 of the same file, which correctly checks this.dialect.supports['UNION ALL'] before choosing the join character. Apply the same validation in unionQuery:
Suggested fix pattern
const joinChar = this.dialect.supports['UNION ALL'] && options.unionAll ? ' UNION ALL ' : ' UNION ';Alternatively, consider adding dialect-specific unionQuery overrides if UNION has other dialect-specific quirks (e.g., MSSQL's ORDER BY requirements with OFFSET ... FETCH).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/abstract-dialect/query-generator.js` around lines 1487 -
1522, The unionQuery method should not use options.unionAll unconditionally;
update the join character selection in unionQuery to check the dialect
capability by using this.dialect.supports['UNION ALL'] && options.unionAll to
decide between ' UNION ALL ' and ' UNION '; change the existing joinChar
assignment accordingly (referencing unionQuery, options.unionAll, and
this.dialect.supports['UNION ALL']) and ensure tests/usage that passed
options.unionAll on dialects without support still produce plain UNION (or add a
dialect-specific override for unionQuery where necessary, e.g., MSSQL).
| if (options.limit != null || options.offset != null) { | ||
| const limitOrder = this.#internals.addLimitAndOffset(options); | ||
| if (limitOrder) { | ||
| query += ` ${limitOrder}`; | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find addLimitAndOffset implementation and its return patterns
rg -n "addLimitAndOffset" packages/core/src/abstract-dialect/query-generator.js -B 2 -A 15 | head -100Repository: sequelize/sequelize
Length of output: 1362
🏁 Script executed:
#!/bin/bash
# Check the selectQuery method context around line 1442 to see how limitOrder is used
sed -n '1435,1460p' packages/core/src/abstract-dialect/query-generator.js | cat -nRepository: sequelize/sequelize
Length of output: 1108
🏁 Script executed:
#!/bin/bash
# Check the unionQuery method context around line 1517 to see the exact code
sed -n '1510,1525p' packages/core/src/abstract-dialect/query-generator.js | cat -nRepository: sequelize/sequelize
Length of output: 586
🏁 Script executed:
#!/bin/bash
# Search for dialect-specific query-generator implementations
find packages -name "query-generator.js" -o -name "query-generator.ts" | grep -v node_modulesRepository: sequelize/sequelize
Length of output: 467
🏁 Script executed:
#!/bin/bash
# Search for addLimitAndOffset method definition in the base query generator
rg -n "addLimitAndOffset\s*\(" packages/core/src/abstract-dialect/query-generator.js -B 1 -A 20 | head -80Repository: sequelize/sequelize
Length of output: 1613
🏁 Script executed:
#!/bin/bash
# Also check if addLimitAndOffset is in the `#internals` object or elsewhere
rg -n "addLimitAndOffset" packages/core/src/abstract-dialect/query-generator.js | head -20Repository: sequelize/sequelize
Length of output: 208
🏁 Script executed:
#!/bin/bash
# Search for `#internals` initialization or definition
rg -n "#internals\s*=" packages/core/src/abstract-dialect/query-generator.js | head -10Repository: sequelize/sequelize
Length of output: 98
🏁 Script executed:
#!/bin/bash
# Search for where addLimitAndOffset is defined - it might be in a separate file or class
rg -n "addLimitAndOffset" packages/core/src/ -B 2 -A 15 --type js --type ts | head -150Repository: sequelize/sequelize
Length of output: 7024
🏁 Script executed:
#!/bin/bash
# Find addLimitAndOffset implementation in a dialect (e.g., postgres)
rg -n "addLimitAndOffset" packages/postgres/src/ -B 2 -A 15Repository: sequelize/sequelize
Length of output: 1213
🏁 Script executed:
#!/bin/bash
# Check another dialect like MySQL
rg -n "addLimitAndOffset" packages/mysql/src/ -B 2 -A 15Repository: sequelize/sequelize
Length of output: 497
🏁 Script executed:
#!/bin/bash
# Check SQLite
rg -n "addLimitAndOffset" packages/sqlite3/src/ -B 2 -A 15Repository: sequelize/sequelize
Length of output: 3148
🏁 Script executed:
#!/bin/bash
# Check the formatMySqlStyleLimitOffset function
rg -n "formatMySqlStyleLimitOffset" packages/mysql/src/ -B 2 -A 20Repository: sequelize/sequelize
Length of output: 2394
🏁 Script executed:
#!/bin/bash
# Check other dialects like MSSQL
rg -n "addLimitAndOffset" packages/mssql/src/ -B 2 -A 15Repository: sequelize/sequelize
Length of output: 1478
🏁 Script executed:
#!/bin/bash
# Find formatMySqlStyleLimitOffset implementation
rg -n "formatMySqlStyleLimitOffset" packages/core/src/ -B 2 -A 20Repository: sequelize/sequelize
Length of output: 1471
Remove the extra space before the LIMIT/OFFSET clause.
The addLimitAndOffset method returns a string that already includes a leading space (e.g., LIMIT 10). Line 1517 prepends an additional space via the template literal, producing double spacing in the generated SQL.
🔧 Proposed fix
if (options.limit != null || options.offset != null) {
const limitOrder = this.#internals.addLimitAndOffset(options);
if (limitOrder) {
- query += ` ${limitOrder}`;
+ query += limitOrder;
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/abstract-dialect/query-generator.js` around lines 1514 -
1519, The code appends an extra leading space before the LIMIT/OFFSET clause
because addLimitAndOffset already returns a leading-space string; in the block
that computes limitOrder (the if checking options.limit/options.offset) replace
the template literal that prepends " " (query += ` ${limitOrder}`) with a direct
append (query += limitOrder) or trim the leading space from limitOrder so the
generated SQL has only a single space before the clause; refer to the
addLimitAndOffset call and the limitOrder variable when making the change.
| const tableNames = {}; | ||
| tableNames[model.table] = true; |
There was a problem hiding this comment.
Dead code: tableNames is populated but never read.
The tableNames object is created and assigned a key but is never used in the rest of the method. This appears to be leftover from copying the Model.findAll pattern.
🧹 Proposed fix: remove dead code
- const tableNames = {};
- tableNames[model.table] = true;
-
// Inject primary key if needed for deduplication📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const tableNames = {}; | |
| tableNames[model.table] = true; | |
| // Inject primary key if needed for deduplication |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/sequelize.js` around lines 272 - 273, Remove the dead
variable assignment: the tableNames object (declared as tableNames and assigned
tableNames[model.table] = true) is never read later; delete the tableNames
declaration and the assignment to tableNames[model.table] so only the actual
logic remains (this was likely copied from a Model.findAll pattern and is
unnecessary here), leaving all references to model and its properties intact.
|
This is some nice work. However, I have a couple refactoring suggestions that I think will really help with the usability of this feature. Added as-is, it is far too brittle for merging. There is a lot of low hanging fruit that can be implemented around typing that should resolve quite a bit of this. First, You need to just improve the typing on this quite a bit. By definition, the spec you set for yourself is very open ended, which is great for extensibility, but makes it extremely brittle and for users to know if what they built works or will even create a valid sql statement. I think a lot of this could be easily rectified with a few typing rules which would provide users with feedback on if they have valid filters before it gets to production. But here's a list of things I think would need to be addressed after review.
This is a good start, but there is some work that needs to be done. If you would like some help let me know. I intentionally avoided doing more for unions (it does identify and use em for things like hasMany Good luck and nice work so far! |
Pull Request Checklist
Description of Changes
Implemented
Sequelize#union(feat #18107) to combine results from multiple queries.This adds support for:
UNION(default) andUNION ALL(via{ unionAll: true }option).ORDER BYclause for the combined result.LIMITandOFFSETclauses.The implementation relies on a new
unionQuerymethod inAbstractQueryGeneratorwhich handles the SQL generation across dialects, properly quoting identifiers and handling dialect-specific syntax (e.g.LIMIT/OFFSEThandling).List of Breaking Changes
Summary by CodeRabbit
New Features
Tests