From 5ed28c428a4d218c8a4dcb4de01dcabe587e285a Mon Sep 17 00:00:00 2001 From: Sebastian Smiley <46581584+sebastianswms@users.noreply.github.com> Date: Tue, 2 Jan 2024 08:43:09 -0500 Subject: [PATCH] fix: ARRAY(JSONB) parsing and add pytest (#333) Closes #331 --- poetry.lock | 82 +++++++++++++++++++++++++++++++++++++++++- tap_postgres/client.py | 29 +++++++-------- tests/test_core.py | 73 ++++++++++++++++++++++++++++++++++++- 3 files changed, 168 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 632270d..f427e7f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = "*" files = [ @@ -15,6 +16,7 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -33,6 +35,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "backoff" version = "2.2.1" description = "Function decoration for backoff and retry" +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -44,6 +47,7 @@ files = [ name = "backports-datetime-fromisoformat" version = "2.0.1" description = "Backport of Python 3.11's datetime.fromisoformat" +category = "main" optional = false python-versions = ">3" files = [ @@ -84,6 +88,7 @@ files = [ name = "backports-zoneinfo" version = "0.2.1" description = "Backport of the standard library zoneinfo module" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -112,6 +117,7 @@ tzdata = ["tzdata"] name = "bcrypt" version = "4.0.1" description = "Modern password hashing for your software and your servers" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -146,6 +152,7 @@ typecheck = ["mypy"] name = "black" version = "23.12.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -192,6 +199,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cachetools" version = "5.3.2" description = "Extensible memoizing collections and decorators" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -203,6 +211,7 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -214,6 +223,7 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -278,6 +288,7 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -289,6 +300,7 @@ files = [ name = "chardet" version = "5.2.0" description = "Universal encoding detector for Python 3" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -300,6 +312,7 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -399,6 +412,7 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -413,6 +427,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -424,6 +439,7 @@ files = [ name = "cryptography" version = "41.0.6" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -469,6 +485,7 @@ test-randomorder = ["pytest-randomly"] name = "distlib" version = "0.3.7" description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -480,6 +497,7 @@ files = [ name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -494,6 +512,7 @@ test = ["pytest (>=6)"] name = "faker" version = "21.0.0" description = "Faker is a Python package that generates fake data for you." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -509,6 +528,7 @@ typing-extensions = {version = ">=3.10.0.1", markers = "python_version <= \"3.8\ name = "filelock" version = "3.13.1" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -525,6 +545,7 @@ typing = ["typing-extensions (>=4.8)"] name = "flake8" version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -541,6 +562,7 @@ pyflakes = ">=3.1.0,<3.2.0" name = "fs" version = "2.4.16" description = "Python's filesystem abstraction layer" +category = "main" optional = false python-versions = "*" files = [ @@ -560,6 +582,7 @@ scandir = ["scandir (>=1.5,<2.0)"] name = "greenlet" version = "3.0.1" description = "Lightweight in-process concurrent programming" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -630,6 +653,7 @@ test = ["objgraph", "psutil"] name = "identify" version = "2.5.31" description = "File identification library for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -644,6 +668,7 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -655,6 +680,7 @@ files = [ name = "importlib-metadata" version = "6.11.0" description = "Read metadata from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -674,6 +700,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.1.0" description = "Read resources from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -692,6 +719,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "inflection" version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -703,6 +731,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -714,6 +743,7 @@ files = [ name = "isort" version = "5.13.2" description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -728,6 +758,7 @@ colors = ["colorama (>=0.4.6)"] name = "joblib" version = "1.3.2" description = "Lightweight pipelining with Python functions" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -739,6 +770,7 @@ files = [ name = "jsonpath-ng" version = "1.6.0" description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." +category = "main" optional = false python-versions = "*" files = [ @@ -753,6 +785,7 @@ ply = "*" name = "jsonschema" version = "4.19.2" description = "An implementation of JSON Schema validation for Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -776,6 +809,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "jsonschema-specifications" version = "2023.7.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -791,6 +825,7 @@ referencing = ">=0.28.0" name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -802,6 +837,7 @@ files = [ name = "memoization" version = "0.4.0" description = "A powerful caching library for Python, with TTL support and multiple algorithm options. (https://github.com/lonelyenvoy/python-memoization)" +category = "main" optional = false python-versions = ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" files = [ @@ -812,6 +848,7 @@ files = [ name = "mypy" version = "1.8.0" description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -859,6 +896,7 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -870,6 +908,7 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -884,6 +923,7 @@ setuptools = "*" name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -895,6 +935,7 @@ files = [ name = "paramiko" version = "3.3.1" description = "SSH2 protocol library" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -916,6 +957,7 @@ invoke = ["invoke (>=2.0)"] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -927,6 +969,7 @@ files = [ name = "pendulum" version = "3.0.0" description = "Python datetimes made easy" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1028,6 +1071,7 @@ test = ["time-machine (>=2.6.0)"] name = "pkgutil-resolve-name" version = "1.3.10" description = "Resolve a name to an object." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1039,6 +1083,7 @@ files = [ name = "platformdirs" version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1054,6 +1099,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1069,6 +1115,7 @@ testing = ["pytest", "pytest-benchmark"] name = "ply" version = "3.11" description = "Python Lex & Yacc" +category = "main" optional = false python-versions = "*" files = [ @@ -1080,6 +1127,7 @@ files = [ name = "pre-commit" version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1098,6 +1146,7 @@ virtualenv = ">=20.10.0" name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1179,6 +1228,7 @@ files = [ name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1190,6 +1240,7 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1201,6 +1252,7 @@ files = [ name = "pydocstyle" version = "6.3.0" description = "Python docstring style checker" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1218,6 +1270,7 @@ toml = ["tomli (>=1.2.3)"] name = "pyflakes" version = "3.1.0" description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1229,6 +1282,7 @@ files = [ name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1246,6 +1300,7 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1272,6 +1327,7 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] name = "pyproject-api" version = "1.6.1" description = "API to interact with the python pyproject.toml based projects" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1291,6 +1347,7 @@ testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytes name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1313,6 +1370,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-durations" version = "1.2.0" description = "Pytest plugin reporting fixtures and test functions execution time." +category = "dev" optional = false python-versions = ">=3.6.2" files = [ @@ -1327,6 +1385,7 @@ pytest = ">=4.6" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1341,6 +1400,7 @@ six = ">=1.5" name = "python-dotenv" version = "0.21.1" description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1355,6 +1415,7 @@ cli = ["click (>=5.0)"] name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -1366,6 +1427,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1425,6 +1487,7 @@ files = [ name = "referencing" version = "0.30.2" description = "JSON Referencing + Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1440,6 +1503,7 @@ rpds-py = ">=0.7.0" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1461,6 +1525,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rpds-py" version = "0.12.0" description = "Python bindings to Rust's persistent data structures (rpds)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1569,6 +1634,7 @@ files = [ name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1585,6 +1651,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "simpleeval" version = "0.9.13" description = "A simple, safe single expression evaluator library." +category = "main" optional = false python-versions = "*" files = [ @@ -1596,6 +1663,7 @@ files = [ name = "simplejson" version = "3.19.2" description = "Simple, fast, extensible JSON encoder/decoder for Python" +category = "main" optional = false python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1703,6 +1771,7 @@ files = [ name = "singer-sdk" version = "0.34.1" description = "A framework for building Singer taps" +category = "main" optional = false python-versions = ">=3.7.1" files = [ @@ -1749,6 +1818,7 @@ testing = ["pytest (>=7.2.1)", "pytest-durations (>=1.2.0)"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1760,6 +1830,7 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" files = [ @@ -1771,6 +1842,7 @@ files = [ name = "sqlalchemy" version = "1.4.50" description = "Database Abstraction Library" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -1829,6 +1901,7 @@ sqlcipher = ["sqlcipher3-binary"] name = "sshtunnel" version = "0.4.0" description = "Pure python SSH tunnels" +category = "main" optional = false python-versions = "*" files = [ @@ -1848,6 +1921,7 @@ test = ["tox (>=1.8.1)"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1859,6 +1933,7 @@ files = [ name = "tox" version = "4.11.4" description = "tox is a generic virtualenv management and test command line tool" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1886,6 +1961,7 @@ testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pol name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1897,6 +1973,7 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -1908,6 +1985,7 @@ files = [ name = "urllib3" version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -1924,6 +2002,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "virtualenv" version = "20.24.6" description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1944,6 +2023,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.8" files = [ diff --git a/tap_postgres/client.py b/tap_postgres/client.py index c8d1432..f09ea74 100644 --- a/tap_postgres/client.py +++ b/tap_postgres/client.py @@ -157,15 +157,6 @@ def to_jsonschema_type( elif isinstance(sql_type, sqlalchemy.types.TypeEngine): type_name = type(sql_type).__name__ - # Should theoretically be th.AnyType().type_dict but that causes errors down - # the line with an error like: - # singer_sdk.helpers._typing.EmptySchemaTypeError: Could not detect type from - # empty type_dict. Did you forget to define a property in the stream schema? - if type_name is not None and type_name in ("JSONB", "JSON"): - return { - "type": ["string", "number", "integer", "array", "object", "boolean"] - } - if ( type_name is not None and isinstance(sql_type, sqlalchemy.dialects.postgresql.ARRAY) @@ -204,6 +195,13 @@ def sdk_typing_object( A compatible JSON Schema type definition. """ + # NOTE: This is an ordered mapping, with earlier mappings taking precedence. If + # the SQL-provided type contains the type name on the left, the mapping will + # return the respective singer type. + # NOTE: jsonb and json should theoretically be th.AnyType().type_dict but that + # causes problems down the line with an error like: + # singer_sdk.helpers._typing.EmptySchemaTypeError: Could not detect type from + # empty type_dict. Did you forget to define a property in the stream schema? sqltype_lookup: dict[ str, th.DateTimeType @@ -213,9 +211,12 @@ def sdk_typing_object( | th.StringType | th.BooleanType, ] = { - # NOTE: This is an ordered mapping, with earlier mappings taking - # precedence. If the SQL-provided type contains the type name on - # the left, the mapping will return the respective singer type. + "jsonb": th.CustomType( + {"type": ["string", "number", "integer", "array", "object", "boolean"]} + ), + "json": th.CustomType( + {"type": ["string", "number", "integer", "array", "object", "boolean"]} + ), "timestamp": th.DateTimeType(), "datetime": th.DateTimeType(), "date": th.DateType(), @@ -378,7 +379,7 @@ def _increment_stream_state( f"stream(replication method={self.replication_method})" ) raise ValueError(msg) - treat_as_sorted = self.is_sorted + treat_as_sorted = self.is_sorted() if not treat_as_sorted and self.state_partitioning_keys is not None: # Streams with custom state partitioning are not resumable. treat_as_sorted = False @@ -457,7 +458,7 @@ def consume(self, message) -> dict | None: "A message payload of %s could not be converted to JSON", message.payload, ) - return + return {} row = {} diff --git a/tests/test_core.py b/tests/test_core.py index cf47d25..1bb8614 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -10,7 +10,15 @@ from singer_sdk.testing import get_tap_test_class, suites from singer_sdk.testing.runners import TapTestRunner from sqlalchemy import Column, DateTime, Integer, MetaData, Numeric, String, Table, text -from sqlalchemy.dialects.postgresql import BIGINT, DATE, JSON, JSONB, TIME, TIMESTAMP +from sqlalchemy.dialects.postgresql import ( + BIGINT, + DATE, + JSON, + JSONB, + TIME, + TIMESTAMP, + ARRAY, +) from tests.settings import DB_SCHEMA_NAME, DB_SQLALCHEMY_URL from tests.test_replication_key import TABLE_NAME, TapTestReplicationKey from tests.test_selected_columns_only import ( @@ -269,6 +277,69 @@ def test_jsonb_json(): assert test_runner.records[altered_table_name][i] == rows[i] +def test_jsonb_array(): + """ARRAYS of JSONB objects had incorrect schemas. See issue #331.""" + table_name = "test_jsonb_array" + engine = sqlalchemy.create_engine(SAMPLE_CONFIG["sqlalchemy_url"], future=True) + + metadata_obj = MetaData() + table = Table( + table_name, + metadata_obj, + Column("column_jsonb_array", ARRAY(JSONB)), + ) + + rows = [ + {"column_jsonb_array": [{"foo": "bar"}]}, + {"column_jsonb_array": [{"foo": 42}]}, + {"column_jsonb_array": [{"foo": 1.414}]}, + {"column_jsonb_array": [{"abc": "def"}, {"ghi": "jkl"}, {"mno": "pqr"}]}, + ] + + with engine.begin() as conn: + table.drop(conn, checkfirst=True) + metadata_obj.create_all(conn) + insert = table.insert().values(rows) + conn.execute(insert) + tap = TapPostgres(config=SAMPLE_CONFIG) + tap_catalog = json.loads(tap.catalog_json_text) + altered_table_name = f"{DB_SCHEMA_NAME}-{table_name}" + for stream in tap_catalog["streams"]: + if stream.get("stream") and altered_table_name not in stream["stream"]: + for metadata in stream["metadata"]: + metadata["metadata"]["selected"] = False + else: + for metadata in stream["metadata"]: + metadata["metadata"]["selected"] = True + if metadata["breadcrumb"] == []: + metadata["metadata"]["replication-method"] = "FULL_TABLE" + + test_runner = PostgresTestRunner( + tap_class=TapPostgres, config=SAMPLE_CONFIG, catalog=tap_catalog + ) + test_runner.sync_all() + for schema_message in test_runner.schema_messages: + if ( + "stream" in schema_message + and schema_message["stream"] == altered_table_name + ): + assert schema_message["schema"]["properties"]["column_jsonb_array"] == { + "items": { + "type": [ + "string", + "number", + "integer", + "array", + "object", + "boolean", + ] + }, + "type": ["array", "null"], + } + for i in range(len(rows)): + assert test_runner.records[altered_table_name][i] == rows[i] + + def test_decimal(): """Schema was wrong for Decimal objects. Check they are correctly selected.""" table_name = "test_decimal"