Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c4434cf
add composite key findByPk support via object
wd-alejandroescobedogarcia Jun 17, 2024
99cb2bb
prettier changes
wd-alejandroescobedogarcia Jun 18, 2024
f5d4c6f
code review changes
wd-alejandroescobedogarcia Jun 18, 2024
7ae58a7
Merge branch 'main' into findbypk-composite-support
wd-alejandroescobedogarcia Jun 20, 2024
8e62a27
prettier changes
wd-alejandroescobedogarcia Jun 20, 2024
c7c88b7
Merge branch 'main' into findbypk-composite-support
wd-alejandroescobedogarcia Jun 21, 2024
613e4cb
Merge branch 'main' into findbypk-composite-support
wd-alejandroescobedogarcia Jun 24, 2024
b561756
check if model has pk first
wd-alejandroescobedogarcia Jul 16, 2024
0717a42
change misused property
wd-alejandroescobedogarcia Jul 17, 2024
05a9b9d
consolidate changes from other branches
wd-alejandroescobedogarcia Dec 9, 2024
d79aada
update types
wd-alejandroescobedogarcia Dec 9, 2024
9767140
add constraintName property
wd-alejandroescobedogarcia Dec 9, 2024
ba11a1d
cleanup models file
wd-alejandroescobedogarcia Dec 9, 2024
5f9268d
add missing tests
wd-alejandroescobedogarcia Dec 9, 2024
4db5dc2
use ? to check if it is safe to push to array in hasmany
wd-alejandroescobedogarcia Dec 10, 2024
da7491d
Fix issue with includes and hasmany
wd-alejandroescobedogarcia Jan 24, 2025
917501d
Merge commit '0717a42ad' into sequelize-core-papandreou9
papandreou Jan 30, 2025
931689d
Merge branch 'feature/composite-primary-key-support-and-associations-…
papandreou Jan 30, 2025
ca5a36a
Replace workspace:* urls
papandreou Dec 9, 2024
6f5577e
Change the package names to sequelize-{postgres,core}-papandreou
papandreou Dec 6, 2024
7cc3cf1
Releases sequelize-{core,postgres}[email protected]
papandreou Jan 30, 2025
cc9941d
Merge branch 'main' into sequelize-core-papandreou9
papandreou Jan 31, 2025
97d4470
Releases sequelize-{core,postgres}[email protected]
papandreou Jan 31, 2025
b8f3a3d
Add bind parameter support to Model#bulkCreate
papandreou Mar 5, 2025
a476d47
Releases sequelize-{core,postgres}[email protected]
papandreou Mar 8, 2025
77cb57d
Revert "Add bind parameter support to Model#bulkCreate"
papandreou Mar 12, 2025
8be524a
add test
gtamasi Apr 9, 2025
28d6770
undone
gtamasi Apr 9, 2025
784e23c
update lock
gtamasi Apr 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sequelize/core",
"description": "Sequelize is a promise-based Node.js ORM tool for Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift, Snowflake’s Data Cloud, Db2, and IBM i. It features solid transaction support, relations, eager and lazy loading, read replication and more.",
"version": "7.0.0-alpha.44",
"version": "7.0.0-alpha.41",
"funding": [
{
"type": "opencollective",
Expand Down Expand Up @@ -64,34 +64,35 @@
"sequelize-pool": "^8.0.0",
"toposort-class": "^1.0.1",
"type-fest": "^4.14.0",
"uuid": "^11.0.0",
"uuid": "^10.0.0",
"validator": "^13.11.0"
},
"devDependencies": {
"@types/chai": "4.3.20",
"@types/chai": "4.3.16",
"@types/chai-as-promised": "7.1.8",
"@types/chai-datetime": "1.0.0",
"@types/lodash": "4.17.15",
"@types/mocha": "10.0.10",
"@types/chai-datetime": "0.0.39",
"@types/lodash": "4.17.5",
"@types/mocha": "10.0.7",
"@types/semver": "7.5.8",
"@types/sinon": "17.0.3",
"@types/sinon-chai": "3.2.12",
"chai": "4.5.0",
"@types/uuid": "9.0.8",
"chai": "4.4.1",
"chai-as-promised": "7.1.2",
"chai-datetime": "1.8.1",
"chai-datetime": "1.8.0",
"delay": "5.0.0",
"expect-type": "0.13.0",
"fs-jetpack": "5.1.0",
"lcov-result-merger": "5.0.1",
"mocha": "11.1.0",
"mocha": "10.4.0",
"moment": "2.30.1",
"nyc": "17.1.0",
"nyc": "17.0.0",
"p-map": "4.0.0",
"p-props": "4.0.0",
"p-settle": "4.1.1",
"p-timeout": "4.1.0",
"rimraf": "5.0.10",
"sinon": "18.0.1",
"rimraf": "5.0.7",
"sinon": "18.0.0",
"sinon-chai": "3.7.0"
},
"keywords": [
Expand All @@ -115,7 +116,8 @@
"db"
],
"publishConfig": {
"access": "public"
"access": "public",
"tag": "alpha"
},
"scripts": {
"----------------------------------------- static analysis -----------------------------------------": "",
Expand Down
230 changes: 152 additions & 78 deletions packages/core/src/abstract-dialect/query-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import isObject from 'lodash/isObject';
import isPlainObject from 'lodash/isPlainObject';
import omit from 'lodash/omit';
import pick from 'lodash/pick';
import reduce from 'lodash/reduce';
import uniq from 'lodash/uniq';
Expand Down Expand Up @@ -1127,6 +1128,8 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
if (typeof options.groupedLimit.on === 'string') {
whereKey = options.groupedLimit.on;
} else if (options.groupedLimit.on instanceof HasManyAssociation) {
// TODO: revisit this whether we still need this or not

whereKey = options.groupedLimit.on.identifierField;
}

Expand Down Expand Up @@ -1207,34 +1210,67 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
).replace(/;$/, '')}) AS sub`; // Every derived table must have its own alias
const splicePos = baseQuery.indexOf(placeholder);

let tablesStr;
if (options.groupedLimit.values && Array.isArray(options.groupedLimit.values)) {
tablesStr = `(${options.groupedLimit.values
.map(value => {
let groupWhere;
if (whereKey) {
groupWhere = {
[whereKey]: value,
};
}

if (include) {
groupWhere = {
[options.groupedLimit.on.foreignIdentifierField]: value,
};
}

return spliceStr(
baseQuery,
splicePos,
placeholder.length,
this.whereItemsQuery(groupWhere, { ...options, mainAlias: groupedTableName }),
);
})
.join(this.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ')})`;
} else {
const valueKeys = Object.keys(omit(options.groupedLimit, ['on', 'should', 'limit']));

// TODO: check if all the arrays have the same length
const firstLength = options.groupedLimit[valueKeys[0]].length;
const whereSplices = [];
for (let i = 0; i < firstLength; i++) {
const groupWhere = {};
for (const key of valueKeys) {
groupWhere[key] = options.groupedLimit[key][i];

whereSplices.push(
spliceStr(
baseQuery,
splicePos,
placeholder.length,
this.whereItemsQuery(
{ [Op.and]: groupWhere },
{ ...options, mainAlias: groupedTableName },
),
),
);
}

tablesStr = `(${whereSplices.join(
this.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ',
)})`;
}
}

mainQueryItems.push(
this.selectFromTableFragment(
options,
mainTable.model,
attributes.main,
`(${options.groupedLimit.values
.map(value => {
let groupWhere;
if (whereKey) {
groupWhere = {
[whereKey]: value,
};
}

if (include) {
groupWhere = {
[options.groupedLimit.on.foreignIdentifierField]: value,
};
}

return spliceStr(
baseQuery,
splicePos,
placeholder.length,
this.whereItemsQuery(groupWhere, { ...options, mainAlias: groupedTableName }),
);
})
.join(this.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ')})`,
tablesStr,
mainTable.quotedAs,
),
);
Expand Down Expand Up @@ -1717,24 +1753,39 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
/* Attributes for the left side */
const left = association.source;
const leftAttributes = left.modelDefinition.attributes;
const conditions = [];

const attrNameLeft =
association instanceof BelongsToAssociation
? association.foreignKey
: association.sourceKeyAttribute;
const columnNameLeft =
association instanceof BelongsToAssociation
? association.identifierField
: leftAttributes.get(association.sourceKeyAttribute).columnName;
let asLeft;
/* Attributes for the right side */
const right = include.model;
const rightAttributes = right.modelDefinition.attributes;
const tableRight = right.table;
const fieldRight =
association instanceof BelongsToAssociation
? rightAttributes.get(association.targetKey).columnName
: association.identifierField;

if (include.association.foreignKeys?.length > 0) {
for (const fKey of include.association.foreignKeys) {
const attrNameLeft =
association instanceof BelongsToAssociation ? fKey.targetKey : fKey.sourceKey;
const columnNameLeft = leftAttributes.get(fKey.sourceKey).columnName;
const fieldRight = rightAttributes.get(fKey.targetKey).columnName;
conditions.push({ attrNameLeft, columnNameLeft, fieldRight });
}
} else {
const attrNameLeft =
association instanceof BelongsToAssociation
? association.foreignKey
: association.sourceKeyAttribute;
const columnNameLeft =
association instanceof BelongsToAssociation
? association.identifierField
: leftAttributes.get(association.sourceKeyAttribute).columnName;

const fieldRight =
association instanceof BelongsToAssociation
? rightAttributes.get(association.targetKey).columnName
: association.identifierField;
conditions.push({ attrNameLeft, columnNameLeft, fieldRight });
}

let asRight = include.as;

while (($parent = ($parent && $parent.parent) || include.parent) && $parent.association) {
Expand All @@ -1751,43 +1802,51 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
asRight = `${asLeft}->${asRight}`;
}

let joinOn = '';

// TODO: use whereItemsQuery to generate the entire "ON" condition.
let joinOn = `${this.quoteTable(asLeft)}.${this.quoteIdentifier(columnNameLeft)}`;
const subqueryAttributes = [];
for (const condition of conditions) {
if (joinOn?.length > 0) {
joinOn += ' AND ';
}

if (
(topLevelInfo.options.groupedLimit && parentIsTop) ||
(topLevelInfo.subQuery && include.parent.subQuery && !include.subQuery)
) {
if (parentIsTop) {
// The main model attributes is not aliased to a prefix
const tableName = parent.as || parent.model.name;
const quotedTableName = this.quoteTable(tableName);

// Check for potential aliased JOIN condition
joinOn =
this._getAliasForField(tableName, attrNameLeft, topLevelInfo.options) ||
`${quotedTableName}.${this.quoteIdentifier(attrNameLeft)}`;

if (topLevelInfo.subQuery) {
const dbIdentifier = `${quotedTableName}.${this.quoteIdentifier(columnNameLeft)}`;
subqueryAttributes.push(
dbIdentifier !== joinOn
? `${dbIdentifier} AS ${this.quoteIdentifier(attrNameLeft)}`
: dbIdentifier,
);
}
} else {
const joinSource = `${asLeft.replaceAll('->', '.')}.${attrNameLeft}`;
joinOn += `${this.quoteTable(asLeft)}.${this.quoteIdentifier(condition.columnNameLeft)}`;

if (
(topLevelInfo.options.groupedLimit && parentIsTop) ||
(topLevelInfo.subQuery && include.parent.subQuery && !include.subQuery)
) {
if (parentIsTop) {
// The main model attributes is not aliased to a prefix
const tableName = parent.as || parent.model.name;
const quotedTableName = this.quoteTable(tableName);

// Check for potential aliased JOIN condition
joinOn =
this._getAliasForField(tableName, condition.attrNameLeft, topLevelInfo.options) ||
`${quotedTableName}.${this.quoteIdentifier(condition.attrNameLeft)}`;

if (topLevelInfo.subQuery) {
const dbIdentifier = `${quotedTableName}.${this.quoteIdentifier(condition.columnNameLeft)}`;
subqueryAttributes.push(
dbIdentifier !== joinOn
? `${dbIdentifier} AS ${this.quoteIdentifier(condition.attrNameLeft)}`
: dbIdentifier,
);
}
} else {
const joinSource = `${asLeft.replaceAll('->', '.')}.${condition.attrNameLeft}`;

// Check for potential aliased JOIN condition
joinOn =
this._getAliasForField(asLeft, joinSource, topLevelInfo.options) ||
this.quoteIdentifier(joinSource);
// Check for potential aliased JOIN condition
joinOn =
this._getAliasForField(asLeft, joinSource, topLevelInfo.options) ||
this.quoteIdentifier(joinSource);
}
}
}

joinOn += ` = ${this.quoteIdentifier(asRight)}.${this.quoteIdentifier(fieldRight)}`;
joinOn += ` = ${this.quoteIdentifier(asRight)}.${this.quoteIdentifier(condition.fieldRight)}`;
}

if (include.on) {
joinOn = this.whereItemsQuery(include.on, {
Expand Down Expand Up @@ -2119,22 +2178,37 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
);
} else {
const isBelongsTo = topAssociation.associationType === 'BelongsTo';
const sourceField = isBelongsTo
? topAssociation.identifierField
: topAssociation.sourceKeyField || topParent.model.primaryKeyField;
const targetField = isBelongsTo
? topAssociation.sourceKeyField || topInclude.model.primaryKeyField
: topAssociation.identifierField;

const join = [
`${this.quoteIdentifier(topInclude.as)}.${this.quoteIdentifier(targetField)}`,
`${this.quoteTable(topParent.as || topParent.model.name)}.${this.quoteIdentifier(sourceField)}`,
].join(' = ');
const hasCompositeReference = topAssociation.foreignKeys?.length > 1;
const sourceFields = isBelongsTo
? [topAssociation.identifierField]
: hasCompositeReference
? topAssociation.foreignKeys.map(fk => fk.sourceKey)
: [topAssociation.sourceKeyField || topParent.model.primaryKeyField];

const targetFields = isBelongsTo
? [topAssociation.sourceKeyField || topInclude.model.primaryKeyField]
: hasCompositeReference
? topAssociation.foreignKeys.map(fk => fk.targetKey)
: [topAssociation.identifierField];

const join = hasCompositeReference
? topAssociation.foreignKeys
.map(fk => {
return [
`${this.quoteIdentifier(topInclude.as)}.${this.quoteIdentifier(fk.targetKey)}`,
`${this.quoteTable(topParent.as || topParent.model.name)}.${this.quoteIdentifier(fk.sourceKey)}`,
].join(' = ');
})
.join(' AND ')
: [
`${this.quoteIdentifier(topInclude.as)}.${this.quoteIdentifier(targetFields[0])}`,
`${this.quoteTable(topParent.as || topParent.model.name)}.${this.quoteIdentifier(sourceFields[0])}`,
].join(' = ');

query = this.selectQuery(
topInclude.model.table,
{
attributes: [targetField],
attributes: [targetFields[0]],
include: _validateIncludedElements(topInclude).include,
model: topInclude.model,
where: {
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/abstract-dialect/query-interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@ export interface IndexField {
/**
* The direction the column should be sorted in
*/
order?: 'ASC' | 'DESC';
order?:
| 'ASC'
| 'DESC'
| 'ASC NULLS FIRST'
| 'ASC NULLS LAST'
| 'DESC NULLS FIRST'
| 'DESC NULLS LAST';

/**
* The collation (sort order) for the column
Expand Down
Loading
Loading