From: Nikita Pettik <korablev@tarantool.org> To: tarantool-patches@freelists.org Cc: kostja@tarantool.org, Nikita Pettik <korablev@tarantool.org> Subject: [tarantool-patches] [PATCH] sql: rfc for foreign keys Date: Wed, 27 Jun 2018 16:11:33 +0300 [thread overview] Message-ID: <20180627131133.82680-1-korablev@tarantool.org> (raw) Part of #3271 --- Branch: https://github.com/tarantool/tarantool/tree/np/gh-3271-foreign-keys-rfc Issue: https://github.com/tarantool/tarantool/issues/3271 doc/rfc/3271-foreign-keys.md | 187 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 doc/rfc/3271-foreign-keys.md diff --git a/doc/rfc/3271-foreign-keys.md b/doc/rfc/3271-foreign-keys.md new file mode 100644 index 000000000..6af162263 --- /dev/null +++ b/doc/rfc/3271-foreign-keys.md @@ -0,0 +1,187 @@ +# Foreign Keys + +* **Status**: In progress +* **Start date**: 27-06-2018 +* **Authors**: Nikita Pettik @korablev77 korablev@tarantool.org +* **Issues**: [#3271](https://github.com/tarantool/tarantool/issues/3271) + +## Summary + +Introduce Foreign Keys (FK) constraints in server. + +## Background and motivation + +Used terminology: + +CREATE TABLE t1 (id1 INT PRIMARY KEY, a INT); +CREATE TABLE t2 (id2 INT PRIMARY KEY REFERENECES t1(id1)); + +t1 - parent or referenced table; id1 - referenced columns; +t2 - child or referencing table; id2 - referencing columns; + +Originally, SQLite provides quite specific way of handling FK constraints: +any checks are deferred as much as possible. For instance, existence of +parent table's existence is verified each time on data manipulation in +child space, but not checked on creation of FK constraint itself. +The same is for UNIQUE index: according to ANSI referenced columns must +make up UNIQUE constraint; otherwise error is raised. But in SQLite +presence of such index is tested only when child table is involved in DML. +On the other hand it allows us to get rid of checks on indexes/spaces +destroying; moreover, it allows us to avoid storing additional dependencies +in internal data dictionary holding only space name and extracting fresh +pointers to DD by lookup in hash. + +To be closer to ANSI FK constraints, it was suggested to rework and +move them to server DD. + +## Implementation details + +1. Prohibit actions with FK constraints which strictly contradict ANSI: + - Ban opportunity to create FK constraint on table which doesn't exist + (except for self-references). Since SQLite allows creating + FK constraints only within <CREATE TABLE> declaration (i.e. there is + no separate <ALTER TABLE ADD CONSTRAINT> statement), this step will + deprive us of ability to create circular FK dependencies. + - Ban opportunity to drop parent table even if it doesn't violate FK + constraints. Child table must always be dropped before parent. + - Ban opportunity to create FK constraints on VIEW. + +2. To return ability to create circular FK dependecies, we are going to + introduce <ALTER TABLE ADD CONSTRAINT> statement. It compels us to + store somehow created constraints. Hence, new system space to + persist FK constraints is added: + + - [350, 1, '_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'}, { + 'name': 'parent_id', 'type': 'unsigned'}, {'name': 'child_id', 'type': 'unsigned'}, + {'name': 'deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'}, + {'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'}, + {'name': 'links', 'type': 'map'}]] + +In other words, FK constraint is completely described by: + - Its name; + - Referenced space; + - Child space; + - Time of resolution (deferred until the end of transaction or not); + - Match clause; + - ON DELETE and ON UPDATE actions; + - Column links; + +3. Insertion on _constraint space leads to creation of FK constraints. + FK constraints in DD are represented as following structs: + ++struct fkey_def { ++ /** id of child space. */ ++ uint32_t child_id; ++ /** id of parent space. */ ++ uint32_t parent_id; ++ /** Number of fields (links) in this key. */ ++ uint32_t field_count; ++ /** True if constraint checking is deferred till COMMIT. */ ++ bool is_deferred; ++ /** Match condition for foreign key. SIMPLE by default. */ ++ enum fkey_match match; ++ /** ON DELETE action. */ ++ enum fkey_action on_delete; ++ /** ON UPDATE action. */ ++ enum fkey_action on_update; ++ /** Mapping of fields in child to fields in parent. */ ++ struct field_link *links; ++ /** Name of the constraint. */ ++ char name[0]; ++}; + ++struct fkey { ++ /** Space containing the REFERENCES clause (aka: child). */ ++ struct space *child; ++ /** Space that the key points to (aka: parent). */ ++ struct space *parent; ++ /** Number of fields (links) in this key. */ ++ uint32_t field_count; ++ /** True if constraint checking is deferred till COMMIT. */ ++ bool is_deferred; ++ /** Match condition for foreign key. SIMPLE by default. */ ++ enum fkey_match match; ++ /** Triggers for actions. */ ++ struct Trigger *on_delete_trigger; ++ struct Trigger *on_update_trigger; ++ /** Contraints are orginized into list. */ ++ struct fkey *fkey_next; ++ /** Mapping of fields in child to fields in parent. */ ++ struct field_link *links; ++ /** Name of the constraint. */ ++ char name[0]; ++}; + +struct fkey_def is used only during creation of struct fkey. +Foreign keys will be stored in struct space as two linked lists: + +@@ -182,7 +182,8 @@ struct space { + */ + struct index **index; + /** Foreign key constraints. */ ++ struct fkey *parent_fkey; ++ struct fkey *child_fkey; + +on_replace_dd_constraint() would create definition of FK constrain +from tuple; from def construct struct fkey, and finally add it +to parent's list of FK constraints and to child's one. +In case of WAL fail, simply remove it from those lists. +On drop of FK actions are inverted: FK constraint is removed from +parent and child lists and if WAL fails, it will be returned back. + +4. Alongside with insertion to _constraint, we must check that tuples + which are already in child table don't violate FK constraint under + construction. There several approaches to resolve this problem. + The first and common one is to handle this routine within + on_replace_dd_constraint trigger: create iterators for parent and + child spaces and for each tuple from child space find appropriate + tuple with given FK key in parent space. The question here is how + to process inserted or deleted tuples until FK constraint is committed? + Another solution is to temporary execute this check only if creation + of FK consraint is occurred via SQL: before insertion to _constraint + run VDBE program which will test this condition. Anyway, now it is + impossible to resolve FK constraints (in common sense) without + involving VDBE: we can't run ON DELETE and ON UPDATE triggers directly + from server. The last and the easiest way is to allow creation of + FK constraint only for empty spaces and remove this restriction when + we will be able to run any VDBE code from server. + +5. Introduce SQL statement to create and drop FK constraints (ANSI syntax): + +ALTER TABLE <referencing table> ADD CONSTRAINT + <referential constraint name> FOREIGN KEY + <left parent> <referencing columns> <right paren> REFERENCES + <referenced table> [ <referenced columns> ] [ MATCH <match type> ] + [ <referential triggered action> ] [ <constraint check time> ] + +ALTER TABLE <referencing table> DROP CONSTRAINT <referential constrain name> + +In terms of our SQL parser: + +cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY + LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R) + defer_subclause_opt(D). + +cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). + +Example: + +ALTER TABLE t1 ADD CONSTRAINT f1 FOREIGN KEY(id, a) REFERENCES t2 (id, b) MATCH FULL; +ALTER TABLE t1 DROP CONSTRAINT f1; + +These statements are going to emit VDBE code to construct appropriate +tuple and insert it into _constraint space (and maybe run VDBE program +to make sure that all tuples in child space satisfy new FK constraint). +The rest of routine will be handled by on_replace_dd_constraint() trigger. + +6. Refactor SQL routine to operate on new struct fkey instead of + obsolete SQLite struct FKey. + +## Open questions + +Should we resolve fields and indexes right after insertion to +_constraint (as it happens in other DBs) or defer it until usage of FK +(as it occurs in SQLite)? If we chose first way, we would have to +implicitly link index to particular index and ban ability to drop such +index until all FK constraints are dropped. Such behaviour may be not +so obvious for users, but it is used for instance in PostgreSQL and MySQL. -- 2.15.1
next reply other threads:[~2018-06-27 13:11 UTC|newest] Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top 2018-06-27 13:11 Nikita Pettik [this message] 2018-06-27 13:13 ` [tarantool-patches] " Vladislav Shpilevoy 2018-06-27 13:38 ` n.pettik 2018-06-28 8:31 ` Konstantin Osipov 2018-07-13 8:46 ` Konstantin Osipov 2018-07-13 13:53 ` n.pettik
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20180627131133.82680-1-korablev@tarantool.org \ --to=korablev@tarantool.org \ --cc=kostja@tarantool.org \ --cc=tarantool-patches@freelists.org \ --subject='Re: [tarantool-patches] [PATCH] sql: rfc for foreign keys' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox