Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH] sql: fix non-working REPLACE optimization
@ 2018-03-27 16:42 Bulat Niatshin
  2018-03-28  8:58 ` [tarantool-patches] " n.pettik
  0 siblings, 1 reply; 3+ messages in thread
From: Bulat Niatshin @ 2018-03-27 16:42 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Bulat Niatshin

SQL PRIMARY KEY constraint with ON CONFLICT REPLACE/IGNORE
optimization (when error action is REPLACE or IGNORE, table
doesn't have related foreign keys, attached triggers and
secondary indexes and because of that uniqueness can be checked
by Tarantool only without bytecode) didn't work. Also ON CONFLICT
IGNORE uniqueness check can be optimized. This patch contrains a
small fix for that.

Closes #3293
---
 src/box/sql/insert.c          | 59 +++++++++++++++++++++++++++++++++----------
 src/box/sql/sqliteInt.h       |  4 +--
 src/box/sql/update.c          |  7 +++--
 test/sql/on-conflict.result   | 32 +++++++++++++++++++++++
 test/sql/on-conflict.test.lua | 13 ++++++++++
 5 files changed, 97 insertions(+), 18 deletions(-)

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 42254ddf4..2c2fc41f2 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -829,10 +829,12 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 		 */
 		int isReplace;	/* Set to true if constraints may cause a replace */
 		int bUseSeek;	/* True to use OPFLAG_SEEKRESULT */
+		int optimization_availability = ON_CONFLICT_ACTION_NONE;
 		sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur,
 						iIdxCur, regIns, 0,
 						ipkColumn >= 0, onError,
-						endOfLoop, &isReplace, 0);
+						endOfLoop, &isReplace, 0,
+						&optimization_availability);
 		sqlite3FkCheck(pParse, pTab, 0, regIns, 0);
 
 		/* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE
@@ -849,7 +851,8 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 						SQLITE_ForeignKeys) == 0 ||
 					       sqlite3FkReferences(pTab) == 0));
 		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx,
-					 bUseSeek, onError);
+					 bUseSeek, onError,
+					 optimization_availability);
 	}
 
 	/* Update the count of rows that are inserted
@@ -1053,7 +1056,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 				u8 overrideError,	/* Override onError to this if not ON_CONFLICT_ACTION_DEFAULT */
 				int ignoreDest,		/* Jump to this label on an ON_CONFLICT_ACTION_IGNORE resolution */
 				int *pbMayReplace,	/* OUT: Set to true if constraint may cause a replace */
-				int *aiChng)		/* column i is unchanged if aiChng[i]<0 */
+				int *aiChng,		/* column i is unchanged if aiChng[i]<0 */
+				int *optimization_opt) 	/* optimization flag */
 {
 	Vdbe *v;		/* VDBE under constrution */
 	Index *pIdx;		/* Pointer to one of the indices */
@@ -1324,19 +1328,30 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 			onError = ON_CONFLICT_ACTION_ABORT;
 		}
 
-		/* Collision detection may be omitted if all of the following are true:
-		 *   (1) The conflict resolution algorithm is REPLACE
-		 *   (2) There are no secondary indexes on the table
-		 *   (3) No delete triggers need to be fired if there is a conflict
-		 *   (4) No FK constraint counters need to be updated if a conflict occurs.
+		/**
+		 * Collision detection may be omitted if all of
+		 * the following are true:
+		 *   (1) The conflict resolution algorithm is
+		 *       REPLACE or IGNORE.
+		 *   (2) There are no secondary indexes on the
+		 *       table.
+		 *   (3) No delete triggers need to be fired if
+		 *       there is a conflict.
+		 *   (4) No FK constraint counters need to be
+		 *       updated if a conflict occurs.
 		 */
 		if ((ix == 0 && pIdx->pNext == 0)	/* Condition 2 */
-		    && onError == ON_CONFLICT_ACTION_REPLACE	/* Condition 1 */
-		    && (0 == (user_session->sql_flags & SQLITE_RecTriggers)	/* Condition 3 */
+		    /* Condition 1 */
+		    && (onError == ON_CONFLICT_ACTION_REPLACE ||
+			onError == ON_CONFLICT_ACTION_IGNORE)
+		    /* Condition 3 */
+		    && (0 == (user_session->sql_flags & SQLITE_RecTriggers)
 			||0 == sqlite3TriggersExist(pTab, TK_DELETE, 0, 0))
-		    && (0 == (user_session->sql_flags & SQLITE_ForeignKeys) ||	/* Condition 4 */
+		    /* Condition 4 */
+		    && (0 == (user_session->sql_flags & SQLITE_ForeignKeys) ||
 			(0 == pTab->pFKey && 0 == sqlite3FkReferences(pTab)))
 		    ) {
+			*optimization_opt = onError;
 			sqlite3VdbeResolveLabel(v, addrUniqueOk);
 			continue;
 		}
@@ -1474,13 +1489,18 @@ sqlite3CompleteInsertion(Parse * pParse,	/* The parser context */
 			 int iIdxCur,		/* Primary index cursor */
 			 int *aRegIdx,		/* Register used by each index.  0 for unused indices */
 			 int useSeekResult,	/* True to set the USESEEKRESULT flag on OP_[Idx]Insert */
-			 u8 onError)
+			 u8 onError,
+			 int optimization_availability)
 {
 	Vdbe *v;		/* Prepared statements under construction */
 	Index *pIdx;		/* An index being inserted or updated */
 	u16 pik_flags;		/* flag values passed to the btree insert */
 	int opcode;
 
+	assert(optimization_availability == ON_CONFLICT_ACTION_NONE ||
+	       optimization_availability == ON_CONFLICT_ACTION_REPLACE ||
+	       optimization_availability == ON_CONFLICT_ACTION_IGNORE);
+
 	v = sqlite3GetVdbe(pParse);
 	assert(v != 0);
 	/* This table is not a VIEW */
@@ -1508,13 +1528,24 @@ sqlite3CompleteInsertion(Parse * pParse,	/* The parser context */
 	}
 	assert(pParse->nested == 0);
 
-	if (onError == ON_CONFLICT_ACTION_REPLACE) {
+	/**
+	 * onError represents override error in queries like
+	 * INSERT/UPDATE OR REPLACE, INSERT/UPDATE OR IGNORE.
+	 * when error action is strictly specified by user and
+	 * therefore should have been used even when optimization
+	 * is possible (uniqueness can be checked by Tarantool).
+	 */
+	if (onError == ON_CONFLICT_ACTION_REPLACE ||
+	    (optimization_availability == ON_CONFLICT_ACTION_REPLACE &&
+	     onError == ON_CONFLICT_ACTION_DEFAULT)) {
 		opcode = OP_IdxReplace;
 	} else {
 		opcode = OP_IdxInsert;
 	}
 
-	if (onError == ON_CONFLICT_ACTION_IGNORE) {
+	if (onError == ON_CONFLICT_ACTION_IGNORE ||
+	    (optimization_availability == ON_CONFLICT_ACTION_IGNORE &&
+	     onError == ON_CONFLICT_ACTION_DEFAULT)) {
 		pik_flags |= OPFLAG_OE_IGNORE;
 	} else if (onError == ON_CONFLICT_ACTION_FAIL) {
 		pik_flags |= OPFLAG_OE_FAIL;
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index e9c0b3195..cc90be537 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3702,8 +3702,8 @@ void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
 int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
 void sqlite3ResolvePartIdxLabel(Parse *, int);
 void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
-				     int, u8, u8, int, int *, int *);
-void sqlite3CompleteInsertion(Parse *, Table *, int, int *, int, u8);
+				     int, u8, u8, int, int *, int *, int *);
+void sqlite3CompleteInsertion(Parse *, Table *, int, int *, int, u8, int);
 int sqlite3OpenTableAndIndices(Parse *, Table *, int, u8, int, u8 *, int *,
 			       int *, u8, u8);
 void sqlite3BeginWriteOperation(Parse *, int);
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index bf413252d..15e02add8 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -562,6 +562,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	if (!isView) {
 		int addr1 = 0;	/* Address of jump instruction */
 		int bReplace = 0;	/* True if REPLACE conflict resolution might happen */
+		int optimization_availability = ON_CONFLICT_ACTION_NONE;
 
 		/* Do constraint checks. */
 		assert(regOldPk > 0);
@@ -569,7 +570,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 						iIdxCur, regNewPk,
 						regOldPk, chngPk, onError,
 						labelContinue, &bReplace,
-						aXRef);
+						aXRef,
+						&optimization_availability);
 
 		/* Do FK constraint checks. */
 		if (hasFK) {
@@ -616,7 +618,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		}
 
 		/* Insert the new index entries and the new record. */
-		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx, 0, onError);
+		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx, 0,
+					 onError, optimization_availability);
 
 		/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
 		 * handle rows (possibly in other tables) that refer via a foreign key
diff --git a/test/sql/on-conflict.result b/test/sql/on-conflict.result
index b011a6e6a..5e892eada 100644
--- a/test/sql/on-conflict.result
+++ b/test/sql/on-conflict.result
@@ -50,6 +50,32 @@ box.sql.execute("SELECT * FROM e")
   - [2, 2]
   - [3, 1]
 ...
+box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
+---
+...
+box.sql.execute("INSERT INTO t1 VALUES (9)")
+---
+...
+box.sql.execute("INSERT INTO t1 VALUES (9)")
+---
+...
+box.sql.execute("SELECT * FROM t1")
+---
+- - [9]
+...
+box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
+---
+...
+box.sql.execute("INSERT INTO t2 VALUES (9)")
+---
+...
+box.sql.execute("INSERT INTO t2 VALUES (9)")
+---
+...
+box.sql.execute("SELECT * FROM t2")
+---
+- - [9]
+...
 box.sql.execute('DROP TABLE t')
 ---
 ...
@@ -62,3 +88,9 @@ box.sql.execute('DROP TABLE p')
 box.sql.execute('DROP TABLE e')
 ---
 ...
+box.sql.execute('DROP TABLE t1')
+---
+...
+box.sql.execute('DROP TABLE t2')
+---
+...
diff --git a/test/sql/on-conflict.test.lua b/test/sql/on-conflict.test.lua
index dbf572a8b..1ff199d32 100644
--- a/test/sql/on-conflict.test.lua
+++ b/test/sql/on-conflict.test.lua
@@ -19,7 +19,20 @@ box.sql.execute("SELECT * FROM p")
 box.sql.execute("INSERT INTO e values (1, 1), (2, 2), (3, 1)")
 box.sql.execute("SELECT * FROM e")
 
+box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
+box.sql.execute("INSERT INTO t1 VALUES (9)")
+box.sql.execute("INSERT INTO t1 VALUES (9)")
+box.sql.execute("SELECT * FROM t1")
+
+box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
+box.sql.execute("INSERT INTO t2 VALUES (9)")
+box.sql.execute("INSERT INTO t2 VALUES (9)")
+
+box.sql.execute("SELECT * FROM t2")
+
 box.sql.execute('DROP TABLE t')
 box.sql.execute('DROP TABLE q')
 box.sql.execute('DROP TABLE p')
 box.sql.execute('DROP TABLE e')
+box.sql.execute('DROP TABLE t1')
+box.sql.execute('DROP TABLE t2')
-- 
2.14.1

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [tarantool-patches] Re: [PATCH] sql: fix non-working REPLACE optimization
  2018-03-27 16:42 [tarantool-patches] [PATCH] sql: fix non-working REPLACE optimization Bulat Niatshin
@ 2018-03-28  8:58 ` n.pettik
  0 siblings, 0 replies; 3+ messages in thread
