Tarantool development patches archive
 help / color / mirror / Atom feed
From: n.pettik <korablev@tarantool.org>
To: tarantool-patches@freelists.org
Cc: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Subject: [tarantool-patches] Re: [PATCH 5/6] sql: return result-set type via IProto
Date: Wed, 24 Oct 2018 02:28:21 +0300	[thread overview]
Message-ID: <3C315E98-880F-4218-85C7-42631453A818@tarantool.org> (raw)
In-Reply-To: <bb196abd-1075-d626-3fb9-7bd3fea80890@tarantool.org>


>>> 4. Why is the type 'UNKNOWN' if the value is integer?
>> It is how bindings work.
> 
> No, bindings as an API have nothing to do with types.
> It is Vdbe's issue, into which the values are inserted,
> and it should detect their type. It is not affinity
> issue, but Vdbe changing after its compilation. Anyway,
> a user placed a number as a parameter and got UNKNOWN -
> it is extra weird.
> 
> By the way, '? as <column_name>' works ok - the result

Yep, but we can’t substitute ‘?' with real column name, only
with constant alias (at least I didn’t manage to find the way
how to do it). I want to do this:

tarantool> cn:execute('select ?, ?, 3 from t1', {1, ‘id'})   
---
- metadata:
  - name: '?'
    type: UNKNOWN
  - name: '?'
    type: UNKNOWN
  - name: '3'
    type: INTEGER
  rows:
  - [1, 'id', 3]
…

But I meant not string literal “id”, but column id.
I don’t see such test case in sql/iproto.test.lua as well.
Also, I can ask similar question: why in the example above
name of bound literals 1 and ‘abc' is ‘?’ but for non-binded
literal 3 is ‘3’? I believe it was made on purpose, and I don’t
mind to display correct type of bound values but it would
look a little bit confusing (I mean real name is not substituted,
but real type is revealed).

> set contains real name instead of '?'. I think, it should
> work for types too.

You are talking about the case when name (or alias) is known
at compilation stage:

cn:execute(’select ? as param1’, {1})

It is quite different case - we don’t know type of parameter
at compilation stage.

Nevertheless, I agree with you that we should
display correct type instead of UNKNOWN.

> If you want to do it as a separate ticket, I do not mind,
> but please consult Kirill to be sure.

I think this issue rather relates to current patch,
so I’ve fixed it right here:

*Extended commit message*

    sql: return result-set type via IProto
    
    Lets evaluate an expression type during processing of expression's AST
    and code generation. It allows to calculate resulting columns data types
    and export them as IProto meta alongside with columns' names.
    Also, correct types are also returned for binding parameters as well.
    
    Note that NULL literal has type "BOOLEAN". It was made on purpose -
    different DBs interpret NULL's type in different ways: some of them
    use INT; others - VARCHAR; still others - UNKNOWN. We've decided that
    NULL is rather of type "BOOLEAN", since NULL is kind if subset of
    "BOOLEAN" values: any comparison with NULL results in neither TRUE nor
    FALSE, but in NULL.
    
    Part of #2620

diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 3dcce078a..16b1044d6 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -36,6 +36,7 @@
 #include "coll.h"
 #include "sqliteInt.h"
 #include "tarantoolInt.h"
+#include "vdbeInt.h"
 #include "box/box.h"
 #include "box/coll_id_cache.h"
 #include "box/schema.h"
@@ -1687,11 +1688,19 @@ generateColumnNames(Parse * pParse,     /* Parser context */
        if (pParse->colNamesSet || db->mallocFailed)
                return;
        assert(v != 0);
+       size_t var_pos_sz =  pParse->nVar * sizeof(uint32_t);
+       uint32_t *var_pos = (uint32_t *) region_alloc(&pParse->region,
+                                                     var_pos_sz);
+       if (var_pos == NULL) {
+               diag_set(OutOfMemory, var_pos_sz, "region", "var_pos");
+               return;
+       }
        assert(pTabList != 0);
        pParse->colNamesSet = 1;
        fullNames = (user_session->sql_flags & SQLITE_FullColNames) != 0;
        shortNames = (user_session->sql_flags & SQLITE_ShortColNames) != 0;
        sqlite3VdbeSetNumCols(v, pEList->nExpr);
+       uint32_t var_count = 0;
        for (i = 0; i < pEList->nExpr; i++) {
                Expr *p;
                p = pEList->a[i].pExpr;
@@ -1715,8 +1724,21 @@ generateColumnNames(Parse * pParse,      /* Parser context */
                        sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, "BLOB",
                                              SQLITE_TRANSIENT);
                        break;
-               default:
-                       sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, "UNKNOWN",
+               default: ;
+                       char *type;
+                       /*
+                        * For variables we set BOOLEAN type since
+                        * unassigned bindings will be replaced
+                        * with NULL automatically, i.e. without
+                        * explicit call of sql_bind_value().
+                        */
+                       if (p->op == TK_VARIABLE) {
+                               var_pos[var_count++] = i;
+                               type = "BOOLEAN";
+                       } else {
n:tarantool n.pettik$ git diff
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 3dcce078a..16b1044d6 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -36,6 +36,7 @@
 #include "coll.h"
 #include "sqliteInt.h"
 #include "tarantoolInt.h"
+#include "vdbeInt.h"
 #include "box/box.h"
 #include "box/coll_id_cache.h"
 #include "box/schema.h"
@@ -1687,11 +1688,19 @@ generateColumnNames(Parse * pParse,     /* Parser context */
        if (pParse->colNamesSet || db->mallocFailed)
                return;
        assert(v != 0);
+       size_t var_pos_sz =  pParse->nVar * sizeof(uint32_t);
+       uint32_t *var_pos = (uint32_t *) region_alloc(&pParse->region,
+                                                     var_pos_sz);
+       if (var_pos == NULL) {
+               diag_set(OutOfMemory, var_pos_sz, "region", "var_pos");
+               return;
+       }
        assert(pTabList != 0);
        pParse->colNamesSet = 1;
        fullNames = (user_session->sql_flags & SQLITE_FullColNames) != 0;
        shortNames = (user_session->sql_flags & SQLITE_ShortColNames) != 0;
        sqlite3VdbeSetNumCols(v, pEList->nExpr);
+       uint32_t var_count = 0;
        for (i = 0; i < pEList->nExpr; i++) {
                Expr *p;
                p = pEList->a[i].pExpr;
@@ -1715,8 +1724,21 @@ generateColumnNames(Parse * pParse,      /* Parser context */
                        sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, "BLOB",
                                              SQLITE_TRANSIENT);
                        break;
-               default:
-                       sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, "UNKNOWN",
+               default: ;
+                       char *type;
+                       /*
+                        * For variables we set BOOLEAN type since
+                        * unassigned bindings will be replaced
+                        * with NULL automatically, i.e. without
+                        * explicit call of sql_bind_value().
+                        */
+                       if (p->op == TK_VARIABLE) {
+                               var_pos[var_count++] = i;
+                               type = "BOOLEAN";
+                       } else {
+                               type = "UNKNOWN";
+                       }
+                       sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, type,
                                              SQLITE_TRANSIENT);
                }
                if (pEList->a[i].zName) {
@@ -1760,6 +1782,16 @@ generateColumnNames(Parse * pParse,      /* Parser context */
                                              SQLITE_DYNAMIC);
                }
        }
+       if (var_count == 0)
+               return;
+       v->var_pos = (uint32_t *) malloc(var_count * sizeof(uint32_t));
+       if (v->var_pos ==  NULL) {
+               diag_set(OutOfMemory, var_count * sizeof(uint32_t),
+                        "malloc", "v->var_pos");
+               return;
+       }
+       memcpy(v->var_pos, var_pos, var_count * sizeof(uint32_t));
+       v->res_var_count = var_count;
 }
 
 /*
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 174f5980f..5ad1e0a80 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -380,6 +380,19 @@ struct Vdbe {
        char *zErrMsg;          /* Error message written here */
        VdbeCursor **apCsr;     /* One element of this array for each open cursor */
        Mem *aVar;              /* Values for the OP_Variable opcode. */
+       /**
+        * Array which contains positions of variables to be
+        * bound in resulting set of SELECT.
+        **/
+       uint32_t *var_pos;
+       /**
+        * Number of variables to be bound in result set.
+        * In other words - size of @var_pos array.
+        * For example:
+        * SELECT ?, ? WHERE id = ?;
+        * Result set consists of two binding variables.
+        */
+       uint32_t res_var_count;
        VList *pVList;          /* Name of variables */
 #ifndef SQLITE_OMIT_TRACE
        i64 startTime;          /* Time when query started - used for profiling */
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index b16e916e9..9e57af051 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -1231,6 +1231,50 @@ vdbeUnbind(Vdbe * p, int i)
        return SQLITE_OK;
 }
 
+/**
+ * This function sets type for bound variable.
+ * We should bind types only for variables which occur in
+ * result set of SELECT query. For example:
+ *
+ * SELECT id, ?, ?, a WHERE id = ?;
+ *
+ * In this case we should set types only for two variables.
+ * That one which is situated under WHERE condition - is out
+ * of our interest.
+ *
+ * For named binding parameters we should propagate type
+ * for all occurrences of this parameter - since binding
+ * routine takes place only once for each DISTINCT parameter
+ * from list.
+ *
+ * @param v Current VDBE.
+ * @param position Ordinal position of binding parameter.
+ * @param type String literal representing type of binding param.
+ * @retval 0 on success.
+ */
+static int
+sql_bind_type(struct Vdbe *v, uint32_t position, const char *type)
+{
+       if (v->res_var_count < position)
+               return 0;
+       int rc = sqlite3VdbeSetColName(v, v->var_pos[position - 1],
+                                      COLNAME_DECLTYPE, type,
+                                      SQLITE_TRANSIENT);
+       const char *bind_name = v->aColName[position - 1].z;
+       if (strcmp(bind_name, "?") == 0)
+               return rc;
+       for (uint32_t i = position; i < v->res_var_count; ++i) {
+               if (strcmp(bind_name,  v->aColName[i].z) == 0) {
+                       rc = sqlite3VdbeSetColName(v, v->var_pos[i],
+                                                  COLNAME_DECLTYPE, type,
+                                                  SQLITE_TRANSIENT);
+                       if (rc != 0)
+                               return rc;
+               }
+       }
+       return 0;
+}
+
 /*
  * Bind a text or BLOB value.
  */
@@ -1251,6 +1295,8 @@ bindText(sqlite3_stmt * pStmt,    /* The statement to bind against */
                if (zData != 0) {
                        pVar = &p->aVar[i - 1];
                        rc = sqlite3VdbeMemSetStr(pVar, zData, nData, 1, xDel);
+                       if (rc == SQLITE_OK)
+                               rc = sql_bind_type(p, i, "TEXT");
                        sqlite3Error(p->db, rc);
                        rc = sqlite3ApiExit(p->db, rc);
                }
@@ -1297,6 +1343,7 @@ sqlite3_bind_double(sqlite3_stmt * pStmt, int i, double rValue)
        Vdbe *p = (Vdbe *) pStmt;
        rc = vdbeUnbind(p, i);
        if (rc == SQLITE_OK) {
+               rc = sql_bind_type(p, i, "NUMERIC");
                sqlite3VdbeMemSetDouble(&p->aVar[i - 1], rValue);
        }
        return rc;
@@ -1315,6 +1362,7 @@ sqlite3_bind_int64(sqlite3_stmt * pStmt, int i, sqlite_int64 iValue)
        Vdbe *p = (Vdbe *) pStmt;
        rc = vdbeUnbind(p, i);
        if (rc == SQLITE_OK) {
+               rc = sql_bind_type(p, i, "INTEGER");
                sqlite3VdbeMemSetInt64(&p->aVar[i - 1], iValue);
        }
        return rc;
@@ -1326,6 +1374,8 @@ sqlite3_bind_null(sqlite3_stmt * pStmt, int i)
        int rc;
        Vdbe *p = (Vdbe *) pStmt;
        rc = vdbeUnbind(p, i);
+       if (rc == SQLITE_OK)
+               rc = sql_bind_type(p, i, "BOOLEAN");
        return rc;
 }
 
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 4bc81bd03..088ac3b3e 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2780,6 +2780,7 @@ sqlite3VdbeDelete(Vdbe * p)
        }
        p->magic = VDBE_MAGIC_DEAD;
        p->db = 0;
+       free(p->var_pos);
        sqlite3DbFree(db, p);
 }
 
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index 16ffd0991..e8b53bf11 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -161,11 +161,11 @@ cn:execute('select ?, ?, ?', {1, 2, 3})
 ---
 - metadata:
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   rows:
   - [1, 2, 3]
 ...
