Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH] sql: xfer optimization issue
@ 2018-04-18 15:32 N.Tatunov
  2018-04-18 16:33 ` [tarantool-patches] " Hollow111
  2018-08-21 16:43 ` Kirill Yukhin
  0 siblings, 2 replies; 38+ messages in thread
From: N.Tatunov @ 2018-04-18 15:32 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, N.Tatunov

Currently insertion from the table to another one
with the same schema using SELECT works wrong.
The problem lies in xfer optimization which opens cursors for
all of the indexes and inserts data excessively.
The bug was fixed so the data should now insert
correctly.

Closes #3307
---

Branch: https://github.com/tarantool/tarantool/tree/N_Tatunov/gh-3307-xfer-optimization-issue
Issue: https://github.com/tarantool/tarantool/issues/3307

 src/box/sql/insert.c                               | 35 ++++++++--------
 .../sql-tap/gh-3307-xfer-optimization-issue.result |  0
 .../gh-3307-xfer-optimization-issue.test.lua       | 49 ++++++++++++++++++++++
 3 files changed, 67 insertions(+), 17 deletions(-)
 create mode 100644 test/sql-tap/gh-3307-xfer-optimization-issue.result
 create mode 100644 test/sql-tap/gh-3307-xfer-optimization-issue.test.lua

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index ae8dafb..b27fc23 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1908,24 +1908,25 @@ xferOptimization(Parse * pParse,	/* Parser context */
 				break;
 		}
 		assert(pSrcIdx);
-		emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
-		sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
-		VdbeComment((v, "%s", pSrcIdx->zName));
-		emit_open_cursor(pParse, iDest, pDestIdx->tnum);
-		sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
-		sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
-		VdbeComment((v, "%s", pDestIdx->zName));
-		addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
-		VdbeCoverage(v);
-		sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
-		sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
-		if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
+		if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY) {
+			emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
+			sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
+			VdbeComment((v, "%s", pSrcIdx->zName));
+			emit_open_cursor(pParse, iDest, pDestIdx->tnum);
+			sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
+			sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
+			VdbeComment((v, "%s", pDestIdx->zName));
+			addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
+			VdbeCoverage(v);
+			sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
 			sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
-		sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
-		VdbeCoverage(v);
-		sqlite3VdbeJumpHere(v, addr1);
-		sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
-		sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+			sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
+			VdbeCoverage(v);
+			sqlite3VdbeJumpHere(v, addr1);
+			sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+			sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+		}
 	}
 	if (emptySrcTest)
 		sqlite3VdbeJumpHere(v, emptySrcTest);
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.result b/test/sql-tap/gh-3307-xfer-optimization-issue.result
new file mode 100644
index 0000000..e69de29
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
new file mode 100644
index 0000000..e45235e
--- /dev/null
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -0,0 +1,49 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(1)
+
+-- gh-3307 - sql: INSERT with SELECT is not working in some cases.
+
+test:do_exesql_test(
+	"xfer-optimization-1.1",
+	[[
+		CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+		INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+		CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+		INSERT INTO t2 SELECT * FROM t1;
+		DROP TABLE t1;
+		DROP TABLE t2;
+	]], {
+		-- <xfer-optimization-1.1>
+		-- <xfer-optimization-1.1>
+	})
+
+test:do_exesql_test(
+	"xfer-optimization-1.2",
+	[[
+		CREATE TABLE t1(id INTEGER PRIMARY KEY, b INTEGER);
+		CREATE TABLE t2(id INTEGER PRIMARY KEY, b INTEGER);
+		CREATE INDEX i1 ON t1(b);
+		CREATE INDEX i2 ON t2(b);
+		INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+		INSERT INTO t2 SELECT * FROM t1;
+		DROP TABLE t1;
+		DROP TABLE t2;
+	]], {
+		-- <xfer-optimization-1.2>
+		-- <xfer-optimization-1.2>
+	})
+		
+test:do_exesql_test(
+	"xfer-optimization-1.1",
+	[[
+		CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+		INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+		CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+		INSERT INTO t2 SELECT * FROM t1;
+		DROP TABLE t1;
+		DROP TABLE t2;
+	]], {
+		-- <xfer-optimization-1.1>
+		-- <xfer-optimization-1.1>
+	})
-- 
2.7.4

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-18 15:32 [tarantool-patches] [PATCH] sql: xfer optimization issue N.Tatunov
@ 2018-04-18 16:33 ` Hollow111
  2018-04-19 11:22   ` n.pettik
  2018-08-21 16:43 ` Kirill Yukhin
  1 sibling, 1 reply; 38+ messages in thread
From: Hollow111 @ 2018-04-18 16:33 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev

[-- Attachment #1: Type: text/plain, Size: 9487 bytes --]

Hello. I have notices some mistakes.
Newer diff:

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index ae8dafb..b27fc23 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1908,24 +1908,25 @@ xferOptimization(Parse * pParse, /* Parser context
*/
  break;
  }
  assert(pSrcIdx);
- emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
- sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
- VdbeComment((v, "%s", pSrcIdx->zName));
- emit_open_cursor(pParse, iDest, pDestIdx->tnum);
- sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
- sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
- VdbeComment((v, "%s", pDestIdx->zName));
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
- VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
- if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
+ if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY) {
+ emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
+ sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
+ VdbeComment((v, "%s", pSrcIdx->zName));
+ emit_open_cursor(pParse, iDest, pDestIdx->tnum);
+ sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
+ sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
+ VdbeComment((v, "%s", pDestIdx->zName));
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
  sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
- sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
- VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
- sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+ sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
+ VdbeCoverage(v);
+ sqlite3VdbeJumpHere(v, addr1);
+ sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+ }
  }
  if (emptySrcTest)
  sqlite3VdbeJumpHere(v, emptySrcTest);
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
new file mode 100755
index 0000000..3b2bcc6
--- /dev/null
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -0,0 +1,52 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(3)
+
+test:do_execsql_test(
+ "xfer-optimization-1.1",
+ [[
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t2 SELECT * FROM t1;
+ DROP TABLE t1;
+ DROP TABLE t2;
+ ]], {
+ -- <xfer-optimization-1.1>
+
+ -- <xfer-optimization-1.1>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.2",
+ [[
+ CREATE TABLE t1(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE TABLE t2(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE INDEX i1 ON t1(b);
+ CREATE INDEX i2 ON t2(b);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ INSERT INTO t2 SELECT * FROM t1;
+ DROP TABLE t1;
+ DROP TABLE t2;
+ ]], {
+ -- <xfer-optimization-1.2>
+
+ -- <xfer-optimization-1.2>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.3",
+ [[
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ DROP TABLE t1;
+ DROP TABLE t2;
+ ]], {
+ -- <xfer-optimization-1.3>
+
+ -- <xfer-optimization-1.3>
+ })
+
+test:finish_test()


ср, 18 апр. 2018 г. в 18:32, N.Tatunov <hollow653@gmail.com>:

> Currently insertion from the table to another one
> with the same schema using SELECT works wrong.
> The problem lies in xfer optimization which opens cursors for
> all of the indexes and inserts data excessively.
> The bug was fixed so the data should now insert
> correctly.
>
> Closes #3307
> ---
>
> Branch:
> https://github.com/tarantool/tarantool/tree/N_Tatunov/gh-3307-xfer-optimization-issue
> Issue: https://github.com/tarantool/tarantool/issues/3307
>
>  src/box/sql/insert.c                               | 35 ++++++++--------
>  .../sql-tap/gh-3307-xfer-optimization-issue.result |  0
>  .../gh-3307-xfer-optimization-issue.test.lua       | 49
> ++++++++++++++++++++++
>  3 files changed, 67 insertions(+), 17 deletions(-)
>  create mode 100644 test/sql-tap/gh-3307-xfer-optimization-issue.result
>  create mode 100644 test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
>
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index ae8dafb..b27fc23 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1908,24 +1908,25 @@ xferOptimization(Parse * pParse,        /* Parser
> context */
>                                 break;
>                 }
>                 assert(pSrcIdx);
> -               emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
> -               sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
> -               VdbeComment((v, "%s", pSrcIdx->zName));
> -               emit_open_cursor(pParse, iDest, pDestIdx->tnum);
> -               sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
> -               sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
> -               VdbeComment((v, "%s", pDestIdx->zName));
> -               addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
> -               VdbeCoverage(v);
> -               sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
> -               sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
> -               if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
> +               if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY) {
> +                       emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
> +                       sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
> +                       VdbeComment((v, "%s", pSrcIdx->zName));
> +                       emit_open_cursor(pParse, iDest, pDestIdx->tnum);
> +                       sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
> +                       sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
> +                       VdbeComment((v, "%s", pDestIdx->zName));
> +                       addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
> +                       VdbeCoverage(v);
> +                       sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
> +                       sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
>                         sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
> -               sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
> -               VdbeCoverage(v);
> -               sqlite3VdbeJumpHere(v, addr1);
> -               sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
> -               sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
> +                       sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
> +                       VdbeCoverage(v);
> +                       sqlite3VdbeJumpHere(v, addr1);
> +                       sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
> +                       sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
> +               }
>         }
>         if (emptySrcTest)
>                 sqlite3VdbeJumpHere(v, emptySrcTest);
> diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.result
> b/test/sql-tap/gh-3307-xfer-optimization-issue.result
> new file mode 100644
> index 0000000..e69de29
> diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> new file mode 100644
> index 0000000..e45235e
> --- /dev/null
> +++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> @@ -0,0 +1,49 @@
> +#!/usr/bin/env tarantool
> +test = require("sqltester")
> +test:plan(1)
> +
> +-- gh-3307 - sql: INSERT with SELECT is not working in some cases.
> +
> +test:do_exesql_test(
> +       "xfer-optimization-1.1",
> +       [[
> +               CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
> +               INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
> +               CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
> +               INSERT INTO t2 SELECT * FROM t1;
> +               DROP TABLE t1;
> +               DROP TABLE t2;
> +       ]], {
> +               -- <xfer-optimization-1.1>
> +               -- <xfer-optimization-1.1>
> +       })
> +
> +test:do_exesql_test(
> +       "xfer-optimization-1.2",
> +       [[
> +               CREATE TABLE t1(id INTEGER PRIMARY KEY, b INTEGER);
> +               CREATE TABLE t2(id INTEGER PRIMARY KEY, b INTEGER);
> +               CREATE INDEX i1 ON t1(b);
> +               CREATE INDEX i2 ON t2(b);
> +               INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
> +               INSERT INTO t2 SELECT * FROM t1;
> +               DROP TABLE t1;
> +               DROP TABLE t2;
> +       ]], {
> +               -- <xfer-optimization-1.2>
> +               -- <xfer-optimization-1.2>
> +       })
> +
> +test:do_exesql_test(
> +       "xfer-optimization-1.1",
> +       [[
> +               CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
> +               INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
> +               CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
> +               INSERT INTO t2 SELECT * FROM t1;
> +               DROP TABLE t1;
> +               DROP TABLE t2;
> +       ]], {
> +               -- <xfer-optimization-1.1>
> +               -- <xfer-optimization-1.1>
> +       })
> --
> 2.7.4
>
>

[-- Attachment #2: Type: text/html, Size: 15258 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-18 16:33 ` [tarantool-patches] " Hollow111
@ 2018-04-19 11:22   ` n.pettik
  2018-04-19 15:36     ` Hollow111
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-04-19 11:22 UTC (permalink / raw)
  To: tarantool-patches; +Cc: N. Tatunov

>The bug was fixed so the data should now insert
>correctly.

Please, instead of mentioning that you just fixed bug (it is obvious),
provide brief information (without digging in details) how the problem was solved. 
(e.g. 'now only PK is used to handle insertion').

Overall, the idea is OK, but implementation could be more elegant.
You don’t need to iterate through all dest/source indexes:
it is possible to get PK using function sqlite3PrimaryKeyIndex();
Thus, complexity reduces from O(n^2) to O(n), where n - number of indexes.

But, there is even better approach: in Tarantool PK always comes with 0 ordinal
number. So, you can do space lookup by id (there is macros, which converts
table->tnum to space id: SQLITE_PAGENO_TO_SPACEID) and fetch real PK
index with O(1) complexity: space_index(space, 0 /* PK */);
It is not mandatory now, only if you are willing to do it.

Also, as we have discussed, remove pls redundant uniqueness check.

>  	}
>  	if (emptySrcTest)
>  		sqlite3VdbeJumpHere(v, emptySrcTest);
> diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> new file mode 100755
> index 0000000..3b2bcc6
> --- /dev/null
> +++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> @@ -0,0 +1,52 @@
> +#!/usr/bin/env tarantool
> +test = require("sqltester")
> +test:plan(3)
> +
> +test:do_execsql_test(
> +	"xfer-optimization-1.1",
> +	[[
> +		CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
> +		INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
> +		CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
> +		INSERT INTO t2 SELECT * FROM t1;
> +		DROP TABLE t1;
> +		DROP TABLE t2;
> +	]], {
> +		-- <xfer-optimization-1.1>
> +
> +		-- <xfer-optimization-1.1>
> +	})

do_execsql_test() returns result of last executed query.
In this case, it is ‘DROP TABLE’, which always (in this particular case)
will return nothing (i.e. table will be successfully dropped).
To catch some error, you can use do_catchsql_test() function.
After you check that insertion occurs without errors, you need
to check that all rows have been transferred from one table to another.
So, you just use do_execsql_test() to test 'SELECT * FROM t2;’.
After all, you may drop tables in the beginning of next test,
since it won’t affect result of last executed statement.

Moreover, I would add more test cases to verify that xfer
optimization in general works: try to rearrange columns/indexes
order, add different ON CONFLICT clauses etc.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-19 11:22   ` n.pettik
@ 2018-04-19 15:36     ` Hollow111
  2018-04-20  1:02       ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Hollow111 @ 2018-04-19 15:36 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 12305 bytes --]

Fixes were made:

чт, 19 апр. 2018 г. в 14:22, n.pettik <korablev@tarantool.org>:

> >The bug was fixed so the data should now insert
> >correctly.
>
> Please, instead of mentioning that you just fixed bug (it is obvious),
> provide brief information (without digging in details) how the problem was
> solved.
> (e.g. 'now only PK is used to handle insertion').
>
> Overall, the idea is OK, but implementation could be more elegant.
> You don’t need to iterate through all dest/source indexes:
> it is possible to get PK using function sqlite3PrimaryKeyIndex();
> Thus, complexity reduces from O(n^2) to O(n), where n - number of indexes.
>
> But, there is even better approach: in Tarantool PK always comes with 0
> ordinal
> number. So, you can do space lookup by id (there is macros, which converts
> table->tnum to space id: SQLITE_PAGENO_TO_SPACEID) and fetch real PK
> index with O(1) complexity: space_index(space, 0 /* PK */);
> It is not mandatory now, only if you are willing to do it.
>
> Also, as we have discussed, remove pls redundant uniqueness check.
>
> >       }
> >       if (emptySrcTest)
> >               sqlite3VdbeJumpHere(v, emptySrcTest);
> > diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> > new file mode 100755
> > index 0000000..3b2bcc6
> > --- /dev/null
> > +++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> > @@ -0,0 +1,52 @@
> > +#!/usr/bin/env tarantool
> > +test = require("sqltester")
> > +test:plan(3)
> > +
> > +test:do_execsql_test(
> > +     "xfer-optimization-1.1",
> > +     [[
> > +             CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
> > +             INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
> > +             CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
> > +             INSERT INTO t2 SELECT * FROM t1;
> > +             DROP TABLE t1;
> > +             DROP TABLE t2;
> > +     ]], {
> > +             -- <xfer-optimization-1.1>
> > +
> > +             -- <xfer-optimization-1.1>
> > +     })
>
> do_execsql_test() returns result of last executed query.
> In this case, it is ‘DROP TABLE’, which always (in this particular case)
> will return nothing (i.e. table will be successfully dropped).
> To catch some error, you can use do_catchsql_test() function.
> After you check that insertion occurs without errors, you need
> to check that all rows have been transferred from one table to another.
> So, you just use do_execsql_test() to test 'SELECT * FROM t2;’.
> After all, you may drop tables in the beginning of next test,
> since it won’t affect result of last executed statement.
>
> Moreover, I would add more test cases to verify that xfer
> optimization in general works: try to rearrange columns/indexes
> order, add different ON CONFLICT clauses etc.
>
>
Currently insertion from the table to another one
with the same schema using SELECT works wrong.
The problem lies in xfer optimization which opens cursors for
all of the indexes and inserts data excessively.
Now only PRIMARY KEY is used to handle insertion.
Moreover analyzing xfer optimization I have noticed
that unnecessary check from sqlite3 were used,
accordingly excessive code cencerned with it
was deleted.

Closes #3307
---

Branch:
https://github.com/tarantool/tarantool/tree/N_Tatunov/gh-3307-xfer-optimization-issue
Issue: https://github.com/tarantool/tarantool/issues/3307

 src/box/sql/insert.c                               |  64 ++++-----
 .../gh-3307-xfer-optimization-issue.test.lua       | 159
+++++++++++++++++++++
 2 files changed, 184 insertions(+), 39 deletions(-)
 create mode 100755 test/sql-tap/gh-3307-xfer-optimization-issue.test.lua

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index ae8dafb..ed134f4 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1711,6 +1711,7 @@ xferOptimization(Parse * pParse, /* Parser context */
  ExprList *pEList; /* The result set of the SELECT */
  Table *pSrc; /* The table in the FROM clause of SELECT */
  Index *pSrcIdx, *pDestIdx; /* Source and destination indices */
+ struct index *src_idx, *dest_idx; /* Source and destination indices */
  struct SrcList_item *pItem; /* An element of pSelect->pSrc */
  int i; /* Loop counter */
  int iSrc, iDest; /* Cursors from source and destination */
@@ -1718,9 +1719,9 @@ xferOptimization(Parse * pParse, /* Parser context */
  int emptyDestTest = 0; /* Address of test for empty pDest */
  int emptySrcTest = 0; /* Address of test for empty pSrc */
  Vdbe *v; /* The VDBE we are building */
- int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */
  int regData, regTupleid; /* Registers holding data and tupleid */
  struct session *user_session = current_session();
+ struct space *space; /* Space pointer for pDest and pSrc */

  if (pSelect == NULL)
  return 0; /* Must be of the form  INSERT INTO ... SELECT ... */
@@ -1830,9 +1831,6 @@ xferOptimization(Parse * pParse, /* Parser context */
  }
  }
  for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- if (index_is_unique(pDestIdx)) {
- destHasUniqueIdx = 1;
- }
  for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
  if (xferCompatibleIndex(pDestIdx, pSrcIdx))
  break;
@@ -1875,52 +1873,40 @@ xferOptimization(Parse * pParse, /* Parser context
*/
  regData = sqlite3GetTempReg(pParse);
  regTupleid = sqlite3GetTempReg(pParse);
  sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);
- assert(destHasUniqueIdx);
- if ((pDest->iPKey < 0 && pDest->pIndex != 0) /* (1) */
-     ||destHasUniqueIdx /* (2) */
-     || (onError != ON_CONFLICT_ACTION_ABORT
- && onError != ON_CONFLICT_ACTION_ROLLBACK) /* (3) */
-     ) {
- /* In some circumstances, we are able to run the xfer optimization
- * only if the destination table is initially empty.
- * This block generates code to make
- * that determination.
- *
- * Conditions under which the destination must be empty:
- *
- * (1) There is no INTEGER PRIMARY KEY but there are indices.
- *
- * (2) The destination has a unique index.  (The xfer optimization
- *     is unable to test uniqueness.)
- *
- * (3) onError is something other than ON_CONFLICT_ACTION_ABORT and
_ROLLBACK.
- */
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
- VdbeCoverage(v);
- emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
- sqlite3VdbeJumpHere(v, addr1);
- }

- for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- for (pSrcIdx = pSrc->pIndex; ALWAYS(pSrcIdx);
-      pSrcIdx = pSrcIdx->pNext) {
- if (xferCompatibleIndex(pDestIdx, pSrcIdx))
- break;
- }
+ /* The xfer optimization is unable to test uniqueness
+ * while we have a unique PRIMARY KEY in any existing table.
+ * This is the reason we can only run it if the destination table
+ * is initially empty.
+ * This block generates code to make that determination.
+ */
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
+ VdbeCoverage(v);
+ emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
+ sqlite3VdbeJumpHere(v, addr1);
+
+ space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
+ dest_idx = space_index(space, 0 /* PK */);
+ space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
+ src_idx = space_index(space, 0 /* PK */);
+ assert(src_idx);
+ assert(dest_idx);
+ pDestIdx = sqlite3PrimaryKeyIndex(pDest);
+ pSrcIdx = sqlite3PrimaryKeyIndex(pSrc);
+ if (xferCompatibleIndex(pDestIdx, pSrcIdx)) {
  assert(pSrcIdx);
  emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
  sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
- VdbeComment((v, "%s", pSrcIdx->zName));
+ VdbeComment((v, "%s", src_idx->def->name));
  emit_open_cursor(pParse, iDest, pDestIdx->tnum);
  sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
  sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
- VdbeComment((v, "%s", pDestIdx->zName));
+ VdbeComment((v, "%s", dest_idx->def->name));
  addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
  VdbeCoverage(v);
  sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
  sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
- if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
- sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+ sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
  sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
  VdbeCoverage(v);
  sqlite3VdbeJumpHere(v, addr1);
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
new file mode 100755
index 0000000..1049621
--- /dev/null
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -0,0 +1,159 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(12)
+
+test:do_catchsql_test(
+ "xfer-optimization-1.1",
+ [[
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.1>
+ 0
+ -- <xfer-optimization-1.1>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.2",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.2>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-oprimization-1.2>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.3",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE TABLE t2(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE INDEX i1 ON t1(b);
+ CREATE INDEX i2 ON t2(b);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.3>
+ 0
+ -- <xfer-optimization-1.3>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.4",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.4>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.4>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.5",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
+ INSERT INTO t1 VALUES (1, 1, 2), (2, 2, 3), (3, 3, 4);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+
+ ]], {
+ -- <xfer-optimization-1.5>
+ 1, "table T2 has 2 columns but 3 values were supplied"
+ -- <xfer-optimization-1.5>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.6",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.6>
+
+ -- <xfer-oprimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.7",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+
+ ]], {
+ -- <xfer-optimization-1.7>
+ 0
+ -- <xfer-optimization-1.7>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.8",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.6>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-oprimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.9",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER, a INTEGER PRIMARY KEY);
+ INSERT INTO t2 SELECT * FROM t1;
+
+ ]], {
+ -- <xfer-optimization-1.9>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.9>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.10",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.10>
+
+ -- <xfer-oprimization-1.10>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.11",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER PRIMARY KEY, a INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+
+ ]], {
+ -- <xfer-optimization-1.11>
+ 0
+ -- <xfer-optimization-1.11>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.12",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.12>
+ 1, 1, 2, 2, 3, 2
+ -- <xfer-oprimization-1.12>
+ })
+
+test:finish_test()
-- 
2.7.4

[-- Attachment #2: Type: text/html, Size: 25713 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-19 15:36     ` Hollow111
@ 2018-04-20  1:02       ` n.pettik
  2018-04-20 15:09         ` Hollow111
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-04-20  1:02 UTC (permalink / raw)
  To: Hollow111; +Cc: tarantool-patches


> Currently insertion from the table to another one
> with the same schema using SELECT works wrong.
> The problem lies in xfer optimization which opens cursors for
> all of the indexes and inserts data excessively.
> Now only PRIMARY KEY is used to handle insertion.
> Moreover analyzing xfer optimization I have noticed

Nitpicking: it is not worth mentioning process of exploration,
just use (in this particular case) statement of facts.

> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index ae8dafb..ed134f4 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1711,6 +1711,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
>  	ExprList *pEList;	/* The result set of the SELECT */
>  	Table *pSrc;		/* The table in the FROM clause of SELECT */
>  	Index *pSrcIdx, *pDestIdx;	/* Source and destination indices */
> +	struct index *src_idx, *dest_idx;	/* Source and destination indices */

We don’t use forward var declaration: it is obsolete rule from ancient C standards.
Moreover, we place comments above the code to be commented.

> +	struct space *space;	/* Space pointer for pDest and pSrc */

The same is here.

> -	assert(destHasUniqueIdx);
> -	if ((pDest->iPKey < 0 && pDest->pIndex != 0)	/* (1) */
> -	    ||destHasUniqueIdx	/* (2) */
> -	    || (onError != ON_CONFLICT_ACTION_ABORT
> -		&& onError != ON_CONFLICT_ACTION_ROLLBACK)	/* (3) */
> -	    ) {
> -		/* In some circumstances, we are able to run the xfer optimization
> -		 * only if the destination table is initially empty.
> -		 * This block generates code to make
> -		 * that determination.
> -		 *
> -		 * Conditions under which the destination must be empty:
> -		 *
> -		 * (1) There is no INTEGER PRIMARY KEY but there are indices.
> -		 *
> -		 * (2) The destination has a unique index.  (The xfer optimization
> -		 *     is unable to test uniqueness.)
> -		 *
> -		 * (3) onError is something other than ON_CONFLICT_ACTION_ABORT and _ROLLBACK.
> -		 */
> -		addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
> -		VdbeCoverage(v);
> -		emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
> -		sqlite3VdbeJumpHere(v, addr1);
> -	}

Why did you delete if-clause? The code which checks that
table is empty is needed only in the cases in if-clauses,
i.e. in some cases xfer optimisation can be applied even if
table is not initially empty (as far as I understand).

> +	/* The xfer optimization is unable to test uniqueness
> +	 * while we have a unique PRIMARY KEY in any existing table.
> +	 * This is the reason we can only run it if the destination table

Nitpicking: comments should fit into 66 chars.

> +	 * is initially empty.
> +	 * This block generates code to make that determination.
> +	 */
> +	addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
> +	VdbeCoverage(v);
> +	emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
> +	sqlite3VdbeJumpHere(v, addr1);
> +
> +	space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
> +	dest_idx = space_index(space, 0 /* PK */);
> +	space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
> +	src_idx = space_index(space, 0 /* PK */);
> +	assert(src_idx);
> +	assert(dest_idx);

We use explicit != NULL checks.

> +	pDestIdx = sqlite3PrimaryKeyIndex(pDest);
> +	pSrcIdx = sqlite3PrimaryKeyIndex(pSrc);

In fact, you don’t need these indexes at all.
Instead of calling emit_open_cursor() you
can simply add two opcodes: OP_LoadPtr and OP_OpenWrite
(as it happens inside that function). Or, you can use pSrc->tnum.
Table’s tnum exactly encodes PK space id (index id is 0).
As for KeyInfo - you are able to remove it at all, since opcodes
below don’t rely on key info (AFAIK, better check it yourself).
The only issue remained - how to check PK compatibility.
I would add to xferCompatibleIndex() another one check:
If first index is PK, then second also must be PK.
It is easy to implement, since Index struct contains appropriate flag.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-20  1:02       ` n.pettik
@ 2018-04-20 15:09         ` Hollow111
  2018-04-20 16:09           ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Hollow111 @ 2018-04-20 15:09 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 4989 bytes --]

