diff --git a/graphql/gen_arguments.lua b/graphql/gen_arguments.lua index ee04a66..af29308 100644 --- a/graphql/gen_arguments.lua +++ b/graphql/gen_arguments.lua @@ -21,7 +21,7 @@ local gen_arguments = {} --- key (and, then, offset) type --- --- @treturn table `offset_type` is a record in case of compound (multi-part) ---- primary key +--- primary key (is not nil) local function get_primary_key_type(db_schema, collection_name) -- get name of field of primary key local _, index_meta = db_schema_helpers.get_primary_index_meta( @@ -47,10 +47,12 @@ local function get_primary_key_type(db_schema, collection_name) assert(type(field_type) == 'string', 'field type must be a string, got ' .. type(field_type)) - offset_fields[#offset_fields + 1] = { - name = field_name, - type = field_type, - } + if field_type ~= nil then + offset_fields[#offset_fields + 1] = { + name = field_name, + type = field_type, + } + end end local offset_type @@ -79,7 +81,8 @@ end --- --- @tparam[opt] function skip_cond --- ---- @return transformed avro-schema or nil (when mathed by skip_cond) +--- @return transformed avro-schema or nil (when the type or all its fields are +--- matched by skip_cond) local function recursive_nullable(e_schema, skip_cond) local avro_t = avro_helpers.avro_type(e_schema) @@ -103,6 +106,7 @@ local function recursive_nullable(e_schema, skip_cond) end end + if #res.fields == 0 then return nil end return res elseif avro_t == 'union' or avro_t == 'array' or avro_t == 'array*' or @@ -121,7 +125,7 @@ end --- --- @param e_schema (table or string) avro-schema with expanded references --- ---- @return transformed avro-schema +--- @return transformed avro-schema or nil (empty fields case) local function recursive_replace_default_with_nullable(e_schema) local avro_t = avro_helpers.avro_type(e_schema) @@ -142,6 +146,7 @@ local function recursive_replace_default_with_nullable(e_schema) table.insert(res.fields, field) end + if #res.fields == 0 then return nil end return res elseif avro_t == 'union' then local res = {} @@ -152,14 +157,17 @@ local function recursive_replace_default_with_nullable(e_schema) table.insert(res, new_child_type) end + if #res == 0 then return nil end return res elseif avro_t == 'array' or avro_t == 'array*' then local res = table.copy(e_schema) res.items = recursive_replace_default_with_nullable(e_schema.items) + if res.items == nil then return nil end return res elseif avro_t == 'map' or avro_t == 'map*' then local res = table.copy(e_schema) res.values = recursive_replace_default_with_nullable(e_schema.values) + if res.values == nil then return nil end return res end @@ -223,6 +231,7 @@ local function get_pcre_argument_type(db_schema, collection_name) return is_non_string_scalar or is_non_record_compound end) + if res == nil then return nil end res.name = collection_name .. '_pcre' return res end @@ -268,10 +277,14 @@ local function get_update_argument_type(db_schema, collection_name) if not is_field_part_of_primary_key then local field = table.copy(field) field.type = recursive_nullable(field.type) - table.insert(schema_update.fields, field) + if field.type ~= nil then + table.insert(schema_update.fields, field) + end end end + if schema_update.fields == nil then return nil end + return schema_update end @@ -303,6 +316,9 @@ function gen_arguments.object_args(db_schema, collection_name) and (avro_t ~= 'record' and avro_t ~= 'record*') return is_non_comparable_scalar or is_non_record_compound end) + + if res == nil then return {} end + return res.fields end @@ -324,7 +340,9 @@ function gen_arguments.list_args(db_schema, collection_name) local pcre_field if rex ~= nil then local pcre_type = get_pcre_argument_type(db_schema, collection_name) - pcre_field = {name = 'pcre', type = pcre_type} + if pcre_type ~= nil then + pcre_field = {name = 'pcre', type = pcre_type} + end end return { @@ -374,30 +392,42 @@ function gen_arguments.extra_args(db_schema, collection_name, opts) local e_schema = db_schema.e_schemas[schema_name] local schema_insert = recursive_replace_default_with_nullable(e_schema) - schema_insert.name = collection_name .. '_insert' - schema_insert.type = 'record*' -- make the record nullable + if schema_insert ~= nil then + schema_insert.name = collection_name .. '_insert' + schema_insert.type = 'record*' -- make the record nullable + end local schema_update = get_update_argument_type(db_schema, collection_name) local schema_delete = 'boolean*' - return { - {name = 'insert', type = schema_insert}, - {name = 'update', type = schema_update}, - {name = 'delete', type = schema_delete}, - }, { - insert = { + local args = {} + local args_meta = {} + + if schema_insert ~= nil then + table.insert(args, {name = 'insert', type = schema_insert}) + args_meta.insert = { add_to_mutations_only = true, add_to_top_fields_only = true, - }, - update = { + } + end + + if schema_update ~= nil then + table.insert(args, {name = 'update', type = schema_update}) + args_meta.update = { add_to_mutations_only = true, add_to_top_fields_only = false, - }, - delete = { + } + end + + if schema_delete ~= nil then + table.insert(args, {name = 'delete', type = schema_delete}) + args_meta.delete = { add_to_mutations_only = true, add_to_top_fields_only = false, - }, - } + } + end + + return args, args_meta end return gen_arguments diff --git a/test/common/no_eq_comparable_fields.test.lua b/test/common/no_eq_comparable_fields.test.lua new file mode 100755 index 0000000..aef72ba --- /dev/null +++ b/test/common/no_eq_comparable_fields.test.lua @@ -0,0 +1,16 @@ +#!/usr/bin/env tarantool + +local fio = require('fio') + +-- require in-repo version of graphql/ sources despite current working directory +package.path = fio.abspath(debug.getinfo(1).source:match("@?(.*/)") + :gsub('/./', '/'):gsub('/+$', '')) .. '/../../?.lua' .. ';' .. package.path + +local test_utils = require('test.test_utils') +local testdata = require('test.testdata.no_eq_comparable_fields_testdata') + +box.cfg({}) + +test_utils.run_testdata(testdata) + +os.exit() diff --git a/test/test_utils.lua b/test/test_utils.lua index 8664a96..ccb56dc 100644 --- a/test/test_utils.lua +++ b/test/test_utils.lua @@ -163,4 +163,20 @@ function test_utils.show_trace(func, ...) )) end +-- needed to compare a dump with floats/doubles, because, say, +-- `tonumber(tostring(1/3)) == 1/3` is `false` +function test_utils.deeply_number_tostring(t) + if type(t) == 'table' then + local res = {} + for k, v in pairs(t) do + res[k] = test_utils.deeply_number_tostring(v) + end + return res + elseif type(t) == 'number' then + return tostring(t) + else + return table.deepcopy(t) + end +end + return test_utils diff --git a/test/testdata/common_testdata.lua b/test/testdata/common_testdata.lua index 355634f..89b7b85 100644 --- a/test/testdata/common_testdata.lua +++ b/test/testdata/common_testdata.lua @@ -12,22 +12,6 @@ local common_testdata = {} -- forward declaration local type_mismatch_cases --- needed to compare a dump with floats/doubles, because, say, --- `tonumber(tostring(1/3)) == 1/3` is `false` -local function deeply_number_tostring(t) - if type(t) == 'table' then - local res = {} - for k, v in pairs(t) do - res[k] = deeply_number_tostring(v) - end - return res - elseif type(t) == 'number' then - return tostring(t) - else - return table.deepcopy(t) - end -end - function common_testdata.get_test_metadata() local schemas = json.decode([[{ "user": { @@ -1004,8 +988,8 @@ function common_testdata.run_queries(gql_wrapper) test_utils.show_trace(function() local variables_6_1 = {limit = 10} local result = gql_query_6:execute(variables_6_1) - local exp_result_6_1 = deeply_number_tostring(exp_result_6_1) - local result = deeply_number_tostring(result) + local exp_result_6_1 = test_utils.deeply_number_tostring(exp_result_6_1) + local result = test_utils.deeply_number_tostring(result) test:is_deeply(result.data, exp_result_6_1, '6_1') end) @@ -1065,16 +1049,16 @@ function common_testdata.run_queries(gql_wrapper) ]]):strip()) test_utils.show_trace(function() - local exp_result_6_2 = deeply_number_tostring(exp_result_6_2) + local exp_result_6_2 = test_utils.deeply_number_tostring(exp_result_6_2) local variables_6_2 = {limit = 10, in_stock = true} local result = gql_query_6:execute(variables_6_2) - local result = deeply_number_tostring(result) + local result = test_utils.deeply_number_tostring(result) test:is_deeply(result.data, exp_result_6_2, '6_2') local variables_6_2 = {limit = 10} local result = gql_query_6_i_true:execute(variables_6_2) - local result = deeply_number_tostring(result) + local result = test_utils.deeply_number_tostring(result) test:is_deeply(result.data, exp_result_6_2, '6_2') end) @@ -1134,16 +1118,16 @@ function common_testdata.run_queries(gql_wrapper) ]]):strip()) test_utils.show_trace(function() - local exp_result_6_3 = deeply_number_tostring(exp_result_6_3) + local exp_result_6_3 = test_utils.deeply_number_tostring(exp_result_6_3) local variables_6_3 = {limit = 10, in_stock = false} local result = gql_query_6:execute(variables_6_3) - local result = deeply_number_tostring(result) + local result = test_utils.deeply_number_tostring(result) test:is_deeply(result.data, exp_result_6_3, '6_3') local variables_6_3 = {limit = 10} local result = gql_query_6_i_false:execute(variables_6_3) - local result = deeply_number_tostring(result) + local result = test_utils.deeply_number_tostring(result) test:is_deeply(result.data, exp_result_6_3, '6_3') end) diff --git a/test/testdata/no_eq_comparable_fields_testdata.lua b/test/testdata/no_eq_comparable_fields_testdata.lua new file mode 100644 index 0000000..15cbc01 --- /dev/null +++ b/test/testdata/no_eq_comparable_fields_testdata.lua @@ -0,0 +1,155 @@ +-- https://github.com/tarantool/graphql/issues/183 +-- +-- The problem is that an InputObject type can be generated with empty fields. + +local tap = require('tap') +local json = require('json') +local yaml = require('yaml') +local test_utils = require('test.test_utils') + +local no_eq_comparable_fields_testdata = {} + +function no_eq_comparable_fields_testdata.get_test_metadata() + local schemas = json.decode([[{ + "no_eq_comparable_fields": { + "type": "record", + "name": "no_eq_comparable_fields", + "fields": [ + { "name": "id", "type": "float"}, + { "name": "float_value", "type": "float" }, + { "name": "double_value", "type": "double" } + ] + } + }]]) + + local collections = json.decode([[{ + "no_eq_comparable_fields": { + "schema_name": "no_eq_comparable_fields", + "connections": [] + } + }]]) + + local service_fields = { + no_eq_comparable_fields = {}, + } + + local indexes = { + no_eq_comparable_fields = { + primary = { + service_fields = {}, + fields = {'id'}, + index_type = 'tree', + unique = true, + primary = true, + } + } + } + + return { + schemas = schemas, + collections = collections, + service_fields = service_fields, + indexes = indexes, + } +end + +function no_eq_comparable_fields_testdata.init_spaces() + -- no_eq_comparable_fields fields + local N_ID_FN = 1 + + box.once('test_space_init_spaces', function() + box.schema.create_space('no_eq_comparable_fields') + box.space.no_eq_comparable_fields:create_index( + 'primary', + {type = 'tree', parts = { + N_ID_FN, 'number' + }} + ) + end) +end + +function no_eq_comparable_fields_testdata.fill_test_data(virtbox, meta) + test_utils.replace_object(virtbox, meta, 'no_eq_comparable_fields', { + id = 1.0, + float_value = 1.0, + double_value = 1.0, + }) + test_utils.replace_object(virtbox, meta, 'no_eq_comparable_fields', { + id = 2.0, + float_value = 2.0, + double_value = 2.0, + }) +end + +function no_eq_comparable_fields_testdata.drop_spaces() + box.space._schema:delete('oncetest_space_init_spaces') + box.space.no_eq_comparable_fields:drop() +end + +function no_eq_comparable_fields_testdata.run_queries(gql_wrapper) + local test = tap.test('no_eq_comparable_fields') + test:plan(2) + + local query_1 = [[ + { + no_eq_comparable_fields(limit: 1) { + id + float_value + double_value + } + } + ]] + + local exp_result_1 = yaml.decode(([[ + --- + no_eq_comparable_fields: + - id: 1.0 + float_value: 1.0 + double_value: 1.0 + ]]):strip()) + + test_utils.show_trace(function() + local gql_query_1 = gql_wrapper:compile(query_1) + local result = gql_query_1:execute({}) + local exp_result_1 = test_utils.deeply_number_tostring(exp_result_1) + local result = test_utils.deeply_number_tostring(result) + test:is_deeply(result.data, exp_result_1, 'limit works') + end) + + local introspection_query = [[ + query IntrospectionQuery { + __schema { + types { + kind + name + fields { + name + } + inputFields { + name + } + } + } + } + ]] + + local result = test_utils.show_trace(function() + local gql_introspection_query = gql_wrapper:compile(introspection_query) + return gql_introspection_query:execute({}) + end) + + local ok = true + for _, t in ipairs(result.data.__schema.types) do + if t.kind == 'OBJECT' or t.kind == 'INPUT_OBJECT' then + local fields = t.kind == 'OBJECT' and t.fields or t.inputFields + ok = ok and type(fields) == 'table' and #fields > 0 + assert(ok, ('wrong %s: %s'):format(t.kind, t.name)) + end + end + + test:ok(ok, 'no Object/InputObject types with no fields') + + assert(test:check(), 'check plan') +end + +return no_eq_comparable_fields_testdata