From: n.pettik @ 2018-03-28  8:58 UTC (permalink / raw)
  To: Bulat Niatshin; +Cc: tarantool-patches

Hello.

Overall comment: functional part of patch seems to be OK
(just because tests are passing), but I don’t like implementation.

Code connected with error actions is very hard to read and understand
owing to mess of actions, constraints order etc. And you make it even worse.
Please, consider some refactoring/renaming/commenting.

> On 27 Mar 2018, at 19:42, Bulat Niatshin <niatshin@tarantool.org> wrote:
> 
> SQL PRIMARY KEY constraint with ON CONFLICT REPLACE/IGNORE
> optimization (when error action is REPLACE or IGNORE, table

It would be better firstly to state what optimisation is, then
enumerate conditions which are required, and explain why
this optimisation didn’t work and how you fixed it.

> doesn't have related foreign keys, attached triggers and
> secondary indexes and because of that uniqueness can be checked
> by Tarantool only without bytecode) didn't work. Also ON CONFLICT
> IGNORE uniqueness check can be optimized. This patch contrains a

Typo: contrains.

> small fix for that.
> 
> Closes #3293
> —

Put link to the branch and issue here.

> src/box/sql/insert.c          | 59 +++++++++++++++++++++++++++++++++----------
> src/box/sql/sqliteInt.h       |  4 +--
> src/box/sql/update.c          |  7 +++--
> test/sql/on-conflict.result   | 32 +++++++++++++++++++++++
> test/sql/on-conflict.test.lua | 13 ++++++++++
> 5 files changed, 97 insertions(+), 18 deletions(-)
> 
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 42254ddf4..2c2fc41f2 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -829,10 +829,12 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
> 		 */
> 		int isReplace;	/* Set to true if constraints may cause a replace */
> 		int bUseSeek;	/* True to use OPFLAG_SEEKRESULT */
> +		int optimization_availability = ON_CONFLICT_ACTION_NONE;
> 		sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur,
> 						iIdxCur, regIns, 0,
> 						ipkColumn >= 0, onError,
> -						endOfLoop, &isReplace, 0);
> +						endOfLoop, &isReplace, 0,
> +						&optimization_availability);
> 		sqlite3FkCheck(pParse, pTab, 0, regIns, 0);
> 
> 		/* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE
> @@ -849,7 +851,8 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
> 						SQLITE_ForeignKeys) == 0 ||
> 					       sqlite3FkReferences(pTab) == 0));
> 		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx,
> -					 bUseSeek, onError);
> +					 bUseSeek, onError,
> +					 optimization_availability);

Rename this variable: generally speaking ‘availability’ is a boolean
property. But you use it as an error action, i.e. assign to values from enum.
Maybe it is worth to move this flag to parse context, maybe move three flags
(bUseSeek, onError, optimisation_availability) to separate struct or pack
them into one bit-flag-variable. 

> 	}
> 
> 	/* Update the count of rows that are inserted
> @@ -1053,7 +1056,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
> 				u8 overrideError,	/* Override onError to this if not ON_CONFLICT_ACTION_DEFAULT */
> 				int ignoreDest,		/* Jump to this label on an ON_CONFLICT_ACTION_IGNORE resolution */
> 				int *pbMayReplace,	/* OUT: Set to true if constraint may cause a replace */
> -				int *aiChng)		/* column i is unchanged if aiChng[i]<0 */
> +				int *aiChng,		/* column i is unchanged if aiChng[i]<0 */
> +				int *optimization_opt) 	/* optimization flag */

