From acdf73115212225e47b6106483fec0e41ab998f9 Mon Sep 17 00:00:00 2001 From: Jochen Topf Date: Sun, 23 Feb 2020 17:05:02 +0100 Subject: [PATCH 1/3] Error when trying to add objects of special Lua types to database --- src/output-flex.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/output-flex.cpp b/src/output-flex.cpp index 3ddd32604..0376416b3 100644 --- a/src/output-flex.cpp +++ b/src/output-flex.cpp @@ -226,6 +226,12 @@ void output_flex_t::write_column( return; } + if (ltype == LUA_TFUNCTION || ltype == LUA_TUSERDATA || + ltype == LUA_TTHREAD) { + throw std::runtime_error{ + "Can not add Lua objects of type function, userdata, or thread."}; + } + if ((column.type() == table_column_type::sql) || (column.type() == table_column_type::text)) { auto const *const str = lua_tolstring(lua_state(), -1, nullptr); From e38e61483c1de10e377e47d7f6c679f00a55c380 Mon Sep 17 00:00:00 2001 From: Jochen Topf Date: Sun, 23 Feb 2020 17:05:58 +0100 Subject: [PATCH 2/3] Throw error when adding NULL to NOT NULL columns Detect this in our C++ code instead of waiting for the COPY command to fail. --- src/output-flex.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/output-flex.cpp b/src/output-flex.cpp index 0376416b3..b199e375b 100644 --- a/src/output-flex.cpp +++ b/src/output-flex.cpp @@ -212,6 +212,17 @@ static int sgn(int val) noexcept return 0; } +static void write_null(db_copy_mgr_t *copy_mgr, + flex_table_column_t const &column) +{ + if (column.not_null()) { + throw std::runtime_error{ + "Can not add NULL to column '{}' declared NOT NULL."_format( + column.name())}; + } + copy_mgr->add_null_column(); +} + void output_flex_t::write_column( db_copy_mgr_t *copy_mgr, flex_table_column_t const &column) @@ -221,7 +232,7 @@ void output_flex_t::write_column( // A Lua nil value is always translated to a database NULL if (ltype == LUA_TNIL) { - copy_mgr->add_null_column(); + write_null(copy_mgr, column); lua_pop(lua_state(), 1); return; } @@ -345,7 +356,7 @@ void output_flex_t::write_row(table_connection_t *table_connection, copy_mgr->add_hex_geom(geom); } else if (column.type() == table_column_type::area) { if (geom.empty()) { - copy_mgr->add_null_column(); + write_null(copy_mgr, column); } else { double const area = get_options()->reproject_area From ea7e9102d0795084049e8018e83df9d348d12a40 Mon Sep 17 00:00:00 2001 From: Jochen Topf Date: Tue, 25 Feb 2020 13:56:15 +0100 Subject: [PATCH 3/3] Clear up the rules for converting Lua to PostgreSQL types More consistent and useful rules. Also added tests. --- docs/flex.md | 57 ++++++--- src/output-flex.cpp | 155 ++++++++++++++++------ tests/CMakeLists.txt | 1 + tests/data/test_output_flex_types.lua | 75 +++++++++++ tests/test-output-flex-types.cpp | 177 ++++++++++++++++++++++++++ 5 files changed, 410 insertions(+), 55 deletions(-) create mode 100644 tests/data/test_output_flex_types.lua create mode 100644 tests/test-output-flex-types.cpp diff --git a/docs/flex.md b/docs/flex.md index 98fe3718b..a00e807dd 100644 --- a/docs/flex.md +++ b/docs/flex.md @@ -96,23 +96,9 @@ geometry. This will only work for (multi)polygons. In addition to id and geometry columns, each table can have any number of "normal" columns using any type supported by PostgreSQL. Some types are -specially recognized by osm2pgsql: - -* `text`: A text string. -* `boolean`: Interprets string values `"true"`, `"yes"` as `true` and all - others as `false`. Boolean and integer values will also work in the usual - way. -* `int2`, `smallint`: 16bit signed integer. Values too large to fit will be - truncated in some unspecified way. -* `int4`, `int`, `integer`: 32bit signed integer. Values too large to fit will - be truncated in some unspecified way. -* `int8`, `bigint`: 64bit signed integer. Values too large to fit will be - truncated in some unspecified way. -* `real`: A real number. -* `hstore`: Automatically filled from a Lua table with only strings as keys - and values. -* `direction`: Interprets values `"true"`, `"yes"`, and `"1"` as 1, `"-1"` as - `-1`, and everything else as `0`. Useful for `oneway` tags etc. +specially recognized by osm2pgsql: `text`, `boolean`, `int2` (`smallint`), +`int4` (`int`, `integer`), `int8` (`bigint`), `real`, `hstore`, and +`direction`. See the "Type conversion" section for details. Instead of the above types you can use any SQL type you want. If you do that you have to supply the PostgreSQL string representation for that type when @@ -289,3 +275,40 @@ flex backend, because they don't make sense in that context: backend, instead use the `data_tablespace` or `index_tablespace` options when defining your table. +## Type conversions + +The `add_row()` command will try its best to convert Lua values into +corresponding PostgreSQL values. But not all conversions make sense. Here +are the detailed rules: + +1. Lua values of type `function`, `userdata`, or `thread` will always result in + an error. +2. The Lua type `nil` is always converted to `NULL`. +3. If the result of a conversion is `NULL` and the column is defined as `NOT + NULL`, an error is thrown. +4. The Lua type `table` is converted to the PostgreSQL type `hstore` if and + only if all keys and values in the table are string values. A Lua `table` + can not be converted to any other PostgreSQL type. +5. For `boolean` columns: The number `0` is converted to `false`, all other + numbers are `true`. Strings are converted as follows: `"yes"`, `"true"`, + `"1"` are `true`; `"no"`, `"false"`, `"0"` are `false`, all others are + `NULL`. +6. For integer columns (`int2`, `int4`, `int8`): Boolean `true` is converted + to `1`, `false` to `0`. Numbers that are not integers or outside the range + of the type result in `NULL`. Strings are converted to integers if possible + otherwise the result is `NULL`. +7. For `real` columns: Booleans result in an error, all numbers are used as + is, strings are converted to a number, if that is not possible the result + is `NULL`. +8. For `direction` columns (stored as `int2` in the database): Boolean `true` + is converted to `1`, `false` to `0`. The number `0` results in `0`, all + positive numbers in `1`, all negative numbers in `-1`. Strings `"yes"` and + `"1"` will result in `1`, `"no"` and `"0"` in `0`, `"-1"` in `-1`. All + other strings will result in `NULL`. +9. For text columns and any other not specially recognized column types, + booleans result in an error and numbers are converted to strings. + +If you want any other conversions, you have to do them yourself in your Lua +code. Osm2pgsql provides some helper functions for other conversions, see +the (lua-lib Documentation)[lua-lib.md]. + diff --git a/src/output-flex.cpp b/src/output-flex.cpp index b199e375b..9ceb393e7 100644 --- a/src/output-flex.cpp +++ b/src/output-flex.cpp @@ -182,26 +182,7 @@ static void push_osm_object_to_lua_stack(lua_State *lua_state, lua_setmetatable(lua_state, -2); } -static bool str2bool(char const *str) noexcept -{ - return (std::strcmp(str, "yes") == 0) || (std::strcmp(str, "true") == 0); -} - -static int str2direction(char const *str) noexcept -{ - if ((std::strcmp(str, "yes") == 0) || (std::strcmp(str, "true") == 0) || - (std::strcmp(str, "1") == 0)) { - return 1; - } - - if (std::strcmp(str, "-1") == 0) { - return -1; - } - - return 0; -} - -static int sgn(int val) noexcept +static int sgn(double val) noexcept { if (val > 0) { return 1; @@ -223,6 +204,60 @@ static void write_null(db_copy_mgr_t *copy_mgr, copy_mgr->add_null_column(); } +static void write_boolean(db_copy_mgr_t *copy_mgr, + flex_table_column_t const &column, char const *str) +{ + if ((std::strcmp(str, "yes") == 0) || (std::strcmp(str, "true") == 0) || + std::strcmp(str, "1")) { + copy_mgr->add_column(true); + return; + } + + if ((std::strcmp(str, "no") == 0) || (std::strcmp(str, "false") == 0) || + std::strcmp(str, "0")) { + copy_mgr->add_column(false); + return; + } + + write_null(copy_mgr, column); +} + +static void +write_direction(db_copy_mgr_t *copy_mgr, + flex_table_column_t const &column, char const *str) +{ + if ((std::strcmp(str, "yes") == 0) || (std::strcmp(str, "1") == 0)) { + copy_mgr->add_column(1); + return; + } + + if ((std::strcmp(str, "no") == 0) || (std::strcmp(str, "0") == 0)) { + copy_mgr->add_column(0); + return; + } + + if (std::strcmp(str, "-1") == 0) { + copy_mgr->add_column(-1); + return; + } + + write_null(copy_mgr, column); +} + +static void write_double(db_copy_mgr_t *copy_mgr, + flex_table_column_t const &column, char const *str) +{ + char *end = nullptr; + double const value = std::strtod(str, &end); + + if (end && *end != '\0') { + write_null(copy_mgr, column); + return; + } + + copy_mgr->add_column(value); +} + void output_flex_t::write_column( db_copy_mgr_t *copy_mgr, flex_table_column_t const &column) @@ -230,6 +265,13 @@ void output_flex_t::write_column( lua_getfield(lua_state(), -1, column.name().c_str()); int const ltype = lua_type(lua_state(), -1); + // Certain Lua types can never be added to the database + if (ltype == LUA_TFUNCTION || ltype == LUA_TUSERDATA || + ltype == LUA_TTHREAD) { + throw std::runtime_error{ + "Can not add Lua objects of type function, userdata, or thread."}; + } + // A Lua nil value is always translated to a database NULL if (ltype == LUA_TNIL) { write_null(copy_mgr, column); @@ -237,12 +279,6 @@ void output_flex_t::write_column( return; } - if (ltype == LUA_TFUNCTION || ltype == LUA_TUSERDATA || - ltype == LUA_TTHREAD) { - throw std::runtime_error{ - "Can not add Lua objects of type function, userdata, or thread."}; - } - if ((column.type() == table_column_type::sql) || (column.type() == table_column_type::text)) { auto const *const str = lua_tolstring(lua_state(), -1, nullptr); @@ -257,11 +293,11 @@ void output_flex_t::write_column( copy_mgr->add_column(lua_toboolean(lua_state(), -1) != 0); break; case LUA_TNUMBER: - copy_mgr->add_column(lua_tointeger(lua_state(), -1) != 0); + copy_mgr->add_column(lua_tonumber(lua_state(), -1) != 0); break; case LUA_TSTRING: - copy_mgr->add_column( - str2bool(lua_tolstring(lua_state(), -1, nullptr))); + write_boolean(copy_mgr, column, + lua_tolstring(lua_state(), -1, nullptr)); break; default: throw std::runtime_error{ @@ -269,15 +305,58 @@ void output_flex_t::write_column( lua_typename(lua_state(), ltype))}; } } else if (column.type() == table_column_type::int2) { - // cast here is on okay, because the database column is only 16bit - copy_mgr->add_column((int16_t)lua_tointeger(lua_state(), -1)); + if (ltype == LUA_TNUMBER) { + int64_t const value = lua_tointeger(lua_state(), -1); + if (value >= std::numeric_limits::min() && + value <= std::numeric_limits::max()) { + copy_mgr->add_column(value); + } else { + write_null(copy_mgr, column); + } + } else if (ltype == LUA_TBOOLEAN) { + copy_mgr->add_column(lua_toboolean(lua_state(), -1)); + } else { + throw std::runtime_error{ + "Invalid type '{}' for int2 column"_format( + lua_typename(lua_state(), ltype))}; + } } else if (column.type() == table_column_type::int4) { - // cast here is on okay, because the database column is only 32bit - copy_mgr->add_column((int32_t)lua_tointeger(lua_state(), -1)); + if (ltype == LUA_TNUMBER) { + int64_t const value = lua_tointeger(lua_state(), -1); + if (value >= std::numeric_limits::min() && + value <= std::numeric_limits::max()) { + copy_mgr->add_column(value); + } else { + write_null(copy_mgr, column); + } + } else if (ltype == LUA_TBOOLEAN) { + copy_mgr->add_column(lua_toboolean(lua_state(), -1)); + } else { + throw std::runtime_error{ + "Invalid type '{}' for int4 column"_format( + lua_typename(lua_state(), ltype))}; + } } else if (column.type() == table_column_type::int8) { - copy_mgr->add_column(lua_tointeger(lua_state(), -1)); + if (ltype == LUA_TNUMBER) { + copy_mgr->add_column(lua_tointeger(lua_state(), -1)); + } else if (ltype == LUA_TBOOLEAN) { + copy_mgr->add_column(lua_toboolean(lua_state(), -1)); + } else { + throw std::runtime_error{ + "Invalid type '{}' for int8 column"_format( + lua_typename(lua_state(), ltype))}; + } } else if (column.type() == table_column_type::real) { - copy_mgr->add_column(lua_tonumber(lua_state(), -1)); + if (ltype == LUA_TNUMBER) { + copy_mgr->add_column(lua_tonumber(lua_state(), -1)); + } else if (ltype == LUA_TSTRING) { + write_double(copy_mgr, column, + lua_tolstring(lua_state(), -1, nullptr)); + } else { + throw std::runtime_error{ + "Invalid type '{}' for real column"_format( + lua_typename(lua_state(), ltype))}; + } } else if (column.type() == table_column_type::hstore) { if (ltype == LUA_TTABLE) { copy_mgr->new_hash(); @@ -316,11 +395,11 @@ void output_flex_t::write_column( copy_mgr->add_column(lua_toboolean(lua_state(), -1)); break; case LUA_TNUMBER: - copy_mgr->add_column(sgn(lua_tointeger(lua_state(), -1))); + copy_mgr->add_column(sgn(lua_tonumber(lua_state(), -1))); break; case LUA_TSTRING: - copy_mgr->add_column( - str2direction(lua_tolstring(lua_state(), -1, nullptr))); + write_direction(copy_mgr, column, + lua_tolstring(lua_state(), -1, nullptr)); break; default: throw std::runtime_error{ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 92b3575a2..5a1569920 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,6 +78,7 @@ if (HAVE_LUA) set_test(test-output-flex-lua-fail) set_test(test-output-flex-uni) set_test(test-output-flex-tablespace LABELS Tablespace) + set_test(test-output-flex-types) set_test(test-output-flex-update) set_test(test-output-flex-validgeom) endif() diff --git a/tests/data/test_output_flex_types.lua b/tests/data/test_output_flex_types.lua new file mode 100644 index 000000000..049b28c07 --- /dev/null +++ b/tests/data/test_output_flex_types.lua @@ -0,0 +1,75 @@ + +local table = osm2pgsql.define_node_table("nodes", { + { column = 'ttext', type = 'text' }, + { column = 'tbool', type = 'boolean' }, + { column = 'tint2', type = 'int2' }, + { column = 'tint4', type = 'int4' }, + { column = 'tint8', type = 'int8' }, + { column = 'treal', type = 'real' }, + { column = 'thstr', type = 'hstore' }, + { column = 'tdirn', type = 'direction' }, + { column = 'tsqlt', type = 'varchar' }, +}) + +function osm2pgsql.process_node(object) + if object.tags.type == 'nil' then + table:add_row{} + return + end + if object.tags.type == 'boolean' then + local row = { tbool = true, tint2 = true, tint4 = true, + tint8 = true, tdirn = true } + table:add_row(row) + + row = { tbool = false, tint2 = false, tint4 = false, + tint8 = false, tdirn = false } + table:add_row(row) + return + end + if object.tags.type == 'boolean-fail' then + table:add_row{ [object.tags.column] = true } + return + end + if object.tags.type == 'number' then + local numbers = { -2^31 - 1, -2^31, -2^31 + 1, + -2^15 - 1, -2^15, -2^15 + 1, + -2, -1, -0.5, 0, 0.5, 1, 2, + 2^15 - 1, 2^15, 2^15 + 1, + 2^31 - 1, 2^31, 2^31 + 1 } + for _, n in ipairs(numbers) do + table:add_row{ + ttext = n, + tbool = n, + tint2 = n, + tint4 = n, + tint8 = n, + treal = n, + tdirn = n, + tsqlt = n, + } + end + return + end + if object.tags.type == 'number-fail' then + table:add_row{ [object.tags.column] = 1 } + return + end + if object.tags.type == 'function-fail' then + table:add_row{ [object.tags.column] = table.insert } + return + end + if object.tags.type == 'table' then + table:add_row{ thstr = {} } + table:add_row{ thstr = { a = 'b', c = 'd' } } + return + end + if object.tags.type == 'table-hstore-fail' then + table:add_row{ thstr = { num = 1, bln = true } } + return + end + if object.tags.type == 'table-fail' then + table:add_row{ [object.tags.column] = {} } + return + end +end + diff --git a/tests/test-output-flex-types.cpp b/tests/test-output-flex-types.cpp new file mode 100644 index 000000000..28f7793a6 --- /dev/null +++ b/tests/test-output-flex-types.cpp @@ -0,0 +1,177 @@ +#include + +#include "common-import.hpp" +#include "common-options.hpp" + +static testing::db::import_t db; + +TEST_CASE("type nil") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + REQUIRE_NOTHROW( + db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=nil\n")); + + auto conn = db.db().connect(); + + CHECK(1 == conn.get_count("nodes")); + CHECK(1 == + conn.get_count( + "nodes", + "ttext IS NULL AND tbool IS NULL AND tint2 IS NULL " + "AND tint4 IS NULL AND tint8 IS NULL AND treal IS " + "NULL AND thstr IS NULL AND tdirn IS NULL AND tsqlt IS NULL")); +} + +TEST_CASE("type boolean") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + REQUIRE_NOTHROW( + db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=boolean\n")); + + auto conn = db.db().connect(); + + CHECK(2 == conn.get_count("nodes")); + CHECK(1 == conn.get_count("nodes", "tbool = true AND tint2 = 1 AND tint4 = " + "1 AND tint8 = 1 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tbool = false AND tint2 = 0 AND tint4 " + "= 0 AND tint8 = 0 AND tdirn = 0")); +} + +TEST_CASE("type boolean in column where it doesn't belong") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + REQUIRE_THROWS(db.run_import( + options, "n10 v1 dV x10.0 y10.0 Ttype=boolean-fail column=ttext\n")); + REQUIRE_THROWS(db.run_import( + options, "n10 v1 dV x10.0 y10.0 Ttype=boolean-fail column=treal\n")); + REQUIRE_THROWS(db.run_import( + options, "n10 v1 dV x10.0 y10.0 Ttype=boolean-fail column=thstr\n")); + REQUIRE_THROWS(db.run_import( + options, "n10 v1 dV x10.0 y10.0 Ttype=boolean-fail column=tsqlt\n")); + + auto conn = db.db().connect(); + + CHECK(0 == conn.get_count("nodes")); +} + +TEST_CASE("type number") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + REQUIRE_NOTHROW( + db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=number\n")); + + auto conn = db.db().connect(); + + CHECK(19 == conn.get_count("nodes")); + + // clang-format off + CHECK(1 == conn.get_count("nodes", "tsqlt = '-2147483649.0' AND ttext = '-2147483649.0' AND tbool = true AND tint2 IS NULL AND tint4 IS NULL AND tint8 = -2147483649 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '-2147483648.0' AND ttext = '-2147483648.0' AND tbool = true AND tint2 IS NULL AND tint4 = -2147483648 AND tint8 = -2147483648 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '-2147483647.0' AND ttext = '-2147483647.0' AND tbool = true AND tint2 IS NULL AND tint4 = -2147483647 AND tint8 = -2147483647 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '-32769.0' AND ttext = '-32769.0' AND tbool = true AND tint2 IS NULL AND tint4 = -32769 AND tint8 = -32769 AND treal = -32769 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '-32768.0' AND ttext = '-32768.0' AND tbool = true AND tint2 = -32768 AND tint4 = -32768 AND tint8 = -32768 AND treal = -32768 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '-32767.0' AND ttext = '-32767.0' AND tbool = true AND tint2 = -32767 AND tint4 = -32767 AND tint8 = -32767 AND treal = -32767 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '-2' AND ttext = '-2' AND tbool = true AND tint2 = -2 AND tint4 = -2 AND tint8 = -2 AND treal = -2 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '-1' AND ttext = '-1' AND tbool = true AND tint2 = -1 AND tint4 = -1 AND tint8 = -1 AND treal = -1 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '-0.5' AND ttext = '-0.5' AND tbool = true AND tint2 = 0 AND tint4 = 0 AND tint8 = 0 AND treal = -0.5 AND tdirn = -1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '0' AND ttext = '0' AND tbool = false AND tint2 = 0 AND tint4 = 0 AND tint8 = 0 AND treal = 0 AND tdirn = 0")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '0.5' AND ttext = '0.5' AND tbool = true AND tint2 = 0 AND tint4 = 0 AND tint8 = 0 AND treal = 0.5 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '1' AND ttext = '1' AND tbool = true AND tint2 = 1 AND tint4 = 1 AND tint8 = 1 AND treal = 1 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '2' AND ttext = '2' AND tbool = true AND tint2 = 2 AND tint4 = 2 AND tint8 = 2 AND treal = 2 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '32767.0' AND ttext = '32767.0' AND tbool = true AND tint2 = 32767 AND tint4 = 32767 AND tint8 = 32767 AND treal = 32767 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '32768.0' AND ttext = '32768.0' AND tbool = true AND tint2 IS NULL AND tint4 = 32768 AND tint8 = 32768 AND treal = 32768 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '32769.0' AND ttext = '32769.0' AND tbool = true AND tint2 IS NULL AND tint4 = 32769 AND tint8 = 32769 AND treal = 32769 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '2147483647.0' AND ttext = '2147483647.0' AND tbool = true AND tint2 IS NULL AND tint4 = 2147483647 AND tint8 = 2147483647 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '2147483648.0' AND ttext = '2147483648.0' AND tbool = true AND tint2 IS NULL AND tint4 IS NULL AND tint8 = 2147483648 AND tdirn = 1")); + CHECK(1 == conn.get_count("nodes", "tsqlt = '2147483649.0' AND ttext = '2147483649.0' AND tbool = true AND tint2 IS NULL AND tint4 IS NULL AND tint8 = 2147483649 AND tdirn = 1")); + // clang-format on +} + +TEST_CASE("type number in column where it doesn't belong") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + REQUIRE_THROWS( + db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=number-fail column=thstr\n")); + + auto conn = db.db().connect(); + + CHECK(0 == conn.get_count("nodes")); +} + +TEST_CASE("Adding a function should always fail") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + std::string types[] = {"ttext", "tbool", "tint2", "tint4", + "tint8", "treal", "thstr", "tdirn", "tsqlt"}; + + for (auto const &type : types) { + auto const line = + "n10 v1 dV x10.0 y10.0 Ttype=function-fail column=" + type + "\n"; + REQUIRE_THROWS(db.run_import(options, line.c_str())); + } + + auto conn = db.db().connect(); + + CHECK(0 == conn.get_count("nodes")); +} + +TEST_CASE("type table") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + REQUIRE_NOTHROW( + db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=table\n")); + + auto conn = db.db().connect(); + + CHECK(2 == conn.get_count("nodes")); + + CHECK(1 == conn.get_count("nodes", "thstr = ''")); + CHECK(1 == conn.get_count("nodes", "thstr = 'a=>b,c=>d'")); +} + +TEST_CASE("Adding a table with non-strings should fail for hstore") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + char const *const line = + "n10 v1 dV x10.0 y10.0 Ttype=table-hstore-fail\n"; + REQUIRE_THROWS(db.run_import(options, line)); + + auto conn = db.db().connect(); + + CHECK(0 == conn.get_count("nodes")); +} + +TEST_CASE("Adding a table should fail except for hstore") +{ + testing::opt_t const options = + testing::opt_t().flex("test_output_flex_types.lua"); + + std::string types[] = {"ttext", "tbool", "tint2", "tint4", + "tint8", "treal", "tdirn", "tsqlt"}; + + for (auto const &type : types) { + auto const line = + "n10 v1 dV x10.0 y10.0 Ttype=table-fail column=" + type + "\n"; + REQUIRE_THROWS(db.run_import(options, line.c_str())); + } + + auto conn = db.db().connect(); + + CHECK(0 == conn.get_count("nodes")); +}