пт, 20 апр. 2018 г. в 4:02, n.pettik <korablev@tarantool.org>:

>
> > Currently insertion from the table to another one
> > with the same schema using SELECT works wrong.
> > The problem lies in xfer optimization which opens cursors for
> > all of the indexes and inserts data excessively.
> > Now only PRIMARY KEY is used to handle insertion.
> > Moreover analyzing xfer optimization I have noticed
>
> Nitpicking: it is not worth mentioning process of exploration,
> just use (in this particular case) statement of facts.
>
> > diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> > index ae8dafb..ed134f4 100644
> > --- a/src/box/sql/insert.c
> > +++ b/src/box/sql/insert.c
> > @@ -1711,6 +1711,7 @@ xferOptimization(Parse * pParse,        /* Parser
> context */
> >       ExprList *pEList;       /* The result set of the SELECT */
> >       Table *pSrc;            /* The table in the FROM clause of SELECT
> */
> >       Index *pSrcIdx, *pDestIdx;      /* Source and destination indices
> */
> > +     struct index *src_idx, *dest_idx;       /* Source and destination
> indices */
>
> We don’t use forward var declaration: it is obsolete rule from ancient C
> standards.
> Moreover, we place comments above the code to be commented.
>
> > +     struct space *space;    /* Space pointer for pDest and pSrc */
>
> The same is here.
>
> > -     assert(destHasUniqueIdx);
> > -     if ((pDest->iPKey < 0 && pDest->pIndex != 0)    /* (1) */
> > -         ||destHasUniqueIdx  /* (2) */
> > -         || (onError != ON_CONFLICT_ACTION_ABORT
> > -             && onError != ON_CONFLICT_ACTION_ROLLBACK)      /* (3) */
> > -         ) {
> > -             /* In some circumstances, we are able to run the xfer
> optimization
> > -              * only if the destination table is initially empty.
> > -              * This block generates code to make
> > -              * that determination.
> > -              *
> > -              * Conditions under which the destination must be empty:
> > -              *
> > -              * (1) There is no INTEGER PRIMARY KEY but there are
> indices.
> > -              *
> > -              * (2) The destination has a unique index.  (The xfer
> optimization
> > -              *     is unable to test uniqueness.)
> > -              *
> > -              * (3) onError is something other than
> ON_CONFLICT_ACTION_ABORT and _ROLLBACK.
> > -              */
> > -             addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
> > -             VdbeCoverage(v);
> > -             emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
> > -             sqlite3VdbeJumpHere(v, addr1);
> > -     }
>
> Why did you delete if-clause? The code which checks that
> table is empty is needed only in the cases in if-clauses,
> i.e. in some cases xfer optimisation can be applied even if
> table is not initially empty (as far as I understand).
>

Yes, that's true this code is needed if any of the
cases in if-clause != 0. But the point is that in Tarantool
there's always a PRIMARY KEY that is obviously unique
which means that the second case is always != 0 and
the result of the boolean expression is always not 0,
hence there's no reason for this if-check.


> > +     /* The xfer optimization is unable to test uniqueness
> > +      * while we have a unique PRIMARY KEY in any existing table.
> > +      * This is the reason we can only run it if the destination table
>
> Nitpicking: comments should fit into 66 chars.
>
> > +      * is initially empty.
> > +      * This block generates code to make that determination.
> > +      */
> > +     addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
> > +     VdbeCoverage(v);
> > +     emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
> > +     sqlite3VdbeJumpHere(v, addr1);
> > +
> > +     space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
> > +     dest_idx = space_index(space, 0 /* PK */);
> > +     space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
> > +     src_idx = space_index(space, 0 /* PK */);
> > +     assert(src_idx);
> > +     assert(dest_idx);
>
> We use explicit != NULL checks.
>
> > +     pDestIdx = sqlite3PrimaryKeyIndex(pDest);
> > +     pSrcIdx = sqlite3PrimaryKeyIndex(pSrc);
>
> In fact, you don’t need these indexes at all.
> Instead of calling emit_open_cursor() you
> can simply add two opcodes: OP_LoadPtr and OP_OpenWrite
> (as it happens inside that function). Or, you can use pSrc->tnum.
> Table’s tnum exactly encodes PK space id (index id is 0).
> As for KeyInfo - you are able to remove it at all, since opcodes
> below don’t rely on key info (AFAIK, better check it yourself).
> The only issue remained - how to check PK compatibility.
> I would add to xferCompatibleIndex() another one check:
> If first index is PK, then second also must be PK.
> It is easy to implement, since Index struct contains appropriate flag.
>
>

[-- Attachment #2: Type: text/html, Size: 6079 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-20 15:09         ` Hollow111
@ 2018-04-20 16:09           ` n.pettik
  2018-04-20 17:59             ` Hollow111
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-04-20 16:09 UTC (permalink / raw)
  To: Hollow111; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 474 bytes --]


> On 20 Apr 2018, at 18:09, Hollow111 <hollow653@gmail.com> wrote:
> 
> Yes, that's true this code is needed if any of the 
> cases in if-clause != 0. But the point is that in Tarantool
> there's always a PRIMARY KEY that is obviously unique
> which means that the second case is always != 0 and
> the result of the boolean expression is always not 0,
> hence there's no reason for this if-check.

Ok, I got it. You are right: I misunderstood conditions under if-clause.



[-- Attachment #2: Type: text/html, Size: 1380 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-20 16:09           ` n.pettik
@ 2018-04-20 17:59             ` Hollow111
  2018-04-23 23:40               ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Hollow111 @ 2018-04-20 17:59 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 9648 bytes --]

Diff for the fixed patch:

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index ae8dafb..87e1bad 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1645,6 +1645,9 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
  assert(pDest->pTable != pSrc->pTable);
  uint32_t nDestCol = index_column_count(pDest);
  uint32_t nSrcCol = index_column_count(pSrc);
+ /* One of them is PK while the other isn't */
+ if ((pDest->idxType == 2 && pSrc->idxType != 2)
+     || (pDest->idxType != 2 && pSrc->idxType == 2))
  if (nDestCol != nSrcCol) {
  return 0; /* Different number of columns */
  }
@@ -1711,16 +1714,21 @@ xferOptimization(Parse * pParse, /* Parser context
*/
  ExprList *pEList; /* The result set of the SELECT */
  Table *pSrc; /* The table in the FROM clause of SELECT */
  Index *pSrcIdx, *pDestIdx; /* Source and destination indices */
+ /* Source and destination indices */
+ struct index *src_idx, *dest_idx;
  struct SrcList_item *pItem; /* An element of pSelect->pSrc */
  int i; /* Loop counter */
  int iSrc, iDest; /* Cursors from source and destination */
  int addr1; /* Loop addresses */
  int emptyDestTest = 0; /* Address of test for empty pDest */
  int emptySrcTest = 0; /* Address of test for empty pSrc */
+ /* Number of memory cell for cursors */
+ int space_ptr_reg;
  Vdbe *v; /* The VDBE we are building */
- int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */
  int regData, regTupleid; /* Registers holding data and tupleid */
  struct session *user_session = current_session();
+ /* Space pointer for pDest and pSrc */
+ struct space *space;

  if (pSelect == NULL)
  return 0; /* Must be of the form  INSERT INTO ... SELECT ... */
@@ -1830,9 +1838,6 @@ xferOptimization(Parse * pParse, /* Parser context */
  }
  }
  for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- if (index_is_unique(pDestIdx)) {
- destHasUniqueIdx = 1;
- }
  for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
  if (xferCompatibleIndex(pDestIdx, pSrcIdx))
  break;
@@ -1875,58 +1880,51 @@ xferOptimization(Parse * pParse, /* Parser context
*/
  regData = sqlite3GetTempReg(pParse);
  regTupleid = sqlite3GetTempReg(pParse);
  sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);
- assert(destHasUniqueIdx);
- if ((pDest->iPKey < 0 && pDest->pIndex != 0) /* (1) */
-     ||destHasUniqueIdx /* (2) */
-     || (onError != ON_CONFLICT_ACTION_ABORT
- && onError != ON_CONFLICT_ACTION_ROLLBACK) /* (3) */
-     ) {
- /* In some circumstances, we are able to run the xfer optimization
- * only if the destination table is initially empty.
- * This block generates code to make
- * that determination.
- *
- * Conditions under which the destination must be empty:
- *
- * (1) There is no INTEGER PRIMARY KEY but there are indices.
- *
- * (2) The destination has a unique index.  (The xfer optimization
- *     is unable to test uniqueness.)
- *
- * (3) onError is something other than ON_CONFLICT_ACTION_ABORT and
_ROLLBACK.
- */
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
- VdbeCoverage(v);
- emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
- sqlite3VdbeJumpHere(v, addr1);
- }

- for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- for (pSrcIdx = pSrc->pIndex; ALWAYS(pSrcIdx);
-      pSrcIdx = pSrcIdx->pNext) {
- if (xferCompatibleIndex(pDestIdx, pSrcIdx))
- break;
- }
- assert(pSrcIdx);
- emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
- sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
- VdbeComment((v, "%s", pSrcIdx->zName));
- emit_open_cursor(pParse, iDest, pDestIdx->tnum);
- sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
- sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
- VdbeComment((v, "%s", pDestIdx->zName));
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
- VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
- if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
- sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
- sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
- VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
- sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
- }
+ /* The xfer optimization is unable to test
+ * uniqueness while we have a unique
+ * PRIMARY KEY in any existing table.
+ * This is the reason we can only run it
+ * if the destination table is initially empty.
+ * This block generates code to make
+ * that determination.
+ */
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
+ VdbeCoverage(v);
+ emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
+ sqlite3VdbeJumpHere(v, addr1);
+
+ pDestIdx = sqlite3PrimaryKeyIndex(pDest);
+ pSrcIdx = sqlite3PrimaryKeyIndex(pSrc);
+ space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
+ if((src_idx = space_index(space, 0 /* PK */)) == NULL)
+ return 0;
+ space_ptr_reg = ++pParse->nMem;
+ sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
+      (void *) space);
+ sqlite3VdbeAddOp3(v, OP_OpenWrite, iSrc,
+   pSrc->tnum, space_ptr_reg);
+ VdbeComment((v, "%s", src_idx->def->name));
+ space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
+ if((dest_idx = space_index(space, 0 /* PK */)) == NULL)
+ return 0;
+ space_ptr_reg = ++pParse->nMem;
+ sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
+      (void *) space);
+ sqlite3VdbeAddOp3(v, OP_OpenWrite, iDest,
+   pSrc->tnum, space_ptr_reg);
+ sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
+ VdbeComment((v, "%s", dest_idx->def->name));
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
+ sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+ sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
+ VdbeCoverage(v);
+ sqlite3VdbeJumpHere(v, addr1);
+ sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
  if (emptySrcTest)
  sqlite3VdbeJumpHere(v, emptySrcTest);
  sqlite3ReleaseTempReg(pParse, regTupleid);
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
new file mode 100755
index 0000000..0824c67
--- /dev/null
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -0,0 +1,155 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(12)
+
+test:do_catchsql_test(
+ "xfer-optimization-1.1",
+ [[
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.1>
+ 0
+ -- <xfer-optimization-1.1>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.2",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.2>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-oprimization-1.2>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.3",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE TABLE t2(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE INDEX i1 ON t1(b);
+ CREATE INDEX i2 ON t2(b);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.3>
+ 0
+ -- <xfer-optimization-1.3>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.4",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.4>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.4>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.5",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
+ INSERT INTO t1 VALUES (1, 1, 2), (2, 2, 3), (3, 3, 4);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.5>
+ 1, "table T2 has 2 columns but 3 values were supplied"
+ -- <xfer-optimization-1.5>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.6",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.6>
+
+ -- <xfer-oprimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.7",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.7>
+ 0
+ -- <xfer-optimization-1.7>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.8",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.6>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-oprimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.9",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER, a INTEGER PRIMARY KEY);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.9>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.9>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.10",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.10>
+
+ -- <xfer-oprimization-1.10>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.11",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER PRIMARY KEY, a INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.11>
+ 0
+ -- <xfer-optimization-1.11>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.12",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.12>
+ 1, 1, 2, 2, 3, 2
+ -- <xfer-oprimization-1.12>
+ })
+
+test:finish_test()


>

[-- Attachment #2: Type: text/html, Size: 24176 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-20 17:59             ` Hollow111
@ 2018-04-23 23:40               ` n.pettik
  2018-04-27 15:45                 ` Hollow111
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-04-23 23:40 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Hollow111

Hello. Consider comments below.

> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index ae8dafb..87e1bad 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1645,6 +1645,9 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
>  	assert(pDest->pTable != pSrc->pTable);
>  	uint32_t nDestCol = index_column_count(pDest);
>  	uint32_t nSrcCol = index_column_count(pSrc);
> +	/* One of them is PK while the other isn't */
> +	if ((pDest->idxType == 2 && pSrc->idxType != 2)
> +	    || (pDest->idxType != 2 && pSrc->idxType == 2))

It is better to use SQLITE_IDXTYPE_PRIMARYKEY macros,
then hardcoded constants.

But the main thing: you forgot to add ‘return 0;’ statement here.

Codestyle nitpicking: put binary operator to previous line.
Also, put dots at the end of sentences.

>  	if (nDestCol != nSrcCol) {
>  		return 0;	/* Different number of columns */
>  	}
> @@ -1711,16 +1714,21 @@ xferOptimization(Parse * pParse,	/* Parser context */
>  	ExprList *pEList;	/* The result set of the SELECT */
>  	Table *pSrc;		/* The table in the FROM clause of SELECT */
>  	Index *pSrcIdx, *pDestIdx;	/* Source and destination indices */
> +	/* Source and destination indices */
> +	struct index *src_idx, *dest_idx;

You didn’t fixed remark from previous review:
>We don’t use forward var declaration: it is obsolete rule from ancient C standards.
Although it is not mandatory, but better fix it.

>  	struct SrcList_item *pItem;	/* An element of pSelect->pSrc */
>  	int i;			/* Loop counter */
>  	int iSrc, iDest;	/* Cursors from source and destination */
>  	int addr1;		/* Loop addresses */
>  	int emptyDestTest = 0;	/* Address of test for empty pDest */
>  	int emptySrcTest = 0;	/* Address of test for empty pSrc */
> +	/* Number of memory cell for cursors */
> +	int space_ptr_reg;

The same is here.

>  	Vdbe *v;		/* The VDBE we are building */
> -	int destHasUniqueIdx = 0;	/* True if pDest has a UNIQUE index */
>  	int regData, regTupleid;	/* Registers holding data and tupleid */
>  	struct session *user_session = current_session();
> +	/* Space pointer for pDest and pSrc */
> +	struct space *space;

And here.

> +	/* The xfer optimization is unable to test
> +	 * uniqueness while we have a unique
> +	 * PRIMARY KEY in any existing table.

Rephrase this sentence. Or I have missed smth:
uniqness is checked while tuple is inserted in space by
Tarantool facilities, isn’t it? For this reason, you can try to return
those if-checks for additional code-generation.
The only concern I have is about ‘on conflict replace'
error action: if source table isn’t initially empty, features 'on replace'
error action and you insert table which violates uniqueness - 
this replace action might fail.
I advise you to investigate such behaviour.

> +	 * This is the reason we can only run it
> +	 * if the destination table is initially empty.
> +	 * This block generates code to make
> +	 * that determination.
> +	 */
> +	addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
> +	VdbeCoverage(v);
> +	emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
> +	sqlite3VdbeJumpHere(v, addr1);
> +
> +	pDestIdx = sqlite3PrimaryKeyIndex(pDest);
> +	pSrcIdx = sqlite3PrimaryKeyIndex(pSrc);
> +	space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
> +	if((src_idx = space_index(space, 0 /* PK */)) == NULL)

Codestyle nitpicking: put space after ‘if’.
Also, do not ‘inline’ comments into code.
And why space can’t feature primary index?
AFAIK, only views don’t have PK, but src table is tested
to be real table:
	if (space_is_view(pSrc)) {
		return 0;	/* tab2 may not be a view */
	} 

> +		return 0;
> +	space_ptr_reg = ++pParse->nMem;
> +	sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
> +			     (void *) space);
> +	sqlite3VdbeAddOp3(v, OP_OpenWrite, iSrc,
> +			  pSrc->tnum, space_ptr_reg);
> +	VdbeComment((v, "%s", src_idx->def->name));
> +	space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));

Don’t be afraid of introducing new variables.
It would be better, if you use src_space and dest_space separately.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-23 23:40               ` n.pettik
@ 2018-04-27 15:45                 ` Hollow111
  2018-05-03 22:57                   ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Hollow111 @ 2018-04-27 15:45 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 12237 bytes --]

Hello. I considered your remarks and the following changes were made:

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index ae8dafb..734ff34 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1645,6 +1645,12 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
  assert(pDest->pTable != pSrc->pTable);
  uint32_t nDestCol = index_column_count(pDest);
  uint32_t nSrcCol = index_column_count(pSrc);
+ /* One of them is PK while the other isn't. */
+ if ((pDest->idxType == SQLITE_IDXTYPE_PRIMARYKEY &&
+      pSrc->idxType != SQLITE_IDXTYPE_PRIMARYKEY) ||
+     (pDest->idxType != SQLITE_IDXTYPE_PRIMARYKEY &&
+      pSrc->idxType == SQLITE_IDXTYPE_PRIMARYKEY))
+ return 0;
  if (nDestCol != nSrcCol) {
  return 0; /* Different number of columns */
  }
@@ -1718,9 +1724,10 @@ xferOptimization(Parse * pParse, /* Parser context */
  int emptyDestTest = 0; /* Address of test for empty pDest */
  int emptySrcTest = 0; /* Address of test for empty pSrc */
  Vdbe *v; /* The VDBE we are building */
- int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */
  int regData, regTupleid; /* Registers holding data and tupleid */
  struct session *user_session = current_session();
+ int dest_has_replace_action = 0;
+ int confl_action_default = 0;

  if (pSelect == NULL)
  return 0; /* Must be of the form  INSERT INTO ... SELECT ... */
@@ -1737,8 +1744,10 @@ xferOptimization(Parse * pParse, /* Parser context */
  if (onError == ON_CONFLICT_ACTION_DEFAULT) {
  if (pDest->iPKey >= 0)
  onError = pDest->keyConf;
- if (onError == ON_CONFLICT_ACTION_DEFAULT)
+ if (onError == ON_CONFLICT_ACTION_DEFAULT) {
  onError = ON_CONFLICT_ACTION_ABORT;
+ confl_action_default = 1;
+ }
  }
  assert(pSelect->pSrc); /* allocated even if there is no FROM clause */
  if (pSelect->pSrc->nSrc != 1) {
@@ -1828,15 +1837,16 @@ xferOptimization(Parse * pParse, /* Parser context
*/
  return 0; /* Default values must be the same for all columns */
  }
  }
+ if (pDestCol->notNull == ON_CONFLICT_ACTION_REPLACE)
+ dest_has_replace_action = 1;
  }
  for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- if (index_is_unique(pDestIdx)) {
- destHasUniqueIdx = 1;
- }
  for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
  if (xferCompatibleIndex(pDestIdx, pSrcIdx))
  break;
  }
+ if (pDestIdx->onError != ON_CONFLICT_ACTION_REPLACE)
+ dest_has_replace_action = 1;
  if (pSrcIdx == 0) {
  return 0; /* pDestIdx has no corresponding index in pSrc */
  }
@@ -1875,58 +1885,63 @@ xferOptimization(Parse * pParse, /* Parser context
*/
  regData = sqlite3GetTempReg(pParse);
  regTupleid = sqlite3GetTempReg(pParse);
  sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);
- assert(destHasUniqueIdx);
- if ((pDest->iPKey < 0 && pDest->pIndex != 0) /* (1) */
-     ||destHasUniqueIdx /* (2) */
-     || (onError != ON_CONFLICT_ACTION_ABORT
- && onError != ON_CONFLICT_ACTION_ROLLBACK) /* (3) */
-     ) {
- /* In some circumstances, we are able to run the xfer optimization
- * only if the destination table is initially empty.
- * This block generates code to make
- * that determination.
- *
- * Conditions under which the destination must be empty:
- *
- * (1) There is no INTEGER PRIMARY KEY but there are indices.
- *
- * (2) The destination has a unique index.  (The xfer optimization
- *     is unable to test uniqueness.)
- *
- * (3) onError is something other than ON_CONFLICT_ACTION_ABORT and
_ROLLBACK.
- */
+
+ /* Xfer optimization is unable to correctly insert
+ * data in case there's a conflict action
+ * other than ON_CONFLICT_ACTION_ROLLBACK or there's
+ * ON_CONFLICT_ACTION_DEFAULT which was transformed into
+ * ON_CONFLICT_ACTION_ABORT for insertion while we have a
+ * ON_CONFLICT_ACTION_REPLACE for any of constraints.
+ * This is the reason we want to only run it
+ * if the destination table is initially empty.
+ * That block generates code to make that determination.
+ */
+
+ if (onError != ON_CONFLICT_ACTION_ROLLBACK ||
+     (confl_action_default == 1 &&
+     dest_has_replace_action == 1)) {
  addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
  VdbeCoverage(v);
  emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
  sqlite3VdbeJumpHere(v, addr1);
  }

- for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- for (pSrcIdx = pSrc->pIndex; ALWAYS(pSrcIdx);
-      pSrcIdx = pSrcIdx->pNext) {
- if (xferCompatibleIndex(pDestIdx, pSrcIdx))
- break;
- }
- assert(pSrcIdx);
- emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
- sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
- VdbeComment((v, "%s", pSrcIdx->zName));
- emit_open_cursor(pParse, iDest, pDestIdx->tnum);
- sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
- sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
- VdbeComment((v, "%s", pDestIdx->zName));
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
- VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
- if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
- sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
- sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
- VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
- sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
- }
+ int space_ptr_reg = ++pParse->nMem;
+ struct space *src_space =
+ space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
+ struct space *dest_space =
+ space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
+ struct index *src_idx = space_index(src_space, 0);
+ struct index *dest_idx;
+
+ pDestIdx = sqlite3PrimaryKeyIndex(pDest);
+ pSrcIdx = sqlite3PrimaryKeyIndex(pSrc);
+ space_ptr_reg = ++pParse->nMem;
+ sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
+      (void *) src_space);
+ sqlite3VdbeAddOp3(v, OP_OpenWrite, iSrc,
+   pSrc->tnum, space_ptr_reg);
+ VdbeComment((v, "%s", src_idx->def->name));
+
+ if((dest_idx = space_index(dest_space, 0)) == NULL)
+ return 0;
+ space_ptr_reg = ++pParse->nMem;
+ sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
+      (void *) dest_space);
+ sqlite3VdbeAddOp3(v, OP_OpenWrite, iDest,
+   pSrc->tnum, space_ptr_reg);
+ sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
+ VdbeComment((v, "%s", dest_idx->def->name));
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
+ sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+ sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
+ VdbeCoverage(v);
+ sqlite3VdbeJumpHere(v, addr1);
+ sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
  if (emptySrcTest)
  sqlite3VdbeJumpHere(v, emptySrcTest);
  sqlite3ReleaseTempReg(pParse, regTupleid);
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
new file mode 100755
index 0000000..860b4c3
--- /dev/null
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -0,0 +1,233 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(18)
+
+test:do_catchsql_test(
+ "xfer-optimization-1.1",
+ [[
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.1>
+ 0
+ -- <xfer-optimization-1.1>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.2",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.2>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-oprimization-1.2>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.3",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE TABLE t2(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE INDEX i1 ON t1(b);
+ CREATE INDEX i2 ON t2(b);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.3>
+ 0
+ -- <xfer-optimization-1.3>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.4",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.4>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.4>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.5",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
+ INSERT INTO t1 VALUES (1, 1, 2), (2, 2, 3), (3, 3, 4);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.5>
+ 1, "table T2 has 2 columns but 3 values were supplied"
+ -- <xfer-optimization-1.5>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.6",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.6>
+
+ -- <xfer-oprimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.7",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.7>
+ 0
+ -- <xfer-optimization-1.7>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.8",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.6>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-oprimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.9",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER, a INTEGER PRIMARY KEY);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.9>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.9>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.10",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.10>
+
+ -- <xfer-oprimization-1.10>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.11",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER PRIMARY KEY, a INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.11>
+ 0
+ -- <xfer-optimization-1.11>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.12",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.12>
+ 1, 1, 2, 2, 3, 2
+ -- <xfer-oprimization-1.12>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.13",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE ON CONFLICT REPLACE);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE ON CONFLICT REPLACE);
+ INSERT INTO t1 VALUES (3, 3), (4, 4), (5, 5);
+ INSERT INTO t2 VALUES (1, 1), (2, 2);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.13>
+ 0
+ -- <xfer-optimization-1.13>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.14",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.14>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
+ -- <xfer-optimization-1.14>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.15",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE ON CONFLICT REPLACE);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE ON CONFLICT REPLACE);
+ INSERT INTO t1 VALUES (2, 2), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (1, 1), (4, 4);
+ INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.15>
+ 0
+ -- <xfer-optimization-1.15>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.16",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.16>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
+ -- <xfer-oprimization-1.16>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.17",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE ON CONFLICT REPLACE);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE ON CONFLICT REPLACE);
+ INSERT INTO t1 VALUES (1, 2), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (1, 1), (4, 4);
+ INSERT OR REPLACE INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.17>
+ 0
+ -- <xfer-optimization-1.17>
+ })
+
+test:do_execsql_test(
+ "xfer-oprimization-1.18",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-oprimization-1.18>
+ 1, 2, 3, 3, 4, 4, 5, 5
+ -- <xfer-oprimization-1.18>
+ })
+
+test:finish_test()

