[tarantool-patches] Re: [PATCH] sql: add check for <WITH> absence in <CREATE VIEW>
Roman Khabibov
roman.habibov at tarantool.org
Wed Sep 4 17:14:10 MSK 2019
> On Aug 29, 2019, at 20:59, Nikita Pettik <korablev at tarantool.org> wrote:
>
> On Wed, Aug 28, 2019 at 03:17:41PM +0300, Roman Khabibov wrote:
>>
>>> On Aug 20, 2019, at 22:41, n.pettik <korablev at tarantool.org> wrote:
>>> You slightly misunderstood me. I proposed to allow such queries
>>> gracefully handling WITH statement and avoiding references
>>> counter incrementation for CTEs.
>>
>> commit 202677704c455208d6def08d373776cfae82fdbe
>> Author: Roman Khabibov <roman.habibov at tarantool.org>
>> Date: Mon Jul 29 18:00:34 2019 +0300
>>
>> sql: allow to create view as <WITH> clause
>>
>> Allow views to use CTEs in <WITH> clauses, which can be in any
>> (nested) select after <AS>.
>
> Please, provide decent commit message which would include problem
> explanation, chosen solution, some of implementation details.
Done.
>> diff --git a/src/box/alter.cc b/src/box/alter.cc
>> index 92f1d5b22..d3d6770cf 100644
>> --- a/src/box/alter.cc
>> +++ b/src/box/alter.cc
>> @@ -1721,6 +1721,8 @@ update_view_references(struct Select *select, int update_value,
>> const char *space_name = sql_src_list_entry_name(list, i);
>> if (space_name == NULL)
>> continue;
>> + if (is_cte(select, space_name) == true)
>> + continue;
>
> If function returns boolean value, you don't need to use comparison
> to check it:
>
> 'if (returns_boolean()) ...' or 'if (! returns_boolean()) ...'
>
> Also, put comment explaining why do you skip reference counter
> increment in case of SELECT containts CTE.
@@ -1759,6 +1759,13 @@ update_view_references(struct Select *select, int update_value,
const char *space_name = sql_src_list_entry_name(list, i);
if (space_name == NULL)
continue;
+ /*
+ * CTE tables isn't held in space cache and we
+ * don't need to increment reference counter for
+ * CTEs.
+ */
+ if (sql_select_has_cte_with_name(select, space_name))
+ continue;
struct space *space = space_by_name(space_name);
if (space == NULL) {
if (! suppress_error) {
>> diff --git a/src/box/sql.h b/src/box/sql.h
>> index 9ccecf28c..c180e40e1 100644
>> --- a/src/box/sql.h
>> +++ b/src/box/sql.h
>> @@ -323,6 +323,17 @@ sql_select_delete(struct sql *db, struct Select *select);
>> struct SrcList *
>> sql_select_expand_from_tables(struct Select *select);
>>
>> +/**
>> + * Check if @a name matches with at least one of CTE names typed
>> + * in <WITH> clauses within @a select.
>> + *
>> + * @param select Select to be checked.
>
> -> Select which may contain CTE.
>
>> + * @retval true Has CTE with @a name.
>> + * @retval false Hasn't CTE with @a name.
>> +*/
>> +bool
>> +is_cte(struct Select *select, const char *name);
>> +
>> /**
>> * Temporary getter in order to avoid including sqlInt.h
>> * in alter.cc.
>> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
>> index c312f61f1..0ee840b89 100644
>> --- a/src/box/sql/select.c
>> +++ b/src/box/sql/select.c
>> @@ -332,6 +332,25 @@ sql_select_expand_from_tables(struct Select *select)
>> return walker.u.pSrcList;
>> }
>>
>> +bool
>> +is_cte(struct Select *select, const char *name)
>
> Such a bad name. Please, get in touch with our naming policy
> and come up with better one.
diff --git a/src/box/sql.h b/src/box/sql.h
index 7644051b4..422c8dfc2 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -323,6 +323,17 @@ sql_select_delete(struct sql *db, struct Select *select);
struct SrcList *
sql_select_expand_from_tables(struct Select *select);
+/**
+ * Check if @a name matches with at least one of CTE names typed
+ * in <WITH> clauses within @a select.
+ *
+ * @param select Select which may contain CTE.
+ * @retval true Has CTE with @a name.
+ * @retval false Hasn't CTE with @a name.
+*/
+bool
+sql_select_has_cte_with_name(struct Select *select, const char *name);
+
>> +{
>> + assert(select != NULL && name != NULL);
>> + struct With *with = select->pWith;
>> + if (with != NULL) {
>> + if (memcmp(name, with->a->zName, strlen(name)) == 0)
>> + return true;
>> + }
>> + struct SrcList *list = select->pSrc;
>> + int item_count = sql_src_list_entry_count(list);
>> + for (int i = 0; i < item_count; ++i) {
>> + if (list->a[i].pSelect != NULL)
>> + if (is_cte(list->a[i].pSelect, name) == true)
>> + return true;
>> + }
>
> It's wrong way of CTE traversal. See sqlTreeViewWith() for correct
> implementation. With your approach this example still fails:
>
> create view v as WITH RECURSIVE
> xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),
> yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),
> m(iter, cx, cy, x, y) AS (
> SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis
> UNION ALL
> SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m
> WHERE (x*x + y*y) < 4.0 AND iter<28
> ),
> m2(iter, cx, cy) AS (
> SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
> ),
> a(t) AS (
> SELECT group_concat( substr(' .+*#', 1+least(iter/7,4), 1), '')
> FROM m2 GROUP BY cy
> )
> SELECT group_concat(trim(t),x'0a') FROM a;
+bool
+sql_select_has_cte_with_name(struct Select *select, const char *name)
+{
+ assert(select != NULL && name != NULL);
+ struct With *with = select->pWith;
+ if (with != NULL) {
+ for (int i = 0; i < with->nCte; i++) {
+ const struct Cte *cte = &with->a[i];
+ if (memcmp(name, cte->zName, strlen(name)) == 0)
+ return true;
+ }
+ }
+ struct SrcList *list = select->pSrc;
+ int item_count = sql_src_list_entry_count(list);
+ for (int i = 0; i < item_count; ++i) {
+ if (list->a[i].pSelect != NULL)
+ if (sql_select_has_cte_with_name(list->a[i].pSelect,
+ name))
+ return true;
+ }
+ return false;
+}
+
>> diff --git a/test/sql-tap/view.test.lua b/test/sql-tap/view.test.lua
>> index 101f4c3e7..4b695fa6a 100755
>> --- a/test/sql-tap/view.test.lua
>> +++ b/test/sql-tap/view.test.lua
>> @@ -1233,4 +1233,99 @@ test:do_catchsql_test(
>> -- </view-23.8>
>> })
>>
>> +-- gh-4149: Check error message for view creation with (nested)
>> +-- select with <WITH> clause.
>
> -> gh-4149: make sure that VIEW can be created as CTE.
>
>> +test:do_execsql_test(
>> + "view-24.1",
>> + [[
>> + CREATE TABLE ts (s1 INT PRIMARY KEY);
>> + INSERT INTO ts VALUES (1);
>> + ]], {
>> + -- <view-24.1>
>> + -- </view-24.1>
>> + })
>> +
>> +test:do_execsql_test(
>> + "view-24.2",
>> + [[
>> + CREATE VIEW v AS WITH w(id) AS (
>> + SELECT 1)
>> + SELECT * FROM ts;
>> + ]], {
>
> Please test not only the fact that view can be created, but also that
> it is queryable (i.e. SELECT * FROM v is processed without accidents).
>
>> + -- <view-24.2>
>> + -- </view-24.2>
>> + })
>>
>
> All examples below are almost identical since nCte for them equals to 1.
>
>> +test:do_execsql_test(
>> + "view-24.3",
>> + [[
>> + DROP VIEW v;
>> + CREATE VIEW v AS WITH RECURSIVE w AS (
>> + SELECT s1 FROM ts
>> + UNION ALL
>> + SELECT s1+1 FROM w WHERE s1 < 4)
>> + SELECT * FROM w;
>> + ]], {
>> + -- <view-24.3>
>> + -- </view-24.3>
>> + })
>> +
>> +test:do_execsql_test(
>> + "view-24.4",
>> + [[
>> + DROP VIEW v;
>> + CREATE VIEW v AS SELECT * FROM (
>> + WITH RECURSIVE w AS (
>> + SELECT s1 FROM ts
>> + UNION ALL
>> + SELECT s1+1 FROM w WHERE s1 < 4)
>> + SELECT * FROM w);
>> + ]], {
>> + -- <view-24.4>
>> + -- </view-24.4>
>> + })
>> +
>> +test:do_execsql_test(
>> + "view-24.5",
>> + [[
>> + DROP VIEW v;
>> + CREATE VIEW v AS SELECT * FROM (
>> + SELECT * FROM (
>> + WITH RECURSIVE w AS (
>> + SELECT s1 FROM ts
>> + UNION ALL
>> + SELECT s1+1 FROM w WHERE s1 < 4)
>> + SELECT * FROM w));
>> + ]], {
>> + -- <view-24.5>
>> + -- </view-24.5>
>> + })
>> +
>> +test:do_execsql_test(
>> + "view-24.6",
>> + [[
>> + DROP VIEW v;
>> + CREATE VIEW v AS SELECT * FROM
>> + (SELECT 1),
>> + (SELECT 2) JOIN
>> + (WITH RECURSIVE w AS (
>> + SELECT s1 FROM ts
>> + UNION ALL
>> + SELECT s1+1 FROM w WHERE s1 < 4)
>> + SELECT * FROM w);
>> + ]], {
>> + -- <view-24.6>
>> + -- </view-24.6>
>> + })
>> +
>> +test:do_execsql_test(
>> + "view-24.7",
>> + [[
>> + DROP VIEW v;
>> + DROP TABLE ts;
>
> You don't have to provide clean-up in SQL-tap suite.
I have reduced the number of tests. Why? I make sure that
nested select are handled too in these tests.
+-- gh-4149: Make sure that VIEW can be created as CTE.
+test:do_execsql_test(
+ "view-24.1",
+ [[
+ CREATE TABLE ts (s1 INT PRIMARY KEY);
+ INSERT INTO ts VALUES (1);
+ ]], {
+ -- <view-24.1>
+ -- </view-24.1>
+ })
+
+test:do_execsql_test(
+ "view-24.2",
+ [[
+ CREATE VIEW v AS WITH w(id) AS (
+ SELECT 1)
+ SELECT * FROM ts;
+ SELECT * FROM v;
+ ]], {
+ -- <view-24.2>
+ 1
+ -- </view-24.2>
+ })
+
+test:do_execsql_test(
+ "view-24.3",
+ [[
+ DROP VIEW v;
+ CREATE VIEW v AS WITH RECURSIVE w AS (
+ SELECT s1 FROM ts
+ UNION ALL
+ SELECT s1+1 FROM w WHERE s1 < 4)
+ SELECT * FROM w;
+ SELECT * FROM v;
+ ]], {
+ -- <view-24.3>
+ 1,2,3,4
+ -- </view-24.3>
+ })
+
+test:do_execsql_test(
+ "view-24.4",
+ [[
+ DROP VIEW v;
+ CREATE VIEW v AS SELECT * FROM
+ (SELECT 1),
+ (SELECT 2) JOIN
+ (WITH RECURSIVE w AS (
+ SELECT s1 FROM ts
+ UNION ALL
+ SELECT s1+1 FROM w WHERE s1 < 4)
+ SELECT * FROM w);
+ SELECT * FROM v;
+ ]], {
+ -- <view-24.4>
+ 1,2,1,1,2,2,1,2,3,1,2,4
+ -- </view-24.4>
+ })
+
+test:do_execsql_test(
+ "view-24.5",
+ [[
+ DROP VIEW v;
+ DROP TABLE ts;
+ CREATE VIEW v AS WITH RECURSIVE
+ xaxis(x) AS (
+ VALUES(-2.0)
+ UNION ALL
+ SELECT x+0.5 FROM xaxis WHERE x<1.2),
+ yaxis(y) AS (
+ VALUES(-1.0)
+ UNION ALL
+ SELECT y+0.5 FROM yaxis WHERE y<1.0),
+ m(iter, cx, cy, x, y) AS (
+ SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis
+ UNION ALL
+ SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m WHERE (x*x + y*y) < 4.0 AND iter<1
+ ),
+ m2(iter, cx, cy) AS (
+ SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
+ ),
+ a(t) AS (
+ SELECT group_concat( substr('a', 1+least(iter/7,4), 1), '') FROM m2 GROUP BY cy
+ )
+ SELECT group_concat(trim(t),x'0a') FROM a;
+ SELECT * FROM v;
+ ]], {
+ -- <view-24.5>
+ "aaaaaaaa\naaaaaaaa\naaaaaaaa\naaaaaaaa\naaaaaaaa"
+ -- </view-24.5>
+ })
+
commit 4f78ff7cf8b4a3f496d72388235f773ae51d6efc
Author: Roman Khabibov <roman.habibov at tarantool.org>
Date: Mon Jul 29 18:00:34 2019 +0300
sql: allow to create view as <WITH> clause
Allow views to use CTEs in <WITH> clauses, which can be in any
(nested) select after <AS>. Before this patch, during view
creation all referenced spaces were fetched by name from SELECT
and their reference counters were incremented to avoid dangling
references. It occurred in update_view_references(). Obviously, CTE
tables weren't held in space cache, ergo error "space doesn’t
exist" was raised. Add check if a space from FROM is CTE. If it is,
don't increment its reference counter and don't raise the error.
Closes #4149
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 4f2e34bf0..a625d8c39 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -1759,6 +1759,13 @@ update_view_references(struct Select *select, int update_value,
const char *space_name = sql_src_list_entry_name(list, i);
if (space_name == NULL)
continue;
+ /*
+ * CTE tables isn't held in space cache and we
+ * don't need to increment reference counter for
+ * CTEs.
+ */
+ if (sql_select_has_cte_with_name(select, space_name))
+ continue;
struct space *space = space_by_name(space_name);
if (space == NULL) {
if (! suppress_error) {
diff --git a/src/box/sql.h b/src/box/sql.h
index 7644051b4..422c8dfc2 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -323,6 +323,17 @@ sql_select_delete(struct sql *db, struct Select *select);
struct SrcList *
sql_select_expand_from_tables(struct Select *select);
+/**
+ * Check if @a name matches with at least one of CTE names typed
+ * in <WITH> clauses within @a select.
+ *
+ * @param select Select which may contain CTE.
+ * @retval true Has CTE with @a name.
+ * @retval false Hasn't CTE with @a name.
+*/
+bool
+sql_select_has_cte_with_name(struct Select *select, const char *name);
+
/**
* Temporary getter in order to avoid including sqlInt.h
* in alter.cc.
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index ddb5de87e..d7dab35cd 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -332,6 +332,29 @@ sql_select_expand_from_tables(struct Select *select)
return walker.u.pSrcList;
}
+bool
+sql_select_has_cte_with_name(struct Select *select, const char *name)
+{
+ assert(select != NULL && name != NULL);
+ struct With *with = select->pWith;
+ if (with != NULL) {
+ for (int i = 0; i < with->nCte; i++) {
+ const struct Cte *cte = &with->a[i];
+ if (memcmp(name, cte->zName, strlen(name)) == 0)
+ return true;
+ }
+ }
+ struct SrcList *list = select->pSrc;
+ int item_count = sql_src_list_entry_count(list);
+ for (int i = 0; i < item_count; ++i) {
+ if (list->a[i].pSelect != NULL)
+ if (sql_select_has_cte_with_name(list->a[i].pSelect,
+ name))
+ return true;
+ }
+ return false;
+}
+
/*
* Given 1 to 3 identifiers preceding the JOIN keyword, determine the
* type of join. Return an integer constant that expresses that type
diff --git a/test/sql-tap/view.test.lua b/test/sql-tap/view.test.lua
index 101f4c3e7..67e068b42 100755
--- a/test/sql-tap/view.test.lua
+++ b/test/sql-tap/view.test.lua
@@ -1,6 +1,6 @@
#!/usr/bin/env tarantool
test = require("sqltester")
-test:plan(73)
+test:plan(78)
--!./tcltestrunner.lua
-- 2002 February 26
@@ -1233,4 +1233,96 @@ test:do_catchsql_test(
-- </view-23.8>
})
+-- gh-4149: Make sure that VIEW can be created as CTE.
+test:do_execsql_test(
+ "view-24.1",
+ [[
+ CREATE TABLE ts (s1 INT PRIMARY KEY);
+ INSERT INTO ts VALUES (1);
+ ]], {
+ -- <view-24.1>
+ -- </view-24.1>
+ })
+
+test:do_execsql_test(
+ "view-24.2",
+ [[
+ CREATE VIEW v AS WITH w(id) AS (
+ SELECT 1)
+ SELECT * FROM ts;
+ SELECT * FROM v;
+ ]], {
+ -- <view-24.2>
+ 1
+ -- </view-24.2>
+ })
+
+test:do_execsql_test(
+ "view-24.3",
+ [[
+ DROP VIEW v;
+ CREATE VIEW v AS WITH RECURSIVE w AS (
+ SELECT s1 FROM ts
+ UNION ALL
+ SELECT s1+1 FROM w WHERE s1 < 4)
+ SELECT * FROM w;
+ SELECT * FROM v;
+ ]], {
+ -- <view-24.3>
+ 1,2,3,4
+ -- </view-24.3>
+ })
+
+test:do_execsql_test(
+ "view-24.4",
+ [[
+ DROP VIEW v;
+ CREATE VIEW v AS SELECT * FROM
+ (SELECT 1),
+ (SELECT 2) JOIN
+ (WITH RECURSIVE w AS (
+ SELECT s1 FROM ts
+ UNION ALL
+ SELECT s1+1 FROM w WHERE s1 < 4)
+ SELECT * FROM w);
+ SELECT * FROM v;
+ ]], {
+ -- <view-24.4>
+ 1,2,1,1,2,2,1,2,3,1,2,4
+ -- </view-24.4>
+ })
+
+test:do_execsql_test(
+ "view-24.5",
+ [[
+ DROP VIEW v;
+ DROP TABLE ts;
+ CREATE VIEW v AS WITH RECURSIVE
+ xaxis(x) AS (
+ VALUES(-2.0)
+ UNION ALL
+ SELECT x+0.5 FROM xaxis WHERE x<1.2),
+ yaxis(y) AS (
+ VALUES(-1.0)
+ UNION ALL
+ SELECT y+0.5 FROM yaxis WHERE y<1.0),
+ m(iter, cx, cy, x, y) AS (
+ SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis
+ UNION ALL
+ SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m WHERE (x*x + y*y) < 4.0 AND iter<1
+ ),
+ m2(iter, cx, cy) AS (
+ SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
+ ),
+ a(t) AS (
+ SELECT group_concat( substr('a', 1+least(iter/7,4), 1), '') FROM m2 GROUP BY cy
+ )
+ SELECT group_concat(trim(t),x'0a') FROM a;
+ SELECT * FROM v;
+ ]], {
+ -- <view-24.5>
+ "aaaaaaaa\naaaaaaaa\naaaaaaaa\naaaaaaaa\naaaaaaaa"
+ -- </view-24.5>
+ })
+
test:finish_test()
More information about the Tarantool-patches
mailing list