File: sqltester.lua

package info (click to toggle)
tarantool 2.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 85,364 kB
  • sloc: ansic: 513,760; cpp: 69,489; sh: 25,650; python: 19,190; perl: 14,973; makefile: 4,173; yacc: 1,329; sql: 1,074; pascal: 620; ruby: 190; awk: 18; lisp: 7
file content (422 lines) | stat: -rw-r--r-- 11,189 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
local tap = require('tap')
local json = require('json')
local test = tap.test("errno")
local sql_tokenizer = require('sql_tokenizer')

-- pcall here, because we allow to run a test w/o test-run; use:
-- LUA_PATH='test/sql-tap/lua/?.lua;test/sql/lua/?.lua;;' \
--     ./test/sql-tap/xxx.test.lua
local ok, test_run = pcall(require, 'test_run')
test_run = ok and test_run.new() or nil

local function flatten(arr)
    local result = { }

    local function flatten(arr)
        for _, v in ipairs(arr) do
            if type(v) == "table" then
                flatten(v)
            elseif box.tuple.is(v) then
                flatten(v:totable())
            else
                table.insert(result, v)
            end
        end
    end
    flatten(arr)
    return result
end

local function finish_test()
    test:check()
    os.exit()
end
test.finish_test = finish_test

-- Check if string is regex pattern.
-- Condition: /.../ or ~/.../
local function string_regex_p(str)
    if type(str) == 'string'
            and (string.sub(str, 1, 1) == '/'
            or string.sub(str, 1, 2) == '~/')
            and string.sub(str, -1) == '/' then
        return true;
    else
        return false;
    end
end

local function table_check_regex_p(t, regex)
    -- regex is definetely regex here, no additional checks
    local nmatch = string.sub(regex, 1, 1) == '~' and 1 or 0
    local regex_tr = string.sub(regex, 2 + nmatch, string.len(regex) - 1)
    for _, v in pairs(t) do
        if nmatch == 1 then
            if type(v) == 'table' and not table_check_regex_p(v, regex) then
                return 0
            end
            if type(v) == 'string' and string.find(v, regex_tr) then
                return 0
            end
        else
            if type(v) == 'table' and table_check_regex_p(v, regex) then
                return 1
            end
            if type(v) == 'string' and string.find(v, regex_tr) then
                return 1
            end
        end
    end

    return nmatch
end

local function is_deeply_regex(got, expected)
    if type(expected) == "number" or type(got) == "number" then
        if got ~= got and expected ~= expected then
            return true -- nan
        end
    end
    if type(expected) == "number" and type(got) == "number" then
        if got == expected then
            return true
        end
        local min_delta = 0.000000001 * math.abs(got)
        return (got - expected < min_delta) and (expected - got < min_delta)
    end

    if string_regex_p(expected) then
        return table_match_regex_p(got, expected)
    end

    if got == nil and expected == nil then return true end

    if type(got) ~= type(expected) then
        return false
    end

    if type(got) ~= 'table' then
        return got == expected
    end

    for i, v in pairs(expected) do
        if string_regex_p(v) then
            return table_check_regex_p(got, v) == 1
        else
            if not is_deeply_regex(got[i], v) then
                return false
            end
        end
    end

    if #got ~= #expected then
        return false
    end

    return true
end
test.is_deeply_regex = is_deeply_regex

local function do_test(self, label, func, expect)
    local ok, result = pcall(func)
    if ok then
        if result == nil then result = { } end

        -- If nothing is expected: just make sure there were no error.
        if expect == nil then
            if table.getn(result) ~= 0 and result[1] ~= 0 then
                test:fail(self, label)
            else
                test:ok(self, label)
            end
        else
            if is_deeply_regex(result, expect) then
                test:ok(self, label)
            else
                io.write(string.format('%s: Miscompare\n', label))
                io.write("Expected: ", json.encode(expect).."\n")
                io.write("Got     : ", json.encode(result).."\n")
                test:fail(label)
            end
        end
    else
        self:fail(string.format('%s: Execution failed: %s\n', label, result))
    end
end
test.do_test = do_test

local function execsql_one_by_one(sql)
    local queries = sql_tokenizer.split_sql(sql)
    local last_res_rows = nil
    local last_res_metadata = nil
    for _, query in pairs(queries) do
        local new_res, err = box.execute(query)
        if err ~= nil then
            error(err)
        end
        if new_res ~= nil and new_res.rows ~= nil then
            last_res_rows = new_res.rows
            last_res_metadata = new_res.metadata
        end
    end
    return last_res_rows, last_res_metadata
end

local function execsql(self, sql)
    local result = execsql_one_by_one(sql)
    if type(result) ~= 'table' then return end

    result = flatten(result)
    for i, c in ipairs(result) do
        if c == nil then
            result[i] = ""
        end
    end
    return result
end
test.execsql = execsql

local function catchsql(self, sql, expect)
    r = {pcall(execsql, self, sql) }
    if r[1] == true then
        r[1] = 0
    else
        r[1] = 1
        if type(r[2]) == 'cdata' then
            r[2] = tostring(r[2])
        end
    end
    return r
end
test.catchsql = catchsql

local function do_catchsql_test(self, label, sql, expect)
    return do_test(self, label, function() return catchsql(self, sql) end, expect)
end
test.do_catchsql_test = do_catchsql_test

local function do_catchsql2_test(self, label, sql, expect)
    return do_test(self, label, function() return test.catchsql2(self, sql) end, expect)
