Skip to content

Commit 79a77f8

Browse files
committed
Merge remote-tracking branch 'origin/main' into log-row-counts
2 parents 9142d91 + f5a66b7 commit 79a77f8

File tree

7 files changed

+93
-17
lines changed

7 files changed

+93
-17
lines changed

sqlsynthgen/make.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,16 +238,24 @@ def _get_provider_for_column(column: Column) -> Tuple[list[str], str, list[str]]
238238

239239
generator_function = mapping.get((column_type, column_size is not None), None)
240240

241+
# Try if we know how to generate for a superclass of this type.
241242
if not generator_function:
242243
for key, value in mapping.items():
243244
if issubclass(column_type, key[0]) and key[1] == (column_size is not None):
244245
generator_function = value
245246
break
246247

248+
# If we still don't have a generator, use null and warn.
247249
if not generator_function:
248-
raise ValueError(f"Unsupported SQLAlchemy type: {column_type}")
249-
250-
if column_size:
250+
generator_function = "generic.null_provider.null"
251+
logger.warning(
252+
"Unsupported SQLAlchemy type %s for column %s. "
253+
"Setting this column to NULL always, "
254+
"you may want to configure a row generator for it instead.",
255+
column_type,
256+
column.name,
257+
)
258+
elif column_size:
251259
generator_arguments.append(str(column_size))
252260

253261
return variable_names, generator_function, generator_arguments

sqlsynthgen/providers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ class Meta:
1717

1818
name = "column_value_provider"
1919

20+
@staticmethod
2021
def column_value(
21-
self, db_connection: Connection, orm_class: Any, column_name: str
22+
db_connection: Connection, orm_class: Any, column_name: str
2223
) -> Any:
2324
"""Return a random value from the column specified."""
2425
query = select(orm_class).order_by(functions.random()).limit(1)
@@ -50,8 +51,8 @@ class Meta:
5051

5152
name = "timedelta_provider"
5253