@@ -191,11 +191,11 @@ cn:execute('select ?, :value1, @value2', parameters)
 ---
 - metadata:
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   - name: :value1
-    type: UNKNOWN
+    type: INTEGER
   - name: '@value2'
-    type: UNKNOWN
+    type: INTEGER
   rows:
   - [10, 11, 12]
 ...
@@ -233,21 +233,21 @@ cn:execute('select :value3, ?, :value1, ?, ?, @value2, ?, :value3', parameters)
 ---
 - metadata:
   - name: :value3
-    type: UNKNOWN
+    type: INTEGER
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   - name: :value1
-    type: UNKNOWN
+    type: INTEGER
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   - name: '@value2'
-    type: UNKNOWN
+    type: INTEGER
   - name: '?'
-    type: UNKNOWN
+    type: BOOLEAN
   - name: :value3
-    type: UNKNOWN
+    type: INTEGER
   rows:
   - [1, 2, 3, 4, 5, 6, null, 1]
 ...
@@ -259,15 +259,15 @@ cn:execute('select ?, ?, ?, ?, ?', {'abc', -123.456, msgpack.NULL, true, false})
 ---
 - metadata:
   - name: '?'
-    type: UNKNOWN
+    type: TEXT
   - name: '?'
-    type: UNKNOWN
+    type: NUMERIC
   - name: '?'
