-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Support for foreign key constraints (#389) #595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ed at table level, not attribute level
|
:+1 @sdepold and @mickhansen this has my vote (looked through the code. The styling seems OK, the only concern is that we're running synchronously in a few places that were previously async, but this I actually support within the PR's context). @optilude nice job man, this is a huge win, I'd just like to get some other opinions in :) |
|
@durango, glad you could look at it so quickly :) The serialisation is a necessary evil (you can't create a table with a foreign key constraint to another table if the other table doesn't exist yet - see implementation note on the PR for more details), but it only happens during operations that are very unlikely to be on any sort of performance critical path: on If you like this one, you can thank me by merging #569 and then Sequelize will (hopefully) do everything I need for my project so I can go back to hacking on that one. ;) |
|
looks awesome. will take a closer look at it this evening. thanks for your work. best PR description ever :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo "unless"
|
rocksolid work. @mickhansen @janmeier any thoughts on this ? +1 from me |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This testcase seems to be duplicated thrice or did i miss something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
D'uh. It was a test copied from the MySQL implementation and then changed to remove MySQL-specific setting of the table engine, which of course left the three all the same. Corrected.
|
Truly solid work! My comments are only very, very minor I don't know what the others think, but given how well-written the description for this PR is, I would have no problem at all letting you write the docs as well! :) |
|
@janmeier I'm happy to write some docs, but I don't understand how they're maintained/how to generate a test site. I need some meta-docs. ;) |
|
@optilude https://github.com/sequelize/sequelize-doc - its a mix of erb and markdown. |
Support for foreign key constraints (#389)
|
I would love to have some documentation about how exactly to use this extra "feature". Because I believe, but I don't know for sure that I can use this. I have sequelize 2.0.0-alpha2 installed, but I really don't know how to use this. Any help or further documentation would be great! I have the next code: var User = sequelize.define("User", { /* */ });
var Group = sequelize.define("Group", { /* */ });
var Apikey = sequelize.define("Apikey", { /* */ });
Group.hasMany(User);
User.hasMany(Apikey);Somehow when trying to
This turns into the next error:
Sounds logical, because the The order of dropping should be:
Because the reference from the Also somehow Many-to-Many relations doesn't create Foreign Keys in the database. Al One-to-Many are executing perfectly! |
|
true. will add it later the day Sascha Depold Am Donnerstag, 13. Juni 2013 um 23:04 schrieb leroybaeyens:
|
|
It would be nice if we could do something like { onDelete: 'restrict', allowNull: false } to make the foreign key field non-nullable |
|
It seems like there is a fair amount of work going on to try to add the foreign key constraints at the same time that the tables are created. This means we need to try to determine what order to add the tables in, and then insert them synchronously in the right order. Wouldn't it be much easier to just create all the tables/fields, then alter them to add the constraints? This also would fix a problem I'm having, which is that you cannot create unary relationship constraints the way we're currently doing it (even though it's perfectly valid SQL). For example: User.hasMany(User, {as: 'Children', foreignKey: 'parentId', useJunctionTable: false, onDelete: 'cascade'})Error: Cyclic dependency found. 'user' is dependent of itself. |
This implements support for creating
FOREIGN KEY ... REFERENCESdeclarations withON DELETEandON UPDATEtriggers when defining associations, for all supported databases.The basic syntax is:
The same works for
hasOne()andbelongsTo(). Valid options are:onDelete-- set to e.g.restrictorcascadeonUpdate-- set to e.g.restrictorcascadeforeignKeyConstraint-- set totrueto gain aREFERENCESdeclaration without eitheronDeleteoronUpdate(that is,foreignKeyConstraintis implied ifonDeleteoronUpdateare set). This may be a helpful performance optimisation in some cases.Some key implementation notes:
PRAGMA FOREIGN_KEYS=ONis issued. We do so when opening the database connection, unless explicitly disabled with a global option, to get parity across implementations. Note that just enabling this doesn't make any difference unless there are actual constraints in place.someId INTEGER REFERENCES someTable(id) ON DELETE CASCADE) or "standalone" (FOREIGN KEY(someId) REFERENCES someTable(id) ON DELETE CASCADEplaced as a separate clause inside aCREATE TABLEstatement). Since associations in sequelize create foreign key attributes the "inline" syntax is used, andattributesToSQLis taught how to add the relevant suffix to any "raw" attribute with the relevant metadata attached. This works for Postgres and SQLite, but MySQL ignores this syntax, requiring the "standalone" approach. For MySQL, we move the declaration to the end with a bit of string manipulation. This is analogous to howPRIMARY KEYis handled and allows this to be done without major refactoring.foohas a foreign key tobarwith a constraint, thenbarhas to exist beforefoocan be created. To make sure this happens, we use a topological sort of relationships (via theToposort-classmodule, added as a dependency) to sequence calls toCREATE TABLEinsync(). This also necessitatessync()being serialised, but given it's an "on startup" operation that shouldn't be too much of an issue.dropAllTables(), but here we don't have enough information to sort the list. Instead, we do one of two things: for SQLite and MySQL, we temporarily disable constraint checking. For Postgres, we useDROP TABLE ... CASCADEto drop relevant constraints when required. (MySQL and SQLite only support the former and Postgres only supports the latter). This is blunt, but OK given that the function is attempting to drop all the tables.dropTable()the caller is expected to sequence calls appropriately, or wrap the call indisableForeignKeyConstraints()andenableForeignKeyConstraints()(MySQL and SQLite; no-ops in Postgres) and/or pass{cascade: true}inoptions(Postgres; no-op in MySQL and SQLite).QueryGenerators and of cascade and restrict behaviour at the integration test level.