54+
@staticmethod
5355
def timedelta(
54-
self,
5556
min_dt: dt.timedelta = dt.timedelta(seconds=0),
5657
# ints bigger than this cause trouble
5758
max_dt: dt.timedelta = dt.timedelta(seconds=2**32),
@@ -75,8 +76,8 @@ class Meta:
7576

7677
name = "timespan_provider"
7778

79+
@staticmethod
7880
def timespan(
79-
self,
8081
earliest_start_year: int,
8182
last_start_year: int,
8283
min_dt: dt.timedelta = dt.timedelta(seconds=0),
@@ -194,6 +195,7 @@ class Meta:
194195

195196
name = "null_provider"
196197

197-
def null(self) -> None:
198+
@staticmethod
199+
def null() -> None:
198200
"""Return `None`."""
199201
return None

tests/examples/example_orm.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Optional
1+
from typing import Any, List, Optional
22

33
from sqlalchemy import (
44
BigInteger,
@@ -18,6 +18,7 @@
1818
UniqueConstraint,
1919
Uuid,
2020
)
21+
from sqlalchemy.dialects.postgresql import BIT, CIDR
2122
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
2223
import datetime
2324

@@ -71,6 +72,15 @@ class Person(Base):
7172
)
7273

7374

75+
class StrangeTypeTable(Base):
76+
__tablename__ = "strange_type_table"
77+
__table_args__ = (PrimaryKeyConstraint("id", name="strange_type_table_pkey"),)
78+
79+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
80+
column_with_unusual_type: Mapped[Optional[Any]] = mapped_column(CIDR)
81+
column_with_unusual_type_and_length: Mapped[Optional[Any]] = mapped_column(BIT(3))
82+
83+
7484
class UnignorableTable(Base):
7585
__tablename__ = "unignorable_table"
7686
__table_args__ = (PrimaryKeyConstraint("id", name="unignorable_table_pkey"),)

tests/examples/expected_ssg.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ def __call__(self, dst_db_conn):
9090
return result
9191

9292

93+
class strange_type_tableGenerator(TableGenerator):
94+
num_rows_per_pass = 1
95+
96+
def __init__(self):
97+
pass
98+
99+
def __call__(self, dst_db_conn):
100+
result = {}
101+
result["column_with_unusual_type"] = generic.null_provider.null()
102+
result["column_with_unusual_type_and_length"] = generic.null_provider.null()
103+
return result
104+
105+
93106
class unique_constraint_testGenerator(TableGenerator):
94107
num_rows_per_pass = 1
95108

@@ -188,6 +201,7 @@ def __call__(self, dst_db_conn):
188201
"data_type_test": data_type_testGenerator(),
189202
"no_pk_test": no_pk_testGenerator(),
190203
"person": personGenerator(),
204+
"strange_type_table": strange_type_tableGenerator(),
191205
"unique_constraint_test": unique_constraint_testGenerator(),
192206
"unique_constraint_test2": unique_constraint_test2Generator(),
193207
"test_entity": test_entityGenerator(),

tests/examples/src.dump

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,18 @@ CREATE TABLE public.table_to_be_ignored (
218218

219219
ALTER TABLE public.table_to_be_ignored OWNER TO postgres;
220220

221+
--
222+
-- Name: strange_type_table; Type: TABLE; Schema: public; Owner: postgres
223+
--
224+
225+
CREATE TABLE public.strange_type_table (
226+
id integer NOT NULL,
227+
column_with_unusual_type cidr,
228+
column_with_unusual_type_and_length bit(3)
229+
);
230+
231+
ALTER TABLE public.strange_type_table OWNER TO postgres;
232+
221233
--
222234
-- Data for Name: concept; Type: TABLE DATA; Schema: public; Owner: postgres
223235
--
@@ -1390,6 +1402,13 @@ ALTER TABLE ONLY public.ref_to_unignorable_table
13901402
ALTER TABLE ONLY public.unignorable_table
13911403
ADD CONSTRAINT unignorable_table_pkey PRIMARY KEY (id);
13921404

1405+
--
1406+
-- Name: strange_type_table strange_type_table_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
1407+
--
1408+
1409+
ALTER TABLE ONLY public.strange_type_table
1410+
ADD CONSTRAINT strange_type_table_pkey PRIMARY KEY (id);
1411+
13931412
--
13941413
-- Name: fki_concept_concept_type_id_fkey; Type: INDEX; Schema: public; Owner: postgres
13951414
--

tests/test_functional.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ class FunctionalTestCase(RequiresDBTestCase):
1414

1515
def test_version_command(self) -> None:
1616
"""Check that the version command works."""
17-
1817
completed_process = run(
1918
["sqlsynthgen", "version"],
2019
capture_output=True,
@@ -40,10 +39,10 @@ class DBFunctionalTestCase(RequiresDBTestCase):
4039
alt_ssg_file_path = Path("my_ssg.py")
4140

4241
vocabulary_file_paths = tuple(
43-
map(Path, ("concept.yaml", "concept_type.yaml", "mitigation_type.yaml"))
42+
map(Path, ("concept.yaml", "concept_type.yaml", "mitigation_type.yaml")),
4443
)
4544
generator_file_paths = tuple(
46-
map(Path, ("story_generators.py", "row_generators.py"))
45+
map(Path, ("story_generators.py", "row_generators.py")),
4746
)
4847
dump_file_path = Path("dst.dump")
4948
config_file_path = Path("example_config.yaml")
@@ -60,7 +59,6 @@ class DBFunctionalTestCase(RequiresDBTestCase):
6059

6160
def setUp(self) -> None:
6261
"""Pre-test setup."""
63-
6462
# Create a mostly-blank destination database
6563
run_psql(self.examples_dir / self.dump_file_path)
6664

@@ -103,6 +101,16 @@ def test_workflow_minimal_args(self) -> None:
103101
# this could mean that we might accidentally violate the constraints. In
104102
# practice this won't happen because we only write one row to an empty table.
105103
self.assertEqual(
104+
"Unsupported SQLAlchemy type "
105+
"<class 'sqlalchemy.dialects.postgresql.types.CIDR'> "
106+
"for column column_with_unusual_type. "
107+
"Setting this column to NULL always, "
108+
"you may want to configure a row generator for it instead.\n"
109+
"Unsupported SQLAlchemy type "
110+
"<class 'sqlalchemy.dialects.postgresql.types.BIT'> "
111+
"for column column_with_unusual_type_and_length. "
112+
"Setting this column to NULL always, "
113+
"you may want to configure a row generator for it instead.\n"
106114
"A unique constraint (ab_uniq) isn't fully covered by one "
107115
"row generator (['a']). Enforcement of the constraint may not work.\n"
108116
"A unique constraint (ab_uniq) isn't fully covered by one "
@@ -188,7 +196,6 @@ def test_workflow_minimal_args(self) -> None:
188196

189197
def test_workflow_maximal_args(self) -> None:
190198
"""Test the CLI workflow runs with optional arguments."""
191-
192199
completed_process = run(
193200
[
194201
"sqlsynthgen",
@@ -257,7 +264,19 @@ def test_workflow_maximal_args(self) -> None:
257264
capture_output=True,
258265
env=self.env,
259266
)
260-
self.assertEqual("", completed_process.stderr.decode("utf-8"))
267+
self.assertEqual(
268+
"Unsupported SQLAlchemy type "
269+
"<class 'sqlalchemy.dialects.postgresql.types.CIDR'> "
270+
"for column column_with_unusual_type. "
271+
"Setting this column to NULL always, "
272+
"you may want to configure a row generator for it instead.\n"
273+
"Unsupported SQLAlchemy type "
274+
"<class 'sqlalchemy.dialects.postgresql.types.BIT'> "
275+
"for column column_with_unusual_type_and_length. "
276+
"Setting this column to NULL always, "
277+
"you may want to configure a row generator for it instead.\n",
278+
completed_process.stderr.decode("utf-8"),
279+
)
261280
self.assertSuccess(completed_process)
262281
self.assertEqual(
263282
f"Making {self.alt_ssg_file_path}.\n"
@@ -345,6 +364,7 @@ def test_workflow_maximal_args(self) -> None:
345364
'Generating data for table "data_type_test".\n'
346365
'Generating data for table "no_pk_test".\n'
347366
'Generating data for table "person".\n'
367+
'Generating data for table "strange_type_table".\n'
348368
'Generating data for table "unique_constraint_test".\n'
349369
'Generating data for table "unique_constraint_test2".\n'
350370
'Generating data for table "test_entity".\n'
@@ -358,6 +378,7 @@ def test_workflow_maximal_args(self) -> None:
358378
'Generating data for table "data_type_test".\n'
359379
'Generating data for table "no_pk_test".\n'
360380
'Generating data for table "person".\n'
381+
'Generating data for table "strange_type_table".\n'
361382
'Generating data for table "unique_constraint_test".\n'
362383
'Generating data for table "unique_constraint_test2".\n'
363384
'Generating data for table "test_entity".\n'
@@ -367,6 +388,7 @@ def test_workflow_maximal_args(self) -> None:
367388
f"hospital_visit: {2*(2*2+3)} rows created.\n"
368389
"data_type_test: 2 rows created.\n"
369390
"no_pk_test: 2 rows created.\n"
391+
"strange_type_table: 2 rows created.\n"
370392
"unique_constraint_test: 2 rows created.\n"
371393
"unique_constraint_test2: 2 rows created.\n"
372394
"test_entity: 2 rows created.\n",
@@ -394,6 +416,7 @@ def test_workflow_maximal_args(self) -> None:
394416
'Truncating table "test_entity".\n'
395417
'Truncating table "unique_constraint_test2".\n'
396418
'Truncating table "unique_constraint_test".\n'
419+
'Truncating table "strange_type_table".\n'
397420
'Truncating table "person".\n'
398421
'Truncating table "no_pk_test".\n'
399422
'Truncating table "data_type_test".\n'
@@ -459,7 +482,6 @@ def test_unique_constraint_fail(self) -> None:
459482
We also deliberately call create-data multiple times to make sure that the
460483
loading of existing keys from the database at start up works as expected.
461484
"""
462-
463485
# This is all exactly the same stuff we run in test_workflow_maximal_args.
464486
run(
465487
[

tests/test_remove.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ class RemoveTestCase(SSGTestCase):
1212

1313
@patch("sqlsynthgen.remove.get_settings", side_effect=get_test_settings)
1414
@patch("sqlsynthgen.remove.create_db_engine")
15-
@patch("sqlsynthgen.remove.delete", side_effect=range(1, 8))
15+
@patch("sqlsynthgen.remove.delete", side_effect=range(1, 9))
1616
def test_remove_db_data(
1717
self, mock_delete: MagicMock, mock_engine: MagicMock, _: MagicMock
1818
) -> None:
1919
"""Test the remove_db_data function."""
2020
config = {"tables": {"unignorable_table": {"ignore": True}}}
2121
remove_db_data(example_orm, remove_ssg, config)
22-
self.assertEqual(mock_delete.call_count, 7)
22+
self.assertEqual(mock_delete.call_count, 8)
2323
mock_delete.assert_has_calls(
2424
[
2525
call(example_orm.Base.metadata.tables[t])
@@ -29,6 +29,7 @@ def test_remove_db_data(
2929
"unique_constraint_test2",
3030
"unique_constraint_test",
3131
"person",
32+
"strange_type_table",
3233
"no_pk_test",
3334
"data_type_test",
3435
)

0 commit comments

Comments
 (0)