From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id 943C020331 for ; Sat, 5 May 2018 15:14:39 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id UHbraYEtqw91 for ; Sat, 5 May 2018 15:14:39 -0400 (EDT) Received: from smtp34.i.mail.ru (smtp34.i.mail.ru [94.100.177.94]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id F295C1F922 for ; Sat, 5 May 2018 15:14:38 -0400 (EDT) From: "n.pettik" Message-Id: <9A9EB2C7-2700-451F-9AB9-E1EC34B98363@tarantool.org> Content-Type: multipart/mixed; boundary="Apple-Mail=_9448364C-9C4F-4B27-82F9-DE4DE10849DB" Mime-Version: 1.0 (Mac OS X Mail 10.3 \(3273\)) Subject: [tarantool-patches] Re: [PATCH 2/4] sql: allow transitive Lua <-> SQL transactions Date: Sat, 5 May 2018 22:14:36 +0300 In-Reply-To: References: <503f7e68588cfa6933aab8b068509b5675da6a9c.1525368399.git.korablev@tarantool.org> Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-help: List-unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-subscribe: List-owner: List-post: List-archive: To: tarantool-patches@freelists.org Cc: Vladislav Shpilevoy --Apple-Mail=_9448364C-9C4F-4B27-82F9-DE4DE10849DB Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Except for your comments, I also have changed behaviour of commit statement with existing deferred FK violations. (I started to read ANSI SQL and some things are different from SQLite = ones) According to ANSI SQL failed commit must always rollback: 17.7 (2006 standard, part 2: Foundation) If any constraint is not satisfied, then any changes to SQL-data or = schemas that were made by the current SQL-transaction are canceled and an exception = condition is raised: transaction rollback =E2=80=94 integrity constraint violation.=20 4.17.2 Checking of constraints (2006 standard, part 2: Foundation) When a is executed, all constraints are effectively = checked and, if any constraint is not satisfied, then an exception condition is = raised and the SQL-transaction is terminated by an implicit . @@ -290,15 +290,15 @@ txn_commit(struct txn *txn) =20 assert(stailq_empty(&txn->stmts) || txn->engine); /* - * If transaction has been started in SQL, foreign key - * constraints must not be violated. If not so, don't - * rollback transaction, just prevent from commiting. + * If transaction has been started in SQL, deferred + * foreign key constraints must not be violated. + * If not so, just rollback transaction. */ if (txn->psql_txn !=3D NULL) { struct sql_txn *sql_txn =3D txn->psql_txn; if (sql_txn->fk_deferred_count !=3D 0) { diag_set(ClientError, = ER_FOREIGN_KEY_CONSTRAINT); - return -1; + goto fail; } >> foreign keys contrains as attributes of transaction, not particular = VDBE. >=20 > 1. contrains -> constraints. Fixed. >> transaction becomes open untill all deferred FK violations are = resolved >=20 > 2. untill -> until. Fixed. >> -/* >> - * This function is called to generate code that runs when table = pTab is >> - * being dropped from the database. The SrcList passed as the second = argument >> - * to this function contains a single entry guaranteed to resolve to >> - * table pTab. >> - * >> - * Normally, no code is required. However, if either >> - * >> - * (a) The table is the parent table of a FK constraint, or >> - * (b) The table is the child table of a deferred FK constraint = and it is >> - * determined at runtime that there are outstanding deferred = FK >> - * constraint violations in the database, >> - * >> - * then the equivalent of "DELETE FROM " is executed in a = single transaction >> - * before dropping the table from the database. If any FK violations = occur, >> - * rollback transaction and halt VDBE. Triggers are disabled while = running this >> - * DELETE, but foreign key actions are not. >> +/** >> + * This function is called to generate code that runs when table >> + * pTab is being dropped from the database. The SrcList passed as >> + * the second argument to this function contains a single entry >> + * guaranteed to resolve to table pTab. >> + * >> + * Normally, no code is required. However, if the table is >> + * parent table of a FK constraint, then the equivalent >> + * of "DELETE FROM " is executed in a single transaction >> + * before dropping the table from the database. If any FK >> + * violations occur, rollback transaction and halt VDBE. Triggers >> + * are disabled while running this DELETE, but foreign key >> + * actions are not. >=20 > 3. Why did you delete this comment > "The table is the child table of a deferred FK constraint" and its = code? Because deferred constraints make sense only within active transaction. On the other hand, Tarantool doesn=E2=80=99t support DDL inside = transaction. Thus, anyway it would lead to execution error, so I just removed excess = check. >> + p->auto_commit =3D txn =3D=3D NULL ? true : false; >=20 > 4. txn =3D=3D NULL is enough. The result is boolean already. - p->auto_commit =3D txn =3D=3D NULL ? true : false; + p->auto_commit =3D txn =3D=3D NULL; >=20 > 5. sqlite3VdbeCreate is called during parsing. When prepared = statements > will be introduced, auto_commit and psql_txn can become garbage on the > second execution. Can you please move these transaction initialization > things into a function, that prepares a compiled Vdbe for further > execution? allocVdbe(Parse * pParse) { Vdbe *v =3D pParse->pVdbe =3D sqlite3VdbeCreate(pParse); - if (v) - sqlite3VdbeAddOp2(v, OP_Init, 0, 1); + if (v =3D=3D NULL) + return NULL; + sqlite3VdbeAddOp2(v, OP_Init, 0, 1); if (pParse->pToplevel =3D=3D 0 && OptimizationEnabled(pParse->db, SQLITE_FactorOutConst) ) { pParse->okConstFactor =3D 1; } + if (sql_vdbe_prepare(v) !=3D 0) { + sqlite3DbFree(pParse->db, v); + return NULL; + } return v; } =20 --- a/src/box/sql/vdbe.h +++ b/src/box/sql/vdbe.h + +/** + * Prepare given VDBE to execution: initialize structs connected + * with transaction routine: autocommit mode, deferred foreign + * keys counter, struct representing SQL savepoint. + * If execution context is already withing active transaction, + * just transfer transaction data to VDBE. + * + * @param vdbe VDBE to be prepared. + * @retval -1 on out of memory, 0 otherwise. + */ +int +sql_vdbe_prepare(struct Vdbe *vdbe); + int sqlite3VdbeAddOp0(Vdbe *, int); int sqlite3VdbeAddOp1(Vdbe *, int, int); int sqlite3VdbeAddOp2(Vdbe *, int, int, int); diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index 219b107eb..19953285a 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -66,8 +66,34 @@ sqlite3VdbeCreate(Parse * pParse) p->magic =3D VDBE_MAGIC_INIT; p->pParse =3D pParse; p->schema_ver =3D box_schema_version(); + assert(pParse->aLabel =3D=3D 0); + assert(pParse->nLabel =3D=3D 0); + assert(pParse->nOpAlloc =3D=3D 0); + assert(pParse->szOpAlloc =3D=3D 0); + return p; +} +int +sql_vdbe_prepare(struct Vdbe *vdbe) +{ + assert(vdbe !=3D NULL); struct txn *txn =3D in_txn(); - p->auto_commit =3D txn =3D=3D NULL ? true : false; + vdbe->auto_commit =3D txn =3D=3D NULL; if (txn !=3D NULL) { /* * If transaction has been started in Lua, then @@ -76,26 +102,17 @@ sqlite3VdbeCreate(Parse * pParse) * check FK violations, at least now. */ if (txn->psql_txn =3D=3D NULL) { - txn->psql_txn =3D = region_alloc_object(&fiber()->gc, - struct = sql_txn); + txn->psql_txn =3D sql_alloc_txn(); if (txn->psql_txn =3D=3D NULL) - sqlite3DbFree(db, p); - diag_set(OutOfMemory, sizeof(struct = sql_txn), - "region", "struct sql_txn"); - return NULL; + return -1; } - txn->psql_txn->pSavepoint =3D NULL; - txn->psql_txn->fk_deferred_count =3D 0; } - p->psql_txn =3D txn->psql_txn; + vdbe->psql_txn =3D txn->psql_txn; } else { - p->psql_txn =3D NULL; + vdbe->psql_txn =3D NULL; } - assert(pParse->aLabel =3D=3D 0); - assert(pParse->nLabel =3D=3D 0); - assert(pParse->nOpAlloc =3D=3D 0); - assert(pParse->szOpAlloc =3D=3D 0); - return p; + return 0; } =20 /* @@ -2496,14 +2513,11 @@ sql_txn_begin(Vdbe *p) struct txn *ptxn =3D txn_begin(false); if (ptxn =3D=3D NULL) return -1; - ptxn->psql_txn =3D region_alloc_object(&fiber()->gc, struct = sql_txn); + ptxn->psql_txn =3D sql_alloc_txn(); if (ptxn->psql_txn =3D=3D NULL) { box_txn_rollback(); - diag_set(OutOfMemory, sizeof(struct sql_txn), "region", - "struct sql_txn"); return -1; } - memset(ptxn->psql_txn, 0, sizeof(struct sql_txn)); p->psql_txn =3D ptxn->psql_txn; return 0; } >=20 >> + if (txn !=3D NULL) { >> + /* >> + * If transaction has been started in Lua, then >> + * sql_txn is NULL. On the other hand, it is not >> + * critical, since in Lua it is impossible to >> + * check FK violations, at least now. >> + */ >> + if (txn->psql_txn =3D=3D NULL) { >> + txn->psql_txn =3D = region_alloc_object(&fiber()->gc, >> + struct = sql_txn); >> + if (txn->psql_txn =3D=3D NULL) { >> + sqlite3DbFree(db, p); >> + diag_set(OutOfMemory, sizeof(struct = sql_txn), >> + "region", "struct sql_txn"); >> + return NULL; >=20 > 6. It is already the second place, where psql_txn is created. Lets = move this > into a function. As you wish: + +struct sql_txn * +sql_alloc_txn() +{ + struct sql_txn *txn =3D region_alloc_object(&fiber()->gc, + struct sql_txn); + if (txn =3D=3D NULL) { + diag_set(OutOfMemory, sizeof(struct sql_txn), "region", + "struct sql_txn"); + return NULL; + } + txn->pSavepoint =3D NULL; + txn->fk_deferred_count =3D 0; + return txn; +} @@ -2496,14 +2513,11 @@ sql_txn_begin(Vdbe *p) struct txn *ptxn =3D txn_begin(false); if (ptxn =3D=3D NULL) return -1; - ptxn->psql_txn =3D region_alloc_object(&fiber()->gc, struct = sql_txn); + ptxn->psql_txn =3D sql_alloc_txn(); if (ptxn->psql_txn =3D=3D NULL) { box_txn_rollback(); - diag_set(OutOfMemory, sizeof(struct sql_txn), "region", - "struct sql_txn"); return -1; } - memset(ptxn->psql_txn, 0, sizeof(struct sql_txn)); p->psql_txn =3D ptxn->psql_txn; return 0; } @@ -191,6 +191,29 @@ typedef struct VdbeOpList VdbeOpList; * for a description of what each of these routines does. */ Vdbe *sqlite3VdbeCreate(Parse *); + +/** + * Allocate and initialize SQL-specific struct which completes + * original Tarantool's txn struct using region allocator. + * + * @retval NULL on OOM, new psql_txn struct on success. + **/ +struct sql_txn * +sql_alloc_txn(); Also, see diff connected with sql_vdbe_prepare(). >> diff --git a/src/box/txn.c b/src/box/txn.c >> index c5aaa8e0b..a831472bd 100644 >> --- a/src/box/txn.c >> +++ b/src/box/txn.c >> @@ -484,5 +497,8 @@ box_txn_rollback_to_savepoint(box_txn_savepoint_t = *svp) >> return -1; >> } >> txn_rollback_to_svp(txn, svp->stmt); >> + if (txn->psql_txn !=3D NULL) { >> + txn->psql_txn->fk_deferred_count =3D = svp->fk_deferred_count; >> + } >=20 > 7. Please, do not wrap single-line 'if' into {}. Sorry. - if (txn->psql_txn !=3D NULL) { + if (txn->psql_txn !=3D NULL) txn->psql_txn->fk_deferred_count =3D = svp->fk_deferred_count; - } >> diff --git a/test/box/misc.result b/test/box/misc.result >> index 4e437d88f..840abbb66 100644 >> --- a/test/box/misc.result >> +++ b/test/box/misc.result >> @@ -389,6 +389,7 @@ t; >> - 'box.error.LOCAL_INSTANCE_ID_IS_READ_ONLY : 128' >> - 'box.error.FUNCTION_EXISTS : 52' >> - 'box.error.UPDATE_ARG_TYPE : 26' >> + - 'box.error.FOREIGN_KEY_CONSTRAINT : 162' >> - 'box.error.CROSS_ENGINE_TRANSACTION : 81' >> - 'box.error.ACTION_MISMATCH : 160' >> - 'box.error.FORMAT_MISMATCH_INDEX_PART : 27' >> diff --git a/test/sql/transitive-transactions.result = b/test/sql/transitive-transactions.result >> new file mode 100644 >> index 000000000..d282a5dfa >> --- /dev/null >> +++ b/test/sql/transitive-transactions.result >> @@ -0,0 +1,124 @@ >> +test_run =3D require('test_run').new() >> +--- >> +... >> +test_run:cmd("setopt delimiter ';'") >=20 > 8. Leading white space (git diff highlights it with red color). Removed. >=20 >> +--- >> +- true >> +... >> +-- These tests are aimed at checking transitive transactions >> +-- between SQL and Lua. In particular, make sure that deferred = foreign keys >> +-- violations are passed correctly. >> +-- >> +-- box.cfg() >=20 > 9. Looks like commented box.cfg that you used to run from console. Removed. >=20 >> +box.begin() box.sql.execute('COMMIT'); >> +--- >> +... >> +box.begin() box.sql.execute('ROLLBACK'); >> +--- >> +... >> +box.sql.execute('BEGIN;') box.commit(); >> +--- >> +... >> +box.sql.execute('BEGIN;') box.rollback(); >> +--- >> +... >> +box.sql.execute('pragma foreign_keys =3D 1;'); >> +--- >> +... >> +box.sql.execute('CREATE TABLE child(id INT PRIMARY KEY, x REFERENCES = parent(y) DEFERRABLE INITIALLY DEFERRED);'); >=20 > 10. Lets swap this and the next statements. AFAIK we are going to = forbid not-existing table > referencing, and the test will be broken. Swapped. >=20 >> +--- >> +... >> +box.sql.execute('CREATE TABLE parent(id INT PRIMARY KEY, y INT = UNIQUE);'); >> +--- >> +... >> +fk_violation_1 =3D function() >> + box.begin() >> + box.sql.execute('INSERT INTO child VALUES (1, 1);') >> + box.sql.execute('COMMIT;') >> +end; >> +--- >> +... >> +fk_violation_1() box.sql.execute('ROLLBACK;') >> +box.space.CHILD:select(); >> +--- >> +- error: 'Can not commit transaction: deferred foreign keys = violations are not resolved' >> +... >=20 > 11. Looks like select and rollback are not executed because of failed = fk_violation1. >=20 > I made this: > [001] -fk_violation_1() box.sql.execute('ROLLBACK;') > [001] +fk_violation_1() box.sql.execute('ROLLBACK;') assert(false) >=20 > And the assertion did not fail. >=20 >> +fk_violation_2 =3D function() >> + box.sql.execute('BEGIN;') >> + box.sql.execute('INSERT INTO child VALUES (1, 1);') >> + box.commit() >> +end; >> +--- >> +... >> +fk_violation_2() box.rollback(); >> +--- >> +- error: 'Can not commit transaction: deferred foreign keys = violations are not resolved' >=20 > 12. Same - rollback is not executed. Since I have changed behaviour (rollback instead of commit failure), I just removed these rollbacks. I am also attaching complete patch, since some of diffs seem to be inconvenient to read. --Apple-Mail=_9448364C-9C4F-4B27-82F9-DE4DE10849DB Content-Disposition: attachment; filename=0002-sql-allow-transitive-Lua-SQL-transactions.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="0002-sql-allow-transitive-Lua-SQL-transactions.patch" Content-Transfer-Encoding: quoted-printable =46rom=20934b0ac08de838b2325382d02330687925c9473c=20Mon=20Sep=2017=20= 00:00:00=202001=0AMessage-Id:=20= <934b0ac08de838b2325382d02330687925c9473c.1525533484.git.korablev@tarantoo= l.org>=0AIn-Reply-To:=20=0A= References:=20=0AFrom:=20= Nikita=20Pettik=20=0ADate:=20Thu,=2026=20Apr=20= 2018=2016:55:11=20+0300=0ASubject:=20[PATCH=202/4]=20sql:=20allow=20= transitive=20Lua=20<->=20SQL=20transactions=0A=0AThis=20patch=20makes=20= possible=20to=20start=20transaction=20in=20Lua=20and=20continue=0A= operations=20in=20SQL=20as=20well,=20and=20vice=20versa.=20Previously,=20= such=20transactions=0Aresult=20in=20assertion=20fault.=20To=20support=20= them,=20it=20is=20required=20to=20hold=20deferred=0Aforeign=20keys=20= constraints=20as=20attributes=20of=20transaction,=20not=20particular=20= VDBE.=0AThus,=20deferred=20foreign=20keys=20counters=20have=20been=20= completely=20removed=20from=0AVDBE=20and=20transfered=20to=20sql_txn=20= struct.=20In=20its=20turn,=20if=20there=20is=20at=20least=0Aone=20= deferred=20foreign=20key=20violation,=20error=20will=20be=20raised=20= alongside=20with=0Arollback=20-=20that=20is=20what=20ANSI=20SQL=20says.=20= Note=20that=20in=20SQLite=20rollback=0Adoesn't=20occur:=20transaction=20= remains=20open=20untill=20explicit=20rollback=20or=0Aresolving=20all=20= FK=20violations.=0A=0AAlso,=20'PRAGMA=20defer_foreign_keys'=20has=20been=20= slightly=20changed:=20now=20it=20is=20not=0Aautomatically=20turned=20off=20= after=20trasaction's=20rollback=20or=20commit.=20It=0Acan=20be=20turned=20= off=20by=20explicit=20PRAGMA=20statement=20only.=20It=20was=20made=20= owing=0Ato=20the=20fact=20that=20execution=20of=20PRAGMA=20statement=20= occurs=20in=20auto-commit=0Amode,=20so=20it=20ends=20with=20COMMIT.=20= Hence,=20it=20turns=20off=20right=20after=20turning=20on=0A(outside=20= the=20transaction).=0A=0ACloses=20#3237=0A---=0A=20src/box/errcode.h=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20|=20= =20=201=20+=0A=20src/box/sql/fkey.c=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20|=20=2066=20++++++----------=0A=20= src/box/sql/main.c=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20|=20=20=205=20--=0A=20src/box/sql/pragma.c=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20|=20=20=203=20-=0A=20= src/box/sql/select.c=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20|=20=20=207=20+-=0A=20src/box/sql/sqliteInt.h=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20|=20=20=202=20-=0A=20= src/box/sql/status.c=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20|=20=20=203=20+-=0A=20src/box/sql/vdbe.c=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20|=20=2025=20+++---=0A=20= src/box/sql/vdbe.h=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20|=20=2023=20++++++=0A=20src/box/sql/vdbeInt.h=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20|=20=2026=20+-----=0A=20= src/box/sql/vdbeapi.c=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20|=20=20=203=20-=0A=20src/box/sql/vdbeaux.c=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20|=20=2079=20++++++++++++-------=0A= =20src/box/txn.c=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20|=20=2017=20+++-=0A=20src/box/txn.h=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20|=20=2022=20+++++-=0A=20test/box/misc.result=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20|=20=20=201=20+=0A=20= test/sql/transitive-transactions.result=20=20=20|=20126=20= ++++++++++++++++++++++++++++++=0A=20= test/sql/transitive-transactions.test.lua=20|=20=2065=20+++++++++++++++=0A= =2017=20files=20changed,=20345=20insertions(+),=20129=20deletions(-)=0A=20= create=20mode=20100644=20test/sql/transitive-transactions.result=0A=20= create=20mode=20100644=20test/sql/transitive-transactions.test.lua=0A=0A= diff=20--git=20a/src/box/errcode.h=20b/src/box/errcode.h=0Aindex=20= e5d882df8..ba5288059=20100644=0A---=20a/src/box/errcode.h=0A+++=20= b/src/box/errcode.h=0A@@=20-214,6=20+214,7=20@@=20struct=20= errcode_record=20{=0A=20=09/*159=20*/_(ER_SQL_BIND_NOT_FOUND,=09= "Parameter=20%s=20was=20not=20found=20in=20the=20statement")=20\=0A=20=09= /*160=20*/_(ER_ACTION_MISMATCH,=09=09"Field=20%d=20contains=20%s=20on=20= conflict=20action,=20but=20%s=20in=20index=20parts")=20\=0A=20=09/*161=20= */_(ER_VIEW_MISSING_SQL,=09=09"Space=20declared=20as=20a=20view=20must=20= have=20SQL=20statement")=20\=0A+=09/*162=20= */_(ER_FOREIGN_KEY_CONSTRAINT,=09"Can=20not=20commit=20transaction:=20= deferred=20foreign=20keys=20violations=20are=20not=20resolved")=20\=0A=20= =0A=20/*=0A=20=20*=20!IMPORTANT!=20Please=20follow=20instructions=20at=20= start=20of=20the=20file=0Adiff=20--git=20a/src/box/sql/fkey.c=20= b/src/box/sql/fkey.c=0Aindex=20fb9a3101a..55edf6ba5=20100644=0A---=20= a/src/box/sql/fkey.c=0A+++=20b/src/box/sql/fkey.c=0A@@=20-748,23=20= +748,19=20@@=20fkTriggerDelete(sqlite3=20*=20dbMem,=20Trigger=20*=20p)=0A= =20=09}=0A=20}=0A=20=0A-/*=0A-=20*=20This=20function=20is=20called=20to=20= generate=20code=20that=20runs=20when=20table=20pTab=20is=0A-=20*=20being=20= dropped=20from=20the=20database.=20The=20SrcList=20passed=20as=20the=20= second=20argument=0A-=20*=20to=20this=20function=20contains=20a=20single=20= entry=20guaranteed=20to=20resolve=20to=0A-=20*=20table=20pTab.=0A-=20*=0A= -=20*=20Normally,=20no=20code=20is=20required.=20However,=20if=20either=0A= -=20*=0A-=20*=20=20=20(a)=20The=20table=20is=20the=20parent=20table=20of=20= a=20FK=20constraint,=20or=0A-=20*=20=20=20(b)=20The=20table=20is=20the=20= child=20table=20of=20a=20deferred=20FK=20constraint=20and=20it=20is=0A-=20= *=20=20=20=20=20=20=20determined=20at=20runtime=20that=20there=20are=20= outstanding=20deferred=20FK=0A-=20*=20=20=20=20=20=20=20constraint=20= violations=20in=20the=20database,=0A-=20*=0A-=20*=20then=20the=20= equivalent=20of=20"DELETE=20FROM=20"=20is=20executed=20in=20a=20= single=20transaction=0A-=20*=20before=20dropping=20the=20table=20from=20= the=20database.=20If=20any=20FK=20violations=20occur,=0A-=20*=20rollback=20= transaction=20and=20halt=20VDBE.=20Triggers=20are=20disabled=20while=20= running=20this=0A-=20*=20DELETE,=20but=20foreign=20key=20actions=20are=20= not.=0A+/**=0A+=20*=20This=20function=20is=20called=20to=20generate=20= code=20that=20runs=20when=20table=0A+=20*=20pTab=20is=20being=20dropped=20= from=20the=20database.=20The=20SrcList=20passed=20as=0A+=20*=20the=20= second=20argument=20to=20this=20function=20contains=20a=20single=20entry=0A= +=20*=20guaranteed=20to=20resolve=20to=20table=20pTab.=0A+=20*=0A+=20*=20= Normally,=20no=20code=20is=20required.=20However,=20if=20the=20table=20= is=0A+=20*=20parent=20table=20of=20a=20FK=20constraint,=20then=20the=20= equivalent=0A+=20*=20of=20"DELETE=20FROM=20"=20is=20executed=20in=20= a=20single=20transaction=0A+=20*=20before=20dropping=20the=20table=20= from=20the=20database.=20If=20any=20FK=0A+=20*=20violations=20occur,=20= rollback=20transaction=20and=20halt=20VDBE.=20Triggers=0A+=20*=20are=20= disabled=20while=20running=20this=20DELETE,=20but=20foreign=20key=0A+=20= *=20actions=20are=20not.=0A=20=20*/=0A=20void=0A=20= sqlite3FkDropTable(Parse=20*=20pParse,=20SrcList=20*=20pName,=20Table=20= *=20pTab)=0A@@=20-774,31=20+770,17=20@@=20sqlite3FkDropTable(Parse=20*=20= pParse,=20SrcList=20*=20pName,=20Table=20*=20pTab)=0A=20=0A=20=09if=20= ((user_session->sql_flags=20&=20SQLITE_ForeignKeys)=20&&=0A=20=09=20=20=20= =20!space_is_view(pTab))=20{=0A-=09=09int=20iSkip=20=3D=200;=0A=20=09=09= Vdbe=20*v=20=3D=20sqlite3GetVdbe(pParse);=0A-=0A-=09=09assert(v);=09/*=20= VDBE=20has=20already=20been=20allocated=20*/=0A-=09=09if=20= (sqlite3FkReferences(pTab)=20=3D=3D=200)=20{=0A-=09=09=09/*=20Search=20= for=20a=20deferred=20foreign=20key=20constraint=20for=20which=20this=20= table=0A-=09=09=09=20*=20is=20the=20child=20table.=20If=20one=20cannot=20= be=20found,=20return=20without=0A-=09=09=09=20*=20generating=20any=20= VDBE=20code.=20If=20one=20can=20be=20found,=20then=20jump=20over=0A-=09=09= =09=20*=20the=20entire=20DELETE=20if=20there=20are=20no=20outstanding=20= deferred=20constraints=0A-=09=09=09=20*=20when=20this=20statement=20is=20= run.=0A-=09=09=09=20*/=0A-=09=09=09FKey=20*p;=0A-=09=09=09for=20(p=20=3D=20= pTab->pFKey;=20p;=20p=20=3D=20p->pNextFrom)=20{=0A-=09=09=09=09if=20= (p->isDeferred=0A-=09=09=09=09=20=20=20=20||=20(user_session->=0A-=09=09=09= =09=09sql_flags=20&=20SQLITE_DeferFKs))=0A-=09=09=09=09=09break;=0A-=09=09= =09}=0A-=09=09=09if=20(!p)=0A+=09=09assert(v=20!=3D=20NULL);=0A+=09=09/*=0A= +=09=09=20*=20Search=20for=20a=20foreign=20key=20constraint=20for=20= which=0A+=09=09=20*=20this=20table=20is=20the=20parent=20table.=20If=20= one=20can't=20be=0A+=09=09=20*=20found,=20return=20without=20generating=20= any=20VDBE=20code,=0A+=09=09=20*=20since=20it=20makes=20no=20sense=20= starting=20transaction=0A+=09=09=20*=20to=20test=20FK=20violations.=0A+=09= =09=20*/=0A+=09=09if=20(sqlite3FkReferences(pTab)=20=3D=3D=20NULL)=0A=20=09= =09=09=09return;=0A-=09=09=09iSkip=20=3D=20sqlite3VdbeMakeLabel(v);=0A-=09= =09=09sqlite3VdbeAddOp2(v,=20OP_FkIfZero,=201,=20iSkip);=0A-=09=09=09= VdbeCoverage(v);=0A-=09=09}=0A-=0A=20=09=09pParse->disableTriggers=20=3D=20= 1;=0A=20=09=09/*=20Staring=20new=20transaction=20before=20DELETE=20FROM=20= =20*/=0A=20=09=09sqlite3VdbeAddOp0(v,=20OP_TTransaction);=0A@@=20= -813,10=20+795,6=20@@=20sqlite3FkDropTable(Parse=20*=20pParse,=20SrcList=20= *=20pName,=20Table=20*=20pTab)=0A=20=09=09=20*=20commit=20changes.=0A=20=09= =09=20*/=0A=20=09=09sqlite3VdbeAddOp0(v,=20OP_FkCheckCommit);=0A-=0A-=09=09= if=20(iSkip)=20{=0A-=09=09=09sqlite3VdbeResolveLabel(v,=20iSkip);=0A-=09=09= }=0A=20=09}=0A=20}=0A=20=0Adiff=20--git=20a/src/box/sql/main.c=20= b/src/box/sql/main.c=0Aindex=208775ef3ad..0acf7bc64=20100644=0A---=20= a/src/box/sql/main.c=0A+++=20b/src/box/sql/main.c=0A@@=20-770,11=20= +770,6=20@@=20sqlite3RollbackAll(Vdbe=20*=20pVdbe,=20int=20tripCode)=0A=20= =09assert((user_session->sql_flags=20&=20SQLITE_InternChanges)=20=3D=3D=20= 0=0A=20=09=20=20=20=20=20=20=20||=20db->init.busy=20=3D=3D=201);=0A=20=0A= -=09/*=20Any=20deferred=20constraint=20violations=20have=20now=20been=20= resolved.=20*/=0A-=09pVdbe->nDeferredCons=20=3D=200;=0A-=09= pVdbe->nDeferredImmCons=20=3D=200;=0A-=09user_session->sql_flags=20&=3D=20= ~SQLITE_DeferFKs;=0A-=0A=20=09/*=20If=20one=20has=20been=20configured,=20= invoke=20the=20rollback-hook=20callback=20*/=0A=20=09if=20= (db->xRollbackCallback=20&&=20(!pVdbe->auto_commit))=20{=0A=20=09=09= db->xRollbackCallback(db->pRollbackArg);=0Adiff=20--git=20= a/src/box/sql/pragma.c=20b/src/box/sql/pragma.c=0Aindex=20= e41f69b02..73cd34cc0=20100644=0A---=20a/src/box/sql/pragma.c=0A+++=20= b/src/box/sql/pragma.c=0A@@=20-320,9=20+320,6=20@@=20sqlite3Pragma(Parse=20= *=20pParse,=20Token=20*=20pId,=09/*=20First=20part=20of=20[schema.]id=20= field=20*/=0A=20=09=09=09=09=09user_session->sql_flags=20|=3D=20mask;=0A=20= =09=09=09=09}=20else=20{=0A=20=09=09=09=09=09user_session->sql_flags=20= &=3D=20~mask;=0A-=09=09=09=09=09if=20(mask=20=3D=3D=20SQLITE_DeferFKs)=20= {=0A-=09=09=09=09=09=09v->nDeferredImmCons=20=3D=200;=0A-=09=09=09=09=09= }=0A=20=09=09=09=09}=0A=20=0A=20=09=09=09=09/*=20Many=20of=20the=20= flag-pragmas=20modify=20the=20code=0Adiff=20--git=20= a/src/box/sql/select.c=20b/src/box/sql/select.c=0Aindex=20= 5a50413a6..b8863a850=20100644=0A---=20a/src/box/sql/select.c=0A+++=20= b/src/box/sql/select.c=0A@@=20-1994,13=20+1994,16=20@@=20static=20= SQLITE_NOINLINE=20Vdbe=20*=0A=20allocVdbe(Parse=20*=20pParse)=0A=20{=0A=20= =09Vdbe=20*v=20=3D=20pParse->pVdbe=20=3D=20sqlite3VdbeCreate(pParse);=0A= -=09if=20(v)=0A-=09=09sqlite3VdbeAddOp2(v,=20OP_Init,=200,=201);=0A+=09= if=20(v=20=3D=3D=20NULL)=0A+=09=09return=20NULL;=0A+=09= sqlite3VdbeAddOp2(v,=20OP_Init,=200,=201);=0A=20=09if=20= (pParse->pToplevel=20=3D=3D=200=0A=20=09=20=20=20=20&&=20= OptimizationEnabled(pParse->db,=20SQLITE_FactorOutConst)=0A=20=09=20=20=20= =20)=20{=0A=20=09=09pParse->okConstFactor=20=3D=201;=0A=20=09}=0A+=09if=20= (sql_vdbe_prepare(v)=20!=3D=200)=0A+=09=09return=20NULL;=0A=20=09return=20= v;=0A=20}=0A=20=0Adiff=20--git=20a/src/box/sql/sqliteInt.h=20= b/src/box/sql/sqliteInt.h=0Aindex=206bfe1352d..27d421b7e=20100644=0A---=20= a/src/box/sql/sqliteInt.h=0A+++=20b/src/box/sql/sqliteInt.h=0A@@=20= -1849,8=20+1849,6=20@@=20struct=20FuncDestructor=20{=0A=20struct=20= Savepoint=20{=0A=20=09box_txn_savepoint_t=20*tnt_savepoint;=20/*=20= Tarantool's=20savepoint=20struct=20*/=0A=20=09char=20*zName;=09=09/*=20= Savepoint=20name=20(nul-terminated)=20*/=0A-=09i64=20nDeferredCons;=09/*=20= Number=20of=20deferred=20fk=20violations=20*/=0A-=09i64=20= nDeferredImmCons;=09/*=20Number=20of=20deferred=20imm=20fk.=20*/=0A=20=09= Savepoint=20*pNext;=09/*=20Parent=20savepoint=20(if=20any)=20*/=0A=20};=0A= =20=0Adiff=20--git=20a/src/box/sql/status.c=20b/src/box/sql/status.c=0A= index=208742bbf8b..84f07cc68=20100644=0A---=20a/src/box/sql/status.c=0A= +++=20b/src/box/sql/status.c=0A@@=20-333,8=20+333,7=20@@=20= sqlite3_db_status(sqlite3=20*=20db,=09/*=20The=20database=20connection=20= whose=20status=20is=20desir=0A=20=09=09=09=09break;=0A=20=09=09=09}=0A=20= =09=09=09const=20struct=20sql_txn=20*psql_txn=20=3D=20ptxn->psql_txn;=0A= -=09=09=09*pCurrent=20=3D=20psql_txn->nDeferredImmConsSave=20>=200=20||=0A= -=09=09=09=09=20=20=20=20psql_txn->nDeferredConsSave=20>=200;=0A+=09=09=09= *pCurrent=20=3D=20psql_txn->fk_deferred_count=20>=200;=0A=20=09=09=09= break;=0A=20=09=09}=0A=20=0Adiff=20--git=20a/src/box/sql/vdbe.c=20= b/src/box/sql/vdbe.c=0Aindex=2043572583f..207410c7f=20100644=0A---=20= a/src/box/sql/vdbe.c=0A+++=20b/src/box/sql/vdbe.c=0A@@=20-1029,7=20= +1029,6=20@@=20case=20OP_Halt:=20{=0A=20=09=09p->rc=20=3D=20SQLITE_BUSY;=0A= =20=09}=20else=20{=0A=20=09=09assert(rc=3D=3DSQLITE_OK=20||=20= (p->rc&0xff)=3D=3DSQLITE_CONSTRAINT);=0A-=09=09assert(rc=3D=3DSQLITE_OK=20= ||=20p->nDeferredCons>0=20||=20p->nDeferredImmCons>0);=0A=20=09=09rc=20=3D= =20p->rc=20?=20SQLITE_ERROR=20:=20SQLITE_DONE;=0A=20=09}=0A=20=09goto=20= vdbe_return;=0A@@=20-2950,8=20+2949,8=20@@=20case=20OP_Savepoint:=20{=0A=20= =09=09=09=09assert(pSavepoint=20=3D=3D=20psql_txn->pSavepoint);=0A=20=09=09= =09=09psql_txn->pSavepoint=20=3D=20pSavepoint->pNext;=0A=20=09=09=09}=20= else=20{=0A-=09=09=09=09p->nDeferredCons=20=3D=20= pSavepoint->nDeferredCons;=0A-=09=09=09=09p->nDeferredImmCons=20=3D=20= pSavepoint->nDeferredImmCons;=0A+=09=09=09=09= p->psql_txn->fk_deferred_count=20=3D=0A+=09=09=09=09=09= pSavepoint->tnt_savepoint->fk_deferred_count;=0A=20=09=09=09}=0A=20=09=09= }=0A=20=09}=0A@@=20-5058,10=20+5057,10=20@@=20case=20OP_Param:=20{=20=20=20= =20=20=20=20=20=20=20=20/*=20out2=20*/=0A=20=20*=20statement=20counter=20= is=20incremented=20(immediate=20foreign=20key=20constraints).=0A=20=20*/=0A= =20case=20OP_FkCounter:=20{=0A-=09if=20(user_session->sql_flags=20&=20= SQLITE_DeferFKs)=20{=0A-=09=09p->nDeferredImmCons=20+=3D=20pOp->p2;=0A-=09= }=20else=20if=20(pOp->p1)=20{=0A-=09=09p->nDeferredCons=20+=3D=20= pOp->p2;=0A+=09if=20((user_session->sql_flags=20&=20SQLITE_DeferFKs=20||=20= pOp->p1=20!=3D=200)=20&&=0A+=09=20=20=20=20!p->auto_commit)=20{=0A+=09=09= assert(p->psql_txn=20!=3D=20NULL);=0A+=09=09= p->psql_txn->fk_deferred_count=20+=3D=20pOp->p2;=0A=20=09}=20else=20{=0A=20= =09=09p->nFkConstraint=20+=3D=20pOp->p2;=0A=20=09}=0A@@=20-5081,12=20= +5080,14=20@@=20case=20OP_FkCounter:=20{=0A=20=20*=20(immediate=20= foreign=20key=20constraint=20violations).=0A=20=20*/=0A=20case=20= OP_FkIfZero:=20{=20=20=20=20=20=20=20=20=20/*=20jump=20*/=0A-=09if=20= (pOp->p1)=20{=0A-=09=09VdbeBranchTaken(db->nDeferredCons=20=3D=3D=200=20= &&=20db->nDeferredImmCons=20=3D=3D=200,=202);=0A-=09=09if=20= (p->nDeferredCons=3D=3D0=20&&=20p->nDeferredImmCons=3D=3D0)=20goto=20= jump_to_p2;=0A+=09if=20((user_session->sql_flags=20&=20SQLITE_DeferFKs=20= ||=20pOp->p1)=20&&=0A+=09=20=20=20=20!p->auto_commit)=20{=0A+=09=09= assert(p->psql_txn=20!=3D=20NULL);=0A+=09=09if=20= (p->psql_txn->fk_deferred_count=20=3D=3D=200)=0A+=09=09=09goto=20= jump_to_p2;=0A=20=09}=20else=20{=0A-=09=09= VdbeBranchTaken(p->nFkConstraint=20=3D=3D=200=20&&=20= db->nDeferredImmCons=20=3D=3D=200,=202);=0A-=09=09if=20= (p->nFkConstraint=3D=3D0=20&&=20p->nDeferredImmCons=3D=3D0)=20goto=20= jump_to_p2;=0A+=09=09if=20(p->nFkConstraint=20=3D=3D=200)=0A+=09=09=09= goto=20jump_to_p2;=0A=20=09}=0A=20=09break;=0A=20}=0Adiff=20--git=20= a/src/box/sql/vdbe.h=20b/src/box/sql/vdbe.h=0Aindex=20= 340ddc766..c31983f21=20100644=0A---=20a/src/box/sql/vdbe.h=0A+++=20= b/src/box/sql/vdbe.h=0A@@=20-191,6=20+191,29=20@@=20typedef=20struct=20= VdbeOpList=20VdbeOpList;=0A=20=20*=20for=20a=20description=20of=20what=20= each=20of=20these=20routines=20does.=0A=20=20*/=0A=20Vdbe=20= *sqlite3VdbeCreate(Parse=20*);=0A+=0A+/**=0A+=20*=20Allocate=20and=20= initialize=20SQL-specific=20struct=20which=20completes=0A+=20*=20= original=20Tarantool's=20txn=20struct=20using=20region=20allocator.=0A+=20= *=0A+=20*=20@retval=20NULL=20on=20OOM,=20new=20psql_txn=20struct=20on=20= success.=0A+=20**/=0A+struct=20sql_txn=20*=0A+sql_alloc_txn();=0A+=0A= +/**=0A+=20*=20Prepare=20given=20VDBE=20to=20execution:=20initialize=20= structs=20connected=0A+=20*=20with=20transaction=20routine:=20autocommit=20= mode,=20deferred=20foreign=0A+=20*=20keys=20counter,=20struct=20= representing=20SQL=20savepoint.=0A+=20*=20If=20execution=20context=20is=20= already=20within=20active=20transaction,=0A+=20*=20just=20transfer=20= transaction=20data=20to=20VDBE.=0A+=20*=0A+=20*=20@param=20vdbe=20VDBE=20= to=20be=20prepared.=0A+=20*=20@retval=20-1=20on=20out=20of=20memory,=200=20= otherwise.=0A+=20*/=0A+int=0A+sql_vdbe_prepare(struct=20Vdbe=20*vdbe);=0A= +=0A=20int=20sqlite3VdbeAddOp0(Vdbe=20*,=20int);=0A=20int=20= sqlite3VdbeAddOp1(Vdbe=20*,=20int,=20int);=0A=20int=20= sqlite3VdbeAddOp2(Vdbe=20*,=20int,=20int,=20int);=0Adiff=20--git=20= a/src/box/sql/vdbeInt.h=20b/src/box/sql/vdbeInt.h=0Aindex=20= 4b1767e24..3a907cd93=20100644=0A---=20a/src/box/sql/vdbeInt.h=0A+++=20= b/src/box/sql/vdbeInt.h=0A@@=20-332,19=20+332,6=20@@=20struct=20= ScanStatus=20{=0A=20=09char=20*zName;=09=09/*=20Name=20of=20table=20or=20= index=20*/=0A=20};=0A=20=0A-=0A-struct=20sql_txn=20{=0A-=09Savepoint=20= *pSavepoint;=09/*=20List=20of=20active=20savepoints=20*/=0A-=09/*=0A-=09=20= *=20This=20variables=20transfer=20deferred=20constraints=20from=20one=0A= -=09=20*=20VDBE=20to=20the=20next=20in=20the=20same=20transaction.=0A-=09= =20*=20We=20have=20to=20do=20it=20this=20way=20because=20some=20VDBE=20= execute=20ddl=20and=20do=20not=0A-=09=20*=20have=20a=20transaction=20= which=20disallows=20to=20always=20store=20this=20vars=20here.=0A-=09=20= */=0A-=09i64=20nDeferredConsSave;=0A-=09i64=20nDeferredImmConsSave;=0A= -};=0A-=0A=20/*=0A=20=20*=20An=20instance=20of=20the=20virtual=20= machine.=20=20This=20structure=20contains=20the=20complete=0A=20=20*=20= state=20of=20the=20virtual=20machine.=0A@@=20-367,8=20+354,6=20@@=20= struct=20Vdbe=20{=0A=20=09int=20iStatement;=09=09/*=20Statement=20number=20= (or=200=20if=20has=20not=20opened=20stmt)=20*/=0A=20=09i64=20= iCurrentTime;=09/*=20Value=20of=20julianday('now')=20for=20this=20= statement=20*/=0A=20=09i64=20nFkConstraint;=09/*=20Number=20of=20imm.=20= FK=20constraints=20this=20VM=20*/=0A-=09i64=20nStmtDefCons;=09/*=20= Number=20of=20def.=20constraints=20when=20stmt=20started=20*/=0A-=09i64=20= nStmtDefImmCons;=09/*=20Number=20of=20def.=20imm=20constraints=20when=20= stmt=20started=20*/=0A=20=09uint32_t=20schema_ver;=09/*=20Schema=20= version=20at=20the=20moment=20of=20VDBE=20creation.=20*/=0A=20=0A=20=09= /*=0A@@=20-379,17=20+364,10=20@@=20struct=20Vdbe=20{=0A=20=09=20*=20= ignoreRaised=20variable=20helps=20to=20track=20such=20situations=0A=20=09= =20*/=0A=20=09u8=20ignoreRaised;=09/*=20Flag=20for=20ON=20CONFLICT=20= IGNORE=20for=20triggers=20*/=0A-=0A-=09struct=20sql_txn=20*psql_txn;=20= /*=20Data=20related=20to=20current=20transaction=20*/=0A+=09/**=20Data=20= related=20to=20current=20transaction.=20*/=0A+=09struct=20sql_txn=20= *psql_txn;=0A=20=09/**=20The=20auto-commit=20flag.=20*/=0A=20=09bool=20= auto_commit;=0A-=09/*=0A-=09=20*=20`nDeferredCons`=20and=20= `nDeferredImmCons`=20are=20stored=20in=20vdbe=20during=0A-=09=20*=20vdbe=20= execution=20and=20moved=20to=20sql_txn=20when=20it=20needs=20to=20be=20= saved=20until=0A-=09=20*=20execution=20of=20the=20next=20vdbe=20in=20the=20= same=20transaction.=0A-=09=20*/=0A-=09i64=20nDeferredCons;=09/*=20Number=20= of=20deferred=20fk=20violations=20*/=0A-=09i64=20nDeferredImmCons;=09/*=20= Number=20of=20deferred=20imm=20fk.=20*/=0A=20=0A=20=09/*=20When=20= allocating=20a=20new=20Vdbe=20object,=20all=20of=20the=20fields=20below=20= should=20be=0A=20=09=20*=20initialized=20to=20zero=20or=20NULL=0Adiff=20= --git=20a/src/box/sql/vdbeapi.c=20b/src/box/sql/vdbeapi.c=0Aindex=20= d8cdefa7c..32af463f6=20100644=0A---=20a/src/box/sql/vdbeapi.c=0A+++=20= b/src/box/sql/vdbeapi.c=0A@@=20-560,9=20+560,6=20@@=20sqlite3Step(Vdbe=20= *=20p)=0A=20=09=09=09db->u1.isInterrupted=20=3D=200;=0A=20=09=09}=0A=20=0A= -=09=09assert(box_txn()=20||=0A-=09=09=20=20=20=20=20=20=20= (p->nDeferredCons=20=3D=3D=200=20&&=20p->nDeferredImmCons=20=3D=3D=20= 0));=0A-=0A=20#ifndef=20SQLITE_OMIT_TRACE=0A=20=09=09if=20((db->xProfile=20= ||=20(db->mTrace=20&=20SQLITE_TRACE_PROFILE)=20!=3D=200)=0A=20=09=09=20=20= =20=20&&=20!db->init.busy=20&&=20p->zSql)=20{=0Adiff=20--git=20= a/src/box/sql/vdbeaux.c=20b/src/box/sql/vdbeaux.c=0Aindex=20= 775e1aae7..e0f665c37=20100644=0A---=20a/src/box/sql/vdbeaux.c=0A+++=20= b/src/box/sql/vdbeaux.c=0A@@=20-65,17=20+65,7=20@@=20= sqlite3VdbeCreate(Parse=20*=20pParse)=0A=20=09db->pVdbe=20=3D=20p;=0A=20=09= p->magic=20=3D=20VDBE_MAGIC_INIT;=0A=20=09p->pParse=20=3D=20pParse;=0A-=09= p->auto_commit=20=3D=20!box_txn();=0A=20=09p->schema_ver=20=3D=20= box_schema_version();=0A-=09if=20(!p->auto_commit)=20{=0A-=09=09= p->psql_txn=20=3D=20in_txn()->psql_txn;=0A-=09=09p->nDeferredCons=20=3D=20= p->psql_txn->nDeferredConsSave;=0A-=09=09p->nDeferredImmCons=20=3D=20= p->psql_txn->nDeferredImmConsSave;=0A-=09}=20else{=0A-=09=09p->psql_txn=20= =3D=20NULL;=0A-=09=09p->nDeferredCons=20=3D=200;=0A-=09=09= p->nDeferredImmCons=20=3D=200;=0A-=09}=0A=20=09assert(pParse->aLabel=20= =3D=3D=200);=0A=20=09assert(pParse->nLabel=20=3D=3D=200);=0A=20=09= assert(pParse->nOpAlloc=20=3D=3D=200);=0A@@=20-83,6=20+73,48=20@@=20= sqlite3VdbeCreate(Parse=20*=20pParse)=0A=20=09return=20p;=0A=20}=0A=20=0A= +struct=20sql_txn=20*=0A+sql_alloc_txn()=0A+{=0A+=09struct=20sql_txn=20= *txn=20=3D=20region_alloc_object(&fiber()->gc,=0A+=09=09=09=09=09=09=20=20= struct=20sql_txn);=0A+=09if=20(txn=20=3D=3D=20NULL)=20{=0A+=09=09= diag_set(OutOfMemory,=20sizeof(struct=20sql_txn),=20"region",=0A+=09=09=09= =20"struct=20sql_txn");=0A+=09=09return=20NULL;=0A+=09}=0A+=09= txn->pSavepoint=20=3D=20NULL;=0A+=09txn->fk_deferred_count=20=3D=200;=0A= +=09return=20txn;=0A+}=0A+=0A+int=0A+sql_vdbe_prepare(struct=20Vdbe=20= *vdbe)=0A+{=0A+=09assert(vdbe=20!=3D=20NULL);=0A+=09struct=20txn=20*txn=20= =3D=20in_txn();=0A+=09vdbe->auto_commit=20=3D=20txn=20=3D=3D=20NULL;=0A+=09= if=20(txn=20!=3D=20NULL)=20{=0A+=09=09/*=0A+=09=09=20*=20If=20= transaction=20has=20been=20started=20in=20Lua,=20then=0A+=09=09=20*=20= sql_txn=20is=20NULL.=20On=20the=20other=20hand,=20it=20is=20not=0A+=09=09= =20*=20critical,=20since=20in=20Lua=20it=20is=20impossible=20to=0A+=09=09= =20*=20check=20FK=20violations,=20at=20least=20now.=0A+=09=09=20*/=0A+=09= =09if=20(txn->psql_txn=20=3D=3D=20NULL)=20{=0A+=09=09=09txn->psql_txn=20= =3D=20sql_alloc_txn();=0A+=09=09=09if=20(txn->psql_txn=20=3D=3D=20NULL)=20= {=0A+=09=09=09=09sqlite3DbFree(vdbe->db,=20vdbe);=0A+=09=09=09=09return=20= -1;=0A+=09=09=09}=0A+=09=09}=0A+=09=09vdbe->psql_txn=20=3D=20= txn->psql_txn;=0A+=09}=20else=20{=0A+=09=09vdbe->psql_txn=20=3D=20NULL;=0A= +=09}=0A+=09return=200;=0A+}=0A+=0A=20/*=0A=20=20*=20Change=20the=20= error=20string=20stored=20in=20Vdbe.zErrMsg=0A=20=20*/=0A@@=20-2439,11=20= +2471,8=20@@=20sqlite3VdbeCloseStatement(Vdbe=20*=20p,=20int=20eOp)=0A=20= =09/*=0A=20=09=20*=20If=20we=20have=20an=20anonymous=20transaction=20= opened=20->=20perform=20eOp.=0A=20=09=20*/=0A-=09if=20(savepoint=20&&=20= eOp=20=3D=3D=20SAVEPOINT_ROLLBACK)=20{=0A+=09if=20(savepoint=20&&=20eOp=20= =3D=3D=20SAVEPOINT_ROLLBACK)=0A=20=09=09rc=20=3D=20= box_txn_rollback_to_savepoint(savepoint->tnt_savepoint);=0A-=09=09= p->nDeferredCons=20=3D=20savepoint->nDeferredCons;=0A-=09=09= p->nDeferredImmCons=20=3D=20savepoint->nDeferredImmCons;=0A-=09}=0A=20=09= p->anonymous_savepoint=20=3D=20NULL;=0A=20=09return=20rc;=0A=20}=0A@@=20= -2462,9=20+2491,9=20@@=20sqlite3VdbeCloseStatement(Vdbe=20*=20p,=20int=20= eOp)=0A=20int=0A=20sqlite3VdbeCheckFk(Vdbe=20*=20p,=20int=20deferred)=0A=20= {=0A-=09if=20((deferred=20&&=20(p->nDeferredCons=20+=20= p->nDeferredImmCons)=20>=200)=0A-=09=20=20=20=20||=20(!deferred=20&&=20= p->nFkConstraint=20>=200)=0A-=09=20=20=20=20)=20{=0A+=09if=20((deferred=20= &&=20p->psql_txn=20!=3D=20NULL=20&&=0A+=09=20=20=20=20=20= p->psql_txn->fk_deferred_count=20>=200)=20||=0A+=09=20=20=20=20= (!deferred=20&&=20p->nFkConstraint=20>=200))=20{=0A=20=09=09p->rc=20=3D=20= SQLITE_CONSTRAINT_FOREIGNKEY;=0A=20=09=09p->errorAction=20=3D=20= ON_CONFLICT_ACTION_ABORT;=0A=20=09=09sqlite3VdbeError(p,=20"FOREIGN=20= KEY=20constraint=20failed");=0A@@=20-2484,14=20+2513,11=20@@=20= sql_txn_begin(Vdbe=20*p)=0A=20=09struct=20txn=20*ptxn=20=3D=20= txn_begin(false);=0A=20=09if=20(ptxn=20=3D=3D=20NULL)=0A=20=09=09return=20= -1;=0A-=09ptxn->psql_txn=20=3D=20region_alloc_object(&fiber()->gc,=20= struct=20sql_txn);=0A+=09ptxn->psql_txn=20=3D=20sql_alloc_txn();=0A=20=09= if=20(ptxn->psql_txn=20=3D=3D=20NULL)=20{=0A=20=09=09box_txn_rollback();=0A= -=09=09diag_set(OutOfMemory,=20sizeof(struct=20sql_txn),=20"region",=0A-=09= =09=09=20"struct=20sql_txn");=0A=20=09=09return=20-1;=0A=20=09}=0A-=09= memset(ptxn->psql_txn,=200,=20sizeof(struct=20sql_txn));=0A=20=09= p->psql_txn=20=3D=20ptxn->psql_txn;=0A=20=09return=200;=0A=20}=0A@@=20= -2518,9=20+2544,7=20@@=20sql_savepoint(Vdbe=20*p,=20const=20char=20= *zName)=0A=20=09if=20(zName)=20{=0A=20=09=09pNew->zName=20=3D=20(char=20= *)&pNew[1];=0A=20=09=09memcpy(pNew->zName,=20zName,=20nName);=0A-=09}=0A= -=09pNew->nDeferredCons=20=3D=20p->nDeferredCons;=0A-=09= pNew->nDeferredImmCons=20=3D=20p->nDeferredImmCons;=0A+=09};=0A=20=09= return=20pNew;=0A=20}=0A=20=0A@@=20-2542,7=20+2566,6=20@@=20= sqlite3VdbeHalt(Vdbe=20*=20p)=0A=20{=0A=20=09int=20rc;=09=09=09/*=20Used=20= to=20store=20transient=20return=20codes=20*/=0A=20=09sqlite3=20*db=20=3D=20= p->db;=0A-=09struct=20session=20*user_session=20=3D=20current_session();=0A= =20=0A=20=09/*=20This=20function=20contains=20the=20logic=20that=20= determines=20if=20a=20statement=20or=0A=20=09=20*=20transaction=20will=20= be=20committed=20or=20rolled=20back=20as=20a=20result=20of=20the=0A@@=20= -2659,10=20+2682,6=20@@=20sqlite3VdbeHalt(Vdbe=20*=20p)=0A=20=09=09=09=09= =09sqlite3RollbackAll(p,=20SQLITE_OK);=0A=20=09=09=09=09=09p->nChange=20= =3D=200;=0A=20=09=09=09=09}=20else=20{=0A-=09=09=09=09=09= p->nDeferredCons=20=3D=200;=0A-=09=09=09=09=09p->nDeferredImmCons=20=3D=20= 0;=0A-=09=09=09=09=09user_session->sql_flags=20&=3D=0A-=09=09=09=09=09=20= =20=20=20~SQLITE_DeferFKs;=0A=20=09=09=09=09=09= sqlite3CommitInternalChanges();=0A=20=09=09=09=09}=0A=20=09=09=09}=20= else=20{=0A@@=20-2741,7=20+2760,7=20@@=20sqlite3VdbeHalt(Vdbe=20*=20p)=0A= =20=09=09fiber_gc();=0A=20=0A=20=09assert(db->nVdbeActive=20>=200=20||=20= box_txn()=20||=0A-=09=09=20=20=20=20=20=20=20p->anonymous_savepoint=20=3D=3D= =20NULL);=0A+=09=20=20=20=20=20=20=20p->anonymous_savepoint=20=3D=3D=20= NULL);=0A=20=09return=20(p->rc=20=3D=3D=20SQLITE_BUSY=20?=20SQLITE_BUSY=20= :=20SQLITE_OK);=0A=20}=0A=20=0Adiff=20--git=20a/src/box/txn.c=20= b/src/box/txn.c=0Aindex=20c5aaa8e0b..a97a04250=20100644=0A---=20= a/src/box/txn.c=0A+++=20b/src/box/txn.c=0A@@=20-289,7=20+289,18=20@@=20= txn_commit(struct=20txn=20*txn)=0A=20=09assert(txn=20=3D=3D=20in_txn());=0A= =20=0A=20=09assert(stailq_empty(&txn->stmts)=20||=20txn->engine);=0A-=0A= +=09/*=0A+=09=20*=20If=20transaction=20has=20been=20started=20in=20SQL,=20= deferred=0A+=09=20*=20foreign=20key=20constraints=20must=20not=20be=20= violated.=0A+=09=20*=20If=20not=20so,=20just=20rollback=20transaction.=0A= +=09=20*/=0A+=09if=20(txn->psql_txn=20!=3D=20NULL)=20{=0A+=09=09struct=20= sql_txn=20*sql_txn=20=3D=20txn->psql_txn;=0A+=09=09if=20= (sql_txn->fk_deferred_count=20!=3D=200)=20{=0A+=09=09=09= diag_set(ClientError,=20ER_FOREIGN_KEY_CONSTRAINT);=0A+=09=09=09goto=20= fail;=0A+=09=09}=0A+=09}=0A=20=09/*=20Do=20transaction=20conflict=20= resolving=20*/=0A=20=09if=20(txn->engine)=20{=0A=20=09=09if=20= (engine_prepare(txn->engine,=20txn)=20!=3D=200)=0A@@=20-458,6=20+469,8=20= @@=20box_txn_savepoint()=0A=20=09}=0A=20=09svp->stmt=20=3D=20= stailq_last(&txn->stmts);=0A=20=09svp->in_sub_stmt=20=3D=20= txn->in_sub_stmt;=0A+=09if=20(txn->psql_txn=20!=3D=20NULL)=0A+=09=09= svp->fk_deferred_count=20=3D=20txn->psql_txn->fk_deferred_count;=0A=20=09= return=20svp;=0A=20}=0A=20=0A@@=20-484,5=20+497,7=20@@=20= box_txn_rollback_to_savepoint(box_txn_savepoint_t=20*svp)=0A=20=09=09= return=20-1;=0A=20=09}=0A=20=09txn_rollback_to_svp(txn,=20svp->stmt);=0A= +=09if=20(txn->psql_txn=20!=3D=20NULL)=0A+=09=09= txn->psql_txn->fk_deferred_count=20=3D=20svp->fk_deferred_count;=0A=20=09= return=200;=0A=20}=0Adiff=20--git=20a/src/box/txn.h=20b/src/box/txn.h=0A= index=209aedd9452..00cf34cdd=20100644=0A---=20a/src/box/txn.h=0A+++=20= b/src/box/txn.h=0A@@=20-43,7=20+43,7=20@@=20extern=20"C"=20{=0A=20=0A=20= /**=20box=20statistics=20*/=0A=20extern=20struct=20rmean=20*rmean_box;=0A= -struct=20sql_txn;=0A+struct=20Savepoint;=0A=20=0A=20struct=20engine;=0A=20= struct=20space;=0A@@=20-95,10=20+95,30=20@@=20struct=20txn_savepoint=20{=0A= =20=09=20*=20an=20empty=20transaction.=0A=20=09=20*/=0A=20=09struct=20= stailq_entry=20*stmt;=0A+=09/**=0A+=09=20*=20Each=20foreign=20key=20= constraint=20is=20classified=20as=20either=0A+=09=20*=20immediate=20(by=20= default)=20or=20deferred.=20In=20autocommit=20mode=0A+=09=20*=20they=20= mean=20the=20same.=20Inside=20separate=20transaction,=0A+=09=20*=20= deferred=20FK=20constraints=20are=20not=20checked=20until=20the=0A+=09=20= *=20transaction=20tries=20to=20commit.=20For=20as=20long=20as=0A+=09=20*=20= a=20transaction=20is=20open,=20it=20is=20allowed=20to=20exist=20in=20a=0A= +=09=20*=20state=20violating=20any=20number=20of=20deferred=20FK=20= constraints.=0A+=09=20*/=0A+=09uint32_t=20fk_deferred_count;=0A=20};=0A=20= =0A=20extern=20double=20too_long_threshold;=0A=20=0A+struct=20sql_txn=20= {=0A+=09/**=20List=20of=20active=20SQL=20savepoints.=20*/=0A+=09struct=20= Savepoint=20*pSavepoint;=0A+=09/**=0A+=09=20*=20This=20variables=20= transfer=20deferred=20constraints=20from=20one=0A+=09=20*=20VDBE=20to=20= the=20next=20in=20the=20same=20transaction.=0A+=09=20*/=0A+=09uint32_t=20= fk_deferred_count;=0A+};=0A+=0A=20struct=20txn=20{=0A=20=09/**=0A=20=09=20= *=20A=20sequentially=20growing=20transaction=20id,=20assigned=20when=0A= diff=20--git=20a/test/box/misc.result=20b/test/box/misc.result=0Aindex=20= 4e437d88f..840abbb66=20100644=0A---=20a/test/box/misc.result=0A+++=20= b/test/box/misc.result=0A@@=20-389,6=20+389,7=20@@=20t;=0A=20=20=20-=20= 'box.error.LOCAL_INSTANCE_ID_IS_READ_ONLY=20:=20128'=0A=20=20=20-=20= 'box.error.FUNCTION_EXISTS=20:=2052'=0A=20=20=20-=20= 'box.error.UPDATE_ARG_TYPE=20:=2026'=0A+=20=20-=20= 'box.error.FOREIGN_KEY_CONSTRAINT=20:=20162'=0A=20=20=20-=20= 'box.error.CROSS_ENGINE_TRANSACTION=20:=2081'=0A=20=20=20-=20= 'box.error.ACTION_MISMATCH=20:=20160'=0A=20=20=20-=20= 'box.error.FORMAT_MISMATCH_INDEX_PART=20:=2027'=0Adiff=20--git=20= a/test/sql/transitive-transactions.result=20= b/test/sql/transitive-transactions.result=0Anew=20file=20mode=20100644=0A= index=20000000000..37b563a17=0A---=20/dev/null=0A+++=20= b/test/sql/transitive-transactions.result=0A@@=20-0,0=20+1,126=20@@=0A= +test_run=20=3D=20require('test_run').new()=0A+---=0A+...=0A= +test_run:cmd("setopt=20delimiter=20';'")=0A+---=0A+-=20true=0A+...=0A= +--=20These=20tests=20are=20aimed=20at=20checking=20transitive=20= transactions=0A+--=20between=20SQL=20and=20Lua.=20In=20particular,=20= make=20sure=20that=20deferred=20foreign=20keys=0A+--=20violations=20are=20= passed=20correctly.=0A+--=0A+box.begin()=20box.sql.execute('COMMIT');=0A= +---=0A+...=0A+box.begin()=20box.sql.execute('ROLLBACK');=0A+---=0A+...=0A= +box.sql.execute('BEGIN;')=20box.commit();=0A+---=0A+...=0A= +box.sql.execute('BEGIN;')=20box.rollback();=0A+---=0A+...=0A= +box.sql.execute('pragma=20foreign_keys=20=3D=201;');=0A+---=0A+...=0A= +box.sql.execute('CREATE=20TABLE=20parent(id=20INT=20PRIMARY=20KEY,=20y=20= INT=20UNIQUE);');=0A+---=0A+...=0A+box.sql.execute('CREATE=20TABLE=20= child(id=20INT=20PRIMARY=20KEY,=20x=20INT=20REFERENCES=20parent(y)=20= DEFERRABLE=20INITIALLY=20DEFERRED);');=0A+---=0A+...=0A+fk_violation_1=20= =3D=20function()=0A+=20=20=20=20box.begin()=0A+=20=20=20=20= box.sql.execute('INSERT=20INTO=20child=20VALUES=20(1,=201);')=0A+=20=20=20= =20box.sql.execute('COMMIT;')=0A+end;=0A+---=0A+...=0A+fk_violation_1();=0A= +---=0A+-=20error:=20'Can=20not=20commit=20transaction:=20deferred=20= foreign=20keys=20violations=20are=20not=20resolved'=0A+...=0A= +box.space.CHILD:select();=0A+---=0A+-=20[]=0A+...=0A+fk_violation_2=20=3D= =20function()=0A+=20=20=20=20box.sql.execute('BEGIN;')=0A+=20=20=20=20= box.sql.execute('INSERT=20INTO=20child=20VALUES=20(1,=201);')=0A+=20=20=20= =20box.commit()=0A+end;=0A+---=0A+...=0A+fk_violation_2();=0A+---=0A+-=20= error:=20'Can=20not=20commit=20transaction:=20deferred=20foreign=20keys=20= violations=20are=20not=20resolved'=0A+...=0A+box.space.CHILD:select();=0A= +---=0A+-=20[]=0A+...=0A+fk_violation_3=20=3D=20function()=0A+=20=20=20=20= box.begin()=0A+=20=20=20=20box.sql.execute('INSERT=20INTO=20child=20= VALUES=20(1,=201);')=0A+=20=20=20=20box.sql.execute('INSERT=20INTO=20= parent=20VALUES=20(1,=201);')=0A+=20=20=20=20box.commit()=0A+end;=0A+---=0A= +...=0A+fk_violation_3();=0A+---=0A+...=0A+box.space.CHILD:select();=0A= +---=0A+-=20-=20[1,=201]=0A+...=0A+box.space.PARENT:select();=0A+---=0A= +-=20-=20[1,=201]=0A+...=0A+--=20Make=20sure=20that=20'PRAGMA=20= defer_foreign_keys'=20works.=0A+--=0A+box.sql.execute('DROP=20TABLE=20= child;')=0A+box.sql.execute('CREATE=20TABLE=20child(id=20INT=20PRIMARY=20= KEY,=20x=20INT=20REFERENCES=20parent(y))')=0A+=0A+fk_defer=20=3D=20= function()=0A+=20=20=20=20box.begin()=0A+=20=20=20=20= box.sql.execute('INSERT=20INTO=20child=20VALUES=20(1,=202);')=0A+=20=20=20= =20box.sql.execute('INSERT=20INTO=20parent=20VALUES=20(2,=202);')=0A+=20=20= =20=20box.commit()=0A+end;=0A+---=0A+...=0A+fk_defer();=0A+---=0A+-=20= error:=20FOREIGN=20KEY=20constraint=20failed=0A+...=0A= +box.space.CHILD:select();=0A+---=0A+-=20[]=0A+...=0A= +box.space.PARENT:select();=0A+---=0A+-=20-=20[1,=201]=0A+...=0A= +box.sql.execute('PRAGMA=20defer_foreign_keys=20=3D=201;')=0A= +fk_defer();=0A+---=0A+...=0A+box.space.CHILD:select();=0A+---=0A+-=20-=20= [1,=202]=0A+...=0A+box.space.PARENT:select();=0A+---=0A+-=20-=20[1,=201]=0A= +=20=20-=20[2,=202]=0A+...=0A+--=20Cleanup=0A+box.sql.execute('DROP=20= TABLE=20child;');=0A+---=0A+...=0A+box.sql.execute('DROP=20TABLE=20= parent;');=0A+---=0A+...=0Adiff=20--git=20= a/test/sql/transitive-transactions.test.lua=20= b/test/sql/transitive-transactions.test.lua=0Anew=20file=20mode=20100644=0A= index=20000000000..14a1e8c6b=0A---=20/dev/null=0A+++=20= b/test/sql/transitive-transactions.test.lua=0A@@=20-0,0=20+1,65=20@@=0A= +test_run=20=3D=20require('test_run').new()=0A+test_run:cmd("setopt=20= delimiter=20';'")=0A+=0A+--=20These=20tests=20are=20aimed=20at=20= checking=20transitive=20transactions=0A+--=20between=20SQL=20and=20Lua.=20= In=20particular,=20make=20sure=20that=20deferred=20foreign=20keys=0A+--=20= violations=20are=20passed=20correctly.=0A+--=0A+=0A+box.begin()=20= box.sql.execute('COMMIT');=0A+box.begin()=20box.sql.execute('ROLLBACK');=0A= +box.sql.execute('BEGIN;')=20box.commit();=0A+box.sql.execute('BEGIN;')=20= box.rollback();=0A+=0A+box.sql.execute('pragma=20foreign_keys=20=3D=20= 1;');=0A+box.sql.execute('CREATE=20TABLE=20parent(id=20INT=20PRIMARY=20= KEY,=20y=20INT=20UNIQUE);');=0A+box.sql.execute('CREATE=20TABLE=20= child(id=20INT=20PRIMARY=20KEY,=20x=20INT=20REFERENCES=20parent(y)=20= DEFERRABLE=20INITIALLY=20DEFERRED);');=0A+=0A+fk_violation_1=20=3D=20= function()=0A+=20=20=20=20box.begin()=0A+=20=20=20=20= box.sql.execute('INSERT=20INTO=20child=20VALUES=20(1,=201);')=0A+=20=20=20= =20box.sql.execute('COMMIT;')=0A+end;=0A+fk_violation_1();=0A= +box.space.CHILD:select();=0A+=0A+fk_violation_2=20=3D=20function()=0A+=20= =20=20=20box.sql.execute('BEGIN;')=0A+=20=20=20=20= box.sql.execute('INSERT=20INTO=20child=20VALUES=20(1,=201);')=0A+=20=20=20= =20box.commit()=0A+end;=0A+fk_violation_2();=0A= +box.space.CHILD:select();=0A+=0A+fk_violation_3=20=3D=20function()=0A+=20= =20=20=20box.begin()=0A+=20=20=20=20box.sql.execute('INSERT=20INTO=20= child=20VALUES=20(1,=201);')=0A+=20=20=20=20box.sql.execute('INSERT=20= INTO=20parent=20VALUES=20(1,=201);')=0A+=20=20=20=20box.commit()=0A+end;=0A= +fk_violation_3();=0A+box.space.CHILD:select();=0A= +box.space.PARENT:select();=0A+=0A+--=20Make=20sure=20that=20'PRAGMA=20= defer_foreign_keys'=20works.=0A+--=0A+box.sql.execute('DROP=20TABLE=20= child;')=0A+box.sql.execute('CREATE=20TABLE=20child(id=20INT=20PRIMARY=20= KEY,=20x=20INT=20REFERENCES=20parent(y))')=0A+=0A+fk_defer=20=3D=20= function()=0A+=20=20=20=20box.begin()=0A+=20=20=20=20= box.sql.execute('INSERT=20INTO=20child=20VALUES=20(1,=202);')=0A+=20=20=20= =20box.sql.execute('INSERT=20INTO=20parent=20VALUES=20(2,=202);')=0A+=20=20= =20=20box.commit()=0A+end;=0A+fk_defer();=0A+box.space.CHILD:select();=0A= +box.space.PARENT:select();=0A+box.sql.execute('PRAGMA=20= defer_foreign_keys=20=3D=201;')=0A+fk_defer();=0A= +box.space.CHILD:select();=0A+box.space.PARENT:select();=0A+=0A+--=20= Cleanup=0A+box.sql.execute('DROP=20TABLE=20child;');=0A= +box.sql.execute('DROP=20TABLE=20parent;');=0A--=20=0A2.15.1=0A=0A= --Apple-Mail=_9448364C-9C4F-4B27-82F9-DE4DE10849DB Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset=us-ascii --Apple-Mail=_9448364C-9C4F-4B27-82F9-DE4DE10849DB--