Useless comment. Moreover, AFAIK we don’t use such commenting style.

> {
> 	Vdbe *v;		/* VDBE under constrution */
> 	Index *pIdx;		/* Pointer to one of the indices */
> @@ -1324,19 +1328,30 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
> 			onError = ON_CONFLICT_ACTION_ABORT;
> 		}
> 
> -		/* Collision detection may be omitted if all of the following are true:
> -		 *   (1) The conflict resolution algorithm is REPLACE
> -		 *   (2) There are no secondary indexes on the table
> -		 *   (3) No delete triggers need to be fired if there is a conflict
> -		 *   (4) No FK constraint counters need to be updated if a conflict occurs.
> +		/**

Comments starting from /** should appear only before functions.
Inside function start it with /*. 

> +		 * Collision detection may be omitted if all of
> +		 * the following are true:
> +		 *   (1) The conflict resolution algorithm is
> +		 *       REPLACE or IGNORE.
> +		 *   (2) There are no secondary indexes on the
> +		 *       table.
> +		 *   (3) No delete triggers need to be fired if
> +		 *       there is a conflict.
> +		 *   (4) No FK constraint counters need to be
> +		 *       updated if a conflict occurs.
> 		 */
> 		if ((ix == 0 && pIdx->pNext == 0)	/* Condition 2 */
> -		    && onError == ON_CONFLICT_ACTION_REPLACE	/* Condition 1 */
> -		    && (0 == (user_session->sql_flags & SQLITE_RecTriggers)	/* Condition 3 */
> +		    /* Condition 1 */
> +		    && (onError == ON_CONFLICT_ACTION_REPLACE ||
> +			onError == ON_CONFLICT_ACTION_IGNORE)
> +		    /* Condition 3 */
> +		    && (0 == (user_session->sql_flags & SQLITE_RecTriggers)
> 			||0 == sqlite3TriggersExist(pTab, TK_DELETE, 0, 0))
> -		    && (0 == (user_session->sql_flags & SQLITE_ForeignKeys) ||	/* Condition 4 */
> +		    /* Condition 4 */
> +		    && (0 == (user_session->sql_flags & SQLITE_ForeignKeys) ||
> 			(0 == pTab->pFKey && 0 == sqlite3FkReferences(pTab)))
> 		    ) {
> +			*optimization_opt = onError;

To be honest, this code looks VERY strange,
At least without explanation. Consider renaming/refactoring.

> 			sqlite3VdbeResolveLabel(v, addrUniqueOk);
> 			continue;
> 		}
> @@ -1474,13 +1489,18 @@ sqlite3CompleteInsertion(Parse * pParse,	/* The parser context */
> 			 int iIdxCur,		/* Primary index cursor */
> 			 int *aRegIdx,		/* Register used by each index.  0 for unused indices */
> 			 int useSeekResult,	/* True to set the USESEEKRESULT flag on OP_[Idx]Insert */
> -			 u8 onError)
> +			 u8 onError,
> +			 int optimization_availability)
> {
> 	Vdbe *v;		/* Prepared statements under construction */
> 	Index *pIdx;		/* An index being inserted or updated */
> 	u16 pik_flags;		/* flag values passed to the btree insert */
> 	int opcode;
> 
> +	assert(optimization_availability == ON_CONFLICT_ACTION_NONE ||
> +	       optimization_availability == ON_CONFLICT_ACTION_REPLACE ||
> +	       optimization_availability == ON_CONFLICT_ACTION_IGNORE);
> +
> 	v = sqlite3GetVdbe(pParse);
> 	assert(v != 0);
> 	/* This table is not a VIEW */
> @@ -1508,13 +1528,24 @@ sqlite3CompleteInsertion(Parse * pParse,	/* The parser context */
> 	}
> 	assert(pParse->nested == 0);
> 
> -	if (onError == ON_CONFLICT_ACTION_REPLACE) {
> +	/**
> +	 * onError represents override error in queries like
> +	 * INSERT/UPDATE OR REPLACE, INSERT/UPDATE OR IGNORE.
> +	 * when error action is strictly specified by user and
> +	 * therefore should have been used even when optimization
> +	 * is possible (uniqueness can be checked by Tarantool).

Re-phrase comment: put emphasis on WHY not WHAT
(WHAT can be seen from source code).

> +	 */
> +	if (onError == ON_CONFLICT_ACTION_REPLACE ||
> +	    (optimization_availability == ON_CONFLICT_ACTION_REPLACE &&
> +	     onError == ON_CONFLICT_ACTION_DEFAULT)) {
> 		opcode = OP_IdxReplace;
> 	} else {
> 		opcode = OP_IdxInsert;
> 	}
> 
> -	if (onError == ON_CONFLICT_ACTION_IGNORE) {
> +	if (onError == ON_CONFLICT_ACTION_IGNORE ||
> +	    (optimization_availability == ON_CONFLICT_ACTION_IGNORE &&
> +	     onError == ON_CONFLICT_ACTION_DEFAULT)) {
> 		pik_flags |= OPFLAG_OE_IGNORE;
> 	} else if (onError == ON_CONFLICT_ACTION_FAIL) {
> 		pik_flags |= OPFLAG_OE_FAIL;
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index e9c0b3195..cc90be537 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -3702,8 +3702,8 @@ void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
> int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
> void sqlite3ResolvePartIdxLabel(Parse *, int);
> void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
> -				     int, u8, u8, int, int *, int *);
> -void sqlite3CompleteInsertion(Parse *, Table *, int, int *, int, u8);
> +				     int, u8, u8, int, int *, int *, int *);
> +void sqlite3CompleteInsertion(Parse *, Table *, int, int *, int, u8, int);
> int sqlite3OpenTableAndIndices(Parse *, Table *, int, u8, int, u8 *, int *,
> 			       int *, u8, u8);
> void sqlite3BeginWriteOperation(Parse *, int);
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index bf413252d..15e02add8 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -562,6 +562,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
> 	if (!isView) {
> 		int addr1 = 0;	/* Address of jump instruction */
> 		int bReplace = 0;	/* True if REPLACE conflict resolution might happen */
> +		int optimization_availability = ON_CONFLICT_ACTION_NONE;
> 
> 		/* Do constraint checks. */
> 		assert(regOldPk > 0);
> @@ -569,7 +570,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
> 						iIdxCur, regNewPk,
> 						regOldPk, chngPk, onError,
> 						labelContinue, &bReplace,
> -						aXRef);
> +						aXRef,
> +						&optimization_availability);
> 
> 		/* Do FK constraint checks. */
> 		if (hasFK) {
> @@ -616,7 +618,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
> 		}
> 
> 		/* Insert the new index entries and the new record. */
> -		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx, 0, onError);
> +		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx, 0,
> +					 onError, optimization_availability);
> 
> 		/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
> 		 * handle rows (possibly in other tables) that refer via a foreign key
> diff --git a/test/sql/on-conflict.result b/test/sql/on-conflict.result
> index b011a6e6a..5e892eada 100644
> --- a/test/sql/on-conflict.result
> +++ b/test/sql/on-conflict.result
> @@ -50,6 +50,32 @@ box.sql.execute("SELECT * FROM e")
>   - [2, 2]
>   - [3, 1]
> ...
> +box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
> +---
> +...
> +box.sql.execute("INSERT INTO t1 VALUES (9)")
> +---
> +...
> +box.sql.execute("INSERT INTO t1 VALUES (9)")
> +---
> +...
> +box.sql.execute("SELECT * FROM t1")
> +---
> +- - [9]
> +...
> +box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
> +---
> +...
> +box.sql.execute("INSERT INTO t2 VALUES (9)")
> +---
> +...
> +box.sql.execute("INSERT INTO t2 VALUES (9)")
> +---
> +...
> +box.sql.execute("SELECT * FROM t2")
> +---
> +- - [9]
> +...
> box.sql.execute('DROP TABLE t')
> ---
> ...
> @@ -62,3 +88,9 @@ box.sql.execute('DROP TABLE p')
> box.sql.execute('DROP TABLE e')
> ---
> ...
> +box.sql.execute('DROP TABLE t1')
> +---
> +...
> +box.sql.execute('DROP TABLE t2')
> +---
> +...
> diff --git a/test/sql/on-conflict.test.lua b/test/sql/on-conflict.test.lua
> index dbf572a8b..1ff199d32 100644
> --- a/test/sql/on-conflict.test.lua
> +++ b/test/sql/on-conflict.test.lua
> @@ -19,7 +19,20 @@ box.sql.execute("SELECT * FROM p")
> box.sql.execute("INSERT INTO e values (1, 1), (2, 2), (3, 1)")
> box.sql.execute("SELECT * FROM e")
> 
> +box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
> +box.sql.execute("INSERT INTO t1 VALUES (9)")
> +box.sql.execute("INSERT INTO t1 VALUES (9)")
> +box.sql.execute("SELECT * FROM t1")
> +
> +box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
> +box.sql.execute("INSERT INTO t2 VALUES (9)")
> +box.sql.execute("INSERT INTO t2 VALUES (9)")
> +
> +box.sql.execute("SELECT * FROM t2")
> +
> box.sql.execute('DROP TABLE t')
> box.sql.execute('DROP TABLE q')
> box.sql.execute('DROP TABLE p')
> box.sql.execute('DROP TABLE e')
> +box.sql.execute('DROP TABLE t1')
> +box.sql.execute('DROP TABLE t2')
> -- 
> 2.14.1
> 
> 

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [tarantool-patches] [PATCH] sql: fix non-working REPLACE optimization
@ 2018-03-29 22:25 Bulat Niatshin
  0 siblings, 0 replies; 3+ messages in thread