-    type: UNKNOWN
+    type: BOOLEAN
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   - name: '?'
-    type: UNKNOWN
+    type: INTEGER
   rows:
   - ['abc', -123.456, null, 1, 0]
 ...
@@ -276,9 +276,9 @@ cn:execute('select ? as kek, ? as kek2', {1, 2})
 ---
 - metadata:
   - name: KEK
-    type: UNKNOWN
+    type: INTEGER
   - name: KEK2
-    type: UNKNOWN
+    type: INTEGER
   rows:
   - [1, 2]
 ...
@@ -529,11 +529,11 @@ cn:execute('select $2, $1, $3', parameters)
 ---
 - metadata:
   - name: $2
-    type: UNKNOWN
+    type: INTEGER
   - name: $1
-    type: UNKNOWN
+    type: INTEGER
   - name: $3
-    type: UNKNOWN
+    type: INTEGER
   rows:
   - [22, 11, 33]
 ...

  reply	other threads:[~2018-10-23 23:28 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-09-17 20:32 [tarantool-patches] [PATCH 0/6] Introduce strict typing for SQL Nikita Pettik
2018-09-17 20:32 ` [tarantool-patches] [PATCH 1/6] sql: split conflict action and affinity for Expr Nikita Pettik
2018-09-19  2:16   ` [tarantool-patches] " Konstantin Osipov
2018-09-27 20:24   ` Vladislav Shpilevoy
2018-10-12 11:18     ` n.pettik
2018-09-17 20:32 ` [tarantool-patches] [PATCH 2/6] sql: annotate SQL functions with return type Nikita Pettik
2018-09-27 20:23   ` [tarantool-patches] " Vladislav Shpilevoy
2018-10-12 11:18     ` n.pettik
2018-09-17 20:32 ` [tarantool-patches] [PATCH 3/6] sql: pass true types of columns to Tarantool Nikita Pettik
2018-09-19  2:23   ` [tarantool-patches] " Konstantin Osipov
2018-10-12 11:19     ` n.pettik
2018-09-27 20:23   ` Vladislav Shpilevoy
2018-10-12 11:18     ` n.pettik
2018-10-17 21:45       ` Vladislav Shpilevoy
2018-10-23 23:28         ` n.pettik
2018-10-29 21:32           ` Vladislav Shpilevoy
2018-11-02  2:36             ` n.pettik
2018-09-17 20:32 ` [tarantool-patches] [PATCH 4/6] sql: enforce implicit type conversions Nikita Pettik
2018-09-19  2:25   ` [tarantool-patches] " Konstantin Osipov
2018-09-27 20:24   ` Vladislav Shpilevoy
2018-10-12 11:19     ` n.pettik
2018-10-17 21:45       ` Vladislav Shpilevoy
2018-10-23 23:28         ` n.pettik
2018-10-29 21:32           ` Vladislav Shpilevoy
2018-11-02  2:36             ` n.pettik
2018-11-02 11:15               ` Vladislav Shpilevoy
2018-11-02 13:26                 ` n.pettik
2018-09-17 20:32 ` [tarantool-patches] [PATCH 5/6] sql: return result-set type via IProto Nikita Pettik
2018-09-19  2:26   ` [tarantool-patches] " Konstantin Osipov
2018-09-27 20:24   ` Vladislav Shpilevoy
2018-10-12 11:19     ` n.pettik
2018-10-17 21:45       ` Vladislav Shpilevoy
2018-10-23 23:28         ` n.pettik [this message]
2018-09-17 20:32 ` [tarantool-patches] [PATCH 6/6] sql: discard numeric conversion by unary plus Nikita Pettik
2018-09-27 20:24   ` [tarantool-patches] " Vladislav Shpilevoy
2018-10-12 11:19     ` n.pettik
2018-09-27 20:24 ` [tarantool-patches] Re: [PATCH 0/6] Introduce strict typing for SQL Vladislav Shpilevoy
2018-10-12 11:18   ` n.pettik
2018-11-03  2:41 ` Kirill Yukhin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3C315E98-880F-4218-85C7-42631453A818@tarantool.org \
    --to=korablev@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --cc=v.shpilevoy@tarantool.org \
    --subject='[tarantool-patches] Re: [PATCH 5/6] sql: return result-set type via IProto' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

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