Skip to content

Commit 89d6afc

Browse files
fix(taps): Support non-nullable but not required SQL columns (#2225)
Closes #2224
1 parent b7b8d15 commit 89d6afc

File tree

4 files changed

+19
-5
lines changed

4 files changed

+19
-5
lines changed

singer_sdk/connectors/sql.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,8 @@ def discover_catalog_entry(
464464
th.Property(
465465
name=column_name,
466466
wrapped=th.CustomType(jsonschema_type),
467-
required=not is_nullable,
467+
nullable=is_nullable,
468+
required=column_name in key_properties if key_properties else False,
468469
),
469470
)
470471
schema = table_schema.to_dict()

singer_sdk/typing.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ class Property(JSONTypeHelper[T], t.Generic[T]):
517517
"""Generic Property. Should be nested within a `PropertiesList`."""
518518

519519
# TODO: Make some of these arguments keyword-only. This is a breaking change.
520-
def __init__(
520+
def __init__( # noqa: PLR0913
521521
self,
522522
name: str,
523523
wrapped: JSONTypeHelper[T] | type[JSONTypeHelper[T]],
@@ -527,6 +527,8 @@ def __init__(
527527
secret: bool | None = False, # noqa: FBT002
528528
allowed_values: list[T] | None = None,
529529
examples: list[T] | None = None,
530+
*,
531+
nullable: bool | None = None,
530532
) -> None:
531533
"""Initialize Property object.
532534
@@ -547,6 +549,7 @@ def __init__(
547549
are permitted. This will define the type as an 'enum'.
548550
examples: Optional. A list of one or more sample values. These may be
549551
displayed to the user as hints of the expected format of inputs.
552+
nullable: If True, the property may be null.
550553
"""
551554
self.name = name
552555
self.wrapped = wrapped
@@ -556,6 +559,7 @@ def __init__(
556559
self.secret = secret
557560
self.allowed_values = allowed_values or None
558561
self.examples = examples or None
562+
self.nullable = nullable
559563

560564
@property
561565
def type_dict(self) -> dict: # type: ignore[override]
@@ -585,7 +589,7 @@ def to_dict(self) -> dict:
585589
A JSON Schema dictionary describing the object.
586590
"""
587591
type_dict = self.type_dict
588-
if self.optional:
592+
if self.nullable or self.optional:
589593
type_dict = append_type(type_dict, "null")
590594
if self.default is not None:
591595
type_dict.update({"default": self.default})

tests/samples/conftest.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ def _sqlite_sample_db(sqlite_connector):
2525
for t in range(3):
2626
conn.execute(sa.text(f"DROP TABLE IF EXISTS t{t}"))
2727
conn.execute(
28-
sa.text(f"CREATE TABLE t{t} (c1 int PRIMARY KEY, c2 varchar(10))"),
28+
sa.text(
29+
f"""
30+
CREATE TABLE t{t} (
31+
c1 int PRIMARY KEY NOT NULL,
32+
c2 varchar(10) NOT NULL
33+
)
34+
"""
35+
),
2936
)
3037
for x in range(100):
3138
conn.execute(

tests/samples/test_tap_sqlite.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ def test_sqlite_discovery(sqlite_sample_tap: SQLTap):
8080

8181
assert stream.metadata.root.table_key_properties == ["c1"]
8282
assert stream.primary_keys == ["c1"]
83+
assert stream.schema["properties"]["c1"] == {"type": ["integer"]}
84+
assert stream.schema["required"] == ["c1"]
8385

8486

8587
def test_sqlite_input_catalog(sqlite_sample_tap: SQLTap):
@@ -90,7 +92,7 @@ def test_sqlite_input_catalog(sqlite_sample_tap: SQLTap):
9092

9193
for schema in [stream.schema, stream.stream_maps[0].transformed_schema]:
9294
assert len(schema["properties"]) == 2
93-
assert schema["properties"]["c1"] == {"type": ["integer", "null"]}
95+
assert schema["properties"]["c1"] == {"type": ["integer"]}
9496
assert schema["properties"]["c2"] == {"type": ["string", "null"]}
9597
assert stream.name == stream.tap_stream_id == "main-t1"
9698

0 commit comments

Comments
 (0)