From: Bulat Niatshin @ 2018-03-29 22:25 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Bulat Niatshin

In some cases ON CONFLICT REPLACE/IGNORE can be optimized. If
following conditions are true:
1) Space has PRIMARY KEY index only.
2) There are no foreign keys to other spaces.
3) There are no delete triggers to be fired if conflict happens.
4) The errt or action is REPLACE or IGNORE.

Then uniqueness can be ensured by Tarantool only, without VDBE
bytecode. This patch contains a small fix for that +
related code was refactored a little bit and necessary comments
were added.

Closes #3293
---

Branch:
https://github.com/tarantool/tarantool/tree/bn/gh-3293-unique-opt

Issue:
https://github.com/tarantool/tarantool/issues/3293

 src/box/sql/insert.c          | 144 ++++++++++++++++++++++++++++++++----------
 src/box/sql/sqliteInt.h       |  27 +++++++-
 src/box/sql/update.c          |   9 ++-
 test/sql/on-conflict.result   |  32 ++++++++++
 test/sql/on-conflict.test.lua |  13 ++++
 5 files changed, 186 insertions(+), 39 deletions(-)

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 0e305d34b..fc9b121b0 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -835,9 +835,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 		 */
 		int isReplace;	/* Set to true if constraints may cause a replace */
 		int bUseSeek;	/* True to use OPFLAG_SEEKRESULT */
