From a43862772911c92ee584b3efa06b9803f5e0ec32 Mon Sep 17 00:00:00 2001 From: Nikita Pettik <korablev@tarantool.org> Date: Wed, 5 Jun 2019 19:41:11 +0300 Subject: [PATCH] sql: allow to specify UNSIGNED column type Since all preparations concerning internal handling of unsigned values have been done, now nothing prevents us from using UNSIGNED type in SQL. This patch allows to specify UNSIGNED as a column type and adds CAST rules, which are the same as for casual INTEGER, but with additional check - result must be positive. Otherwise, error is raised. Closes #4015 --- extra/mkkeywordhash.c | 1 + src/box/sql/parse.y | 1 + src/box/sql/vdbe.c | 11 +++++-- src/box/sql/vdbemem.c | 10 +++++- test/sql/types.result | 71 +++++++++++++++++++++++++++++++++++++++++ test/sql/types.test.lua | 17 ++++++++++ 6 files changed, 107 insertions(+), 4 deletions(-) diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c index b83294cb38..64a5bbfc6e 100644 --- a/extra/mkkeywordhash.c +++ b/extra/mkkeywordhash.c @@ -185,6 +185,7 @@ static Keyword aKeywordTable[] = { { "UNION", "TK_UNION", COMPOUND, true }, { "UNIQUE", "TK_UNIQUE", ALWAYS, true }, { "UNKNOWN", "TK_NULL", ALWAYS, true }, + { "UNSIGNED", "TK_UNSIGNED", ALWAYS, true }, { "UPDATE", "TK_UPDATE", ALWAYS, true }, { "USING", "TK_USING", ALWAYS, true }, { "VALUES", "TK_VALUES", ALWAYS, true }, diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y index 2a60ad25b7..d4e1ec8590 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -1755,6 +1755,7 @@ typedef(A) ::= VARCHAR char_len(B) . { typedef(A) ::= number_typedef(A) . number_typedef(A) ::= FLOAT_KW|REAL|DOUBLE . { A.type = FIELD_TYPE_NUMBER; } number_typedef(A) ::= INT|INTEGER_KW . { A.type = FIELD_TYPE_INTEGER; } +number_typedef(A) ::= UNSIGNED . { A.type = FIELD_TYPE_UNSIGNED; } /** * NUMERIC type is temporary disabled. To be enabled when diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index c7fdec6b9b..9142932e9d 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -306,8 +306,6 @@ mem_apply_type(struct Mem *record, enum field_type type) switch (type) { case FIELD_TYPE_INTEGER: case FIELD_TYPE_UNSIGNED: - if ((record->flags & MEM_Int) == MEM_Int) - return 0; if ((record->flags & MEM_UInt) == MEM_UInt) return 0; if ((record->flags & MEM_Real) == MEM_Real) { @@ -317,7 +315,14 @@ mem_apply_type(struct Mem *record, enum field_type type) record->u.r <= -1); return 0; } - return sqlVdbeMemIntegerify(record, false); + if (sqlVdbeMemIntegerify(record, false) != 0) + return -1; + if ((record->flags & MEM_Int) == MEM_Int) { + if (type == FIELD_TYPE_UNSIGNED) + return -1; + return 0; + } + return 0; case FIELD_TYPE_BOOLEAN: if ((record->flags & MEM_Bool) == MEM_Bool) return 0; diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c index 77f16ba906..847a6b0cec 100644 --- a/src/box/sql/vdbemem.c +++ b/src/box/sql/vdbemem.c @@ -693,11 +693,14 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type) return 0; return -1; case FIELD_TYPE_INTEGER: + case FIELD_TYPE_UNSIGNED: if ((pMem->flags & MEM_Blob) != 0) { bool is_neg; int64_t val; if (sql_atoi64(pMem->z, &val, &is_neg, pMem->n) != 0) return -1; + if (type == FIELD_TYPE_UNSIGNED && is_neg) + return -1; mem_set_int(pMem, val, is_neg); return 0; } @@ -706,7 +709,12 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type) MemSetTypeFlag(pMem, MEM_UInt); return 0; } - return sqlVdbeMemIntegerify(pMem, true); + if (sqlVdbeMemIntegerify(pMem, true) != 0) + return -1; + if (type == FIELD_TYPE_UNSIGNED && + (pMem->flags & MEM_UInt) == 0) + return -1; + return 0; case FIELD_TYPE_NUMBER: return sqlVdbeMemRealify(pMem); default: diff --git a/test/sql/types.result b/test/sql/types.result index 3b106cfb0f..4589b2d582 100644 --- a/test/sql/types.result +++ b/test/sql/types.result @@ -1680,3 +1680,74 @@ box.execute("SELECT CAST('18446744073709551615' AS INTEGER);") rows: - [18446744073709551615] ... +-- gh-4015: introduce unsigned type in SQL. +-- +box.execute("CREATE TABLE t1 (id UNSIGNED PRIMARY KEY);") +--- +- row_count: 1 +... +box.execute("INSERT INTO t1 VALUES (0), (1), (2);") +--- +- row_count: 3 +... +box.execute("INSERT INTO t1 VALUES (-3);") +--- +- error: 'Type mismatch: can not convert -3 to unsigned' +... +box.execute("SELECT id FROM t1;") +--- +- metadata: + - name: ID + type: unsigned + rows: + - [0] + - [1] + - [2] +... +box.execute("SELECT CAST(123 AS UNSIGNED);") +--- +- metadata: + - name: CAST(123 AS UNSIGNED) + type: unsigned + rows: + - [123] +... +box.execute("SELECT CAST(-123 AS UNSIGNED);") +--- +- error: 'Type mismatch: can not convert -123 to unsigned' +... +box.execute("SELECT CAST(1.5 AS UNSIGNED);") +--- +- metadata: + - name: CAST(1.5 AS UNSIGNED) + type: unsigned + rows: + - [1] +... +box.execute("SELECT CAST(-1.5 AS UNSIGNED);") +--- +- error: 'Type mismatch: can not convert -1 to unsigned' +... +box.execute("SELECT CAST(true AS UNSIGNED);") +--- +- metadata: + - name: CAST(true AS UNSIGNED) + type: unsigned + rows: + - [1] +... +box.execute("SELECT CAST('123' AS UNSIGNED);") +--- +- metadata: + - name: CAST('123' AS UNSIGNED) + type: unsigned + rows: + - [123] +... +box.execute("SELECT CAST('-123' AS UNSIGNED);") +--- +- error: 'Type mismatch: can not convert -123 to unsigned' +... +box.space.T1:drop() +--- +... diff --git a/test/sql/types.test.lua b/test/sql/types.test.lua index e3abae2b9c..f07a90b371 100644 --- a/test/sql/types.test.lua +++ b/test/sql/types.test.lua @@ -382,3 +382,20 @@ box.execute("SELECT CAST(18446744073709551615 AS TEXT);") box.execute("SELECT CAST(18446744073709551615 AS SCALAR);") box.execute("SELECT CAST(18446744073709551615 AS BOOLEAN);") box.execute("SELECT CAST('18446744073709551615' AS INTEGER);") + +-- gh-4015: introduce unsigned type in SQL. +-- +box.execute("CREATE TABLE t1 (id UNSIGNED PRIMARY KEY);") +box.execute("INSERT INTO t1 VALUES (0), (1), (2);") +box.execute("INSERT INTO t1 VALUES (-3);") +box.execute("SELECT id FROM t1;") + +box.execute("SELECT CAST(123 AS UNSIGNED);") +box.execute("SELECT CAST(-123 AS UNSIGNED);") +box.execute("SELECT CAST(1.5 AS UNSIGNED);") +box.execute("SELECT CAST(-1.5 AS UNSIGNED);") +box.execute("SELECT CAST(true AS UNSIGNED);") +box.execute("SELECT CAST('123' AS UNSIGNED);") +box.execute("SELECT CAST('-123' AS UNSIGNED);") + +box.space.T1:drop() -- GitLab