[-- Attachment #2: Type: text/html, Size: 30805 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-27 15:45                 ` Hollow111
@ 2018-05-03 22:57                   ` n.pettik
  2018-05-04 12:54                     ` Hollow111
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-05-03 22:57 UTC (permalink / raw)
  To: tarantool-patches; +Cc: N. Tatunov


> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index ae8dafb..734ff34 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1645,6 +1645,12 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
>  	assert(pDest->pTable != pSrc->pTable);
>  	uint32_t nDestCol = index_column_count(pDest);
>  	uint32_t nSrcCol = index_column_count(pSrc);
> +	/* One of them is PK while the other isn't. */
> +	if ((pDest->idxType == SQLITE_IDXTYPE_PRIMARYKEY &&
> +	     pSrc->idxType != SQLITE_IDXTYPE_PRIMARYKEY) ||
> +	    (pDest->idxType != SQLITE_IDXTYPE_PRIMARYKEY &&
> +	     pSrc->idxType == SQLITE_IDXTYPE_PRIMARYKEY))
> +		return 0;

Why don’t just compare their types? Wouldn’t it be easier?
Yep, such condition would be little bit stronger, but I think it is worth it.

> @@ -1718,9 +1724,10 @@ xferOptimization(Parse * pParse,	/* Parser context */
>  	int emptyDestTest = 0;	/* Address of test for empty pDest */
>  	int emptySrcTest = 0;	/* Address of test for empty pSrc */
>  	Vdbe *v;		/* The VDBE we are building */
> -	int destHasUniqueIdx = 0;	/* True if pDest has a UNIQUE index */
>  	int regData, regTupleid;	/* Registers holding data and tupleid */
>  	struct session *user_session = current_session();
> +	int dest_has_replace_action = 0;
> +	int confl_action_default = 0;

Call it error action. Also, it is better to use bool type
(even if bool might be really int underhood). And bool values usually
comes with ‘is_’ or ‘has_' prefix.

> @@ -1737,8 +1744,10 @@ xferOptimization(Parse * pParse,	/* Parser context */
>  	if (onError == ON_CONFLICT_ACTION_DEFAULT) {
>  		if (pDest->iPKey >= 0)
>  			onError = pDest->keyConf;
> -		if (onError == ON_CONFLICT_ACTION_DEFAULT)
> +		if (onError == ON_CONFLICT_ACTION_DEFAULT) {
>  			onError = ON_CONFLICT_ACTION_ABORT;
> +			confl_action_default = 1;

Why do you need this variable at all? I mean, DEFAULT always
is an alias to ABORT, isn’t it?

> +		}
>  	}
>  	assert(pSelect->pSrc);	/* allocated even if there is no FROM clause */
>  	if (pSelect->pSrc->nSrc != 1) {
> @@ -1828,15 +1837,16 @@ xferOptimization(Parse * pParse,	/* Parser context */
>  				return 0;	/* Default values must be the same for all columns */
>  			}
>  		}
> +		if (pDestCol->notNull == ON_CONFLICT_ACTION_REPLACE)
> +			dest_has_replace_action = 1;

Don’t confuse NOT NULL error action for field and conflict action for index:
Index->onError is not the same as Column->notNull.

>  	for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
> -		if (index_is_unique(pDestIdx)) {
> -			destHasUniqueIdx = 1;
> -		}
>  		for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
>  			if (xferCompatibleIndex(pDestIdx, pSrcIdx))
>  				break;
>  		}
> +		if (pDestIdx->onError != ON_CONFLICT_ACTION_REPLACE)
> +			dest_has_replace_action = 1;

Wait, you assign ’true’ to variable ‘has_replace’ when it is not really replace…
It looks very strange.

> @@ -1875,58 +1885,63 @@ xferOptimization(Parse * pParse,	/* Parser context */
> +
> +	/* Xfer optimization is unable to correctly insert
> +	 * data in case there's a conflict action
> +	 * other than ON_CONFLICT_ACTION_ROLLBACK or there's
> +	 * ON_CONFLICT_ACTION_DEFAULT which was transformed into
> +	 * ON_CONFLICT_ACTION_ABORT for insertion while we have a
> +	 * ON_CONFLICT_ACTION_REPLACE for any of constraints.

I would use only names of actions, these enum names are too long.
But it is up to you.

> +	if (onError != ON_CONFLICT_ACTION_ROLLBACK ||
> +	    (confl_action_default == 1 &&
> +	    dest_has_replace_action == 1)) {

You can fit two lines above into one...

> +	int space_ptr_reg = ++pParse->nMem;
> +	struct space *src_space =
> +		space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
> +	struct space *dest_space =
> +		space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
> +	struct index *src_idx = space_index(src_space, 0);
> +	struct index *dest_idx;

Why don’t make index lookup for destination space right here?

> +	pDestIdx = sqlite3PrimaryKeyIndex(pDest);
> +	pSrcIdx = sqlite3PrimaryKeyIndex(pSrc);

These variables are unused.

> diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> new file mode 100755
> index 0000000..860b4c3
> --- /dev/null
> +++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> @@ -0,0 +1,233 @@
> +#!/usr/bin/env tarantool
> +test = require("sqltester")
> +test:plan(18)

The only one thing I don’t really like in these tests is the absence
of verification of occurred optimisation. Could you come up with the way
how to check it?

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-05-03 22:57                   ` n.pettik
@ 2018-05-04 12:54                     ` Hollow111
  2018-06-28 10:18                       ` Alexander Turenko
  0 siblings, 1 reply; 38+ messages in thread
From: Hollow111 @ 2018-05-04 12:54 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 957 bytes --]

> @@ -1737,8 +1744,10 @@ xferOptimization(Parse * pParse,       /* Parser
context */
>       if (onError == ON_CONFLICT_ACTION_DEFAULT) {
>               if (pDest->iPKey >= 0)
>                       onError = pDest->keyConf;
> -             if (onError == ON_CONFLICT_ACTION_DEFAULT)
> +             if (onError == ON_CONFLICT_ACTION_DEFAULT) {
>                       onError = ON_CONFLICT_ACTION_ABORT;
> +                     confl_action_default = 1;

Why do you need this variable at all? I mean, DEFAULT always
is an alias to ABORT, isn’t it?

Yes, it is, but there's a little difference between directly specified
ABORT for an
insert stmt (INSERT OR ABORT) and just INSERT without any specified error
action
(ABORT specified by the internals). When you directly specify it ABORT is a
higher priority
action than in case there's a column with REPLACE error action. Thus we can
even insert
not in the empty destination table.

[-- Attachment #2: Type: text/html, Size: 3832 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-05-04 12:54                     ` Hollow111
@ 2018-06-28 10:18                       ` Alexander Turenko
  2018-07-09 15:50                         ` Alexander Turenko
  0 siblings, 1 reply; 38+ messages in thread
From: Alexander Turenko @ 2018-06-28 10:18 UTC (permalink / raw)
  To: Kirill Yukhin, Nikita Pettik; +Cc: tarantool-patches, Hollow111

On Fri, May 04, 2018 at 12:54:30PM +0000, Hollow111 wrote:
>    > @@ -1737,8 +1744,10 @@ xferOptimization(Parse * pParse,       /*
>    Parser context */
>    >       if (onError == ON_CONFLICT_ACTION_DEFAULT) {
>    >               if (pDest->iPKey >= 0)
>    >                       onError = pDest->keyConf;
>    > -             if (onError == ON_CONFLICT_ACTION_DEFAULT)
>    > +             if (onError == ON_CONFLICT_ACTION_DEFAULT) {
>    >                       onError = ON_CONFLICT_ACTION_ABORT;
>    > +                     confl_action_default = 1;
>    Why do you need this variable at all? I mean, DEFAULT always
>    is an alias to ABORT, isn’t it?
>    Yes, it is, but there's a little difference between directly specified
>    ABORT for an
>    insert stmt (INSERT OR ABORT) and just INSERT without any specified
>    error action
>    (ABORT specified by the internals). When you directly specify it ABORT
>    is a higher priority
>    action than in case there's a column with REPLACE error action. Thus we
>    can even insert
>    not in the empty destination table.

If an user asks for ABORT explicitly we should make abort, I think.

As I understood the extra variable appears due to the fact than we can
have per-column conflict clauses in CREATE TABLE and per-table clause
with INSERT OR ABORT. The latter should have precedence, I think.

I don't sure whether something (behaviour? code?) should be different
from SQLite here in light of #2963 changes. Kirill, Nikita, can you
comment, please?

WBR, Alexander Turenko.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-06-28 10:18                       ` Alexander Turenko
@ 2018-07-09 15:50                         ` Alexander Turenko
  2018-07-16 12:54                           ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: Alexander Turenko @ 2018-07-09 15:50 UTC (permalink / raw)
  To: Nikita Tatunov; +Cc: tarantool-patches, Nikita Pettik, Kirill Yukhin

Hi Nikita!

Please, consider comments below.

WBR, Alexander Turenko.

> +test:do_execsql_test(
> +       "xfer-oprimization-1.8",
> ...
> +test:do_execsql_test(
> +       "xfer-oprimization-1.10",
> ...
> +test:do_execsql_test(
> +       "xfer-oprimization-1.12",
> ...
> +test:do_execsql_test(
> +       "xfer-oprimization-1.16",
> +test:do_execsql_test(
> +       "xfer-oprimization-1.18",

oprimization -> optimization

It seems that review questions from the last Nikita email in the thread
was not fixed or answered (at least some of them).

> +       sqlite3VdbeAddOp3(v, OP_OpenWrite, iSrc,
> +                         pSrc->tnum, space_ptr_reg);

Why do you open source space for write?

How your changes to xferCompatibleIndex and empty check enabling
condition is motivated? It is hard to understand it without more
detailed description.

---
EOF.

On Thu, Jun 28, 2018 at 01:18:39PM +0300, Alexander Turenko wrote:
> On Fri, May 04, 2018 at 12:54:30PM +0000, Hollow111 wrote:
> >    > @@ -1737,8 +1744,10 @@ xferOptimization(Parse * pParse,       /*
> >    Parser context */
> >    >       if (onError == ON_CONFLICT_ACTION_DEFAULT) {
> >    >               if (pDest->iPKey >= 0)
> >    >                       onError = pDest->keyConf;
> >    > -             if (onError == ON_CONFLICT_ACTION_DEFAULT)
> >    > +             if (onError == ON_CONFLICT_ACTION_DEFAULT) {
> >    >                       onError = ON_CONFLICT_ACTION_ABORT;
> >    > +                     confl_action_default = 1;
> >    Why do you need this variable at all? I mean, DEFAULT always
> >    is an alias to ABORT, isn’t it?
> >    Yes, it is, but there's a little difference between directly specified
> >    ABORT for an
> >    insert stmt (INSERT OR ABORT) and just INSERT without any specified
> >    error action
> >    (ABORT specified by the internals). When you directly specify it ABORT
> >    is a higher priority
> >    action than in case there's a column with REPLACE error action. Thus we
> >    can even insert
> >    not in the empty destination table.
> 
> If an user asks for ABORT explicitly we should make abort, I think.
> 
> As I understood the extra variable appears due to the fact than we can
> have per-column conflict clauses in CREATE TABLE and per-table clause
> with INSERT OR ABORT. The latter should have precedence, I think.
> 
> I don't sure whether something (behaviour? code?) should be different
> from SQLite here in light of #2963 changes. Kirill, Nikita, can you
> comment, please?
> 
> WBR, Alexander Turenko.
> 

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-09 15:50                         ` Alexander Turenko
@ 2018-07-16 12:54                           ` Nikita Tatunov
  2018-07-16 13:06                             ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-16 12:54 UTC (permalink / raw)
  To: alexander.turenko; +Cc: tarantool-patches, korablev, kyukhin

[-- Attachment #1: Type: text/plain, Size: 3383 bytes --]

пн, 9 июл. 2018 г. в 18:50, Alexander Turenko <
alexander.turenko@tarantool.org>:

> Hi Nikita!
>
> Please, consider comments below.
>
> WBR, Alexander Turenko.
>
> > +test:do_execsql_test(
> > +       "xfer-oprimization-1.8",
> > ...
> > +test:do_execsql_test(
> > +       "xfer-oprimization-1.10",
> > ...
> > +test:do_execsql_test(
> > +       "xfer-oprimization-1.12",
> > ...
> > +test:do_execsql_test(
> > +       "xfer-oprimization-1.16",
> > +test:do_execsql_test(
> > +       "xfer-oprimization-1.18",
>
> oprimization -> optimization
>

Is already fixed.


> It seems that review questions from the last Nikita email in the thread
> was not fixed or answered (at least some of them).
>
> > +       sqlite3VdbeAddOp3(v, OP_OpenWrite, iSrc,
> > +                         pSrc->tnum, space_ptr_reg);
>
> Why do you open source space for write?
>
>
I changed it to 'vdbe_emit_open_cursor' which now is used everywhere for
both reading and writing (internally it uses 'OP_OpenWrite')

How your changes to xferCompatibleIndex and empty check enabling
> condition is motivated? It is hard to understand it without more
> detailed description.
>
>
We open cursors on PK for both tables, additional check that we have opened
'em on PK isn't redundunt.
What's about empty check enabling: I only want to add some cases where we
don't demand empty destination table for xferOptimization to be used. Thus
it will be used a little bit more frequently (it's faster than inserting
non-raw data for about 13%).


> ---
> EOF.
>
> On Thu, Jun 28, 2018 at 01:18:39PM +0300, Alexander Turenko wrote:
> > On Fri, May 04, 2018 at 12:54:30PM +0000, Hollow111 wrote:
> > >    > @@ -1737,8 +1744,10 @@ xferOptimization(Parse * pParse,       /*
> > >    Parser context */
> > >    >       if (onError == ON_CONFLICT_ACTION_DEFAULT) {
> > >    >               if (pDest->iPKey >= 0)
> > >    >                       onError = pDest->keyConf;
> > >    > -             if (onError == ON_CONFLICT_ACTION_DEFAULT)
> > >    > +             if (onError == ON_CONFLICT_ACTION_DEFAULT) {
> > >    >                       onError = ON_CONFLICT_ACTION_ABORT;
> > >    > +                     confl_action_default = 1;
> > >    Why do you need this variable at all? I mean, DEFAULT always
> > >    is an alias to ABORT, isn’t it?
> > >    Yes, it is, but there's a little difference between directly
> specified
> > >    ABORT for an
> > >    insert stmt (INSERT OR ABORT) and just INSERT without any specified
> > >    error action
> > >    (ABORT specified by the internals). When you directly specify it
> ABORT
> > >    is a higher priority
> > >    action than in case there's a column with REPLACE error action.
> Thus we
> > >    can even insert
> > >    not in the empty destination table.
> >
> > If an user asks for ABORT explicitly we should make abort, I think.
> >
> > As I understood the extra variable appears due to the fact than we can
> > have per-column conflict clauses in CREATE TABLE and per-table clause
> > with INSERT OR ABORT. The latter should have precedence, I think.
> >
> > I don't sure whether something (behaviour? code?) should be different
> > from SQLite here in light of #2963 changes. Kirill, Nikita, can you
> > comment, please?
> >
> > WBR, Alexander Turenko.
> >
>

[-- Attachment #2: Type: text/html, Size: 4715 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-16 12:54                           ` Nikita Tatunov
@ 2018-07-16 13:06                             ` n.pettik
  2018-07-16 13:20                               ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-07-16 13:06 UTC (permalink / raw)
  To: tarantool-patches; +Cc: N. Tatunov

Could you please rebase patch on fresh 2.0?
After that, I will give final assessment.

Also, did you manage to check that xfer optimisation really works at runtime?

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-16 13:06                             ` n.pettik
@ 2018-07-16 13:20                               ` Nikita Tatunov
  2018-07-16 18:37                                 ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-16 13:20 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 398 bytes --]

пн, 16 июл. 2018 г. в 16:06, n.pettik <korablev@tarantool.org>:

> Could you please rebase patch on fresh 2.0?
> After that, I will give final assessment.
>
>
Yes, no problem.


> Also, did you manage to check that xfer optimisation really works at
> runtime?
>

Yes, I have it in newer tests and involved an int variable on this purpose.
Only need to make small fixes in it.

[-- Attachment #2: Type: text/html, Size: 809 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-16 13:20                               ` Nikita Tatunov
@ 2018-07-16 18:37                                 ` Nikita Tatunov
  2018-07-16 19:12                                   ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-16 18:37 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 768 bytes --]

Here's diff for the fixed and rebased patch. There're some unexpected
things concerned with the check of emptiness of destination table:
internals can only correctly deal with ABORT conflict action and also my
'small optimization of optimization' was incorrect.

пн, 16 июл. 2018 г. в 16:20, Nikita Tatunov <hollow653@gmail.com>:

>
> пн, 16 июл. 2018 г. в 16:06, n.pettik <korablev@tarantool.org>:
>
>> Could you please rebase patch on fresh 2.0?
>> After that, I will give final assessment.
>>
>>
> Yes, no problem.
>
>
>> Also, did you manage to check that xfer optimisation really works at
>> runtime?
>>
>
> Yes, I have it in newer tests and involved an int variable on this
> purpose. Only need to make small fixes in it.
>

[-- Attachment #2: Type: text/html, Size: 1420 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-16 18:37                                 ` Nikita Tatunov
@ 2018-07-16 19:12                                   ` n.pettik
  2018-07-16 21:27                                     ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-07-16 19:12 UTC (permalink / raw)
  To: tarantool-patches; +Cc: N. Tatunov


> Here's diff for the fixed and rebased patch. There're some unexpected things concerned with the check of emptiness of destination table: internals can only correctly deal with ABORT conflict action and also my 'small optimization of optimization' was incorrect.

I see no diff, actually. You haven’t attached it to the letter.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-16 19:12                                   ` n.pettik
@ 2018-07-16 21:27                                     ` Nikita Tatunov
  2018-07-18 15:13                                       ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-16 21:27 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 24104 bytes --]

diff --git a/src/box/sql.c b/src/box/sql.c
index fdce224..398b2a6 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1636,10 +1636,12 @@ sql_debug_info(struct info_handler *h)
  extern int sql_search_count;
  extern int sql_sort_count;
  extern int sql_found_count;
+ extern int sql_xferOpt_count;
  info_begin(h);
  info_append_int(h, "sql_search_count", sql_search_count);
  info_append_int(h, "sql_sort_count", sql_sort_count);
  info_append_int(h, "sql_found_count", sql_found_count);
+ info_append_int(h, "sql_xferOpt_count", sql_xferOpt_count);
  info_end(h);
 }

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 2c9188e..9a99bab 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1635,7 +1635,7 @@ sqlite3OpenTableAndIndices(Parse * pParse, /* Parsing
context */
  * purposes only - to make sure the transfer optimization really
  * is happening when it is supposed to.
  */
-int sqlite3_xferopt_count;
+int sql_xferOpt_count = 0;
 #endif /* SQLITE_TEST */

 #ifndef SQLITE_OMIT_XFER_OPT
@@ -1658,6 +1658,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
  assert(pDest->pTable != pSrc->pTable);
  uint32_t nDestCol = index_column_count(pDest);
  uint32_t nSrcCol = index_column_count(pSrc);
+ if ((pDest->idxType != pSrc->idxType))
+ return 0;
  if (nDestCol != nSrcCol) {
  return 0; /* Different number of columns */
  }
@@ -1725,9 +1727,9 @@ xferOptimization(Parse * pParse, /* Parser context */
  int emptyDestTest = 0; /* Address of test for empty pDest */
  int emptySrcTest = 0; /* Address of test for empty pSrc */
  Vdbe *v; /* The VDBE we are building */
- int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */
  int regData, regTupleid; /* Registers holding data and tupleid */
  struct session *user_session = current_session();
+ bool is_err_action_default = false;

  if (pSelect == NULL)
  return 0; /* Must be of the form  INSERT INTO ... SELECT ... */
@@ -1744,8 +1746,10 @@ xferOptimization(Parse * pParse, /* Parser context */
  if (onError == ON_CONFLICT_ACTION_DEFAULT) {
  if (pDest->iPKey >= 0)
  onError = pDest->keyConf;
- if (onError == ON_CONFLICT_ACTION_DEFAULT)
+ if (onError == ON_CONFLICT_ACTION_DEFAULT) {
  onError = ON_CONFLICT_ACTION_ABORT;
+ is_err_action_default = true;
+ }
  }
  assert(pSelect->pSrc); /* allocated even if there is no FROM clause */
  if (pSelect->pSrc->nSrc != 1) {
@@ -1848,9 +1852,6 @@ xferOptimization(Parse * pParse, /* Parser context */
  }
  }
  for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- if (index_is_unique(pDestIdx)) {
- destHasUniqueIdx = 1;
- }
  for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
  if (xferCompatibleIndex(pDestIdx, pSrcIdx))
  break;
@@ -1888,72 +1889,60 @@ xferOptimization(Parse * pParse, /* Parser context
*/
  * least a possibility, though it might only work if the destination
  * table (tab1) is initially empty.
  */
-#ifdef SQLITE_TEST
- sqlite3_xferopt_count++;
-#endif
+
  v = sqlite3GetVdbe(pParse);
  iSrc = pParse->nTab++;
  iDest = pParse->nTab++;
  regData = sqlite3GetTempReg(pParse);
  regTupleid = sqlite3GetTempReg(pParse);
  sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);
- assert(destHasUniqueIdx);
- if ((pDest->iPKey < 0 && pDest->pIndex != 0) /* (1) */
-     ||destHasUniqueIdx /* (2) */
-     || (onError != ON_CONFLICT_ACTION_ABORT
- && onError != ON_CONFLICT_ACTION_ROLLBACK) /* (3) */
-     ) {
- /* In some circumstances, we are able to run the xfer optimization
- * only if the destination table is initially empty.
- * This block generates code to make
- * that determination.
- *
- * Conditions under which the destination must be empty:
- *
- * (1) There is no INTEGER PRIMARY KEY but there are indices.
- *
- * (2) The destination has a unique index.  (The xfer optimization
- *     is unable to test uniqueness.)
- *
- * (3) onError is something other than ON_CONFLICT_ACTION_ABORT and
_ROLLBACK.
- */
+
+ struct space *src_space =
+ space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
+ struct space *dest_space =
+ space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
+ struct index *src_idx = space_index(src_space, 0);
+ struct index *dest_idx = space_index(dest_space, 0);
+
+ /* Xfer optimization is unable to correctly insert data
+ * in case there's a conflict action other than *_ABORT.
+ * This is the reason we want to only run it if the
+ * destination table is initially empty.
+ * That block generates code to make that determination.
+ */
+
+ if (!(onError == ON_CONFLICT_ACTION_ABORT &&
+     is_err_action_default == false)) {
  addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
  VdbeCoverage(v);
  emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
  sqlite3VdbeJumpHere(v, addr1);
+#ifdef SQLITE_TEST
+ if (dest_idx->vtab->count(dest_idx, ITER_ALL, NULL, 0) == 0)
+ sql_xferOpt_count++;
+#endif
  }
-
- for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- for (pSrcIdx = pSrc->pIndex; ALWAYS(pSrcIdx);
-      pSrcIdx = pSrcIdx->pNext) {
- if (xferCompatibleIndex(pDestIdx, pSrcIdx))
- break;
- }
- assert(pSrcIdx);
- struct space *src_space =
- space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrcIdx->tnum));
- vdbe_emit_open_cursor(pParse, iSrc,
-       SQLITE_PAGENO_TO_INDEXID(pSrcIdx->tnum),
-       src_space);
- VdbeComment((v, "%s", pSrcIdx->zName));
- struct space *dest_space =
- space_by_id(SQLITE_PAGENO_TO_SPACEID(pDestIdx->tnum));
- vdbe_emit_open_cursor(pParse, iDest,
-       SQLITE_PAGENO_TO_INDEXID(pDestIdx->tnum),
-       dest_space);
- VdbeComment((v, "%s", pDestIdx->zName));
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
- VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
- if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
- sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
- sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
- VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
- sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+#ifdef SQLITE_TEST
+ else {
+ sql_xferOpt_count++;
  }
+#endif
+
+ vdbe_emit_open_cursor(pParse, iSrc, 0, src_space);
+ VdbeComment((v, "%s", src_idx->def->name));
+ vdbe_emit_open_cursor(pParse, iDest, 0, dest_space);
+ VdbeComment((v, "%s", dest_idx->def->name));
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
+ sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+ sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
+ VdbeCoverage(v);
+ sqlite3VdbeJumpHere(v, addr1);
+ sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+
  if (emptySrcTest)
  sqlite3VdbeJumpHere(v, emptySrcTest);
  sqlite3ReleaseTempReg(pParse, regTupleid);
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
new file mode 100755
index 0000000..e75fabc
--- /dev/null
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -0,0 +1,706 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(44)
+
+local bfr, aftr
+
+test:do_catchsql_test(
+ "xfer-optimization-1.1",
+ [[
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.1>
+ 0
+ -- <xfer-optimization-1.1>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.2",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.2>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.2>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.3",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE TABLE t2(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE INDEX i1 ON t1(b);
+ CREATE INDEX i2 ON t2(b);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.3>
+ 0
+ -- <xfer-optimization-1.3>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.4",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.4>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.4>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.5",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
+ INSERT INTO t1 VALUES (1, 1, 2), (2, 2, 3), (3, 3, 4);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.5>
+ 1, "table T2 has 2 columns but 3 values were supplied"
+ -- <xfer-optimization-1.5>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.6",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.6>
+
+ -- <xfer-optimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.7",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.7>
+ 0
+ -- <xfer-optimization-1.7>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.8",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.6>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.9",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER, a INTEGER PRIMARY KEY);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.9>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.9>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.10",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.10>
+
+ -- <xfer-optimization-1.10>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.11",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER PRIMARY KEY, a INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.11>
+ 0
+ -- <xfer-optimization-1.11>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.12",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.12>
+ 1, 1, 2, 2, 3, 2
+ -- <xfer-optimization-1.12>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.13",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (3, 3), (4, 4), (5, 5);
+ INSERT INTO t2 VALUES (1, 1), (2, 2);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.13>
+ 0
+ -- <xfer-optimization-1.13>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.14",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.14>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
+ -- <xfer-optimization-1.14>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.15",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE);
+ INSERT INTO t1 VALUES (2, 2), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (1, 1), (4, 4);
+ INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.15>
+ 0
+ -- <xfer-optimization-1.15>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.16",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.16>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
+ -- <xfer-optimization-1.16>
+ })
+
+-- The following tests are supposed to test if xfer-optimization is
actually
+-- used in the given cases (if the conflict actually occurs):
+-- 1.0) insert w/o explicit confl. action & w/o index replace action
+-- 1.1) insert w/o explicit confl. action & w/ index replace action &
empty dest_table
+-- 1.2) insert w/o explicit confl. action & w/ index replace action &
non-empty dest_table
+-- 2) insert with abort
+-- 3.0) insert with rollback (into empty table)
+-- 3.1) insert with rollback (into non-empty table)
+-- 4) insert with replace
+-- 5) insert with fail
+-- 6) insert with ignore
+
+
+-- 1.0) insert w/o explicit confl. action & w/o index replace action
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.17",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.17>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.17>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.18",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.18>
+ 2, 2, 3, 4, 4, 4, 10, 10
+ -- <xfer-optimization-1.18>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_test(
+ "xfer-optimization-1.19",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.19>
+ 0
+ -- <xfer-optimization-1.19>
+ })
+
+-- 1.1) insert w/o explicit confl. action & w/ index replace action &
empty dest_table
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.20",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
+ CREATE TABLE t3(id INT PRIMARY KEY);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ BEGIN;
+ INSERT INTO t3 VALUES (1);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.20>
+ 0
+ -- <xfer-optimization-1.20>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.21",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.21>
+ 1, 1, 3, 3, 5, 5, 10, 10
+ -- <xfer-optimization-1.21>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_execsql_test(
+ "xfer-optimization-1.22",
+ [[
+ SELECT * FROM t3;
+ ]], {
+ -- <xfer-optimization-1.22>
+ 1
+ -- <xfer-optimization-1.22>
+ })
+
+test:do_test(
+ "xfer-optimization-1.23",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.23>
+ 1
+ -- <xfer-optimization-1.23>
+ })
+
+-- 1.2) insert w/o explicit confl. action & w/ index replace action &
non-empty dest_table
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.24",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ DROP TABLE t3;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.24>
+ 0
+ -- <xfer-optimization-1.24>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.25",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.25>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 10, 10
+ -- <xfer-optimization-1.25>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_test(
+ "xfer-optimization-1.26",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.26>
+ 0
+ -- <xfer-optimization-1.26>
+ })
+
+-- 2) insert with abort
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.27",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR ABORT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.27>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.27>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.28",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.28>
+ 2, 2, 3, 4, 4, 4, 10, 10
+ -- <xfer-optimization-1.28>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_test(
+ "xfer-optimization-1.29",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.29>
+ 1
+ -- <xfer-optimization-1.29>
+ })
+
+-- 3.0) insert with rollback (into empty table)
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.30",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ BEGIN;
+ INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.30>
+ 0
+ -- <xfer-optimization-1.30>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.31",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.31>
+ 1, 1, 3, 3, 5, 5, 10, 10
+ -- <xfer-optimization-1.31>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_test(
+ "xfer-optimization-1.32",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.32>
+ 1
+ -- <xfer-optimization-1.32>
+ })
+
+-- 3.1) insert with rollback (into non-empty table)
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.33",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.33>
+ 1, "UNIQUE constraint failed: T2.A"
+ -- <xfer-optimization-1.33>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.34",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.34>
+ 2, 2, 3, 4
+ -- <xfer-optimization-1.34>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_test(
+ "xfer-optimization-1.35",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.35>
+ 0
+ -- <xfer-optimization-1.35>
+ })
+
+-- 4) insert with replace
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.36",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR REPLACE INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.36>
+ 0
+ -- <xfer-optimization-1.36>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.37",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.37>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 10, 10
+ -- <xfer-optimization-1.37>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_test(
+ "xfer-optimization-1.38",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.38>
+ 0
+ -- <xfer-optimization-1.38>
+ })
+
+-- 5) insert with fail
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.39",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR FAIL INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.39>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.39>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.40",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.40>
+ 1, 1, 2, 2, 3, 4, 4, 4, 10, 10
+ -- <xfer-optimization-1.40>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_test(
+ "xfer-optimization-1.41",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.41>
+ 0
+ -- <xfer-optimization-1.41>
+ })
+
+-- 6) insert with ignore
+-------------------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xferOpt_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.42",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR IGNORE INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.42>
+ 0
+ -- <xfer-optimization-1.42>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.43",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.43>
+ 1, 1, 2, 2, 3, 4, 4, 4, 5, 5, 10, 10
+ -- <xfer-optimization-1.43>
+ })
+
+aftr = box.sql.debug().sql_xferOpt_count
+
+test:do_test(
+ "xfer-optimization-1.44",
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ return {2}
+ end, {
+ -- <xfer-optimization-1.44>
+ 0
+ -- <xfer-optimization-1.44>
+ })
+
+test:finish_test()


пн, 16 июл. 2018 г. в 22:12, n.pettik <korablev@tarantool.org>:

>
> > Here's diff for the fixed and rebased patch. There're some unexpected
> things concerned with the check of emptiness of destination table:
> internals can only correctly deal with ABORT conflict action and also my
> 'small optimization of optimization' was incorrect.
>
> I see no diff, actually. You haven’t attached it to the letter.

[-- Attachment #2: Type: text/html, Size: 60612 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-16 21:27                                     ` Nikita Tatunov
@ 2018-07-18 15:13                                       ` n.pettik
  2018-07-18 20:18                                         ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-07-18 15:13 UTC (permalink / raw)
  To: tarantool-patches; +Cc: N. Tatunov

Please, add to commit message results of benchmark to indicate
that this optimisation really matters.

> On 17 Jul 2018, at 00:27, Nikita Tatunov <hollow653@gmail.com> wrote:
> 
> diff --git a/src/box/sql.c b/src/box/sql.c
> index fdce224..398b2a6 100644
> --- a/src/box/sql.c
> +++ b/src/box/sql.c
> @@ -1636,10 +1636,12 @@ sql_debug_info(struct info_handler *h)
>  	extern int sql_search_count;
>  	extern int sql_sort_count;
>  	extern int sql_found_count;
> +	extern int sql_xferOpt_count;

Don’t use camel notation. Lets call it simply ’sql_xfer_count’.

>  	info_begin(h);
>  	info_append_int(h, "sql_search_count", sql_search_count);
>  	info_append_int(h, "sql_sort_count", sql_sort_count);
>  	info_append_int(h, "sql_found_count", sql_found_count);
> +	info_append_int(h, "sql_xferOpt_count", sql_xferOpt_count);
>  	info_end(h);
>  }
>  
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 2c9188e..9a99bab 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1635,7 +1635,7 @@ sqlite3OpenTableAndIndices(Parse * pParse,	/* Parsing context */
>   * purposes only - to make sure the transfer optimization really
>   * is happening when it is supposed to.
>   */
> -int sqlite3_xferopt_count;
> +int sql_xferOpt_count = 0;
>  #endif				/* SQLITE_TEST */
>  
>  #ifndef SQLITE_OMIT_XFER_OPT
> @@ -1658,6 +1658,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
>  	assert(pDest->pTable != pSrc->pTable);
>  	uint32_t nDestCol = index_column_count(pDest);
>  	uint32_t nSrcCol = index_column_count(pSrc);
> +	if ((pDest->idxType != pSrc->idxType))
> +		return 0;
>  	if (nDestCol != nSrcCol) {
>  		return 0;	/* Different number of columns */
>  	}
> @@ -1725,9 +1727,9 @@ xferOptimization(Parse * pParse,	/* Parser context */
>  	int emptyDestTest = 0;	/* Address of test for empty pDest */
>  	int emptySrcTest = 0;	/* Address of test for empty pSrc */
>  	Vdbe *v;		/* The VDBE we are building */
> -	int destHasUniqueIdx = 0;	/* True if pDest has a UNIQUE index */
>  	int regData, regTupleid;	/* Registers holding data and tupleid */
>  	struct session *user_session = current_session();
> +	bool is_err_action_default = false;

Again: why do you need this flag? Default action is just synonym for ABORT,
so why should we care about it?

> +	struct space *src_space =
> +		space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
> +	struct space *dest_space =
> +		space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));

You don’t need to again proceed lookup: space is found few lines above.
Moreover, I see those lookups are executed inside ‘for' loop. Lets move
them outside it.

> +	struct index *src_idx = space_index(src_space, 0);
> +	struct index *dest_idx = space_index(dest_space, 0);
> +
> +	/* Xfer optimization is unable to correctly insert data
> +	 * in case there's a conflict action other than *_ABORT.
> +	 * This is the reason we want to only run it if the
> +	 * destination table is initially empty.
> +	 * That block generates code to make that determination.
> +	 */

Multi-line comment should be formatted as following:

/*
 * Comment starts here.
 * …
 */

> +
> +	if (!(onError == ON_CONFLICT_ACTION_ABORT &&
> +	    is_err_action_default == false)) {
>  		addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
>  		VdbeCoverage(v);
>  		emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
>  		sqlite3VdbeJumpHere(v, addr1);
> +#ifdef SQLITE_TEST
> +		if (dest_idx->vtab->count(dest_idx, ITER_ALL, NULL, 0) == 0)
> +			sql_xferOpt_count++;

Actually, I don’t like this approach.
Look, query may be compiled and saved into cache (even thought it is still
not implemented yet). So it might be executed later and it might be not empty.
Moreover, we are going to avoid doing space lookups and use only def.
With only def you can’t execute count.

Personally, I wanted you to defer incrementing sql_xfer_count till
VDBE execution. For instance, you may add special flag and pass it
to OP_RowData indicating that xFer is currently processing.

> +#endif
> +	vdbe_emit_open_cursor(pParse, iSrc, 0, src_space);
> +	VdbeComment((v, "%s", src_idx->def->name));
> +	vdbe_emit_open_cursor(pParse, iDest, 0, dest_space);

I see few lines above:

sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);

So, basically you don’t need to open it again.

> +	VdbeComment((v, "%s", dest_idx->def->name));
> +	addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
> +	VdbeCoverage(v);
> +	sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
> +	sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
> +	sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
> +	sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
> +	VdbeCoverage(v);
> +	sqlite3VdbeJumpHere(v, addr1);
> +	sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
> +	sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
> +
>  	if (emptySrcTest)
>  		sqlite3VdbeJumpHere(v, emptySrcTest);
>  	sqlite3ReleaseTempReg(pParse, regTupleid);
> diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> new file mode 100755
> index 0000000..e75fabc
> --- /dev/null
> +test:do_catchsql_test(
> +	"xfer-optimization-1.15",
> +	[[
> +		DROP TABLE t1;
> +		DROP TABLE t2;
> +		CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
> +		CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE);
> +		INSERT INTO t1 VALUES (2, 2), (3, 3), (5, 5);
> +		INSERT INTO t2 VALUES (1, 1), (4, 4);
> +		INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;

INSERT OT ROLLBACK outside transaction works the same as ABORT and DEFAULT.
So, surround it with transaction and check that it really rollbacks.

> +	]], {
> +		-- <xfer-optimization-1.15>
> +		0
> +		-- <xfer-optimization-1.15>
> +	})
> +
> +test:do_execsql_test(
> +	"xfer-optimization-1.16",
> +	[[
> +		SELECT * FROM t2;
> +	]], {
> +		-- <xfer-optimization-1.16>
> +		1, 1, 2, 2, 3, 3, 4, 4, 5, 5
> +		-- <xfer-optimization-1.16>
> +	})
> +
> +-- The following tests are supposed to test if xfer-optimization is actually
> +-- used in the given cases (if the conflict actually occurs):
> +-- 	1.0) insert w/o explicit confl. action & w/o index replace action
> +-- 	1.1) insert w/o explicit confl. action & w/ index replace action & empty dest_table
> +-- 	1.2) insert w/o explicit confl. action & w/ index replace action & non-empty dest_table
> +-- 	2) insert with abort
> +-- 	3.0) insert with rollback (into empty table)
> +-- 	3.1) insert with rollback (into non-empty table)
> +-- 	4) insert with replace
> +-- 	5) insert with fail
> +-- 	6) insert with ignore
> +
> +
> +-- 1.0) insert w/o explicit confl. action & w/o index replace action
> +-------------------------------------------------------------------------------------------
> +
> +bfr = box.sql.debug().sql_xferOpt_count
> +
> +test:do_catchsql_test(
> +	"xfer-optimization-1.17",
> +	[[
> +		DROP TABLE t1;
> +		DROP TABLE t2;
> +		CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
> +		CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
> +		INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
> +		INSERT INTO t2 VALUES (2, 2), (3, 4);
> +		BEGIN;
> +			INSERT INTO t2 VALUES (4, 4);
> +			INSERT INTO t2 SELECT * FROM t1;
> +	]], {
> +		-- <xfer-optimization-1.17>
> +		1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space 'T2'"
> +		-- <xfer-optimization-1.17>
> +	})
> +
> +test:do_execsql_test(
> +	"xfer-optimization-1.18",
> +	[[
> +			INSERT INTO t2 VALUES (10, 10);
> +		COMMIT;
> +		SELECT * FROM t2;
> +	]], {
> +		-- <xfer-optimization-1.18>
> +		2, 2, 3, 4, 4, 4, 10, 10
> +		-- <xfer-optimization-1.18>
> +	})
> +
> +aftr = box.sql.debug().sql_xferOpt_count
> +
> +test:do_test(
> +	"xfer-optimization-1.19",
> +	function()
> +		if (aftr - bfr == 1) then
> +			return {1}
> +		end
> +		if (aftr == bfr) then
> +			return {0}
> +		end
> +		return {2}

Why do you repeat this snippet each time? You can declare it as named
function once and use it everywhere.

> +	end, {
> +		-- <xfer-optimization-1.19>
> +		0
> +		-- <xfer-optimization-1.19>
> +	})
> +
> +-- 1.1) insert w/o explicit confl. action & w/ index replace action & empty dest_table

Even in tests lets not exceed 80 chars (here and in other places).

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-18 15:13                                       ` n.pettik
@ 2018-07-18 20:18                                         ` Nikita Tatunov
  2018-07-19  0:20                                           ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-18 20:18 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 36333 bytes --]

Hello, Nikita! Here's newer version of the patch.
Diff can be found at the end.

ср, 18 июл. 2018 г. в 18:13, n.pettik <korablev@tarantool.org>:

> Please, add to commit message results of benchmark to indicate
> that this optimisation really matters.
>
>
Added.


> > On 17 Jul 2018, at 00:27, Nikita Tatunov <hollow653@gmail.com> wrote:
> >
> > diff --git a/src/box/sql.c b/src/box/sql.c
> > index fdce224..398b2a6 100644
> > --- a/src/box/sql.c
> > +++ b/src/box/sql.c
> > @@ -1636,10 +1636,12 @@ sql_debug_info(struct info_handler *h)
> >       extern int sql_search_count;
> >       extern int sql_sort_count;
> >       extern int sql_found_count;
> > +     extern int sql_xferOpt_count;
>
> Don’t use camel notation. Lets call it simply ’sql_xfer_count’.
>
>
Fixed.


> >       info_begin(h);
> >       info_append_int(h, "sql_search_count", sql_search_count);
> >       info_append_int(h, "sql_sort_count", sql_sort_count);
> >       info_append_int(h, "sql_found_count", sql_found_count);
> > +     info_append_int(h, "sql_xferOpt_count", sql_xferOpt_count);
> >       info_end(h);
> >  }
> >
> > diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> > index 2c9188e..9a99bab 100644
> > --- a/src/box/sql/insert.c
> > +++ b/src/box/sql/insert.c
> > @@ -1635,7 +1635,7 @@ sqlite3OpenTableAndIndices(Parse * pParse,      /*
> Parsing context */
> >   * purposes only - to make sure the transfer optimization really
> >   * is happening when it is supposed to.
> >   */
> > -int sqlite3_xferopt_count;
> > +int sql_xferOpt_count = 0;
> >  #endif                               /* SQLITE_TEST */
> >
> >  #ifndef SQLITE_OMIT_XFER_OPT
> > @@ -1658,6 +1658,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
> >       assert(pDest->pTable != pSrc->pTable);
> >       uint32_t nDestCol = index_column_count(pDest);
> >       uint32_t nSrcCol = index_column_count(pSrc);
> > +     if ((pDest->idxType != pSrc->idxType))
> > +             return 0;
> >       if (nDestCol != nSrcCol) {
> >               return 0;       /* Different number of columns */
> >       }
> > @@ -1725,9 +1727,9 @@ xferOptimization(Parse * pParse,        /* Parser
> context */
> >       int emptyDestTest = 0;  /* Address of test for empty pDest */
> >       int emptySrcTest = 0;   /* Address of test for empty pSrc */
> >       Vdbe *v;                /* The VDBE we are building */
> > -     int destHasUniqueIdx = 0;       /* True if pDest has a UNIQUE
> index */
> >       int regData, regTupleid;        /* Registers holding data and
> tupleid */
> >       struct session *user_session = current_session();
> > +     bool is_err_action_default = false;
>
> Again: why do you need this flag? Default action is just synonym for ABORT,
> so why should we care about it?
>
>
It's all about conflict action priorities as I said before.
Consider the following example:
```
CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
CREATE TABLE t2(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
INSERT INTO t2 VALUES (2, 2), (3, 4);
BEGIN;
INSERT INTO t2 VALUES (4, 4);
INSERT INTO t2 SELECT * FROM t1;
INSERT INTO t2 VALUES (10, 10);
COMMIT;
```
As we understand *_REPLACE should work in this case but
onError == *_ABORT (as it was converted from the default one).
It leads to a situation where an error will occur if xferOptimization is
used.

> +     struct space *src_space =
> > +             space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
> > +     struct space *dest_space =
> > +             space_by_id(SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
>
> You don’t need to again proceed lookup: space is found few lines above.
> Moreover, I see those lookups are executed inside ‘for' loop. Lets move
> them outside it.
>

Fixed it.


>
> > +     struct index *src_idx = space_index(src_space, 0);
> > +     struct index *dest_idx = space_index(dest_space, 0);
> > +
> > +     /* Xfer optimization is unable to correctly insert data
> > +      * in case there's a conflict action other than *_ABORT.
> > +      * This is the reason we want to only run it if the
> > +      * destination table is initially empty.
> > +      * That block generates code to make that determination.
> > +      */
>
> Multi-line comment should be formatted as following:
>
> /*
>  * Comment starts here.
>  * …
>  */
>

Fixed.


>
> > +
> > +     if (!(onError == ON_CONFLICT_ACTION_ABORT &&
> > +         is_err_action_default == false)) {
> >               addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
> >               VdbeCoverage(v);
> >               emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
> >               sqlite3VdbeJumpHere(v, addr1);
> > +#ifdef SQLITE_TEST
> > +             if (dest_idx->vtab->count(dest_idx, ITER_ALL, NULL, 0) ==
> 0)
> > +                     sql_xferOpt_count++;
>
> Actually, I don’t like this approach.
> Look, query may be compiled and saved into cache (even thought it is still
> not implemented yet). So it might be executed later and it might be not
> empty.
> Moreover, we are going to avoid doing space lookups and use only def.
> With only def you can’t execute count.
>
> Personally, I wanted you to defer incrementing sql_xfer_count till
> VDBE execution. For instance, you may add special flag and pass it
> to OP_RowData indicating that xFer is currently processing.
>
> > +#endif
> > +     vdbe_emit_open_cursor(pParse, iSrc, 0, src_space);
> > +     VdbeComment((v, "%s", src_idx->def->name));
> > +     vdbe_emit_open_cursor(pParse, iDest, 0, dest_space);
>
> I see few lines above:
>
> sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);
>
> So, basically you don’t need to open it again.
>

Fixed it.


>
> > +     VdbeComment((v, "%s", dest_idx->def->name));
> > +     addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
> > +     VdbeCoverage(v);
> > +     sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
> > +     sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
> > +     sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
> > +     sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
> > +     VdbeCoverage(v);
> > +     sqlite3VdbeJumpHere(v, addr1);
> > +     sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
> > +     sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
> > +
> >       if (emptySrcTest)
> >               sqlite3VdbeJumpHere(v, emptySrcTest);
> >       sqlite3ReleaseTempReg(pParse, regTupleid);
> > diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> > new file mode 100755
> > index 0000000..e75fabc
> > --- /dev/null
> > +test:do_catchsql_test(
> > +     "xfer-optimization-1.15",
> > +     [[
> > +             DROP TABLE t1;
> > +             DROP TABLE t2;
> > +             CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
> > +             CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE);
> > +             INSERT INTO t1 VALUES (2, 2), (3, 3), (5, 5);
> > +             INSERT INTO t2 VALUES (1, 1), (4, 4);
> > +             INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
>
> INSERT OT ROLLBACK outside transaction works the same as ABORT and DEFAULT.
> So, surround it with transaction and check that it really rollbacks.
>
>
There are basically almost the same tests surrounded by transactions (1.30
- 1.35).


> > +     ]], {
> > +             -- <xfer-optimization-1.15>
> > +             0
> > +             -- <xfer-optimization-1.15>
> > +     })
> > +
> > +test:do_execsql_test(
> > +     "xfer-optimization-1.16",
> > +     [[
> > +             SELECT * FROM t2;
> > +     ]], {
> > +             -- <xfer-optimization-1.16>
> > +             1, 1, 2, 2, 3, 3, 4, 4, 5, 5
> > +             -- <xfer-optimization-1.16>
> > +     })
> > +
> > +-- The following tests are supposed to test if xfer-optimization is
> actually
> > +-- used in the given cases (if the conflict actually occurs):
> > +--   1.0) insert w/o explicit confl. action & w/o index replace action
> > +--   1.1) insert w/o explicit confl. action & w/ index replace action &
> empty dest_table
> > +--   1.2) insert w/o explicit confl. action & w/ index replace action &
> non-empty dest_table
> > +--   2) insert with abort
> > +--   3.0) insert with rollback (into empty table)
> > +--   3.1) insert with rollback (into non-empty table)
> > +--   4) insert with replace
> > +--   5) insert with fail
> > +--   6) insert with ignore
> > +
> > +
> > +-- 1.0) insert w/o explicit confl. action & w/o index replace action
> >
> +-------------------------------------------------------------------------------------------
> > +
> > +bfr = box.sql.debug().sql_xferOpt_count
> > +
> > +test:do_catchsql_test(
> > +     "xfer-optimization-1.17",
> > +     [[
> > +             DROP TABLE t1;
> > +             DROP TABLE t2;
> > +             CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
> > +             CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
> > +             INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
> > +             INSERT INTO t2 VALUES (2, 2), (3, 4);
> > +             BEGIN;
> > +                     INSERT INTO t2 VALUES (4, 4);
> > +                     INSERT INTO t2 SELECT * FROM t1;
> > +     ]], {
> > +             -- <xfer-optimization-1.17>
> > +             1, "Duplicate key exists in unique index
> 'sqlite_autoindex_T2_1' in space 'T2'"
> > +             -- <xfer-optimization-1.17>
> > +     })
> > +
> > +test:do_execsql_test(
> > +     "xfer-optimization-1.18",
> > +     [[
> > +                     INSERT INTO t2 VALUES (10, 10);
> > +             COMMIT;
> > +             SELECT * FROM t2;
> > +     ]], {
> > +             -- <xfer-optimization-1.18>
> > +             2, 2, 3, 4, 4, 4, 10, 10
> > +             -- <xfer-optimization-1.18>
> > +     })
> > +
> > +aftr = box.sql.debug().sql_xferOpt_count
> > +
> > +test:do_test(
> > +     "xfer-optimization-1.19",
> > +     function()
> > +             if (aftr - bfr == 1) then
> > +                     return {1}
> > +             end
> > +             if (aftr == bfr) then
> > +                     return {0}
> > +             end
> > +             return {2}
>
> Why do you repeat this snippet each time? You can declare it as named
> function once and use it everywhere.
>
> > +     end, {
> > +             -- <xfer-optimization-1.19>
> > +             0
> > +             -- <xfer-optimization-1.19>
> > +     })
> > +
> > +-- 1.1) insert w/o explicit confl. action & w/ index replace action &
> empty dest_table
>
> Even in tests lets not exceed 80 chars (here and in other places).
>

diff --git a/src/box/sql.c b/src/box/sql.c
index fdce224..656ba17 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1636,10 +1636,12 @@ sql_debug_info(struct info_handler *h)
  extern int sql_search_count;
  extern int sql_sort_count;
  extern int sql_found_count;
+ extern int sql_xfer_count;
  info_begin(h);
  info_append_int(h, "sql_search_count", sql_search_count);
  info_append_int(h, "sql_sort_count", sql_sort_count);
  info_append_int(h, "sql_found_count", sql_found_count);
+ info_append_int(h, "sql_xfer_count", sql_xfer_count);
  info_end(h);
 }

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 2c9188e..c2df1c2 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1628,16 +1628,6 @@ sqlite3OpenTableAndIndices(Parse * pParse, /*
Parsing context */
  return i;
 }

-#ifdef SQLITE_TEST
-/*
- * The following global variable is incremented whenever the
- * transfer optimization is used.  This is used for testing
- * purposes only - to make sure the transfer optimization really
- * is happening when it is supposed to.
- */
-int sqlite3_xferopt_count;
-#endif /* SQLITE_TEST */
-
 #ifndef SQLITE_OMIT_XFER_OPT
 /*
  * Check to see if index pSrc is compatible as a source of data
@@ -1658,6 +1648,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
  assert(pDest->pTable != pSrc->pTable);
  uint32_t nDestCol = index_column_count(pDest);
  uint32_t nSrcCol = index_column_count(pSrc);
+ if ((pDest->idxType != pSrc->idxType))
+ return 0;
  if (nDestCol != nSrcCol) {
  return 0; /* Different number of columns */
  }
@@ -1724,10 +1716,9 @@ xferOptimization(Parse * pParse, /* Parser context */
  int addr1; /* Loop addresses */
  int emptyDestTest = 0; /* Address of test for empty pDest */
  int emptySrcTest = 0; /* Address of test for empty pSrc */
- Vdbe *v; /* The VDBE we are building */
- int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */
  int regData, regTupleid; /* Registers holding data and tupleid */
  struct session *user_session = current_session();
+ bool is_err_action_default = false;

  if (pSelect == NULL)
  return 0; /* Must be of the form  INSERT INTO ... SELECT ... */
@@ -1744,8 +1735,10 @@ xferOptimization(Parse * pParse, /* Parser context */
  if (onError == ON_CONFLICT_ACTION_DEFAULT) {
  if (pDest->iPKey >= 0)
  onError = pDest->keyConf;
- if (onError == ON_CONFLICT_ACTION_DEFAULT)
+ if (onError == ON_CONFLICT_ACTION_DEFAULT) {
  onError = ON_CONFLICT_ACTION_ABORT;
+ is_err_action_default = true;
+ }
  }
  assert(pSelect->pSrc); /* allocated even if there is no FROM clause */
  if (pSelect->pSrc->nSrc != 1) {
@@ -1807,6 +1800,10 @@ xferOptimization(Parse * pParse, /* Parser context */
  /* Both tables must have the same INTEGER PRIMARY KEY. */
  if (pDest->iPKey != pSrc->iPKey)
  return 0;
+ uint32_t src_space_id = SQLITE_PAGENO_TO_SPACEID(pSrc->tnum);
+ struct space *src_space = space_by_id(src_space_id);
+ uint32_t dest_space_id = SQLITE_PAGENO_TO_SPACEID(pDest->tnum);
+ struct space *dest_space = space_by_id(dest_space_id);
  for (i = 0; i < (int)pDest->def->field_count; i++) {
  enum affinity_type dest_affinity =
  pDest->def->fields[i].affinity;
@@ -1826,14 +1823,6 @@ xferOptimization(Parse * pParse, /* Parser context */
  }
  /* Default values for second and subsequent columns need to match. */
  if (i > 0) {
- uint32_t src_space_id =
- SQLITE_PAGENO_TO_SPACEID(pSrc->tnum);
- struct space *src_space =
- space_cache_find(src_space_id);
- uint32_t dest_space_id =
- SQLITE_PAGENO_TO_SPACEID(pDest->tnum);
- struct space *dest_space =
- space_cache_find(dest_space_id);
  assert(src_space != NULL && dest_space != NULL);
  char *src_expr_str =
  src_space->def->fields[i].default_value;
@@ -1848,9 +1837,6 @@ xferOptimization(Parse * pParse, /* Parser context */
  }
  }
  for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- if (index_is_unique(pDestIdx)) {
- destHasUniqueIdx = 1;
- }
  for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
  if (xferCompatibleIndex(pDestIdx, pSrcIdx))
  break;
@@ -1860,10 +1846,8 @@ xferOptimization(Parse * pParse, /* Parser context */
  }
  }
  /* Get server checks. */
- ExprList *pCheck_src = space_checks_expr_list(
- SQLITE_PAGENO_TO_SPACEID(pSrc->tnum));
- ExprList *pCheck_dest = space_checks_expr_list(
- SQLITE_PAGENO_TO_SPACEID(pDest->tnum));
+ ExprList *pCheck_src = space_checks_expr_list(src_space_id);
+ ExprList *pCheck_dest = space_checks_expr_list(dest_space_id);
  if (pCheck_dest != NULL &&
      sqlite3ExprListCompare(pCheck_src, pCheck_dest, -1) != 0) {
  /* Tables have different CHECK constraints.  Ticket #2252 */
@@ -1888,72 +1872,51 @@ xferOptimization(Parse * pParse, /* Parser context
*/
  * least a possibility, though it might only work if the destination
  * table (tab1) is initially empty.
  */
-#ifdef SQLITE_TEST
- sqlite3_xferopt_count++;
-#endif
- v = sqlite3GetVdbe(pParse);
+
+ /* The Vdbe we're building*/
+ Vdbe *v = sqlite3GetVdbe(pParse);
  iSrc = pParse->nTab++;
  iDest = pParse->nTab++;
  regData = sqlite3GetTempReg(pParse);
  regTupleid = sqlite3GetTempReg(pParse);
- sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);
- assert(destHasUniqueIdx);
- if ((pDest->iPKey < 0 && pDest->pIndex != 0) /* (1) */
-     ||destHasUniqueIdx /* (2) */
-     || (onError != ON_CONFLICT_ACTION_ABORT
- && onError != ON_CONFLICT_ACTION_ROLLBACK) /* (3) */
-     ) {
- /* In some circumstances, we are able to run the xfer optimization
- * only if the destination table is initially empty.
- * This block generates code to make
- * that determination.
- *
- * Conditions under which the destination must be empty:
- *
- * (1) There is no INTEGER PRIMARY KEY but there are indices.
- *
- * (2) The destination has a unique index.  (The xfer optimization
- *     is unable to test uniqueness.)
- *
- * (3) onError is something other than ON_CONFLICT_ACTION_ABORT and
_ROLLBACK.
- */
+
+ vdbe_emit_open_cursor(pParse, iDest, 0, dest_space);
+ VdbeComment((v, "%s", pDest->def->name));
+
+ /*
+ * Xfer optimization is unable to correctly insert data
+ * in case there's a conflict action other than *_ABORT.
+ * This is the reason we want to only run it if the
+ * destination table is initially empty.
+ * That block generates code to make that determination.
+ */
+
+ if (!(onError == ON_CONFLICT_ACTION_ABORT &&
+     is_err_action_default == false)) {
  addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
  VdbeCoverage(v);
  emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
  sqlite3VdbeJumpHere(v, addr1);
  }

- for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
- for (pSrcIdx = pSrc->pIndex; ALWAYS(pSrcIdx);
-      pSrcIdx = pSrcIdx->pNext) {
- if (xferCompatibleIndex(pDestIdx, pSrcIdx))
- break;
- }
- assert(pSrcIdx);
- struct space *src_space =
- space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrcIdx->tnum));
- vdbe_emit_open_cursor(pParse, iSrc,
-       SQLITE_PAGENO_TO_INDEXID(pSrcIdx->tnum),
-       src_space);
- VdbeComment((v, "%s", pSrcIdx->zName));
- struct space *dest_space =
- space_by_id(SQLITE_PAGENO_TO_SPACEID(pDestIdx->tnum));
- vdbe_emit_open_cursor(pParse, iDest,
-       SQLITE_PAGENO_TO_INDEXID(pDestIdx->tnum),
-       dest_space);
- VdbeComment((v, "%s", pDestIdx->zName));
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
- VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
- if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
- sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
- sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
- VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
- sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
- }
+ vdbe_emit_open_cursor(pParse, iSrc, 0, src_space);
+ VdbeComment((v, "%s", pSrc->def->name));
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
+
+#ifdef SQLITE_TEST
+ sqlite3VdbeChangeP5(v, OPFLAG_XFER_OPT);
+#endif
+
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
+ sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+ sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
+ VdbeCoverage(v);
+ sqlite3VdbeJumpHere(v, addr1);
+ sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+
  if (emptySrcTest)
  sqlite3VdbeJumpHere(v, emptySrcTest);
  sqlite3ReleaseTempReg(pParse, regTupleid);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 18bf949..4b84695 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3008,6 +3008,10 @@ struct Parse {
 #define OPFLAG_NOOP_IF_NULL  0x02 /* OP_FCopy: if source register is NULL
  * then do nothing
  */
+/* OP_RowData: xferOptimization started processing */
+#ifdef SQLITE_TEST
+#define OPFLAG_XFER_OPT      0x01
+#endif

 /*
  * Each trigger present in the database schema is stored as an
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index f50e389..5f9bc13 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -77,6 +77,16 @@
 int sql_search_count = 0;
 #endif

+#ifdef SQLITE_TEST
+/*
+ * The following global variable is incremented whenever the
+ * transfer optimization is used.  This is used for testing
+ * purposes only - to make sure the transfer optimization really
+ * is happening when it is supposed to.
+ */
+int sql_xfer_count = 0;
+#endif
+
 /*
  * When this global variable is positive, it gets decremented once before
  * each instruction in the VDBE.  When it reaches zero, the
u1.isInterrupted
@@ -3976,7 +3986,7 @@ case OP_SorterData: {
  break;
 }

-/* Opcode: RowData P1 P2 * * *
+/* Opcode: RowData P1 P2 * * P5
  * Synopsis: r[P2]=data
  *
  * Write into register P2 the complete row content for the row at
@@ -3984,6 +3994,8 @@ case OP_SorterData: {
  * There is no interpretation of the data.
  * It is just copied onto the P2 register exactly as
  * it is found in the database file.
+ * P5 can be used in debug mode to check if xferOptimization has
+ * actually started processing.
  *
  * If cursor P1 is an index, then the content is the key of the row.
  * If cursor P2 is a table, then the content extracted is the data.
@@ -3996,6 +4008,13 @@ case OP_RowData: {
  BtCursor *pCrsr;
  u32 n;

+#ifdef SQLITE_TEST
+ if (pOp->p5 == 1) {
+ pOp->p5 = 0;
+ sql_xfer_count++;
+ }
+#endif
+
  pOut = &aMem[pOp->p2];
  memAboutToChange(p, pOut);

diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
new file mode 100755
index 0000000..34f603f
--- /dev/null
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -0,0 +1,601 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(44)
+
+local bfr, aftr
+
+test:do_catchsql_test(
+ "xfer-optimization-1.1",
+ [[
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.1>
+ 0
+ -- <xfer-optimization-1.1>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.2",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.2>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.2>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.3",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE TABLE t2(id INTEGER PRIMARY KEY, b INTEGER);
+ CREATE INDEX i1 ON t1(b);
+ CREATE INDEX i2 ON t2(b);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.3>
+ 0
+ -- <xfer-optimization-1.3>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.4",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.4>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.4>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.5",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
+ INSERT INTO t1 VALUES (1, 1, 2), (2, 2, 3), (3, 3, 4);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.5>
+ 1, "table T2 has 2 columns but 3 values were supplied"
+ -- <xfer-optimization-1.5>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.6",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.6>
+
+ -- <xfer-optimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.7",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.7>
+ 0
+ -- <xfer-optimization-1.7>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.8",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.6>
+ 1, 1, 2, 2, 3, 3
+ -- <xfer-optimization-1.6>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.9",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER, a INTEGER PRIMARY KEY);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.9>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.9>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.10",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.10>
+
+ -- <xfer-optimization-1.10>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.11",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 2);
+ CREATE TABLE t2(b INTEGER PRIMARY KEY, a INTEGER);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.11>
+ 0
+ -- <xfer-optimization-1.11>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.12",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.12>
+ 1, 1, 2, 2, 3, 2
+ -- <xfer-optimization-1.12>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.13",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (3, 3), (4, 4), (5, 5);
+ INSERT INTO t2 VALUES (1, 1), (2, 2);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.13>
+ 0
+ -- <xfer-optimization-1.13>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.14",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.14>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
+ -- <xfer-optimization-1.14>
+ })
+
+test:do_catchsql_test(
+ "xfer-optimization-1.15",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE);
+ INSERT INTO t1 VALUES (2, 2), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (1, 1), (4, 4);
+ INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.15>
+ 0
+ -- <xfer-optimization-1.15>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.16",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.16>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
+ -- <xfer-optimization-1.16>
+ })
+
+-- The following tests are supposed to test if xfer-optimization is
actually
+-- used in the given cases (if the conflict actually occurs):
+-- 1.0) insert w/o explicit confl. action & w/o index replace action
+-- 1.1) insert w/o explicit confl. action & w/ index replace action &
+-- empty dest_table
+-- 1.2) insert w/o explicit confl. action & w/ index replace action &
+-- non-empty dest_table
+-- 2) insert with abort
+-- 3.0) insert with rollback (into empty table)
+-- 3.1) insert with rollback (into non-empty table)
+-- 4) insert with replace
+-- 5) insert with fail
+-- 6) insert with ignore
+
+local function do_xfer_test(test_number, return_code)
+ test_name = string.format("xfer-optimization-1.%d", test_number)
+ test:do_test(
+ test_name,
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ end, {
+ -- <test_name>
+ return_code
+ -- <test_name>
+ })
+end
+
+-- 1.0) insert w/o explicit confl. action & w/o index replace action
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.17",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.17>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.17>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.18",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.18>
+ 2, 2, 3, 4, 4, 4, 10, 10
+ -- <xfer-optimization-1.18>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+do_xfer_test(19, 0)
+
+-- 1.1) insert w/o explicit confl. action & w/
+--      index replace action & empty dest_table
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.20",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
+ CREATE TABLE t3(id INT PRIMARY KEY);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ BEGIN;
+ INSERT INTO t3 VALUES (1);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.20>
+ 0
+ -- <xfer-optimization-1.20>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.21",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.21>
+ 1, 1, 3, 3, 5, 5, 10, 10
+ -- <xfer-optimization-1.21>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+test:do_execsql_test(
+ "xfer-optimization-1.22",
+ [[
+ SELECT * FROM t3;
+ ]], {
+ -- <xfer-optimization-1.22>
+ 1
+ -- <xfer-optimization-1.22>
+ })
+
+do_xfer_test(23, 1)
+
+-- 1.2) insert w/o explicit confl. action & w/
+-- index replace action & non-empty dest_table
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.24",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ DROP TABLE t3;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.24>
+ 0
+ -- <xfer-optimization-1.24>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.25",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.25>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 10, 10
+ -- <xfer-optimization-1.25>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+do_xfer_test(26, 0)
+
+-- 2) insert with abort
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.27",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR ABORT INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.27>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.27>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.28",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.28>
+ 2, 2, 3, 4, 4, 4, 10, 10
+ -- <xfer-optimization-1.28>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+do_xfer_test(29, 1)
+
+-- 3.0) insert with rollback (into empty table)
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.30",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ BEGIN;
+ INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.30>
+ 0
+ -- <xfer-optimization-1.30>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.31",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.31>
+ 1, 1, 3, 3, 5, 5, 10, 10
+ -- <xfer-optimization-1.31>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+do_xfer_test(32, 1)
+
+-- 3.1) insert with rollback (into non-empty table)
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.33",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.33>
+ 1, "UNIQUE constraint failed: T2.A"
+ -- <xfer-optimization-1.33>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.34",
+ [[
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.34>
+ 2, 2, 3, 4
+ -- <xfer-optimization-1.34>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+do_xfer_test(35, 0)
+
+-- 4) insert with replace
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.36",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR REPLACE INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.36>
+ 0
+ -- <xfer-optimization-1.36>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.37",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.37>
+ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 10, 10
+ -- <xfer-optimization-1.37>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+do_xfer_test(38, 0)
+
+-- 5) insert with fail
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.39",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR FAIL INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.39>
+ 1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
+ -- <xfer-optimization-1.39>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.40",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.40>
+ 1, 1, 2, 2, 3, 4, 4, 4, 10, 10
+ -- <xfer-optimization-1.40>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+do_xfer_test(41, 0)
+
+-- 6) insert with ignore
+------------------------------------------------------------------------------
+
+bfr = box.sql.debug().sql_xfer_count
+
+test:do_catchsql_test(
+ "xfer-optimization-1.42",
+ [[
+ DROP TABLE t1;
+ DROP TABLE t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
+ INSERT INTO t2 VALUES (2, 2), (3, 4);
+ BEGIN;
+ INSERT INTO t2 VALUES (4, 4);
+ INSERT OR IGNORE INTO t2 SELECT * FROM t1;
+ ]], {
+ -- <xfer-optimization-1.42>
+ 0
+ -- <xfer-optimization-1.42>
+ })
+
+test:do_execsql_test(
+ "xfer-optimization-1.43",
+ [[
+ INSERT INTO t2 VALUES (10, 10);
+ COMMIT;
+ SELECT * FROM t2;
+ ]], {
+ -- <xfer-optimization-1.43>
+ 1, 1, 2, 2, 3, 4, 4, 4, 5, 5, 10, 10
+ -- <xfer-optimization-1.43>
+ })
+
+aftr = box.sql.debug().sql_xfer_count
+
+do_xfer_test(44, 0)
+
+test:finish_test()

[-- Attachment #2: Type: text/html, Size: 74317 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-18 20:18                                         ` Nikita Tatunov
@ 2018-07-19  0:20                                           ` n.pettik
  2018-07-19 17:26                                             ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-07-19  0:20 UTC (permalink / raw)
  To: tarantool-patches; +Cc: N. Tatunov

When answering on review, include only chunks related to your answers.
Otherwise, letter becomes really long..

> 
> > @@ -1725,9 +1727,9 @@ xferOptimization(Parse * pParse,        /* Parser context */
> >       int emptyDestTest = 0;  /* Address of test for empty pDest */
> >       int emptySrcTest = 0;   /* Address of test for empty pSrc */
> >       Vdbe *v;                /* The VDBE we are building */
> > -     int destHasUniqueIdx = 0;       /* True if pDest has a UNIQUE index */
> >       int regData, regTupleid;        /* Registers holding data and tupleid */
> >       struct session *user_session = current_session();
> > +     bool is_err_action_default = false;
> 
> Again: why do you need this flag? Default action is just synonym for ABORT,
> so why should we care about it?
> 
> 
> It's all about conflict action priorities as I said before.
> Consider the following example:
> ```
> 		CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
> 		CREATE TABLE t2(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b);
> 		INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
> 		INSERT INTO t2 VALUES (2, 2), (3, 4);
> 		BEGIN;
> 			INSERT INTO t2 VALUES (4, 4);
> 			INSERT INTO t2 SELECT * FROM t1;
> 			INSERT INTO t2 VALUES (10, 10);
> 		COMMIT;

onError is an action of whole statement, not of index.
In your example onError == ABORT == DEFAULT.
Replace action of index is not involved in code you wrote.

>+	uint32_t src_space_id = SQLITE_PAGENO_TO_SPACEID(pSrc->tnum);
>+	struct space *src_space = space_by_id(src_space_id);
>+	uint32_t dest_space_id = SQLITE_PAGENO_TO_SPACEID(pDest->tnum);
>+	struct space *dest_space = space_by_id(dest_space_id);

Move here also assert:

assert(src_space != NULL && dest_space != NULL);

> > diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> > new file mode 100755
> > index 0000000..e75fabc
> > --- /dev/null
> > +test:do_catchsql_test(
> > +     "xfer-optimization-1.15",
> > +     [[
> > +             DROP TABLE t1;
> > +             DROP TABLE t2;
> > +             CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
> > +             CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE);
> > +             INSERT INTO t1 VALUES (2, 2), (3, 3), (5, 5);
> > +             INSERT INTO t2 VALUES (1, 1), (4, 4);
> > +             INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
> 
> INSERT OT ROLLBACK outside transaction works the same as ABORT and DEFAULT.
> So, surround it with transaction and check that it really rollbacks.
> 
> There are basically almost the same tests surrounded by transactions (1.30 - 1.35).

If so, remove redundant tests pls.

> -/* Opcode: RowData P1 P2 * * *
> +/* Opcode: RowData P1 P2 * * P5
>   * Synopsis: r[P2]=data
>   *
>   * Write into register P2 the complete row content for the row at
> @@ -3984,6 +3994,8 @@ case OP_SorterData: {
>   * There is no interpretation of the data.
>   * It is just copied onto the P2 register exactly as
>   * it is found in the database file.
> + * P5 can be used in debug mode to check if xferOptimization has
> + * actually started processing.
>   *
>   * If cursor P1 is an index, then the content is the key of the row.
>   * If cursor P2 is a table, then the content extracted is the data.
> @@ -3996,6 +4008,13 @@ case OP_RowData: {
>  	BtCursor *pCrsr;
>  	u32 n;
>  
> +#ifdef SQLITE_TEST
> +	if (pOp->p5 == 1) {

Use named value (i.e. OPFLAG_XFER_OPT) even if they are the same.

> +
> +test:do_execsql_test(
> +	"xfer-optimization-1.37",
> +	[[
> +			INSERT INTO t2 VALUES (10, 10);

Here (and in some other places) smth wrong with indentation, fix it pls.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-19  0:20                                           ` n.pettik
@ 2018-07-19 17:26                                             ` Nikita Tatunov
  2018-07-20  3:20                                               ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-19 17:26 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 23587 bytes --]

чт, 19 июл. 2018 г. в 3:20, n.pettik <korablev@tarantool.org>:

> When answering on review, include only chunks related to your answers.
> Otherwise, letter becomes really long..
>
>
I'm sorry.


> >
> > > @@ -1725,9 +1727,9 @@ xferOptimization(Parse * pParse,        /*
> Parser context */
> > >       int emptyDestTest = 0;  /* Address of test for empty pDest */
> > >       int emptySrcTest = 0;   /* Address of test for empty pSrc */
> > >       Vdbe *v;                /* The VDBE we are building */
> > > -     int destHasUniqueIdx = 0;       /* True if pDest has a UNIQUE
> index */
> > >       int regData, regTupleid;        /* Registers holding data and
> tupleid */
> > >       struct session *user_session = current_session();
> > > +     bool is_err_action_default = false;
> >
> > Again: why do you need this flag? Default action is just synonym for
> ABORT,
> > so why should we care about it?
> >
> >
> > It's all about conflict action priorities as I said before.
> > Consider the following example:
> > ```
> >               CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE,
> b);
> >               CREATE TABLE t2(a INTEGER PRIMARY KEY ON CONFLICT REPLACE,
> b);
> >               INSERT INTO t1 VALUES (1, 1), (3, 3), (5, 5);
> >               INSERT INTO t2 VALUES (2, 2), (3, 4);
> >               BEGIN;
> >                       INSERT INTO t2 VALUES (4, 4);
> >                       INSERT INTO t2 SELECT * FROM t1;
> >                       INSERT INTO t2 VALUES (10, 10);
> >               COMMIT;
>
> onError is an action of whole statement, not of index.
> In your example onError == ABORT == DEFAULT.
> Replace action of index is not involved in code you wrote.
>

As we discussed, was *_ABORT explicitly set or not makes sense
till we have conflict actions on indices.


>
> >+      uint32_t src_space_id = SQLITE_PAGENO_TO_SPACEID(pSrc->tnum);
> >+      struct space *src_space = space_by_id(src_space_id);
> >+      uint32_t dest_space_id = SQLITE_PAGENO_TO_SPACEID(pDest->tnum);
> >+      struct space *dest_space = space_by_id(dest_space_id);
>
> Move here also assert:
>
> assert(src_space != NULL && dest_space != NULL);
>
>
Done.


> > > diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> > > new file mode 100755
> > > index 0000000..e75fabc
> > > --- /dev/null
> > > +test:do_catchsql_test(
> > > +     "xfer-optimization-1.15",
> > > +     [[
> > > +             DROP TABLE t1;
> > > +             DROP TABLE t2;
> > > +             CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
> > > +             CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE);
> > > +             INSERT INTO t1 VALUES (2, 2), (3, 3), (5, 5);
> > > +             INSERT INTO t2 VALUES (1, 1), (4, 4);
> > > +             INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
> >
> > INSERT OT ROLLBACK outside transaction works the same as ABORT and
> DEFAULT.
> > So, surround it with transaction and check that it really rollbacks.
> >
> > There are basically almost the same tests surrounded by transactions
> (1.30 - 1.35).
>
> If so, remove redundant tests pls.
>
>
Removed them. Also added tests that check if xfer
was actually used in first bunch of tests.


> > -/* Opcode: RowData P1 P2 * * *
> > +/* Opcode: RowData P1 P2 * * P5
> >   * Synopsis: r[P2]=data
> >   *
> >   * Write into register P2 the complete row content for the row at
> > @@ -3984,6 +3994,8 @@ case OP_SorterData: {
> >   * There is no interpretation of the data.
> >   * It is just copied onto the P2 register exactly as
> >   * it is found in the database file.
> > + * P5 can be used in debug mode to check if xferOptimization has
> > + * actually started processing.
> >   *
> >   * If cursor P1 is an index, then the content is the key of the row.
> >   * If cursor P2 is a table, then the content extracted is the data.
> > @@ -3996,6 +4008,13 @@ case OP_RowData: {
> >       BtCursor *pCrsr;
> >       u32 n;
> >
> > +#ifdef SQLITE_TEST
> > +     if (pOp->p5 == 1) {
>
> Use named value (i.e. OPFLAG_XFER_OPT) even if they are the same.
>
>
Thank you, forgot about this place when defined it.


> > +
> > +test:do_execsql_test(
> > +     "xfer-optimization-1.37",
> > +     [[
> > +                     INSERT INTO t2 VALUES (10, 10);
>
> Here (and in some other places) smth wrong with indentation, fix it pls.
>
>
Indentation shows if stmt is in transaction, probably it looks a little
weird,
but I'm more inclined to leave it there.

Diff that compares patch to prev. one:

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index c2df1c2..3c3bf37 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1733,12 +1733,8 @@ xferOptimization(Parse * pParse, /* Parser context */
  if (space_trigger_list(pDest->def->id) != NULL)
  return 0;
  if (onError == ON_CONFLICT_ACTION_DEFAULT) {
- if (pDest->iPKey >= 0)
- onError = pDest->keyConf;
- if (onError == ON_CONFLICT_ACTION_DEFAULT) {
- onError = ON_CONFLICT_ACTION_ABORT;
- is_err_action_default = true;
- }
+ onError = ON_CONFLICT_ACTION_ABORT;
+ is_err_action_default = true;
  }
  assert(pSelect->pSrc); /* allocated even if there is no FROM clause */
  if (pSelect->pSrc->nSrc != 1) {
@@ -1804,6 +1800,7 @@ xferOptimization(Parse * pParse, /* Parser context */
  struct space *src_space = space_by_id(src_space_id);
  uint32_t dest_space_id = SQLITE_PAGENO_TO_SPACEID(pDest->tnum);
  struct space *dest_space = space_by_id(dest_space_id);
+ assert(src_space != NULL && dest_space != NULL);
  for (i = 0; i < (int)pDest->def->field_count; i++) {
  enum affinity_type dest_affinity =
  pDest->def->fields[i].affinity;
@@ -1823,7 +1820,6 @@ xferOptimization(Parse * pParse, /* Parser context */
  }
  /* Default values for second and subsequent columns need to match. */
  if (i > 0) {
- assert(src_space != NULL && dest_space != NULL);
  char *src_expr_str =
  src_space->def->fields[i].default_value;
  char *dest_expr_str =
@@ -1885,12 +1881,11 @@ xferOptimization(Parse * pParse, /* Parser context
*/

  /*
  * Xfer optimization is unable to correctly insert data
- * in case there's a conflict action other than *_ABORT.
- * This is the reason we want to only run it if the
- * destination table is initially empty.
+ * in case there's a conflict action other than
+ * explicit *_ABORT. This is the reason we want to only
+ * run it if the destination table is initially empty.
  * That block generates code to make that determination.
  */
-
  if (!(onError == ON_CONFLICT_ACTION_ABORT &&
      is_err_action_default == false)) {
  addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 5f9bc13..bc169d9 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4009,7 +4009,7 @@ case OP_RowData: {
  u32 n;

 #ifdef SQLITE_TEST
- if (pOp->p5 == 1) {
+ if (pOp->p5 == OPFLAG_XFER_OPT) {
  pOp->p5 = 0;
  sql_xfer_count++;
  }
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
index 34f603f..29f0efe 100755
--- a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -1,9 +1,29 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(44)
+test:plan(46)

 local bfr, aftr

+local function do_xfer_test(test_number, return_code)
+ test_name = string.format("xfer-optimization-1.%d", test_number)
+ test:do_test(
+ test_name,
+ function()
+ if (aftr - bfr == 1) then
+ return {1}
+ end
+ if (aftr == bfr) then
+ return {0}
+ end
+ end, {
+ -- <test_name>
+ return_code
+ -- <test_name>
+ })
+end
+
+bfr = box.sql.debug().sql_xfer_count
+
 test:do_catchsql_test(
  "xfer-optimization-1.1",
  [[
@@ -17,6 +37,8 @@ test:do_catchsql_test(
  -- <xfer-optimization-1.1>
  })

+aftr = box.sql.debug().sql_xfer_count
+
 test:do_execsql_test(
  "xfer-optimization-1.2",
  [[
@@ -27,8 +49,12 @@ test:do_execsql_test(
  -- <xfer-optimization-1.2>
  })

+do_xfer_test(3, 1)
+
+bfr = box.sql.debug().sql_xfer_count
+
 test:do_catchsql_test(
- "xfer-optimization-1.3",
+ "xfer-optimization-1.4",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -39,23 +65,29 @@ test:do_catchsql_test(
  INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.3>
+ -- <xfer-optimization-1.4>
  0
- -- <xfer-optimization-1.3>
+ -- <xfer-optimization-1.4>
  })

+aftr = box.sql.debug().sql_xfer_count
+
 test:do_execsql_test(
- "xfer-optimization-1.4",
+ "xfer-optimization-1.5",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.4>
+ -- <xfer-optimization-1.5>
  1, 1, 2, 2, 3, 3
- -- <xfer-optimization-1.4>
+ -- <xfer-optimization-1.5>
  })

+do_xfer_test(6, 1)
+
+bfr = box.sql.debug().sql_xfer_count
+
 test:do_catchsql_test(
- "xfer-optimization-1.5",
+ "xfer-optimization-1.7",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -64,23 +96,29 @@ test:do_catchsql_test(
  CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.5>
+ -- <xfer-optimization-1.7>
  1, "table T2 has 2 columns but 3 values were supplied"
- -- <xfer-optimization-1.5>
+ -- <xfer-optimization-1.7>
  })

+aftr = box.sql.debug().sql_xfer_count
+
 test:do_execsql_test(
- "xfer-optimization-1.6",
+ "xfer-optimization-1.8",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.6>
+ -- <xfer-optimization-1.8>

- -- <xfer-optimization-1.6>
+ -- <xfer-optimization-1.8>
  })

+do_xfer_test(9, 0)
+
+bfr = box.sql.debug().sql_xfer_count
+
 test:do_catchsql_test(
- "xfer-optimization-1.7",
+ "xfer-optimization-1.10",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -89,23 +127,29 @@ test:do_catchsql_test(
  CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.7>
+ -- <xfer-optimization-1.10>
  0
- -- <xfer-optimization-1.7>
+ -- <xfer-optimization-1.10>
  })

+aftr = box.sql.debug().sql_xfer_count
+
 test:do_execsql_test(
- "xfer-optimization-1.8",
+ "xfer-optimization-1.11",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.6>
+ -- <xfer-optimization-1.11>
  1, 1, 2, 2, 3, 3
- -- <xfer-optimization-1.6>
+ -- <xfer-optimization-1.11>
  })

+do_xfer_test(12, 1);
+
+bfr = box.sql.debug().sql_xfer_count
+
 test:do_catchsql_test(
- "xfer-optimization-1.9",
+ "xfer-optimization-1.13",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -114,23 +158,29 @@ test:do_catchsql_test(
  CREATE TABLE t2(b INTEGER, a INTEGER PRIMARY KEY);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.9>
+ -- <xfer-optimization-1.13>
  1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
- -- <xfer-optimization-1.9>
+ -- <xfer-optimization-1.13>
  })

+aftr = box.sql.debug().sql_xfer_count
+
 test:do_execsql_test(
- "xfer-optimization-1.10",
+ "xfer-optimization-1.14",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.10>
+ -- <xfer-optimization-1.14>

- -- <xfer-optimization-1.10>
+ -- <xfer-optimization-1.14>
  })

+do_xfer_test(15, 0)
+
+bfr = box.sql.debug().sql_xfer_count
+
 test:do_catchsql_test(
- "xfer-optimization-1.11",
+ "xfer-optimization-1.16",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -139,72 +189,24 @@ test:do_catchsql_test(
  CREATE TABLE t2(b INTEGER PRIMARY KEY, a INTEGER);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.11>
+ -- <xfer-optimization-1.16>
  0
- -- <xfer-optimization-1.11>
- })
-
-test:do_execsql_test(
- "xfer-optimization-1.12",
- [[
- SELECT * FROM t2;
- ]], {
- -- <xfer-optimization-1.12>
- 1, 1, 2, 2, 3, 2
- -- <xfer-optimization-1.12>
+ -- <xfer-optimization-1.16>
  })

-test:do_catchsql_test(
- "xfer-optimization-1.13",
- [[
- DROP TABLE t1;
- DROP TABLE t2;
- CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
- CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
- INSERT INTO t1 VALUES (3, 3), (4, 4), (5, 5);
- INSERT INTO t2 VALUES (1, 1), (2, 2);
- INSERT INTO t2 SELECT * FROM t1;
- ]], {
- -- <xfer-optimization-1.13>
- 0
- -- <xfer-optimization-1.13>
- })
+aftr = box.sql.debug().sql_xfer_count

 test:do_execsql_test(
- "xfer-optimization-1.14",
+ "xfer-optimization-1.17",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.14>
- 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
- -- <xfer-optimization-1.14>
+ -- <xfer-optimization-1.17>
+ 1, 1, 2, 2, 3, 2
+ -- <xfer-optimization-1.17>
  })

-test:do_catchsql_test(
- "xfer-optimization-1.15",
- [[
- DROP TABLE t1;
- DROP TABLE t2;
- CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
- CREATE TABLE t2(a INTEGER PRIMARY KEY, b UNIQUE);
- INSERT INTO t1 VALUES (2, 2), (3, 3), (5, 5);
- INSERT INTO t2 VALUES (1, 1), (4, 4);
- INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
- ]], {
- -- <xfer-optimization-1.15>
- 0
- -- <xfer-optimization-1.15>
- })
-
-test:do_execsql_test(
- "xfer-optimization-1.16",
- [[
- SELECT * FROM t2;
- ]], {
- -- <xfer-optimization-1.16>
- 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
- -- <xfer-optimization-1.16>
- })
+do_xfer_test(18, 1)

 -- The following tests are supposed to test if xfer-optimization is
actually
 -- used in the given cases (if the conflict actually occurs):
@@ -220,23 +222,6 @@ test:do_execsql_test(
 -- 5) insert with fail
 -- 6) insert with ignore

-local function do_xfer_test(test_number, return_code)
- test_name = string.format("xfer-optimization-1.%d", test_number)
- test:do_test(
- test_name,
- function()
- if (aftr - bfr == 1) then
- return {1}
- end
- if (aftr == bfr) then
- return {0}
- end
- end, {
- -- <test_name>
- return_code
- -- <test_name>
- })
-end

 -- 1.0) insert w/o explicit confl. action & w/o index replace action
 ------------------------------------------------------------------------------
@@ -244,7 +229,7 @@ end
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.17",
+ "xfer-optimization-1.19",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -256,26 +241,26 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.17>
+ -- <xfer-optimization-1.19>
  1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
- -- <xfer-optimization-1.17>
+ -- <xfer-optimization-1.19>
  })

 test:do_execsql_test(
- "xfer-optimization-1.18",
+ "xfer-optimization-1.20",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.18>
+ -- <xfer-optimization-1.20>
  2, 2, 3, 4, 4, 4, 10, 10
- -- <xfer-optimization-1.18>
+ -- <xfer-optimization-1.20>
  })

 aftr = box.sql.debug().sql_xfer_count

-do_xfer_test(19, 0)
+do_xfer_test(21, 0)

 -- 1.1) insert w/o explicit confl. action & w/
 --      index replace action & empty dest_table
@@ -284,7 +269,7 @@ do_xfer_test(19, 0)
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.20",
+ "xfer-optimization-1.22",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -296,36 +281,36 @@ test:do_catchsql_test(
  INSERT INTO t3 VALUES (1);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.20>
+ -- <xfer-optimization-1.22>
  0
- -- <xfer-optimization-1.20>
+ -- <xfer-optimization-1.22>
  })

 test:do_execsql_test(
- "xfer-optimization-1.21",
+ "xfer-optimization-1.23",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.21>
+ -- <xfer-optimization-1.23>
  1, 1, 3, 3, 5, 5, 10, 10
- -- <xfer-optimization-1.21>
+ -- <xfer-optimization-1.23>
  })

 aftr = box.sql.debug().sql_xfer_count

 test:do_execsql_test(
- "xfer-optimization-1.22",
+ "xfer-optimization-1.24",
  [[
  SELECT * FROM t3;
  ]], {
- -- <xfer-optimization-1.22>
+ -- <xfer-optimization-1.24>
  1
- -- <xfer-optimization-1.22>
+ -- <xfer-optimization-1.24>
  })

-do_xfer_test(23, 1)
+do_xfer_test(25, 1)

 -- 1.2) insert w/o explicit confl. action & w/
 -- index replace action & non-empty dest_table
@@ -334,7 +319,7 @@ do_xfer_test(23, 1)
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.24",
+ "xfer-optimization-1.26",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -347,26 +332,26 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.24>
+ -- <xfer-optimization-1.26>
  0
- -- <xfer-optimization-1.24>
+ -- <xfer-optimization-1.26>
  })

 test:do_execsql_test(
- "xfer-optimization-1.25",
+ "xfer-optimization-1.27",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.25>
+ -- <xfer-optimization-1.27>
  1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 10, 10
- -- <xfer-optimization-1.25>
+ -- <xfer-optimization-1.27>
  })

 aftr = box.sql.debug().sql_xfer_count

-do_xfer_test(26, 0)
+do_xfer_test(28, 0)

 -- 2) insert with abort
 ------------------------------------------------------------------------------
@@ -374,7 +359,7 @@ do_xfer_test(26, 0)
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.27",
+ "xfer-optimization-1.29",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -386,26 +371,26 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR ABORT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.27>
+ -- <xfer-optimization-1.29>
  1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
- -- <xfer-optimization-1.27>
+ -- <xfer-optimization-1.29>
  })

 test:do_execsql_test(
- "xfer-optimization-1.28",
+ "xfer-optimization-1.30",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.28>
+ -- <xfer-optimization-1.30>
  2, 2, 3, 4, 4, 4, 10, 10
- -- <xfer-optimization-1.28>
+ -- <xfer-optimization-1.30>
  })

 aftr = box.sql.debug().sql_xfer_count

-do_xfer_test(29, 1)
+do_xfer_test(31, 1)

 -- 3.0) insert with rollback (into empty table)
 ------------------------------------------------------------------------------
@@ -413,7 +398,7 @@ do_xfer_test(29, 1)
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.30",
+ "xfer-optimization-1.32",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -423,26 +408,26 @@ test:do_catchsql_test(
  BEGIN;
  INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.30>
+ -- <xfer-optimization-1.32>
  0
- -- <xfer-optimization-1.30>
+ -- <xfer-optimization-1.32>
  })

 test:do_execsql_test(
- "xfer-optimization-1.31",
+ "xfer-optimization-1.33",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.31>
+ -- <xfer-optimization-1.33>
  1, 1, 3, 3, 5, 5, 10, 10
- -- <xfer-optimization-1.31>
+ -- <xfer-optimization-1.33>
  })

 aftr = box.sql.debug().sql_xfer_count

-do_xfer_test(32, 1)
+do_xfer_test(34, 1)

 -- 3.1) insert with rollback (into non-empty table)
 ------------------------------------------------------------------------------
@@ -450,7 +435,7 @@ do_xfer_test(32, 1)
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.33",
+ "xfer-optimization-1.35",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -462,24 +447,24 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.33>
+ -- <xfer-optimization-1.35>
  1, "UNIQUE constraint failed: T2.A"
- -- <xfer-optimization-1.33>
+ -- <xfer-optimization-1.35>
  })

 test:do_execsql_test(
- "xfer-optimization-1.34",
+ "xfer-optimization-1.36",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.34>
+ -- <xfer-optimization-1.36>
  2, 2, 3, 4
- -- <xfer-optimization-1.34>
+ -- <xfer-optimization-1.36>
  })

 aftr = box.sql.debug().sql_xfer_count

-do_xfer_test(35, 0)
+do_xfer_test(37, 0)

 -- 4) insert with replace
 ------------------------------------------------------------------------------
@@ -487,7 +472,7 @@ do_xfer_test(35, 0)
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.36",
+ "xfer-optimization-1.38",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -499,26 +484,26 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR REPLACE INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.36>
+ -- <xfer-optimization-1.38>
  0
- -- <xfer-optimization-1.36>
+ -- <xfer-optimization-1.38>
  })

 test:do_execsql_test(
- "xfer-optimization-1.37",
+ "xfer-optimization-1.39",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.37>
+ -- <xfer-optimization-1.39>
  1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 10, 10
- -- <xfer-optimization-1.37>
+ -- <xfer-optimization-1.39>
  })

 aftr = box.sql.debug().sql_xfer_count

-do_xfer_test(38, 0)
+do_xfer_test(40, 0)

 -- 5) insert with fail
 ------------------------------------------------------------------------------
@@ -526,7 +511,7 @@ do_xfer_test(38, 0)
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.39",
+ "xfer-optimization-1.41",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -538,26 +523,26 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR FAIL INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.39>
+ -- <xfer-optimization-1.41>
  1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
- -- <xfer-optimization-1.39>
+ -- <xfer-optimization-1.41>
  })

 test:do_execsql_test(
- "xfer-optimization-1.40",
+ "xfer-optimization-1.42",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.40>
+ -- <xfer-optimization-1.42>
  1, 1, 2, 2, 3, 4, 4, 4, 10, 10
- -- <xfer-optimization-1.40>
+ -- <xfer-optimization-1.42>
  })

 aftr = box.sql.debug().sql_xfer_count

-do_xfer_test(41, 0)
+do_xfer_test(43, 0)

 -- 6) insert with ignore
 ------------------------------------------------------------------------------
@@ -565,7 +550,7 @@ do_xfer_test(41, 0)
 bfr = box.sql.debug().sql_xfer_count

 test:do_catchsql_test(
- "xfer-optimization-1.42",
+ "xfer-optimization-1.44",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -577,25 +562,25 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR IGNORE INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.42>
+ -- <xfer-optimization-1.44>
  0
- -- <xfer-optimization-1.42>
+ -- <xfer-optimization-1.44>
  })

 test:do_execsql_test(
- "xfer-optimization-1.43",
+ "xfer-optimization-1.45",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.43>
+ -- <xfer-optimization-1.45>
  1, 1, 2, 2, 3, 4, 4, 4, 5, 5, 10, 10
- -- <xfer-optimization-1.43>
+ -- <xfer-optimization-1.45>
  })

 aftr = box.sql.debug().sql_xfer_count

-do_xfer_test(44, 0)
+do_xfer_test(46, 0)

 test:finish_test()

[-- Attachment #2: Type: text/html, Size: 53814 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-19 17:26                                             ` Nikita Tatunov
@ 2018-07-20  3:20                                               ` n.pettik
  2018-07-20 11:56                                                 ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-07-20  3:20 UTC (permalink / raw)
  To: tarantool-patches; +Cc: N. Tatunov

Several minor remarks.

> @@ -1885,12 +1881,11 @@ xferOptimization(Parse * pParse,	/* Parser context */
>  
>  	/*
>  	 * Xfer optimization is unable to correctly insert data
> -	 * in case there's a conflict action other than *_ABORT.
> -	 * This is the reason we want to only run it if the
> -	 * destination table is initially empty.
> +	 * in case there's a conflict action other than
> +	 * explicit *_ABORT. This is the reason we want to only
> +	 * run it if the destination table is initially empty.
>  	 * That block generates code to make that determination.
>  	 */
> -
>  	if (!(onError == ON_CONFLICT_ACTION_ABORT &&
>  	    is_err_action_default == false)) {

AFAIK we don’t use == false comparison. Instead simply use ! is_rr_action_default.

>  		addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 5f9bc13..bc169d9 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -4009,7 +4009,7 @@ case OP_RowData: {
>  	u32 n;
>  
>  #ifdef SQLITE_TEST
> -	if (pOp->p5 == 1) {
> +	if (pOp->p5 == OPFLAG_XFER_OPT) {

Flags are usually tested with & operation:

if (pOp->p5 & OPFLAG_XFER_OPT != 0)

>  		pOp->p5 = 0;
>  		sql_xfer_count++;
>  	}
> diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> index 34f603f..29f0efe 100755
> --- a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> +++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> @@ -1,9 +1,29 @@
>  
> +local function do_xfer_test(test_number, return_code)
> +	test_name = string.format("xfer-optimization-1.%d", test_number)
> +	test:do_test(
> +		test_name,
> +		function()
> +			if (aftr - bfr == 1) then
> +				return {1}
> +			end
> +			if (aftr == bfr) then
> +				return {0}
> +			end

You can simplify it to:

return aftr - bfr;

:)

Also

>+	/* The Vdbe we're building*/
>+	Vdbe *v = sqlite3GetVdbe(pParse);

Use ’struct’ modifier for complex types.
And put dot at the end of comment.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-20  3:20                                               ` n.pettik
@ 2018-07-20 11:56                                                 ` Nikita Tatunov
  2018-07-20 16:43                                                   ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-20 11:56 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 4392 bytes --]

Hello, Nikita! Thank you for review!

пт, 20 июл. 2018 г. в 6:20, n.pettik <korablev@tarantool.org>:

> Several minor remarks.
>
> > @@ -1885,12 +1881,11 @@ xferOptimization(Parse * pParse,      /* Parser
> context */
> >
> >       /*
> >        * Xfer optimization is unable to correctly insert data
> > -      * in case there's a conflict action other than *_ABORT.
> > -      * This is the reason we want to only run it if the
> > -      * destination table is initially empty.
> > +      * in case there's a conflict action other than
> > +      * explicit *_ABORT. This is the reason we want to only
> > +      * run it if the destination table is initially empty.
> >        * That block generates code to make that determination.
> >        */
> > -
> >       if (!(onError == ON_CONFLICT_ACTION_ABORT &&
> >           is_err_action_default == false)) {
>
> AFAIK we don’t use == false comparison. Instead simply use !
> is_rr_action_default.
>
>
Done.


> >               addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index 5f9bc13..bc169d9 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -4009,7 +4009,7 @@ case OP_RowData: {
> >       u32 n;
> >
> >  #ifdef SQLITE_TEST
> > -     if (pOp->p5 == 1) {
> > +     if (pOp->p5 == OPFLAG_XFER_OPT) {
>
> Flags are usually tested with & operation:
>
> if (pOp->p5 & OPFLAG_XFER_OPT != 0)
>
>
Fixed it.


> >               pOp->p5 = 0;
> >               sql_xfer_count++;
> >       }
> > diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> > index 34f603f..29f0efe 100755
> > --- a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> > +++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
> > @@ -1,9 +1,29 @@
> >
> > +local function do_xfer_test(test_number, return_code)
> > +     test_name = string.format("xfer-optimization-1.%d", test_number)
> > +     test:do_test(
> > +             test_name,
> > +             function()
> > +                     if (aftr - bfr == 1) then
> > +                             return {1}
> > +                     end
> > +                     if (aftr == bfr) then
> > +                             return {0}
> > +                     end
>
> You can simplify it to:
>
> return aftr - bfr;
>
> :)
>
>
True. Fixed it.


> Also
>
> >+      /* The Vdbe we're building*/
> >+      Vdbe *v = sqlite3GetVdbe(pParse);
>
> Use ’struct’ modifier for complex types.
> And put dot at the end of comment.
>
>
Fixed.

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 3c3bf37..4f52fa5 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1869,7 +1869,7 @@ xferOptimization(Parse * pParse, /* Parser context */
  * table (tab1) is initially empty.
  */

- /* The Vdbe we're building*/
+ /* The Vdbe struct we're building. */
  Vdbe *v = sqlite3GetVdbe(pParse);
  iSrc = pParse->nTab++;
  iDest = pParse->nTab++;
@@ -1887,7 +1887,7 @@ xferOptimization(Parse * pParse, /* Parser context */
  * That block generates code to make that determination.
  */
  if (!(onError == ON_CONFLICT_ACTION_ABORT &&
-     is_err_action_default == false)) {
+     !is_err_action_default)) {
  addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
  VdbeCoverage(v);
  emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index bc169d9..aa7f250 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4009,7 +4009,7 @@ case OP_RowData: {
  u32 n;

 #ifdef SQLITE_TEST
- if (pOp->p5 == OPFLAG_XFER_OPT) {
+ if ((pOp->p5 & OPFLAG_XFER_OPT) != 0) {
  pOp->p5 = 0;
  sql_xfer_count++;
  }
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
index 29f0efe..05d30c6 100755
--- a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -9,12 +9,7 @@ local function do_xfer_test(test_number, return_code)
  test:do_test(
  test_name,
  function()
- if (aftr - bfr == 1) then
- return {1}
- end
- if (aftr == bfr) then
- return {0}
- end
+ return {aftr - bfr}
  end, {
  -- <test_name>
  return_code

[-- Attachment #2: Type: text/html, Size: 7657 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-20 11:56                                                 ` Nikita Tatunov
@ 2018-07-20 16:43                                                   ` n.pettik
  2018-07-20 16:58                                                     ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-07-20 16:43 UTC (permalink / raw)
  To: tarantool-patches; +Cc: N. Tatunov

[-- Attachment #1: Type: text/plain, Size: 482 bytes --]

LGTM.

> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 3c3bf37..4f52fa5 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1869,7 +1869,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
>  	 * table (tab1) is initially empty.
>  	 */
>  
> -	/* The Vdbe we're building*/
> +	/* The Vdbe struct we're building. */

You misunderstood me. What I mean is:

struct Vibe *v = …;

>  	Vdbe *v = sqlite3GetVdbe(pParse);


[-- Attachment #2: Type: text/html, Size: 2185 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-20 16:43                                                   ` n.pettik
@ 2018-07-20 16:58                                                     ` Nikita Tatunov
  2018-07-29  1:12                                                       ` Alexander Turenko
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-20 16:58 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 619 bytes --]

Ooops. Thank you! fixed it and pushed.

пт, 20 июл. 2018 г. в 19:43, n.pettik <korablev@tarantool.org>:

> LGTM.
>
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 3c3bf37..4f52fa5 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1869,7 +1869,7 @@ xferOptimization(Parse * pParse, /* Parser context
> */
>    * table (tab1) is initially empty.
>    */
>
> - /* The Vdbe we're building*/
> + /* The Vdbe struct we're building. */
>
>
> You misunderstood me. What I mean is:
>
> struct Vibe *v = …;
>
>   Vdbe *v = sqlite3GetVdbe(pParse);
>
>
>

[-- Attachment #2: Type: text/html, Size: 2057 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-20 16:58                                                     ` Nikita Tatunov
@ 2018-07-29  1:12                                                       ` Alexander Turenko
  2018-07-29 11:23                                                         ` n.pettik
  2018-07-31 11:48                                                         ` Nikita Tatunov
  0 siblings, 2 replies; 38+ messages in thread
From: Alexander Turenko @ 2018-07-29  1:12 UTC (permalink / raw)
  To: Nikita Tatunov; +Cc: tarantool-patches

Hi!

Please consider my comments and questions below.

WBR, Alexander Turenko.

> +       /*
> +        * Xfer optimization is unable to correctly insert data
> +        * in case there's a conflict action other than
> +        * explicit *_ABORT. This is the reason we want to only
> +        * run it if the destination table is initially empty.
> +        * That block generates code to make that determination.
> +        */
> +       if (!(onError == ON_CONFLICT_ACTION_ABORT &&
> +           !is_err_action_default)) {

Do you mean that:

1. The optimization non-empty table case correctly works only with ABORT
   conflict action (default or explicit).
2. The default conflict action can be overwritten on per-column basis, so
   'default abort' can really be replace or other conflict action.

If so, your description doesn't give this information.

To more on that, can we check per-column conflict actions instead of check
whether the conflict action default or explicit? This would enable more cases
with non-empty tables for the optimization. And this would look less confusing,
IMHO.

I have one more question on that. It seems that SQLite has this optimization
working with ROLLBACK conflict action. We cannot doing so, because of some
problem? Did this problem described / trackerized somewhere? Or something
changes underhood and makes this impossible? Are we know exact reason or just
observing it does not work?

> +#ifdef SQLITE_TEST
> +/*
> + * The following global variable is incremented whenever the
> + * transfer optimization is used.  This is used for testing
> + * purposes only - to make sure the transfer optimization really
> + * is happening when it is supposed to.
> + */
> +int sql_xfer_count = 0;
> +#endif

I think it would be good to mention the opcode where the counter is
incremented. You can follow the style in which other counters are
described (they are mostly mention opcodes).

> -/* Opcode: RowData P1 P2 * * *
> +/* Opcode: RowData P1 P2 * * P5

We can increase the counter on per-operation basis instead of per-row by
adding the flag to OP_OpenWrite. It will save some CPU cycles :)

> +#ifdef SQLITE_TEST
> +       if ((pOp->p5 & OPFLAG_XFER_OPT) != 0) {
> +               pOp->p5 = 0;
> +               sql_xfer_count++;
> +       }
> +#endif

1. Not actual due to 2, but it would be better to use
   `pOp->p5 &= ~OPFLAG_XFER_OPT` to drop just that flag.
2. It is counter-intuitive, IMHO, to change operation flags during that
   operation. So, said above, vote to move it to OP_OpenWrite.

> +local bfr, aftr
> +

What do you plan to do with saved letters? :) Really, such abbreviations
just makes reading harder with no gains.

> +local function do_xfer_test(test_number, return_code)
> +       test_name = string.format("xfer-optimization-1.%d", test_number)
> +       test:do_test(
> +               test_name,
> +               function()
> +                       return {aftr - bfr}
> +               end, {
> +                       -- <test_name>
> +                       return_code
> +                       -- <test_name>
> +               })
> +end

That code can be written simpler (consider tap module documentation):

test:is(after - before, exp, test_name)

I suggest to create wrappers like so (I didn't test it):

local function do_xfer_test(test_func, test, test_name, func, exp, opts)
    local opts = opts or {}
    local exp_xfer_count = opts.exp_xfer_count
    local before = box.sql.debug().sql_xfer_count
    local ok = test_func(test, test_name, func, exp)
    local after = box.sql.debug().sql_xfer_count
    if exp_xfer_count ~= nil then
        ok = ok and test:is(after - before, exp_xfer_count, test_name ..
            '_xfer_count')
    end
    return ok
end

test.do_execsql_xfer_test = function(test, test_name, func, exp, opts)
    return do_xfer_test(test.do_execsql_test, test, test_name, func, exp, opts)
end

test.do_catchsql_xfer_test = function(test, test_name, func, exp, opts)
    return do_xfer_test(test.do_catchsql_test, test, test_name, func, exp, opts)
end

And use it like so:

test:do_catchsql_xfer_test(
    "xfer-optimization-1.1",
    [[
        CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
        INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
        CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
        INSERT INTO t2 SELECT * FROM t1;
    ]], {
        -- <xfer-optimization-1.1>
        0
        -- <xfer-optimization-1.1>
    }), {
        exp_xfer_count = 1
    }
)

By the way, you can revive xfer cases in test/sql-tap/with2.test.lua. Or drop
it if your new test includes all related cases from with2.

On Fri, Jul 20, 2018 at 07:58:48PM +0300, Nikita Tatunov wrote:
>    Ooops. Thank you! fixed it and pushed.
> 
>    пт, 20 июл. 2018 г. в 19:43, n.pettik <[1]korablev@tarantool.org>:
> 
>    LGTM.
> 
>    diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
>    index 3c3bf37..4f52fa5 100644
>    --- a/src/box/sql/insert.c
>    +++ b/src/box/sql/insert.c
>    @@ -1869,7 +1869,7 @@ xferOptimization(Parse * pParse, /* Parser
>    context */
>       * table (tab1) is initially empty.
>       */
> 
>    - /* The Vdbe we're building*/
>    + /* The Vdbe struct we're building. */
> 
>    You misunderstood me. What I mean is:
>    struct Vibe *v = …;
> 
>      Vdbe *v = sqlite3GetVdbe(pParse);
> 
> References
> 
>    1. mailto:korablev@tarantool.org

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-29  1:12                                                       ` Alexander Turenko
@ 2018-07-29 11:23                                                         ` n.pettik
  2018-07-29 15:16                                                           ` Alexander Turenko
  2018-07-31 11:48                                                         ` Nikita Tatunov
  1 sibling, 1 reply; 38+ messages in thread
From: n.pettik @ 2018-07-29 11:23 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Alexander Turenko, N. Tatunov

[-- Attachment #1: Type: text/plain, Size: 463 bytes --]


> 1. Not actual due to 2, but it would be better to use
>   `pOp->p5 &= ~OPFLAG_XFER_OPT` to drop just that flag.
> 2. It is counter-intuitive, IMHO, to change operation flags during that
>   operation. So, said above, vote to move it to OP_OpenWrite.

Well, actually moving it to OP_OpenWrite seems to be bad idea.
Even if code for xFer optimisation is generated, it
still might not be executed. The only opcode ensuring xFer is
under processing - OP_RowData.


[-- Attachment #2: Type: text/html, Size: 3411 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-29 11:23                                                         ` n.pettik
@ 2018-07-29 15:16                                                           ` Alexander Turenko
  2018-07-30 18:33                                                             ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: Alexander Turenko @ 2018-07-29 15:16 UTC (permalink / raw)
  To: n.pettik; +Cc: Nikita Tatunov, tarantool-patches

On Sun, Jul 29, 2018 at 02:23:30PM +0300, n.pettik wrote:
> >    1. Not actual due to 2, but it would be better to use
> >      `pOp->p5 &= ~OPFLAG_XFER_OPT` to drop just that flag.
> >    2. It is counter-intuitive, IMHO, to change operation flags during
> >       that operation. So, said above, vote to move it to OP_OpenWrite.
> 
>    Well, actually moving it to OP_OpenWrite seems to be bad idea.
> 
>    Even if code for xFer optimisation is generated, it
> 
>    still might not be executed. The only opcode ensuring xFer is
> 
>    under processing - OP_RowData.

We have separate OpenWrite opcodes in xfer and regular insert code. We
open destination space curson always (to determine whether the space is
empty), but we can set the flag when open source space cursor. But this
will forbid to check source space for emptiness in xfer code or will
require to 'workaround' it using two cursors.

By the way, I observed that the following code is dead:

>         if (emptySrcTest)
>                 sqlite3VdbeJumpHere(v, emptySrcTest);

WBR, Alexander Turenko.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-29 15:16                                                           ` Alexander Turenko
@ 2018-07-30 18:33                                                             ` Nikita Tatunov
  2018-07-30 22:17                                                               ` Alexander Turenko
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-30 18:33 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: korablev, tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 1731 bytes --]

вс, 29 июл. 2018 г. в 18:16, Alexander Turenko <
alexander.turenko@tarantool.org>:

> On Sun, Jul 29, 2018 at 02:23:30PM +0300, n.pettik wrote:
> > >    1. Not actual due to 2, but it would be better to use
> > >      `pOp->p5 &= ~OPFLAG_XFER_OPT` to drop just that flag.
> > >    2. It is counter-intuitive, IMHO, to change operation flags during
> > >       that operation. So, said above, vote to move it to OP_OpenWrite.
> >
> >    Well, actually moving it to OP_OpenWrite seems to be bad idea.
> >
> >    Even if code for xFer optimisation is generated, it
> >
> >    still might not be executed. The only opcode ensuring xFer is
> >
> >    under processing - OP_RowData.
>
> We have separate OpenWrite opcodes in xfer and regular insert code. We
> open destination space curson always (to determine whether the space is
> empty), but we can set the flag when open source space cursor. But this
> will forbid to check source space for emptiness in xfer code or will
> require to 'workaround' it using two cursors.
>
> By the way, I observed that the following code is dead:
>
> >         if (emptySrcTest)
> >                 sqlite3VdbeJumpHere(v, emptySrcTest);
>
> WBR, Alexander Turenko.
>

I'm not sured if changing p5 in OP_OpenWrite is a good idea since
1) Even if p5 isn't used while xfer is processed it's reserved and
there are no free parameters.
2) Who knows, probably we will need p5 for something in future.
3) AFAIK OP_OpenWrite is a legacy opcode and probably gonna be
removed.
4) For me it's more counter-intuitive to put incrementation in
opcode that barely related to xfer optimization idea.
5) Do CPU cycles matter for debugging purpose only global variable?

[-- Attachment #2: Type: text/html, Size: 2259 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-30 18:33                                                             ` Nikita Tatunov
@ 2018-07-30 22:17                                                               ` Alexander Turenko
  0 siblings, 0 replies; 38+ messages in thread
From: Alexander Turenko @ 2018-07-30 22:17 UTC (permalink / raw)
  To: Nikita Tatunov; +Cc: korablev, tarantool-patches

On Mon, Jul 30, 2018 at 09:33:55PM +0300, Nikita Tatunov wrote:
>    вс, 29 июл. 2018 г. в 18:16, Alexander Turenko
>    <[1]alexander.turenko@tarantool.org>:
> 
>      On Sun, Jul 29, 2018 at 02:23:30PM +0300, n.pettik wrote:
>      > >    1. Not actual due to 2, but it would be better to use
>      > >      `pOp->p5 &= ~OPFLAG_XFER_OPT` to drop just that flag.
>      > >    2. It is counter-intuitive, IMHO, to change operation flags
>      during
>      > >       that operation. So, said above, vote to move it to
>      OP_OpenWrite.
>      >
>      >    Well, actually moving it to OP_OpenWrite seems to be bad idea.
>      >
>      >    Even if code for xFer optimisation is generated, it
>      >
>      >    still might not be executed. The only opcode ensuring xFer is
>      >
>      >    under processing - OP_RowData.
>      We have separate OpenWrite opcodes in xfer and regular insert code.
>      We
>      open destination space curson always (to determine whether the space
>      is
>      empty), but we can set the flag when open source space cursor. But
>      this
>      will forbid to check source space for emptiness in xfer code or will
>      require to 'workaround' it using two cursors.
>      By the way, I observed that the following code is dead:
>      >         if (emptySrcTest)
>      >                 sqlite3VdbeJumpHere(v, emptySrcTest);
>      WBR, Alexander Turenko.
> 
>    I'm not sured if changing p5 in OP_OpenWrite is a good idea since
>    1) Even if p5 isn't used while xfer is processed it's reserved and
>    there are no free parameters.
>    2) Who knows, probably we will need p5 for something in future.
>    3) AFAIK OP_OpenWrite is a legacy opcode and probably gonna be
>    removed.
>    4) For me it's more counter-intuitive to put incrementation in
>    opcode that barely related to xfer optimization idea.
>    5) Do CPU cycles matter for debugging purpose only global variable?

Ok. So, I think you need to comment in the opcode description that the
flag is cleared after the first opcode execution.

What with the rest of review from the two previous email from me?

WBR, Alexander Turenko.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-29  1:12                                                       ` Alexander Turenko
  2018-07-29 11:23                                                         ` n.pettik
@ 2018-07-31 11:48                                                         ` Nikita Tatunov
  2018-07-31 13:29                                                           ` Alexander Turenko
  1 sibling, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-31 11:48 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches, korablev

[-- Attachment #1: Type: text/plain, Size: 30826 bytes --]

вс, 29 июл. 2018 г. в 4:12, Alexander Turenko <
alexander.turenko@tarantool.org>:

> Hi!
>
> Please consider my comments and questions below.
>
> WBR, Alexander Turenko.
>
> > +       /*
> > +        * Xfer optimization is unable to correctly insert data
> > +        * in case there's a conflict action other than
> > +        * explicit *_ABORT. This is the reason we want to only
> > +        * run it if the destination table is initially empty.
> > +        * That block generates code to make that determination.
> > +        */
> > +       if (!(onError == ON_CONFLICT_ACTION_ABORT &&
> > +           !is_err_action_default)) {
>
> Do you mean that:
>
> 1. The optimization non-empty table case correctly works only with ABORT
>    conflict action (default or explicit).
> 2. The default conflict action can be overwritten on per-column basis, so
>    'default abort' can really be replace or other conflict action.
>
> If so, your description doesn't give this information.
>
> To more on that, can we check per-column conflict actions instead of check
> whether the conflict action default or explicit? This would enable more
> cases
> with non-empty tables for the optimization. And this would look less
> confusing,
> IMHO.
>

Well, basically, you're right. But the thing is that we're going to remove
column conflict actions, thus, I suppose, it doesn't make sense.
Nikita, Correct me if I'm wrong please.


>
> I have one more question on that. It seems that SQLite has this
> optimization
> working with ROLLBACK conflict action. We cannot doing so, because of some
> problem? Did this problem described / trackerized somewhere? Or something
> changes underhood and makes this impossible? Are we know exact reason or
> just
> observing it does not work?
>

I investigated that a little. OP_IdxInsert was changed so that we can
process
ABORT, FAIL or IGNORE. I took it into account in newer version hence
it is also used in these cases even when the destination table is not empty.


>
> > +#ifdef SQLITE_TEST
> > +/*
> > + * The following global variable is incremented whenever the
> > + * transfer optimization is used.  This is used for testing
> > + * purposes only - to make sure the transfer optimization really
> > + * is happening when it is supposed to.
> > + */
> > +int sql_xfer_count = 0;
> > +#endif
>
> I think it would be good to mention the opcode where the counter is
> incremented. You can follow the style in which other counters are
> described (they are mostly mention opcodes).
>

Added.


>
> > -/* Opcode: RowData P1 P2 * * *
> > +/* Opcode: RowData P1 P2 * * P5
>
> We can increase the counter on per-operation basis instead of per-row by
> adding the flag to OP_OpenWrite. It will save some CPU cycles :)


>
> +#ifdef SQLITE_TEST
> > +       if ((pOp->p5 & OPFLAG_XFER_OPT) != 0) {
> > +               pOp->p5 = 0;
> > +               sql_xfer_count++;
> > +       }
> > +#endif
>
> 1. Not actual due to 2, but it would be better to use
>    `pOp->p5 &= ~OPFLAG_XFER_OPT` to drop just that flag.
> 2. It is counter-intuitive, IMHO, to change operation flags during that
>    operation. So, said above, vote to move it to OP_OpenWrite.
>
>
Made the first.


> > +local bfr, aftr
> > +
>
> What do you plan to do with saved letters? :) Really, such abbreviations
> just makes reading harder with no gains.
>

I made dis :(
(p.s. Changed it)


>
> > +local function do_xfer_test(test_number, return_code)
> > +       test_name = string.format("xfer-optimization-1.%d", test_number)
> > +       test:do_test(
> > +               test_name,
> > +               function()
> > +                       return {aftr - bfr}
> > +               end, {
> > +                       -- <test_name>
> > +                       return_code
> > +                       -- <test_name>
> > +               })
> > +end
>
> That code can be written simpler (consider tap module documentation):
>
> test:is(after - before, exp, test_name)
>
> I suggest to create wrappers like so (I didn't test it):
>
> local function do_xfer_test(test_func, test, test_name, func, exp, opts)
>     local opts = opts or {}
>     local exp_xfer_count = opts.exp_xfer_count
>     local before = box.sql.debug().sql_xfer_count
>     local ok = test_func(test, test_name, func, exp)
>     local after = box.sql.debug().sql_xfer_count
>     if exp_xfer_count ~= nil then
>         ok = ok and test:is(after - before, exp_xfer_count, test_name ..
>             '_xfer_count')
>     end
>     return ok
> end
>
> test.do_execsql_xfer_test = function(test, test_name, func, exp, opts)
>     return do_xfer_test(test.do_execsql_test, test, test_name, func, exp,
> opts)
> end
>
> test.do_catchsql_xfer_test = function(test, test_name, func, exp, opts)
>     return do_xfer_test(test.do_catchsql_test, test, test_name, func, exp,
> opts)
> end
>
> And use it like so:
>
> test:do_catchsql_xfer_test(
>     "xfer-optimization-1.1",
>     [[
>         CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
>         INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
>         CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
>         INSERT INTO t2 SELECT * FROM t1;
>     ]], {
>         -- <xfer-optimization-1.1>
>         0
>         -- <xfer-optimization-1.1>
>     }), {
>         exp_xfer_count = 1
>     }
> )
>
>
Thank you for suggestion. Added that implementation.
(Needed to change it a little bit)


> By the way, you can revive xfer cases in test/sql-tap/with2.test.lua. Or
> drop
> it if your new test includes all related cases from with2.
>
>
They're alive now!


> On Fri, Jul 20, 2018 at 07:58:48PM +0300, Nikita Tatunov wrote:
> >    Ooops. Thank you! fixed it and pushed.
> >
> >    пт, 20 июл. 2018 г. в 19:43, n.pettik <[1]korablev@tarantool.org>:
> >
> >    LGTM.
> >
> >    diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> >    index 3c3bf37..4f52fa5 100644
> >    --- a/src/box/sql/insert.c
> >    +++ b/src/box/sql/insert.c
> >    @@ -1869,7 +1869,7 @@ xferOptimization(Parse * pParse, /* Parser
> >    context */
> >       * table (tab1) is initially empty.
> >       */
> >
> >    - /* The Vdbe we're building*/
> >    + /* The Vdbe struct we're building. */
> >
> >    You misunderstood me. What I mean is:
> >    struct Vibe *v = …;
> >
> >      Vdbe *v = sqlite3GetVdbe(pParse);
> >
> > References
> >
> >    1. mailto:korablev@tarantool.org


Diff:

 diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 3c45920..c3cf94a 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1715,7 +1715,6 @@ xferOptimization(Parse * pParse, /* Parser context */
  int iSrc, iDest; /* Cursors from source and destination */
  int addr1; /* Loop addresses */
  int emptyDestTest = 0; /* Address of test for empty pDest */
- int emptySrcTest = 0; /* Address of test for empty pSrc */
  int regData, regTupleid; /* Registers holding data and tupleid */
  struct session *user_session = current_session();
  bool is_err_action_default = false;
@@ -1881,13 +1880,15 @@ xferOptimization(Parse * pParse, /* Parser context
*/

  /*
  * Xfer optimization is unable to correctly insert data
- * in case there's a conflict action other than
- * explicit *_ABORT. This is the reason we want to only
+ * in case there's a conflict action other than *_ABORT,
+ * *_FAIL or *_IGNORE. This is the reason we want to only
  * run it if the destination table is initially empty.
  * That block generates code to make that determination.
  */
- if (!(onError == ON_CONFLICT_ACTION_ABORT &&
-     !is_err_action_default)) {
+ if (!(onError == ON_CONFLICT_ACTION_ABORT ||
+     onError == ON_CONFLICT_ACTION_FAIL ||
+     onError == ON_CONFLICT_ACTION_IGNORE) ||
+     is_err_action_default) {
  addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
  VdbeCoverage(v);
  emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
@@ -1905,15 +1906,23 @@ xferOptimization(Parse * pParse, /* Parser context
*/
 #endif

  sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
- sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+ switch (onError) {
+ case ON_CONFLICT_ACTION_IGNORE:
+ sqlite3VdbeChangeP5(v, OPFLAG_OE_IGNORE);
+ break;
+ case ON_CONFLICT_ACTION_FAIL:
+ sqlite3VdbeChangeP5(v, OPFLAG_OE_FAIL);
+ break;
+ default:
+ sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+ break;
+ }
  sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
  VdbeCoverage(v);
  sqlite3VdbeJumpHere(v, addr1);
  sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
  sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);

- if (emptySrcTest)
- sqlite3VdbeJumpHere(v, emptySrcTest);
  sqlite3ReleaseTempReg(pParse, regTupleid);
  sqlite3ReleaseTempReg(pParse, regData);
  if (emptyDestTest) {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index aa7f250..ce01039 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -79,10 +79,10 @@ int sql_search_count = 0;

 #ifdef SQLITE_TEST
 /*
- * The following global variable is incremented whenever the
- * transfer optimization is used.  This is used for testing
- * purposes only - to make sure the transfer optimization really
- * is happening when it is supposed to.
+ * The following global variable is incremented in OP_RowData
+ * whenever the xfer optimization is used. This is used on
+ * testing purposes only - to make sure the transfer optimization
+ * really is happening when it is supposed to.
  */
 int sql_xfer_count = 0;
 #endif
@@ -4008,9 +4008,13 @@ case OP_RowData: {
  BtCursor *pCrsr;
  u32 n;

+/*
+ * Flag P5 is cleared after the first insertion using xfer
+ * optimization.
+ */
 #ifdef SQLITE_TEST
  if ((pOp->p5 & OPFLAG_XFER_OPT) != 0) {
- pOp->p5 = 0;
+ pOp->p5 &= ~OPFLAG_XFER_OPT;
  sql_xfer_count++;
  }
 #endif
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
index 05d30c6..b99de2b 100755
--- a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -2,24 +2,28 @@
 test = require("sqltester")
 test:plan(46)

-local bfr, aftr
-
-local function do_xfer_test(test_number, return_code)
- test_name = string.format("xfer-optimization-1.%d", test_number)
- test:do_test(
- test_name,
- function()
- return {aftr - bfr}
- end, {
- -- <test_name>
- return_code
- -- <test_name>
- })
+local function do_xfer_test(test, test_func, test_name, func, exp, opts)
+ local opts = opts or {}
+ local exp_xfer_count = opts.exp_xfer_count
+ local before = box.sql.debug().sql_xfer_count
+ local ok, result = pcall(test_func, test, test_name, func, exp)
+ local after = box.sql.debug().sql_xfer_count
+ if exp_xfer_count ~= nil then
+ ok = ok and test:is(after - before, exp_xfer_count,
+                     test_name .. '-xfer-count')
+ end
+ return ok
 end

-bfr = box.sql.debug().sql_xfer_count
+test.do_execsql_xfer_test = function(test, test_name, func, exp, opts)
+ return do_xfer_test(test, test.do_execsql_test, test_name, func, exp,
opts)
+end
+
+test.do_catchsql_xfer_test = function(test, test_name, func, exp, opts)
+ return do_xfer_test(test, test.do_catchsql_test, test_name, func, exp,
opts)
+end

-test:do_catchsql_test(
+test:do_catchsql_xfer_test(
  "xfer-optimization-1.1",
  [[
  CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER UNIQUE);
@@ -30,10 +34,10 @@ test:do_catchsql_test(
  -- <xfer-optimization-1.1>
  0
  -- <xfer-optimization-1.1>
+ }, {
+ exp_xfer_count = 1
  })

-aftr = box.sql.debug().sql_xfer_count
-
 test:do_execsql_test(
  "xfer-optimization-1.2",
  [[
@@ -44,12 +48,8 @@ test:do_execsql_test(
  -- <xfer-optimization-1.2>
  })

-do_xfer_test(3, 1)
-
-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.4",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.3",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -60,15 +60,15 @@ test:do_catchsql_test(
  INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.4>
+ -- <xfer-optimization-1.3>
  0
- -- <xfer-optimization-1.4>
+ -- <xfer-optimization-1.3>
+ }, {
+ exp_xfer_count = 1
  })

-aftr = box.sql.debug().sql_xfer_count
-
 test:do_execsql_test(
- "xfer-optimization-1.5",
+ "xfer-optimization-1.4",
  [[
  SELECT * FROM t2;
  ]], {
@@ -77,12 +77,8 @@ test:do_execsql_test(
  -- <xfer-optimization-1.5>
  })

-do_xfer_test(6, 1)
-
-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.7",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.5",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -91,29 +87,25 @@ test:do_catchsql_test(
  CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.7>
+ -- <xfer-optimization-1.5>
  1, "table T2 has 2 columns but 3 values were supplied"
- -- <xfer-optimization-1.7>
+ -- <xfer-optimization-1.5>
+ }, {
+ exp_xfer_count = 0
  })

-aftr = box.sql.debug().sql_xfer_count
-
 test:do_execsql_test(
- "xfer-optimization-1.8",
+ "xfer-optimization-1.6",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.8>
+ -- <xfer-optimization-1.6>

- -- <xfer-optimization-1.8>
+ -- <xfer-optimization-1.6>
  })

-do_xfer_test(9, 0)
-
-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.10",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.7",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -122,29 +114,25 @@ test:do_catchsql_test(
  CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.10>
+ -- <xfer-optimization-1.7>
  0
- -- <xfer-optimization-1.10>
+ -- <xfer-optimization-1.7>
+ }, {
+ exp_xfer_count = 1
  })

-aftr = box.sql.debug().sql_xfer_count
-
 test:do_execsql_test(
- "xfer-optimization-1.11",
+ "xfer-optimization-1.8",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.11>
+ -- <xfer-optimization-1.8>
  1, 1, 2, 2, 3, 3
- -- <xfer-optimization-1.11>
+ -- <xfer-optimization-1.8>
  })

-do_xfer_test(12, 1);
-
-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.13",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.9",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -153,29 +141,25 @@ test:do_catchsql_test(
  CREATE TABLE t2(b INTEGER, a INTEGER PRIMARY KEY);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.13>
+ -- <xfer-optimization-1.9>
  1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
- -- <xfer-optimization-1.13>
+ -- <xfer-optimization-1.9>
+ }, {
+ exp_xfer_count = 0
  })

-aftr = box.sql.debug().sql_xfer_count
-
 test:do_execsql_test(
- "xfer-optimization-1.14",
+ "xfer-optimization-1.10",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.14>
+ -- <xfer-optimization-1.10>

- -- <xfer-optimization-1.14>
+ -- <xfer-optimization-1.10>
  })

-do_xfer_test(15, 0)
-
-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.16",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.11",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -184,25 +168,23 @@ test:do_catchsql_test(
  CREATE TABLE t2(b INTEGER PRIMARY KEY, a INTEGER);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.16>
+ -- <xfer-optimization-1.11>
  0
- -- <xfer-optimization-1.16>
+ -- <xfer-optimization-1.11>
+ }, {
+ exp_xfer_count = 1
  })

-aftr = box.sql.debug().sql_xfer_count
-
 test:do_execsql_test(
- "xfer-optimization-1.17",
+ "xfer-optimization-1.12",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.17>
+ -- <xfer-optimization-1.12>
  1, 1, 2, 2, 3, 2
- -- <xfer-optimization-1.17>
+ -- <xfer-optimization-1.12>
  })

-do_xfer_test(18, 1)
-
 -- The following tests are supposed to test if xfer-optimization is
actually
 -- used in the given cases (if the conflict actually occurs):
 -- 1.0) insert w/o explicit confl. action & w/o index replace action
@@ -221,10 +203,8 @@ do_xfer_test(18, 1)
 -- 1.0) insert w/o explicit confl. action & w/o index replace action
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.19",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.13",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -236,35 +216,31 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.19>
+ -- <xfer-optimization-1.13>
  1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
- -- <xfer-optimization-1.19>
+ -- <xfer-optimization-1.13>
+ }, {
+ exp_xfer_count = 0
  })

 test:do_execsql_test(
- "xfer-optimization-1.20",
+ "xfer-optimization-1.14",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.20>
+ -- <xfer-optimization-1.14>
  2, 2, 3, 4, 4, 4, 10, 10
- -- <xfer-optimization-1.20>
+ -- <xfer-optimization-1.14>
  })

-aftr = box.sql.debug().sql_xfer_count
-
-do_xfer_test(21, 0)
-
 -- 1.1) insert w/o explicit confl. action & w/
 --      index replace action & empty dest_table
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.22",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.15",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -276,45 +252,41 @@ test:do_catchsql_test(
  INSERT INTO t3 VALUES (1);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.22>
+ -- <xfer-optimization-1.15>
  0
- -- <xfer-optimization-1.22>
+ -- <xfer-optimization-1.15>
+ }, {
+ exp_xfer_count = 1
  })

 test:do_execsql_test(
- "xfer-optimization-1.23",
+ "xfer-optimization-1.16",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.23>
+ -- <xfer-optimization-1.16>
  1, 1, 3, 3, 5, 5, 10, 10
- -- <xfer-optimization-1.23>
+ -- <xfer-optimization-1.16>
  })

-aftr = box.sql.debug().sql_xfer_count
-
 test:do_execsql_test(
- "xfer-optimization-1.24",
+ "xfer-optimization-1.17",
  [[
  SELECT * FROM t3;
  ]], {
- -- <xfer-optimization-1.24>
+ -- <xfer-optimization-1.17>
  1
- -- <xfer-optimization-1.24>
+ -- <xfer-optimization-1.17>
  })

-do_xfer_test(25, 1)
-
 -- 1.2) insert w/o explicit confl. action & w/
 -- index replace action & non-empty dest_table
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.26",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.18",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -327,34 +299,30 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.26>
+ -- <xfer-optimization-1.18>
  0
- -- <xfer-optimization-1.26>
+ -- <xfer-optimization-1.18>
+ }, {
+ exp_xfer_count = 0
  })

 test:do_execsql_test(
- "xfer-optimization-1.27",
+ "xfer-optimization-1.19",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.27>
+ -- <xfer-optimization-1.19>
  1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 10, 10
- -- <xfer-optimization-1.27>
+ -- <xfer-optimization-1.19>
  })

-aftr = box.sql.debug().sql_xfer_count
-
-do_xfer_test(28, 0)
-
 -- 2) insert with abort
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.29",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.20",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -366,34 +334,30 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR ABORT INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.29>
+ -- <xfer-optimization-1.20>
  1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
- -- <xfer-optimization-1.29>
+ -- <xfer-optimization-1.20>
+ }, {
+ exp_xfer_count = 1
  })

 test:do_execsql_test(
- "xfer-optimization-1.30",
+ "xfer-optimization-1.21",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.30>
+ -- <xfer-optimization-1.21>
  2, 2, 3, 4, 4, 4, 10, 10
- -- <xfer-optimization-1.30>
+ -- <xfer-optimization-1.21>
  })

-aftr = box.sql.debug().sql_xfer_count
-
-do_xfer_test(31, 1)
-
 -- 3.0) insert with rollback (into empty table)
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.32",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.22",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -403,34 +367,30 @@ test:do_catchsql_test(
  BEGIN;
  INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.32>
+ -- <xfer-optimization-1.22>
  0
- -- <xfer-optimization-1.32>
+ -- <xfer-optimization-1.22>
+ }, {
+ exp_xfer_count = 1
  })

 test:do_execsql_test(
- "xfer-optimization-1.33",
+ "xfer-optimization-1.23",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.33>
+ -- <xfer-optimization-1.23>
  1, 1, 3, 3, 5, 5, 10, 10
- -- <xfer-optimization-1.33>
+ -- <xfer-optimization-1.23>
  })

-aftr = box.sql.debug().sql_xfer_count
-
-do_xfer_test(34, 1)
-
 -- 3.1) insert with rollback (into non-empty table)
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.35",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.24",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -442,32 +402,28 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR ROLLBACK INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.35>
+ -- <xfer-optimization-1.24>
  1, "UNIQUE constraint failed: T2.A"
- -- <xfer-optimization-1.35>
+ -- <xfer-optimization-1.24>
+ }, {
+ exp_xfer_count = 0
  })

 test:do_execsql_test(
- "xfer-optimization-1.36",
+ "xfer-optimization-1.25",
  [[
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.36>
+ -- <xfer-optimization-1.25>
  2, 2, 3, 4
- -- <xfer-optimization-1.36>
+ -- <xfer-optimization-1.25>
  })

-aftr = box.sql.debug().sql_xfer_count
-
-do_xfer_test(37, 0)
-
 -- 4) insert with replace
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.38",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.26",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -479,34 +435,30 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR REPLACE INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.38>
+ -- <xfer-optimization-1.26>
  0
- -- <xfer-optimization-1.38>
+ -- <xfer-optimization-1.26>
+ }, {
+ exp_xfer_count = 0
  })

 test:do_execsql_test(
- "xfer-optimization-1.39",
+ "xfer-optimization-1.27",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.39>
+ -- <xfer-optimization-1.27>
  1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 10, 10
- -- <xfer-optimization-1.39>
+ -- <xfer-optimization-1.27>
  })

-aftr = box.sql.debug().sql_xfer_count
-
-do_xfer_test(40, 0)
-
 -- 5) insert with fail
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.41",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.28",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -518,34 +470,30 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR FAIL INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.41>
+ -- <xfer-optimization-1.28>
  1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space
'T2'"
- -- <xfer-optimization-1.41>
+ -- <xfer-optimization-1.28>
+ }, {
+ exp_xfer_count = 1
  })

 test:do_execsql_test(
- "xfer-optimization-1.42",
+ "xfer-optimization-1.29",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.42>
+ -- <xfer-optimization-1.29>
  1, 1, 2, 2, 3, 4, 4, 4, 10, 10
- -- <xfer-optimization-1.42>
+ -- <xfer-optimization-1.29>
  })

-aftr = box.sql.debug().sql_xfer_count
-
-do_xfer_test(43, 0)
-
 -- 6) insert with ignore
 ------------------------------------------------------------------------------

-bfr = box.sql.debug().sql_xfer_count
-
-test:do_catchsql_test(
- "xfer-optimization-1.44",
+test:do_catchsql_xfer_test(
+ "xfer-optimization-1.30",
  [[
  DROP TABLE t1;
  DROP TABLE t2;
@@ -557,25 +505,23 @@ test:do_catchsql_test(
  INSERT INTO t2 VALUES (4, 4);
  INSERT OR IGNORE INTO t2 SELECT * FROM t1;
  ]], {
- -- <xfer-optimization-1.44>
+ -- <xfer-optimization-1.30>
  0
- -- <xfer-optimization-1.44>
+ -- <xfer-optimization-1.30>
+ }, {
+ exp_xfer_count = 1
  })

 test:do_execsql_test(
- "xfer-optimization-1.45",
+ "xfer-optimization-1.31",
  [[
  INSERT INTO t2 VALUES (10, 10);
  COMMIT;
  SELECT * FROM t2;
  ]], {
- -- <xfer-optimization-1.45>
+ -- <xfer-optimization-1.31>
  1, 1, 2, 2, 3, 4, 4, 4, 5, 5, 10, 10
- -- <xfer-optimization-1.45>
+ -- <xfer-optimization-1.31>
  })

-aftr = box.sql.debug().sql_xfer_count
-
-do_xfer_test(46, 0)
-
 test:finish_test()
diff --git a/test/sql-tap/with2.test.lua b/test/sql-tap/with2.test.lua
index fbd1f4e..bd0187f 100755
--- a/test/sql-tap/with2.test.lua
+++ b/test/sql-tap/with2.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(44)
+test:plan(59)

 --!./tcltestrunner.lua
 -- 2014 January 11
@@ -382,43 +382,136 @@ genstmt(255), {
 --     -- </4.7>
 -- })

------------------------------------------------------------------------------
--- Check that adding a WITH clause to an INSERT disables the xfer
+-----------------------------------------------------------------
+-- Check that adding a WITH clause to an INSERT disables the xfer
 -- optimization.
---
--- Tarantool: `sqlite3_xferopt_count` is not exported
---   Need to understand if this optimization works at all and if we really
need it.
---   Commented so far.
--- function do_xfer_test(tn, bXfer, sql, res)
---     res = res or ""
---     sqlite3_xferopt_count = 0
---     X(267, "X!cmd", [=[["uplevel",[["list","do_test",["tn"],["\n    set
dres [db eval {",["sql"],"}]\n    list [set ::sqlite3_xferopt_count] [set
dres]\n  "],[["list",["bXfer"],["res"]]]]]]]=])
--- end

--- test:do_execsql_test(
---     5.1,
---     [[
---         DROP TABLE IF EXISTS t1;
---         DROP TABLE IF EXISTS t2;
---         CREATE TABLE t1(a PRIMARY KEY, b);
---         CREATE TABLE t2(a PRIMARY KEY, b);
---     ]])
-
--- do_xfer_test(5.2, 1, " INSERT INTO t1 SELECT * FROM t2 ")
--- do_xfer_test(5.3, 0, " INSERT INTO t1 SELECT a, b FROM t2 ")
--- do_xfer_test(5.4, 0, " INSERT INTO t1 SELECT b, a FROM t2 ")
--- do_xfer_test(5.5, 0, [[
---   WITH x AS (SELECT a, b FROM t2) INSERT INTO t1 SELECT * FROM x
--- ]])
--- do_xfer_test(5.6, 0, [[
---   WITH x AS (SELECT a, b FROM t2) INSERT INTO t1 SELECT * FROM t2
--- ]])
--- do_xfer_test(5.7, 0, [[
---  INSERT INTO t1 WITH x AS ( SELECT * FROM t2 ) SELECT * FROM x
--- ]])
--- do_xfer_test(5.8, 0, [[
---  INSERT INTO t1 WITH x(a,b) AS ( SELECT * FROM t2 ) SELECT * FROM x
--- ]])
+local function do_xfer_test(test, test_func, test_name, func, exp, opts)
+ local opts = opts or {}
+ local exp_xfer_count = opts.exp_xfer_count
+ local before = box.sql.debug().sql_xfer_count
+ local ok, result = pcall(test_func, test, test_name, func, exp)
+ local after = box.sql.debug().sql_xfer_count
+ if exp_xfer_count ~= nil then
+ ok = ok and test:is(after - before, exp_xfer_count,
+                     test_name .. '-xfer-count')
+ end
+ return ok
+end
+
+test.do_execsql_xfer_test = function(test, test_name, func, exp, opts)
+ return do_xfer_test(test, test.do_execsql_test, test_name, func, exp,
opts)
+end
+
+test.do_catchsql_xfer_test = function(test, test_name, func, exp, opts)
+ return do_xfer_test(test, test.do_catchsql_test, test_name, func, exp,
opts)
+end
+
+test:do_execsql_test(
+ 5.1,
+ [[
+ DROP TABLE IF EXISTS t1;
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t1(a PRIMARY KEY, b);
+ CREATE TABLE t2(a PRIMARY KEY, b);
+ INSERT INTO t2 VALUES (1, 1), (2, 2);
+ ]], {
+ -- <5.1>
+
+ -- <5.1>
+ })
+
+test:do_execsql_xfer_test(
+ 5.2,
+ [[
+ INSERT INTO t1 SELECT * FROM t2;
+ DELETE FROM t1;
+ ]], {
+ -- <5.2>
+
+ -- <5.2>
+ },  {
+ exp_xfer_count = 1
+ })
+
+test:do_execsql_xfer_test(
+ 5.3,
+ [[
+ INSERT INTO t1 SELECT a, b FROM t2;
+ DELETE FROM t1;
+ ]], {
+ -- <5.3>
+
+ -- <5.3>
+ },  {
+ exp_xfer_count = 0
+ })
+
+test:do_execsql_xfer_test(
+ 5.4,
+ [[
+ INSERT INTO t1 SELECT b, a FROM t2;
+ DELETE FROM t1;
+ ]], {
+ -- <5.4>
+
+ -- <5.4>
+ },  {
+ exp_xfer_count = 0
+ })
+
+test:do_execsql_xfer_test(
+ 5.5,
+ [[
+ WITH x AS (SELECT a, b FROM t2) INSERT INTO t1 SELECT * FROM x;
+ DELETE FROM t1;
+ ]], {
+ -- <5.5>
+
+ -- <5.5>
+ },  {
+ exp_xfer_count = 0
+ })
+
+test:do_execsql_xfer_test(
+ 5.6,
+ [[
+ WITH x AS (SELECT a, b FROM t2) INSERT INTO t1 SELECT * FROM t2;
+ DELETE FROM t1;
+ ]], {
+ -- <5.6>
+
+ -- <5.6>
+ },  {
+ exp_xfer_count = 0
+ })
+
+test:do_execsql_xfer_test(
+ 5.7,
+ [[
+ INSERT INTO t1 WITH x AS (SELECT * FROM t2) SELECT * FROM x;
+ DELETE FROM t1;
+ ]], {
+ -- <5.7>
+
+ -- <5.7>
+ },  {
+ exp_xfer_count = 0
+ })
+
+test:do_execsql_xfer_test(
+ 5.8,
+ [[
+ INSERT INTO t1 WITH x(a,b) AS (SELECT * FROM t2) SELECT * FROM x;
+ DELETE FROM t1;
+ ]], {
+ -- <5.8>
+
+ -- <5.8>
+ },  {
+ exp_xfer_count = 0
+ })
+
 -----------------------------------------------------------------------------
 -- Check that syntax (and other) errors in statements with WITH clauses
 -- attached to them do not cause problems (e.g. memory leaks).

[-- Attachment #2: Type: text/html, Size: 66886 bytes --]

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-31 11:48                                                         ` Nikita Tatunov
@ 2018-07-31 13:29                                                           ` Alexander Turenko
  2018-07-31 17:04                                                             ` Nikita Tatunov
  0 siblings, 1 reply; 38+ messages in thread
From: Alexander Turenko @ 2018-07-31 13:29 UTC (permalink / raw)
  To: Nikita Tatunov; +Cc: tarantool-patches, korablev

Hi!

Thanks for updates.

Answered inline.

WBR, Alexander Turenko.

> > > +       /*
> > > +        * Xfer optimization is unable to correctly insert data
> > > +        * in case there's a conflict action other than
> > > +        * explicit *_ABORT. This is the reason we want to only
> > > +        * run it if the destination table is initially empty.
> > > +        * That block generates code to make that determination.
> > > +        */
> > > +       if (!(onError == ON_CONFLICT_ACTION_ABORT &&
> > > +           !is_err_action_default)) {
> >
> > Do you mean that:
> >
> > 1. The optimization non-empty table case correctly works only with ABORT
> >    conflict action (default or explicit).
> > 2. The default conflict action can be overwritten on per-column basis, so
> >    'default abort' can really be replace or other conflict action.
> >
> > If so, your description doesn't give this information.
> >
> > To more on that, can we check per-column conflict actions instead of check
> > whether the conflict action default or explicit? This would enable more
> > cases
> > with non-empty tables for the optimization. And this would look less
> > confusing,
> > IMHO.
> >
> 
> Well, basically, you're right. But the thing is that we're going to remove
> column conflict actions, thus, I suppose, it doesn't make sense.
> Nikita, Correct me if I'm wrong please.

I recommend to comment such things in the code or commit message (as you
think it is more appropriate). It allows to decrease review iterations
at least :) I don't insist, though.

> > I have one more question on that. It seems that SQLite has this
> > optimization
> > working with ROLLBACK conflict action. We cannot doing so, because of some
> > problem? Did this problem described / trackerized somewhere? Or something
> > changes underhood and makes this impossible? Are we know exact reason or
> > just
> > observing it does not work?
> >
> 
> I investigated that a little. OP_IdxInsert was changed so that we can
> process
> ABORT, FAIL or IGNORE. I took it into account in newer version hence
> it is also used in these cases even when the destination table is not empty.

So I understand it as follows: sqlite3's OP_IdxInsert just copy rows and
cannot handle non-trivial conflict actions, but tarantool's OP_IdxInsert
can handle it. However we have some non-investigated problems with
ROLLBACK and REPLACE as well as per-column conflict actions.

I think that is is worth to investigate it a little deeper and file
issue(s), but this is not the blocker for this patch.

>   sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
> - sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
> + switch (onError) {
> + case ON_CONFLICT_ACTION_IGNORE:
> + sqlite3VdbeChangeP5(v, OPFLAG_OE_IGNORE);
> + break;
> + case ON_CONFLICT_ACTION_FAIL:
> + sqlite3VdbeChangeP5(v, OPFLAG_OE_FAIL);
> + break;
> + default:
> + sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
> + break;
> + }

OPFLAG_NCHANGE is independent with other flags I think.

> +local function do_xfer_test(test, test_func, test_name, func, exp, opts)
> + local opts = opts or {}
> + local exp_xfer_count = opts.exp_xfer_count
> + local before = box.sql.debug().sql_xfer_count
> + local ok, result = pcall(test_func, test, test_name, func, exp)
> + local after = box.sql.debug().sql_xfer_count
> + if exp_xfer_count ~= nil then
> + ok = ok and test:is(after - before, exp_xfer_count,
> +                     test_name .. '-xfer-count')
> + end
> + return ok
>  end

I missed that do_execsql_test don't return a result and do_catchsql_test
returns {0} or {1}. So just don't same `ok` and `result` and remove
pcall.

By the way, we have 4 spaces indent for Lua accorsing to Lua Style
Guide.

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-31 13:29                                                           ` Alexander Turenko
@ 2018-07-31 17:04                                                             ` Nikita Tatunov
  2018-07-31 17:44                                                               ` Alexander Turenko
  0 siblings, 1 reply; 38+ messages in thread
From: Nikita Tatunov @ 2018-07-31 17:04 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches, korablev

Hello! Thanks for review, Diff is in the end!

вт, 31 июл. 2018 г. в 16:29, Alexander Turenko
<alexander.turenko@tarantool.org>:
>
> Hi!
>
> Thanks for updates.
>
> Answered inline.
>
> WBR, Alexander Turenko.
>
> > > > +       /*
> > > > +        * Xfer optimization is unable to correctly insert data
> > > > +        * in case there's a conflict action other than
> > > > +        * explicit *_ABORT. This is the reason we want to only
> > > > +        * run it if the destination table is initially empty.
> > > > +        * That block generates code to make that determination.
> > > > +        */
> > > > +       if (!(onError == ON_CONFLICT_ACTION_ABORT &&
> > > > +           !is_err_action_default)) {
> > >
> > > Do you mean that:
> > >
> > > 1. The optimization non-empty table case correctly works only with ABORT
> > >    conflict action (default or explicit).
> > > 2. The default conflict action can be overwritten on per-column basis, so
> > >    'default abort' can really be replace or other conflict action.
> > >
> > > If so, your description doesn't give this information.
> > >
> > > To more on that, can we check per-column conflict actions instead of check
> > > whether the conflict action default or explicit? This would enable more
> > > cases
> > > with non-empty tables for the optimization. And this would look less
> > > confusing,
> > > IMHO.
> > >
> >
> > Well, basically, you're right. But the thing is that we're going to remove
> > column conflict actions, thus, I suppose, it doesn't make sense.
> > Nikita, Correct me if I'm wrong please.
>
> I recommend to comment such things in the code or commit message (as you
> think it is more appropriate). It allows to decrease review iterations
> at least :) I don't insist, though.
>



> > > I have one more question on that. It seems that SQLite has this
> > > optimization
> > > working with ROLLBACK conflict action. We cannot doing so, because of some
> > > problem? Did this problem described / trackerized somewhere? Or something
> > > changes underhood and makes this impossible? Are we know exact reason or
> > > just
> > > observing it does not work?
> > >
> >
> > I investigated that a little. OP_IdxInsert was changed so that we can
> > process
> > ABORT, FAIL or IGNORE. I took it into account in newer version hence
> > it is also used in these cases even when the destination table is not empty.
>
> So I understand it as follows: sqlite3's OP_IdxInsert just copy rows and
> cannot handle non-trivial conflict actions, but tarantool's OP_IdxInsert
> can handle it. However we have some non-investigated problems with
> ROLLBACK and REPLACE as well as per-column conflict actions.
>
> I think that is is worth to investigate it a little deeper and file
> issue(s), but this is not the blocker for this patch.
>

Yes, I'm on it.

> >   sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
> > - sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
> > + switch (onError) {
> > + case ON_CONFLICT_ACTION_IGNORE:
> > + sqlite3VdbeChangeP5(v, OPFLAG_OE_IGNORE);
> > + break;
> > + case ON_CONFLICT_ACTION_FAIL:
> > + sqlite3VdbeChangeP5(v, OPFLAG_OE_FAIL);
> > + break;
> > + default:
> > + sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
> > + break;
> > + }
>
> OPFLAG_NCHANGE is independent with other flags I think.
>

Fixed.

> > +local function do_xfer_test(test, test_func, test_name, func, exp, opts)
> > + local opts = opts or {}
> > + local exp_xfer_count = opts.exp_xfer_count
> > + local before = box.sql.debug().sql_xfer_count
> > + local ok, result = pcall(test_func, test, test_name, func, exp)
> > + local after = box.sql.debug().sql_xfer_count
> > + if exp_xfer_count ~= nil then
> > + ok = ok and test:is(after - before, exp_xfer_count,
> > +                     test_name .. '-xfer-count')
> > + end
> > + return ok
> >  end
>
> I missed that do_execsql_test don't return a result and do_catchsql_test
> returns {0} or {1}. So just don't same `ok` and `result` and remove
> pcall.
>

Fixed.

> By the way, we have 4 spaces indent for Lua accorsing to Lua Style
> Guide.

Fixed.

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index c3cf94a..041601c 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1908,10 +1908,10 @@ xferOptimization(Parse * pParse, /* Parser context */
  sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
  switch (onError) {
  case ON_CONFLICT_ACTION_IGNORE:
- sqlite3VdbeChangeP5(v, OPFLAG_OE_IGNORE);
+ sqlite3VdbeChangeP5(v, OPFLAG_OE_IGNORE | OPFLAG_NCHANGE);
  break;
  case ON_CONFLICT_ACTION_FAIL:
- sqlite3VdbeChangeP5(v, OPFLAG_OE_FAIL);
+ sqlite3VdbeChangeP5(v, OPFLAG_OE_FAIL | OPFLAG_NCHANGE);
  break;
  default:
  sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
diff --git a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
index b99de2b..7778fce 100755
--- a/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
+++ b/test/sql-tap/gh-3307-xfer-optimization-issue.test.lua
@@ -6,14 +6,11 @@ local function do_xfer_test(test, test_func,
test_name, func, exp, opts)
     local opts = opts or {}
     local exp_xfer_count = opts.exp_xfer_count
     local before = box.sql.debug().sql_xfer_count
- local ok, result = pcall(test_func, test, test_name, func, exp)
+    test_func(test, test_name, func, exp)
     local after = box.sql.debug().sql_xfer_count
- if exp_xfer_count ~= nil then
- ok = ok and test:is(after - before, exp_xfer_count,
+    return test:is(after - before, exp_xfer_count,
                    test_name .. '-xfer-count')
 end
- return ok
-end

 test.do_execsql_xfer_test = function(test, test_name, func, exp, opts)
     return do_xfer_test(test, test.do_execsql_test, test_name, func, exp, opts)
diff --git a/test/sql-tap/with2.test.lua b/test/sql-tap/with2.test.lua
index bd0187f..a59bac7 100755
--- a/test/sql-tap/with2.test.lua
+++ b/test/sql-tap/with2.test.lua
@@ -390,14 +390,11 @@ local function do_xfer_test(test, test_func,
test_name, func, exp, opts)
     local opts = opts or {}
     local exp_xfer_count = opts.exp_xfer_count
     local before = box.sql.debug().sql_xfer_count
- local ok, result = pcall(test_func, test, test_name, func, exp)
+    test_func(test, test_name, func, exp)
     local after = box.sql.debug().sql_xfer_count
- if exp_xfer_count ~= nil then
- ok = ok and test:is(after - before, exp_xfer_count,
+    return test:is(after - before, exp_xfer_count,
                    test_name .. '-xfer-count')
 end
- return ok
-end

 test.do_execsql_xfer_test = function(test, test_name, func, exp, opts)
     return do_xfer_test(test, test.do_execsql_test, test_name, func, exp, opts)

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-07-31 17:04                                                             ` Nikita Tatunov
@ 2018-07-31 17:44                                                               ` Alexander Turenko
  0 siblings, 0 replies; 38+ messages in thread
From: Alexander Turenko @ 2018-07-31 17:44 UTC (permalink / raw)
  To: Nikita Tatunov, Kirill Yukhin; +Cc: tarantool-patches, korablev

Now LGTM.

Kirill, can you look at the patch?

WBR, Alexander Turenko.

On Tue, Jul 31, 2018 at 08:04:32PM +0300, Nikita Tatunov wrote:
> Hello! Thanks for review, Diff is in the end!
> 
> вт, 31 июл. 2018 г. в 16:29, Alexander Turenko
> <alexander.turenko@tarantool.org>:
> >
> > Hi!
> >
> > Thanks for updates.
> >
> > Answered inline.
> >
> > WBR, Alexander Turenko.
> <...>

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

* [tarantool-patches] Re: [PATCH] sql: xfer optimization issue
  2018-04-18 15:32 [tarantool-patches] [PATCH] sql: xfer optimization issue N.Tatunov
  2018-04-18 16:33 ` [tarantool-patches] " Hollow111
@ 2018-08-21 16:43 ` Kirill Yukhin
  1 sibling, 0 replies; 38+ messages in thread
From: Kirill Yukhin @ 2018-08-21 16:43 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, N.Tatunov

Hello,
On 18 апр 18:32, N.Tatunov wrote:
> Currently insertion from the table to another one
> with the same schema using SELECT works wrong.
> The problem lies in xfer optimization which opens cursors for
> all of the indexes and inserts data excessively.
> The bug was fixed so the data should now insert
> correctly.
> 
> Closes #3307
> ---
> 
> Branch: https://github.com/tarantool/tarantool/tree/N_Tatunov/gh-3307-xfer-optimization-issue
> Issue: https://github.com/tarantool/tarantool/issues/3307
I've checked in the patch into 2.0 branch.

--
Regards, Kirill Yukhin

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

end of thread, other threads:[~2018-08-21 16:44 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-04-18 15:32 [tarantool-patches] [PATCH] sql: xfer optimization issue N.Tatunov
2018-04-18 16:33 ` [tarantool-patches] " Hollow111
2018-04-19 11:22   ` n.pettik
2018-04-19 15:36     ` Hollow111
2018-04-20  1:02       ` n.pettik
2018-04-20 15:09         ` Hollow111
2018-04-20 16:09           ` n.pettik
2018-04-20 17:59             ` Hollow111
2018-04-23 23:40               ` n.pettik
2018-04-27 15:45                 ` Hollow111
2018-05-03 22:57                   ` n.pettik
2018-05-04 12:54                     ` Hollow111
2018-06-28 10:18                       ` Alexander Turenko
2018-07-09 15:50                         ` Alexander Turenko
2018-07-16 12:54                           ` Nikita Tatunov
2018-07-16 13:06                             ` n.pettik
2018-07-16 13:20                               ` Nikita Tatunov
2018-07-16 18:37                                 ` Nikita Tatunov
2018-07-16 19:12                                   ` n.pettik
2018-07-16 21:27                                     ` Nikita Tatunov
2018-07-18 15:13                                       ` n.pettik
2018-07-18 20:18                                         ` Nikita Tatunov
2018-07-19  0:20                                           ` n.pettik
2018-07-19 17:26                                             ` Nikita Tatunov
2018-07-20  3:20                                               ` n.pettik
2018-07-20 11:56                                                 ` Nikita Tatunov
2018-07-20 16:43                                                   ` n.pettik
2018-07-20 16:58                                                     ` Nikita Tatunov
2018-07-29  1:12                                                       ` Alexander Turenko
2018-07-29 11:23                                                         ` n.pettik
2018-07-29 15:16                                                           ` Alexander Turenko
2018-07-30 18:33                                                             ` Nikita Tatunov
2018-07-30 22:17                                                               ` Alexander Turenko
2018-07-31 11:48                                                         ` Nikita Tatunov
2018-07-31 13:29                                                           ` Alexander Turenko
2018-07-31 17:04                                                             ` Nikita Tatunov
2018-07-31 17:44                                                               ` Alexander Turenko
2018-08-21 16:43 ` Kirill Yukhin

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