+
+		struct on_conflict on_conflict;
+		on_conflict.override_error = onError;
+		on_conflict.optimized_on_conflict = ON_CONFLICT_ACTION_NONE;
 		sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur,
 						iIdxCur, regIns, 0,
-						ipkColumn >= 0, onError,
+						ipkColumn >= 0, &on_conflict,
 						endOfLoop, &isReplace, 0);
 		sqlite3FkCheck(pParse, pTab, 0, regIns, 0);
 
@@ -855,7 +859,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 						SQLITE_ForeignKeys) == 0 ||
 					       sqlite3FkReferences(pTab) == 0));
 		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx,
-					 bUseSeek, onError);
+					 bUseSeek, &on_conflict);
 	}
 
 	/* Update the count of rows that are inserted
@@ -1056,7 +1060,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 				int regNewData,		/* First register in a range holding values to insert */
 				int regOldData,		/* Previous content.  0 for INSERTs */
 				u8 pkChng,		/* Non-zero if the PRIMARY KEY changed */
-				u8 overrideError,	/* Override onError to this if not ON_CONFLICT_ACTION_DEFAULT */
+				struct on_conflict *on_conflict,
 				int ignoreDest,		/* Jump to this label on an ON_CONFLICT_ACTION_IGNORE resolution */
 				int *pbMayReplace,	/* OUT: Set to true if constraint may cause a replace */
 				int *aiChng)		/* column i is unchanged if aiChng[i]<0 */
