From: Timur Safin via Tarantool-patches <tarantool-patches@dev.tarantool.org> To: tarantool-patches@dev.tarantool.org, imeevma@tarantool.org Subject: [Tarantool-patches] [PATCH 3/3] sql: introduced explicit casts test e_casts.test.lua Date: Tue, 25 May 2021 12:01:02 +0300 [thread overview] Message-ID: <a6617484e2b9eb954aaa6ae10fd667082d6915de.1621894609.git.tsafin@tarantool.org> (raw) In-Reply-To: <cover.1621894609.git.tsafin@tarantool.org> * e_casts.test.lua is generating expressions of `cast(input as output)` form. And checks whether we expectedly succeed or fail, given the previously agreed excplicit conversion table from /doc/rfc/5910-consistent-sql-lua-types.md; * At the moment we skip DECIMALs, UUIDs, ARRAYs and MAPs as not yet fully supported. Need to be reenabled later; * there is `-verbose` mode one may activate to enable more detailed reporting. Relates to #5910, #6009 Part of #4407 --- test/sql-tap/e_casts.test.lua | 355 ++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100755 test/sql-tap/e_casts.test.lua diff --git a/test/sql-tap/e_casts.test.lua b/test/sql-tap/e_casts.test.lua new file mode 100755 index 000000000..eafebd5bc --- /dev/null +++ b/test/sql-tap/e_casts.test.lua @@ -0,0 +1,355 @@ +#!/usr/bin/env tarantool +local test = require("sqltester") +test:plan(222) + +local yaml = require("yaml") +local ffi = require("ffi") + +local verbose = 0 + +if arg[1] == '-v' or arg[1] == '--verbose' then + verbose = 1 +end + +ffi.cdef [[ + enum field_type { + FIELD_TYPE_ANY = 0, + FIELD_TYPE_UNSIGNED, + FIELD_TYPE_STRING, + FIELD_TYPE_NUMBER, + FIELD_TYPE_DOUBLE, + FIELD_TYPE_INTEGER, + FIELD_TYPE_BOOLEAN, + FIELD_TYPE_VARBINARY, + FIELD_TYPE_SCALAR, + FIELD_TYPE_DECIMAL, + FIELD_TYPE_UUID, + FIELD_TYPE_ARRAY, + FIELD_TYPE_MAP, + field_type_MAX + }; +]] + +-- Date/time/interval types to be uncommented and used +-- once corresponding box implementation completed +local t_any = ffi.C.FIELD_TYPE_ANY +local t_unsigned = ffi.C.FIELD_TYPE_UNSIGNED +local t_string = ffi.C.FIELD_TYPE_STRING +local t_number = ffi.C.FIELD_TYPE_NUMBER +local t_double = ffi.C.FIELD_TYPE_DOUBLE +local t_integer = ffi.C.FIELD_TYPE_INTEGER +local t_boolean = ffi.C.FIELD_TYPE_BOOLEAN +local t_varbinary = ffi.C.FIELD_TYPE_VARBINARY +local t_scalar = ffi.C.FIELD_TYPE_SCALAR +local t_decimal = ffi.C.FIELD_TYPE_DECIMAL +-- local t_date = -1 +-- local t_time = -2 +-- local t_timestamp = -3 +-- local t_interval = -4 +local t_uuid = ffi.C.FIELD_TYPE_UUID +local t_array = ffi.C.FIELD_TYPE_ARRAY +local t_map = ffi.C.FIELD_TYPE_MAP + +local proper_order = { + t_any, + t_unsigned, + t_string, + t_double, + t_integer, + t_boolean, + t_varbinary, + t_number, + t_decimal, + t_uuid, + -- t_date, + -- t_time, + -- t_timestamp, + -- t_interval, + t_array, + t_map, + t_scalar, +} + +local type_names = { + [t_any] = 'any', + [t_unsigned] = 'unsigned', + [t_string] = 'string', + [t_double] = 'double', + [t_integer] = 'integer', + [t_boolean] = 'boolean', + [t_varbinary] = 'varbinary', + [t_number] = 'number', + [t_decimal] = 'decimal', + [t_uuid] = 'uuid', + -- [t_date] = 'date', + -- [t_time] = 'time', + -- [t_timestamp] = 'timestamp', + -- [t_interval] = 'interval', + [t_array] = 'array', + [t_map] = 'map', + [t_scalar] = 'scalar', +} + +-- not all types implemented/enabled at the moment +-- but we do keep their projected status in the +-- spec table +local enabled_type = { + [t_any] = false, -- there is no way in SQL to instantiate ANY type expression + [t_unsigned] = true, + [t_string] = true, + [t_double] = true, + [t_integer] = true, + [t_boolean] = true, + [t_varbinary] = true, + [t_number] = true, + [t_decimal] = false, + [t_uuid] = false, + -- [t_date] = false, + -- [t_time] = false, + -- [t_timestamp]= false, + -- [t_interval] = False, + [t_array] = false, + [t_map] = false, + [t_scalar] = true, +} + +-- table of _TSV_ (tab separated values) +-- copied from sql-lua-tables-v5.xls // TNT implicit today +local explicit_casts_table_spec = { + [t_any] = {"Y", "S", "Y", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"}, + [t_unsigned]= {"Y", "Y", "Y", "Y", "Y", "" , "" , "Y", "Y", "" , "" , "" , "Y"}, + [t_string] = {"Y", "S", "Y", "S", "S", "S", "Y", "S", "S", "S", "S", "S", "Y"}, + [t_double] = {"Y", "S", "Y", "Y", "S", "" , "" , "Y", "Y", "" , "" , "" , "Y"}, + [t_integer] = {"Y", "S", "Y", "Y", "Y", "" , "" , "Y", "Y", "" , "" , "" , "Y"}, + [t_boolean] = {"Y", "" , "Y", "" , "" , "Y", "" , "" , "" , "" , "" , "" , "Y"}, + [t_varbinary]={"Y", "" , "Y", "N", "" , "" , "Y", "" , "" , "S", "" , "" , "Y"}, + [t_number] = {"Y", "S", "Y", "Y", "S", "" , "" , "Y", "Y", "" , "" , "" , "Y"}, + [t_decimal] = {"Y", "S", "Y", "S", "S", "" , "" , "Y", "Y", "" , "" , "" , "Y"}, + [t_uuid] = {"Y", "" , "Y", "" , "" , "" , "Y", "" , "" , "Y", "" , "" , "Y"}, + [t_array] = {"Y", "N", "Y", "N", "N", "N", "" , "" , "" , "" , "Y", "" , "N"}, + [t_map] = {"Y", "N", "Y", "N", "N", "N", "" , "" , "" , "" , "" , "Y", "N"}, + [t_scalar] = {"Y", "S", "Y", "S", "S", "S", "S", "S", "S", "S", "" , "" , "Y"}, +} + +-- local extra_checks = false +local explicit_casts = {} +-- local implicit_casts = {} + +-- implicit conversion table is considered consistent if +-- it's sort of symmetric against diagonal +-- (not necessary that always/sometimes are matching +-- but at least something should be presented) + +--[[ local function check_table_consistency(table) + for _, i in ipairs(proper_order) do + local string = '' + for _, j in ipairs(proper_order) do + print(i, j) + -- local item = implicit_casts[i][j] + -- string = string .. (xlat[item] or ' ') + end + print(string) + end +end +]] + + -- if there is enabled extra checks then check ocnsistency of input tables + -- just to make sure their sanity +--[[ if extra_checks then + check_table_consistency() + end + ]] + +local c_no = 0 +local c_maybe = 1 +local c_yes = 2 + +local function normalize_cast(v) + local xlat = { + ['Y'] = c_yes, + ['S'] = c_maybe, + ['N'] = c_no, + } + return xlat[v ~= nil and v or 'N'] +end + +local function human_cast(v) + local xlat = { + [c_yes] = 'Y', + [c_maybe] = 'S', + [c_no] = ' ' + } + return xlat[v ~= nil and v or c_no] +end + +local function load_casts_spec(spec_table) + local casts = {} + for i, t_from in ipairs(proper_order) do + local row = spec_table[t_from] + casts[t_from] = {} + for j, t_to in ipairs(proper_order) do + if enabled_type[t_from] and enabled_type[t_to] then + casts[t_from][t_to] = normalize_cast(spec_table[t_from][j]) + end + end + end + -- if there is enabled extra checks then check ocnsistency of input tables + -- just to make sure their sanity +--[[ if extra_checks then + check_table_consistency() + end ]] + + return casts +end + +explicit_casts = load_casts_spec(explicit_casts_table_spec) + +if verbose > 0 then + local function show_casts_table(table) + local max_len = #"12. varbinary" + 1 + + -- show banner + local col_names = '' + for i, t_val in ipairs(proper_order) do + col_names = col_names .. string.format("%2d |", t_val) + end + col_names = string.sub(col_names, 1, #col_names-1) + print(string.format("%"..max_len.."s|%s|", "", col_names)) + -- show splitter + local banner = '+---+---+---+---+---+---+---+---+---+---+---+---+---+' + print(string.format("%"..max_len.."s%s", "", banner)) + + for i, from in ipairs(proper_order) do + local line = '' + for j, to in ipairs(proper_order) do + line = line .. string.format("%2s |", human_cast(table[from][to])) + end + line = string.sub(line, 1, #line-1) + local s = string.format("%2d.%10s |%s|", from, type_names[from], line) + print(s) + end + print(string.format("%"..max_len.."s%s", "", banner)) + end + + show_casts_table(explicit_casts) +end + +local function merge_tables(...) + local n = select('#', ...) + local tables = {...} + local result = {} + + for i=1,n do + local t = tables[i] + --print(yaml.encode(t)) + assert(type(tables[i]) == 'table') + for j,v in pairs(t) do + table.insert(result, v) + end + end + return result +end + +local gen_type_samples = { + [t_unsigned] = {"0", "1", "2"}, + [t_integer] = {"-678", "-1", "0", "1", "2", "3", "678"}, + [t_double] = {"0.0", "123.4", "-567.8"}, + [t_string] = {"''", "'1'", "'abc'", "'def'", "'TRUE'", "'FALSE'"}, + [t_boolean] = {"false", "true", "null"}, + [t_varbinary] = {"X'312E3233'", "X'2D392E3837'", "X'302E30303031'"}, +} + +local function gen_type_exprs(type) + if type == t_number then + return merge_tables(gen_type_samples[t_unsigned], + gen_type_samples[t_integer], + gen_type_samples[t_double]) + end + if type == t_scalar then + return merge_tables(gen_type_samples[t_unsigned], + gen_type_samples[t_integer], + gen_type_samples[t_double], + gen_type_samples[t_string], + gen_type_samples[t_boolean], + gen_type_samples[t_varbinary]) + end + return gen_type_samples[type] or {} +end + +local function gen_sql_cast_from_to(t_from, t_to) + local queries = {} + local from_exprs = gen_type_exprs(t_from) + local to_typename = type_names[t_to] + for _, expr in pairs(from_exprs) do + local query = string.format([[ select cast(%s as %s); ]], expr, to_typename) + table.insert(queries, query) + end + return queries +end + +local function catch_query(query) + local result = {pcall(box.execute, query)} + + if not result[1] or result[3] ~= nil then + return false, result[3] + end + return true, result[2] +end + +local function label_for(from, to, query) + local parent_frame = debug.getinfo(2, "nSl") + local filename = parent_frame.source:sub(1,1) == "@" and parent_frame.source:sub(2) + local line = parent_frame.currentline + return string.format("%s+%d:[%s,%s] %s", filename, line, + type_names[from], type_names[to], query) +end + +for i, from in ipairs(proper_order) do + for j, to in ipairs(proper_order) do + -- skip ANY, DECIMAL, UUID, etc. + if enabled_type[from] and enabled_type[to] then + local cell = explicit_casts[from][to] + local gen = gen_sql_cast_from_to(from, to) + local failures = {} + local successes = {} + local castable = false + local expected = explicit_casts[from][to] + if verbose > 0 then + print(expected, yaml.encode(gen)) + end + for i, v in pairs(gen) do + local ok, result + ok, result = catch_query(v) + if verbose > 0 then + print(string.format("ok = %s, result = %s, query = %s", + ok, result, v)) + + end + -- print(v, 'ok'..yaml.encode(ok), 'result'..yaml.encode(result)) + if expected == c_yes then + test:ok(true == ok, label_for(from, to, v)) + elseif expected == c_no then + test:ok(false == ok, label_for(from, to, v)) + else + -- we can't report immediately for c_maybe because some + -- cases allowed to fail, so postpone decision + if ok then + castable = true + table.insert(successes, {result, v}) + else + table.insert(failures, {result, v}) + end + end + end + + -- ok, we aggregated stats for c_maybe mode - check it now + if expected == c_maybe then + test:ok(castable, label_for(from, to, #gen and gen[1] or ''), + failures) + end + end + end +end + + +test:finish_test() -- 2.29.2
next prev parent reply other threads:[~2021-05-25 9:03 UTC|newest] Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top 2021-05-25 9:00 [Tarantool-patches] [PATCH 0/3] sql: modify explicit conversion tables Timur Safin via Tarantool-patches 2021-05-25 9:01 ` [Tarantool-patches] [PATCH 1/3] sql: fixes for boolean expressions in explicit converstion tables Timur Safin via Tarantool-patches 2021-06-01 14:02 ` Mergen Imeev via Tarantool-patches 2021-06-02 21:03 ` Timur Safin via Tarantool-patches 2021-06-02 21:10 ` Timur Safin via Tarantool-patches 2021-05-25 9:01 ` [Tarantool-patches] [PATCH 2/3] sql: enabled ANY as target for explicit conversions Timur Safin via Tarantool-patches 2021-06-01 14:02 ` Mergen Imeev via Tarantool-patches 2021-06-02 21:04 ` Timur Safin via Tarantool-patches 2021-05-25 9:01 ` Timur Safin via Tarantool-patches [this message] 2021-06-01 14:02 ` [Tarantool-patches] [PATCH 3/3] sql: introduced explicit casts test e_casts.test.lua Mergen Imeev via Tarantool-patches 2021-06-02 21:04 ` Timur Safin via Tarantool-patches 2021-06-01 14:02 ` [Tarantool-patches] [PATCH 0/3] sql: modify explicit conversion tables Mergen Imeev via Tarantool-patches 2021-06-02 21:04 ` Timur Safin via Tarantool-patches
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=a6617484e2b9eb954aaa6ae10fd667082d6915de.1621894609.git.tsafin@tarantool.org \ --to=tarantool-patches@dev.tarantool.org \ --cc=imeevma@tarantool.org \ --cc=tsafin@tarantool.org \ --subject='Re: [Tarantool-patches] [PATCH 3/3] sql: introduced explicit casts test e_casts.test.lua' \ /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