end
test.do_catchsql2_test = do_catchsql2_test

local function do_execsql_test(self, label, sql, expect)
    return do_test(self, label, function() return test.execsql(self, sql) end, expect)
end
test.do_execsql_test = do_execsql_test

local function do_execsql2_test(self, label, sql, expect)
    return do_test(self, label, function() return test.execsql2(self, sql) end, expect)
end
test.do_execsql2_test = do_execsql2_test

local function flattern_with_column_names(result, metadata)
    local ret = {}
    for i = 1, #result, 1 do
        for j = 1, #metadata, 1 do
            table.insert(ret, metadata[j].name)
            table.insert(ret, result[i][j])
        end
    end
    return ret
end

function test.do_catchsql_set_test(self, testcases, prefix)
    -- testcases structure:
    -- {
    --      {
    --          TEST_CASE_NAME,
    --          SQL_STATEMENTS,
    --          RESULT (AS IN CATCHSQL TEST)
    --      }
    -- }
    if prefix == nil then prefix = "" end
    for _, testcase in ipairs(testcases) do
        test:do_catchsql_test(
                              prefix..testcase[1],
                              testcase[2],
                              testcase[3])
    end
end

local function execsql2(self, sql)
    local result, metadata = execsql_one_by_one(sql)
    if type(result) ~= 'table' then return end
    -- shift rows down, revealing column names
    result = flattern_with_column_names(result, metadata)
    return result
end
test.execsql2 = execsql2

local function sortsql(self, sql)
    local result = execsql(self, sql)
    table.sort(result, function(a,b) return a[2] < b[2] end)
    return result
end
test.sortsql = sortsql

local function catchsql2(self, sql)
    r = {pcall(execsql2, self, sql) }
    -- 0 means ok
    -- 1 means not ok
    r[1] = r[1] == true and 0 or 1
    if r[1] == 1 then
        r[2] = tostring(r[2])
    end
    return r
end
test.catchsql2 = catchsql2

-- Show the VDBE program for an SQL statement but omit the Trace
-- opcode at the beginning.  This procedure can be used to prove
-- that different SQL statements generate exactly the same VDBE code.
local function explain_no_trace(self, sql)
    tr = execsql(self, "EXPLAIN "..sql)
    for i=1,8 do
        table.remove(tr,1)
    end
    return tr
end
test.explain_no_trace = explain_no_trace
json = require("json")

function test.drop_all_tables(self)
    local entry_count = 0
    for _, v in box.space._space:pairs() do
        if v[1] >= 512 then
            box.space[v[3]]:drop()
            entry_count = entry_count + 1
        end
    end
    return entry_count
end

function test.drop_all_views(self)
    local entry_count = 0
    for _, v in box.space._space:pairs() do
        if v[1] > 512 and v[6].view == true then
            box.space[v[3]]:drop()
            entry_count = entry_count + 1
        end
    end
    return entry_count
end

function test.do_select_tests(self, label, tests)
    for _, test_case in ipairs(tests) do
        local tn = test_case[1]
        local sql = test_case[2]
        local result = test_case[3]
        test:do_test(
            label..'.'..tn,
            function()
                return test:execsql(sql)
            end,
            result)
    end
end

function test.sf_execsql(self, sql)
    local old = box.stat.sql().sql_search_count
    local r = test:execsql(sql)
    local new = box.stat.sql().sql_search_count - old

    return {new, r}
end

function test.do_sf_execsql_test(self, label, sql, result)
    return test:do_test(label,
                        function()
                            return test:sf_execsql(sql)
                        end,
                        result)
end

local function db(self, cmd, ...)
    if cmd == 'eval' then
        return execsql(self, ...)
    end
end
test.db = db

-- returns first occurance of seed in input or -1
local function lsearch(self, input, seed)
    local index = 1
    local success = false
    local function search(arr)
        if type(arr) == 'table' then
            for _, v in ipairs(arr) do
                search(v)
                if success == true then
                    return
                end
            end
        else
            if type(arr) == 'string' and arr:find(seed) ~= nil then
                success = true
            else
                index = index + 1
            end
        end
    end
    search(input)
    return success == true and index or -1
end
test.lsearch = lsearch

function test.lindex(arr, pos)
    return arr[pos+1]
end

--function capable()
--    return true
--end

function test.randstr(Length)
    -- Length (number)
    local Chars = {}
    for Loop = 0, 255 do
        Chars[Loop+1] = string.char(Loop)
    end
    local String = table.concat(Chars)
    local Result = {}
    local Lookup = Chars
    local Range = #Lookup

    for Loop = 1,Length do
        Result[Loop] = Lookup[math.random(1, Range)]
    end

    return table.concat(Result)
end

test.do_eqp_test = function (self, label, sql, result)
    test:do_test(
        label,
        function()
            local result = execsql_one_by_one("EXPLAIN QUERY PLAN "..sql)
            for k,v in pairs(result) do
                result[k] = v:totable()
            end
            return result
        end,
        result)
end

setmetatable(_G, nil)

-- start the database
box.cfg{
    memtx_max_tuple_size=4996109;
    vinyl_max_tuple_size=4996109;
    log="tarantool.log";
}

local engine = test_run and test_run:get_cfg('engine') or 'memtx'
box.space._session_settings:update('sql_default_engine', {{'=', 2, engine}})

function test.engine(self)
    return engine
end

return test