@@ -1075,6 +1079,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 	u8 isUpdate;		/* True if this is an UPDATE operation */
 	u8 bAffinityDone = 0;	/* True if the OP_Affinity operation has been run */
 	struct session *user_session = current_session();
+	int override_error = on_conflict->override_error;
 
 	isUpdate = regOldData != 0;
 	db = pParse->db;
@@ -1107,8 +1112,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 			continue;	/* This column is allowed to be NULL */
 
 		onError = table_column_nullable_action(pTab, i);
-		if (overrideError != ON_CONFLICT_ACTION_DEFAULT) {
-			onError = overrideError;
+		if (override_error != ON_CONFLICT_ACTION_DEFAULT) {
+			onError = override_error;
 		} else if (onError == ON_CONFLICT_ACTION_DEFAULT) {
 			onError = ON_CONFLICT_ACTION_ABORT;
 		}
@@ -1166,9 +1171,12 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 			     SQLITE_IgnoreChecks) == 0) {
 		ExprList *pCheck = pTab->pCheck;
 		pParse->ckBase = regNewData + 1;
-		onError =
-		    overrideError != ON_CONFLICT_ACTION_DEFAULT ? overrideError
-			: ON_CONFLICT_ACTION_ABORT;
+
+		if (override_error != ON_CONFLICT_ACTION_DEFAULT)
+			onError = override_error;
+		else
+			onError = ON_CONFLICT_ACTION_ABORT;
+
 		for (i = 0; i < pCheck->nExpr; i++) {
 			int allOk;
 			Expr *pExpr = pCheck->a[i].pExpr;
@@ -1210,10 +1218,15 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 		int addrUniqueOk;	/* Jump here if the UNIQUE constraint is satisfied */
 		bool uniqueByteCodeNeeded = false;
 
+		/*
+		 * ABORT and DEFAULT error actions can be handled
+		 * by Tarantool only without emitting VDBE
+		 * bytecode.
+		 */
 		if ((pIdx->onError != ON_CONFLICT_ACTION_ABORT &&
 		     pIdx->onError != ON_CONFLICT_ACTION_DEFAULT) ||
-		    (overrideError != ON_CONFLICT_ACTION_ABORT &&
-		     overrideError != ON_CONFLICT_ACTION_DEFAULT)) {
+		    (override_error != ON_CONFLICT_ACTION_ABORT &&
+		     override_error != ON_CONFLICT_ACTION_DEFAULT)) {
 			uniqueByteCodeNeeded = true;
 		}
 
@@ -1318,41 +1331,73 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 			continue;
 		}
 
-		/* Find out what action to take in case there is a uniqueness conflict */
-		onError = pIdx->onError;
-		if (onError == ON_CONFLICT_ACTION_NONE) {
+		if (!IsUniqueIndex(pIdx)) {
 			sqlite3VdbeResolveLabel(v, addrUniqueOk);
-			continue;	/* pIdx is not a UNIQUE index */
+			continue;
 		}
-		/* If pIdx is not a UNIQUE or we are doing INSERT OR IGNORE,
-		 * INSERT OR FAIL then skip uniqueness checks and let it to be
-		 * done by Tarantool.
+
+		/*
+		 * Find out what action to take in case there is
+		 * a uniqueness conflict.
+		 */
+		onError = pIdx->onError;
+
+		/*
+		 * If we are doing INSERT OR IGNORE,
+		 * INSERT OR REPLACE then uniqueness can be
+		 * ensured by Tarantool only without bytecode,
+		 * because IGNORE/REPLACE error action will be
+		 * used for all indexes.
 		 */
-		if (overrideError == ON_CONFLICT_ACTION_FAIL ||
-		    overrideError == ON_CONFLICT_ACTION_IGNORE ||
-		    overrideError == ON_CONFLICT_ACTION_ABORT) {
+		if (override_error == ON_CONFLICT_ACTION_FAIL ||
+		    override_error == ON_CONFLICT_ACTION_IGNORE ||
+		    override_error == ON_CONFLICT_ACTION_ABORT) {
 			sqlite3VdbeResolveLabel(v, addrUniqueOk);
 			continue;	/* pIdx is not a UNIQUE index */
 		}
-		if (overrideError != ON_CONFLICT_ACTION_DEFAULT) {
-			onError = overrideError;
+
+		/*
+		 * override_error is an action which is directly
+		 * specified by user in queries like
+		 * INSERT OR <override_error> and therefore
+		 * should have higher priority than indexes
+		 * error actions.
+		 */
+		if (override_error != ON_CONFLICT_ACTION_DEFAULT) {
+			onError = override_error;
 		} else if (onError == ON_CONFLICT_ACTION_DEFAULT) {
 			onError = ON_CONFLICT_ACTION_ABORT;
 		}
 
-		/* Collision detection may be omitted if all of the following are true:
-		 *   (1) The conflict resolution algorithm is REPLACE
-		 *   (2) There are no secondary indexes on the table
-		 *   (3) No delete triggers need to be fired if there is a conflict
-		 *   (4) No FK constraint counters need to be updated if a conflict occurs.
+		/*
+		 * Collision detection may be omitted if all of
+		 * the following are true:
+		 *   (1) The conflict resolution algorithm is
+		 *       REPLACE or IGNORE.
+		 *   (2) There are no secondary indexes on the
+		 *       table.
+		 *   (3) No delete triggers need to be fired if
+		 *       there is a conflict.
+		 *   (4) No FK constraint counters need to be
+		 *       updated if a conflict occurs.
 		 */
 		if ((ix == 0 && pIdx->pNext == 0)	/* Condition 2 */
-		    && onError == ON_CONFLICT_ACTION_REPLACE	/* Condition 1 */
-		    && (0 == (user_session->sql_flags & SQLITE_RecTriggers)	/* Condition 3 */
+		    /* Condition 1 */
+		    && (onError == ON_CONFLICT_ACTION_REPLACE ||
+			onError == ON_CONFLICT_ACTION_IGNORE)
+		    /* Condition 3 */
+		    && (0 == (user_session->sql_flags & SQLITE_RecTriggers)
 			||0 == sqlite3TriggersExist(pTab, TK_DELETE, 0, 0))
-		    && (0 == (user_session->sql_flags & SQLITE_ForeignKeys) ||	/* Condition 4 */
+		    /* Condition 4 */
+		    && (0 == (user_session->sql_flags & SQLITE_ForeignKeys) ||
 			(0 == pTab->pFKey && 0 == sqlite3FkReferences(pTab)))
 		    ) {
+			/*
+			 * In that case assign optimized error
+			 * action as IGNORE or REPLACE.
+			 */
+			on_conflict->optimized_on_conflict = onError;
+
 			sqlite3VdbeResolveLabel(v, addrUniqueOk);
 			continue;
 		}
@@ -1425,7 +1470,19 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 			}
 		}
 
-		/* Generate code that executes if the new index entry is not unique */
+		/*
+		 * Generate bytecode which executes when entry is
+		 * not unique for constraints with following
+		 * error actions:
+		 * 1) ROLLBACK.
+		 * 2) FAIL.
+		 * 3) IGNORE.
+		 * 4) REPLACE.
+		 *
+		 * ON CONFLICT ABORT is a default error action
+		 * for constraints and therefore can be handled
+		 * by Tarantool only.
+		 */
 		assert(onError == ON_CONFLICT_ACTION_ROLLBACK
 		       || onError == ON_CONFLICT_ACTION_ABORT
 		       || onError == ON_CONFLICT_ACTION_FAIL
@@ -1490,12 +1547,18 @@ sqlite3CompleteInsertion(Parse * pParse,	/* The parser context */
 			 int iIdxCur,		/* Primary index cursor */
 			 int *aRegIdx,		/* Register used by each index.  0 for unused indices */
 			 int useSeekResult,	/* True to set the USESEEKRESULT flag on OP_[Idx]Insert */
-			 u8 onError)
+			 struct on_conflict *on_conflict)
 {
 	Vdbe *v;		/* Prepared statements under construction */
 	Index *pIdx;		/* An index being inserted or updated */
 	u16 pik_flags;		/* flag values passed to the btree insert */
 	int opcode;
+	int override_error = on_conflict->override_error;
+	int optimized_error_action = on_conflict->optimized_on_conflict;
+
+	assert(optimized_error_action == ON_CONFLICT_ACTION_NONE ||
+	       optimized_error_action == ON_CONFLICT_ACTION_REPLACE ||
+	       optimized_error_action == ON_CONFLICT_ACTION_IGNORE);
 
 	v = sqlite3GetVdbe(pParse);
 	assert(v != 0);
@@ -1524,15 +1587,26 @@ sqlite3CompleteInsertion(Parse * pParse,	/* The parser context */
 	}
 	assert(pParse->nested == 0);
 
-	if (onError == ON_CONFLICT_ACTION_REPLACE) {
+	/*
+	 * override_error represents error action in queries like
+	 * INSERT/UPDATE OR REPLACE, INSERT/UPDATE OR IGNORE,
+	 * which is strictly specified by user and therefore
+	 * should have been used even when optimization is
+	 * possible (uniqueness can be checked by Tarantool).
+	 */
+	if (override_error == ON_CONFLICT_ACTION_REPLACE ||
+	    (optimized_error_action == ON_CONFLICT_ACTION_REPLACE &&
+	     override_error == ON_CONFLICT_ACTION_DEFAULT)) {
 		opcode = OP_IdxReplace;
 	} else {
 		opcode = OP_IdxInsert;
 	}
 
-	if (onError == ON_CONFLICT_ACTION_IGNORE) {
+	if (override_error == ON_CONFLICT_ACTION_IGNORE ||
+	    (optimized_error_action == ON_CONFLICT_ACTION_IGNORE &&
+	     override_error == ON_CONFLICT_ACTION_DEFAULT)) {
 		pik_flags |= OPFLAG_OE_IGNORE;
-	} else if (onError == ON_CONFLICT_ACTION_FAIL) {
+	} else if (override_error == ON_CONFLICT_ACTION_FAIL) {
 		pik_flags |= OPFLAG_OE_FAIL;
 	}
 
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index dc045f97b..5492ca44c 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -666,6 +666,27 @@ enum sql_type {
 	SQLITE_NULL = 5,
 };
 
+/**
+ * Structure for internal usage during INSERT/UPDATE
+ * statements compilation.
+ */
+struct on_conflict {
+	/**
+	 * Represents an error action in queries like
+	 * INSERT/UPDATE OR <override_error>, which
+	 * overrides all space constraints error actions.
+	 */
+	int override_error;
+	/**
+	 * Represents an ON CONFLICT action which can be
+	 * optimized and executed without VDBE bytecode, by
+	 * Tarantool only. If optimization is not available, then
+	 * the value is ON_CONFLICT_ACTION_NONE, otherwise it is
+	 * ON_CONFLICT_ACTON_IGNORE or ON_CONFLICT_ACTION_REPLACE.
+	 */
+	int optimized_on_conflict;
+};
+
 void *
 sqlite3_user_data(sqlite3_context *);
 
@@ -3709,8 +3730,10 @@ void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
 int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
 void sqlite3ResolvePartIdxLabel(Parse *, int);
 void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
-				     int, u8, u8, int, int *, int *);
-void sqlite3CompleteInsertion(Parse *, Table *, int, int *, int, u8);
+				     int, u8, struct on_conflict *, int,
+				     int *, int *);
+void sqlite3CompleteInsertion(Parse *, Table *, int, int *, int,
+			      struct on_conflict *);
 int sqlite3OpenTableAndIndices(Parse *, Table *, int, u8, int, u8 *, int *,
 			       int *, u8, u8);
 void sqlite3BeginWriteOperation(Parse *, int);
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index bf413252d..c888aab1a 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -563,11 +563,15 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		int addr1 = 0;	/* Address of jump instruction */
 		int bReplace = 0;	/* True if REPLACE conflict resolution might happen */
 
+		struct on_conflict on_conflict;
+		on_conflict.override_error = onError;
+		on_conflict.optimized_on_conflict = ON_CONFLICT_ACTION_NONE;
+
 		/* Do constraint checks. */
 		assert(regOldPk > 0);
 		sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur,
 						iIdxCur, regNewPk,
-						regOldPk, chngPk, onError,
+						regOldPk, chngPk, &on_conflict,
 						labelContinue, &bReplace,
 						aXRef);
 
@@ -616,7 +620,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		}
 
 		/* Insert the new index entries and the new record. */
-		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx, 0, onError);
+		sqlite3CompleteInsertion(pParse, pTab, iIdxCur, aRegIdx, 0,
+					 &on_conflict);
 
 		/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
 		 * handle rows (possibly in other tables) that refer via a foreign key
diff --git a/test/sql/on-conflict.result b/test/sql/on-conflict.result
index b011a6e6a..5e892eada 100644
--- a/test/sql/on-conflict.result
+++ b/test/sql/on-conflict.result
@@ -50,6 +50,32 @@ box.sql.execute("SELECT * FROM e")
   - [2, 2]
   - [3, 1]
 ...
+box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
+---
+...
+box.sql.execute("INSERT INTO t1 VALUES (9)")
+---
+...
+box.sql.execute("INSERT INTO t1 VALUES (9)")
+---
+...
+box.sql.execute("SELECT * FROM t1")
+---
+- - [9]
+...
+box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
+---
+...
+box.sql.execute("INSERT INTO t2 VALUES (9)")
+---
+...
+box.sql.execute("INSERT INTO t2 VALUES (9)")
+---
+...
+box.sql.execute("SELECT * FROM t2")
+---
+- - [9]
+...
 box.sql.execute('DROP TABLE t')
 ---
 ...
@@ -62,3 +88,9 @@ box.sql.execute('DROP TABLE p')
 box.sql.execute('DROP TABLE e')
 ---
 ...
+box.sql.execute('DROP TABLE t1')
+---
+...
+box.sql.execute('DROP TABLE t2')
+---
+...
diff --git a/test/sql/on-conflict.test.lua b/test/sql/on-conflict.test.lua
index dbf572a8b..1ff199d32 100644
--- a/test/sql/on-conflict.test.lua
+++ b/test/sql/on-conflict.test.lua
@@ -19,7 +19,20 @@ box.sql.execute("SELECT * FROM p")
 box.sql.execute("INSERT INTO e values (1, 1), (2, 2), (3, 1)")
 box.sql.execute("SELECT * FROM e")
 
+box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
+box.sql.execute("INSERT INTO t1 VALUES (9)")
+box.sql.execute("INSERT INTO t1 VALUES (9)")
+box.sql.execute("SELECT * FROM t1")
+
+box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
+box.sql.execute("INSERT INTO t2 VALUES (9)")
+box.sql.execute("INSERT INTO t2 VALUES (9)")
+
+box.sql.execute("SELECT * FROM t2")
+
 box.sql.execute('DROP TABLE t')
 box.sql.execute('DROP TABLE q')
 box.sql.execute('DROP TABLE p')
 box.sql.execute('DROP TABLE e')
+box.sql.execute('DROP TABLE t1')
+box.sql.execute('DROP TABLE t2')
-- 
2.14.1

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2018-03-29 22:25 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-03-27 16:42 [tarantool-patches] [PATCH] sql: fix non-working REPLACE optimization Bulat Niatshin
2018-03-28  8:58 ` [tarantool-patches] " n.pettik
2018-03-29 22:25 [tarantool-patches] " Bulat Niatshin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox