Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sequelize/sequelize
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v6.2.4
Choose a base ref
...
head repository: sequelize/sequelize
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v6.3.0
Choose a head ref
  • 2 commits
  • 5 files changed
  • 2 contributors

Commits on Jul 4, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4d82907 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5cabcbc View commit details
39 changes: 38 additions & 1 deletion docs/manual/other-topics/typescript.md
Original file line number Diff line number Diff line change
@@ -15,12 +15,13 @@ In order to avoid installation bloat for non TS users, you must install the foll

Example of a minimal TypeScript project with strict type-checking for attributes.

**NOTE:** Keep the following code in sync with `typescriptDocs/ModelInit.ts` to ensure it typechecks correctly.
**NOTE:** Keep the following code in sync with `/types/test/typescriptDocs/ModelInit.ts` to ensure it typechecks correctly.

```ts
import {
Sequelize,
Model,
ModelDefined,
DataTypes,
HasManyGetAssociationsMixin,
HasManyAddAssociationMixin,
@@ -104,6 +105,16 @@ class Address extends Model<AddressAttributes> implements AddressAttributes {
public readonly updatedAt!: Date;
}

// You can also define modules in a functional way
interface NoteAttributes {
id: number;
title: string;
content: string;
}

// You can also set multiple attributes optional at once
interface NoteCreationAttributes extends Optional<NoteAttributes, 'id' | 'title'> {};

Project.init(
{
id: {
@@ -164,6 +175,32 @@ Address.init(
}
);

// And with a functional approach defining a module looks like this
const Note: ModelDefined<
NoteAttributes,
NoteCreationAttributes
> = sequelize.define(
'Note',
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
title: {
type: new DataTypes.STRING(64),
defaultValue: 'Unnamed Note',
},
content: {
type: new DataTypes.STRING(4096),
allowNull: false,
},
},
{
tableName: 'notes',
}
);

// Here we associate which actually populates out pre-declared `association` static and other methods.
User.hasMany(Project, {
sourceKey: "id",
2 changes: 1 addition & 1 deletion lib/dialects/mssql/query-generator.js
Original file line number Diff line number Diff line change
@@ -465,7 +465,7 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
* Exclude NULL Composite PK/UK. Partial Composite clauses should also be excluded as it doesn't guarantee a single row
*/
for (const key in clause) {
if (!clause[key]) {
if (typeof clause[key] === 'undefined' || clause[key] == null) {
valid = false;
break;
}
52 changes: 52 additions & 0 deletions test/unit/dialects/mssql/query-generator.test.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@
const Support = require('../../support');
const expectsql = Support.expectsql;
const current = Support.sequelize;
const DataTypes = require('../../../../lib/data-types');
const Op = require('../../../../lib/operators');
const TableHints = require('../../../../lib/table-hints');
const QueryGenerator = require('../../../../lib/dialects/mssql/query-generator');

@@ -21,6 +23,56 @@ if (current.dialect.name === 'mssql') {
});
});

it('upsertQuery with falsey values', function() {
const testTable = this.sequelize.define(
'test_table',
{
Name: {
type: DataTypes.STRING,
primaryKey: true
},
Age: {
type: DataTypes.INTEGER
},
IsOnline: {
type: DataTypes.BOOLEAN,
primaryKey: true
}
},
{
freezeTableName: true,
timestamps: false
}
);

const insertValues = {
Name: 'Charlie',
Age: 24,
IsOnline: false
};

const updateValues = {
Age: 24
};

const whereValues = [
{
Name: 'Charlie',
IsOnline: false
}
];

const where = {
[Op.or]: whereValues
};

// the main purpose of this test is to validate this does not throw
expectsql(this.queryGenerator.upsertQuery('test_table', updateValues, insertValues, where, testTable), {
mssql:
"MERGE INTO [test_table] WITH(HOLDLOCK) AS [test_table_target] USING (VALUES(24)) AS [test_table_source]([Age]) ON [test_table_target].[Name] = [test_table_source].[Name] AND [test_table_target].[IsOnline] = [test_table_source].[IsOnline] WHEN MATCHED THEN UPDATE SET [test_table_target].[Name] = N'Charlie', [test_table_target].[Age] = 24, [test_table_target].[IsOnline] = 0 WHEN NOT MATCHED THEN INSERT ([Age]) VALUES(24) OUTPUT $action, INSERTED.*;"
});
});

it('createDatabaseQuery with collate', function() {
expectsql(this.queryGenerator.createDatabaseQuery('myDatabase', { collate: 'Latin1_General_CS_AS_KS_WS' }), {
mssql: "IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'myDatabase' ) BEGIN CREATE DATABASE [myDatabase] COLLATE N'Latin1_General_CS_AS_KS_WS'; END;"
2 changes: 2 additions & 0 deletions types/lib/model.d.ts
Original file line number Diff line number Diff line change
@@ -2849,6 +2849,8 @@ export type ModelType = typeof Model;
// must come first for unknown reasons.
export type ModelCtor<M extends Model> = typeof Model & { new(): M };

export type ModelDefined<S, T> = ModelCtor<Model<S, T>>;

export type ModelStatic<M extends Model> = { new(): M };

export default Model;
74 changes: 56 additions & 18 deletions types/test/typescriptDocs/ModelInit.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import {
Sequelize,
Model,
ModelDefined,
DataTypes,
HasManyGetAssociationsMixin,
HasManyAddAssociationMixin,
@@ -12,9 +13,9 @@ import {
HasManyCountAssociationsMixin,
HasManyCreateAssociationMixin,
Optional,
} from 'sequelize';
} from "sequelize";

const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb");

// These are all the attributes in the User model
interface UserAttributes {
@@ -24,7 +25,7 @@ interface UserAttributes {
}

// Some attributes are optional in `User.build` and `User.create` calls
interface UserCreationAttributes extends Optional<UserAttributes, 'id'> {}
interface UserCreationAttributes extends Optional<UserAttributes, "id"> {}

class User extends Model<UserAttributes, UserCreationAttributes>
implements UserAttributes {
@@ -60,7 +61,7 @@ interface ProjectAttributes {
name: string;
}

interface ProjectCreationAttributes extends Optional<ProjectAttributes, 'id'> {}
interface ProjectCreationAttributes extends Optional<ProjectAttributes, "id"> {}

class Project extends Model<ProjectAttributes, ProjectCreationAttributes>
implements ProjectAttributes {
@@ -87,6 +88,17 @@ class Address extends Model<AddressAttributes> implements AddressAttributes {
public readonly updatedAt!: Date;
}

// You can also define modules in a functional way
interface NoteAttributes {
id: number;
title: string;
content: string;
}

// You can also set multiple attributes optional at once
interface NoteCreationAttributes
extends Optional<NoteAttributes, "id" | "title"> {}

Project.init(
{
id: {
@@ -105,8 +117,8 @@ Project.init(
},
{
sequelize,
tableName: 'projects',
},
tableName: "projects",
}
);

User.init(
@@ -126,9 +138,9 @@ User.init(
},
},
{
tableName: 'users',
tableName: "users",
sequelize, // passing the `sequelize` instance is required
},
}
);

Address.init(
@@ -142,30 +154,56 @@ Address.init(
},
},
{
tableName: 'address',
tableName: "address",
sequelize, // passing the `sequelize` instance is required
}
);

// And with a functional approach defining a module looks like this
const Note: ModelDefined<
NoteAttributes,
NoteCreationAttributes
> = sequelize.define(
"Note",
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
title: {
type: new DataTypes.STRING(64),
defaultValue: "Unnamed Note",
},
content: {
type: new DataTypes.STRING(4096),
allowNull: false,
},
},
{
tableName: "notes",
}
);

// Here we associate which actually populates out pre-declared `association` static and other methods.
User.hasMany(Project, {
sourceKey: 'id',
foreignKey: 'ownerId',
as: 'projects', // this determines the name in `associations`!
sourceKey: "id",
foreignKey: "ownerId",
as: "projects", // this determines the name in `associations`!
});

Address.belongsTo(User, { targetKey: 'id' });
User.hasOne(Address, { sourceKey: 'id' });
Address.belongsTo(User, { targetKey: "id" });
User.hasOne(Address, { sourceKey: "id" });

async function doStuffWithUser() {
const newUser = await User.create({
name: 'Johnny',
preferredName: 'John',
name: "Johnny",
preferredName: "John",
});
console.log(newUser.id, newUser.name, newUser.preferredName);

const project = await newUser.createProject({
name: 'first!',
name: "first!",
});

const ourUser = await User.findByPk(1, {
@@ -176,4 +214,4 @@ async function doStuffWithUser() {
// Note the `!` null assertion since TS can't know if we included
// the model or not
console.log(ourUser.projects![0].name);
}
}