diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 5844dfc0..fe020f9c 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -1175,7 +1175,10 @@ def insert_upsert_implementation( ) else: raise - if tracker is not None: + # An empty CSV (header row but no data rows) never creates the table, + # so there's nothing to transform. Without this guard, --detect-types + # crashes on header-only input (see #702). + if tracker is not None and db[table].exists(): db.table(table).transform(types=tracker.types) # Clean up open file-like objects diff --git a/tests/test_cli.py b/tests/test_cli.py index 40b36854..5f8e5e77 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2318,6 +2318,23 @@ def test_upsert_detect_types(tmpdir, option): ] +@pytest.mark.parametrize("option", ("-d", "--detect-types")) +def test_insert_csv_header_only_with_detect_types(tmpdir, option): + """Header-only CSVs (no data rows) used to crash --detect-types because + the transform step ran against a table that was never created. Now we + skip transform when there's nothing to transform. (#702)""" + db_path = str(tmpdir / "test.db") + result = CliRunner().invoke( + cli.cli, + ["insert", db_path, "empty", "-", "--csv", option], + catch_exceptions=False, + input="name,age,weight\n", + ) + assert result.exit_code == 0 + db = Database(db_path) + assert "empty" not in db.table_names() + + def test_csv_detect_types_creates_real_columns(tmpdir): """Test that CSV import creates REAL columns for floats (default behavior)""" db_path = str(tmpdir / "test.db")