Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions vector/v.db.connect/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,16 @@ int main(int argc, char **argv)
dbkey = G_define_standard_option(G_OPT_DB_KEYCOLUMN);

field_opt = G_define_standard_option(G_OPT_V_FIELD);
field_opt->description = _("Format: layer number[/layer name]");
field_opt->gisprompt = "new,layer,layer";
field_opt->description =
_("Layer number or name (format: layer number[/layer name])");
/* Clear gisprompt so the parser does not apply filename-style validation
* (G_legal_filename) to the layer option. G_OPT_V_FIELD defaults to
* gisprompt "old,layer,layer", which causes values like "1/bridges" to
* trigger "Illegal filename ... Character </> not allowed". This module
* documents and uses layer number[/layer name] (see -p/-g output and
* Vect_open_old2); the C code parses the string (atoi, strchr) and never
* treats it as a file path. */
field_opt->gisprompt = NULL;

sep_opt = G_define_standard_option(G_OPT_F_SEP);
sep_opt->answer = NULL;
Expand Down
54 changes: 54 additions & 0 deletions vector/v.db.connect/testsuite/test_v_db_connect.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import re

from grass.gunittest.case import TestCase
from grass.gunittest.main import test
from grass.script.core import read_command, parse_command
from grass.gunittest.gmodules import SimpleModule


class TestVDbConnect(TestCase):
Expand Down Expand Up @@ -113,6 +116,57 @@ def test_columns_csv(self):
).splitlines()
self.assertEqual(actual, expected)

def _assert_no_layer_filename_warning(self, stderr):
"""Assert stderr does not contain the parser 'illegal filename' regression."""
stderr = stderr or ""
self.assertIsNone(
re.search(r"illegal filename", stderr, re.IGNORECASE),
msg=stderr,
)
self.assertIsNone(
re.search(r"character\s*<\s*/\s*>\s*not allowed", stderr, re.IGNORECASE),
msg=stderr,
)

def _columns_output(self, layer_arg=None):
"""Run v.db.connect -c and return stdout lines (column list)."""
args = {"map": "bridges", "flags": "c"}
if layer_arg is not None:
args["layer"] = layer_arg
m = SimpleModule("v.db.connect", **args)
self.assertModule(m)
self._assert_no_layer_filename_warning(m.outputs.stderr)
return m.outputs.stdout.strip().splitlines()

def test_layer_number_only(self):
"""layer=1 (number only) works and produces column list"""
lines = self._columns_output(layer_arg="1")
self.assertGreater(len(lines), 0)
self.assertIn("INTEGER|cat", lines)

def test_layer_number_slash_name(self):
"""layer=1/bridges (number/name) works like layer=1 with no warning"""
ref_lines = self._columns_output(layer_arg="1")
lines = self._columns_output(layer_arg="1/bridges")
self.assertEqual(
lines,
ref_lines,
"layer=1/bridges should produce same columns as layer=1",
)

# -p regression coverage: original warning was triggered during parsing for 1/name
def test_layer_number_slash_name_print(self):
"""layer=1/bridges works with -p (print) without illegal-filename warning"""
m = SimpleModule("v.db.connect", map="bridges", layer="1/bridges", flags="p")
self.assertModule(m)
self._assert_no_layer_filename_warning(m.outputs.stderr)

def test_layer_name_only(self):
"""layer=bridges (name only) works and matches layer=1 output"""
ref_lines = self._columns_output(layer_arg="1")
lines = self._columns_output(layer_arg="bridges")
self.assertEqual(lines, ref_lines)

def test_columns_json(self):
"""Test -c flag with JSON format"""
actual = parse_command("v.db.connect", map="bridges", flags="c", format="json")
Expand Down
Loading