示例#1
0
def configure_test_db(
    cursor: psycopg2.extras.DictCursor,
) -> typing.Generator[pypgoutput.LogicalReplicationReader, None, None]:
    cursor.execute(f"DROP PUBLICATION IF EXISTS {PUBLICATION_NAME};")
    try:
        cursor.execute(f"SELECT pg_drop_replication_slot('{SLOT_NAME}');")
    except psycopg_errors.UndefinedObject as err:
        logger.warning(f"slot {SLOT_NAME} could not be dropped because it does not exist. {err}")
    cursor.execute(f"CREATE PUBLICATION {PUBLICATION_NAME} FOR ALL TABLES;")
    cursor.execute(f"SELECT * FROM pg_create_logical_replication_slot('{SLOT_NAME}', 'pgoutput');")
    cdc_reader = pypgoutput.LogicalReplicationReader(
        publication_name=PUBLICATION_NAME,
        slot_name=SLOT_NAME,
        host=HOST,
        database=DATABASE_NAME,
        port=PORT,
        user=USER,
        password=PASSWORD,
    )
    # assumes all tables are in publication
    query = """DROP TABLE IF EXISTS public.integration CASCADE;
    CREATE TABLE public.integration (
        id integer primary key,
        json_data jsonb,
        amount numeric(10, 2),
        updated_at timestamptz not null
    );"""
    cursor.execute(query)
    yield cdc_reader
    logger.info("Closing test CDC reader")
    try:
        cdc_reader.stop()
    except Exception as err:
        logger.warning("Test failed but reader is already closed", err)
示例#2
0
def test_003_delete(
    cursor: psycopg2.extras.DictCursor, configure_test_db: typing.Generator[pypgoutput.ChangeEvent, None, None]
) -> None:
    """with default replica identity"""
    cdc_reader = configure_test_db
    cursor.execute("DELETE FROM public.integration WHERE id = 10;")
    message = next(cdc_reader)
    assert message.op == "D"
    assert message.table_schema.db == "test_db"
    assert message.table_schema.schema_name == "public"
    assert message.table_schema.table == "integration"

    assert message.table_schema.column_definitions[0].name == "id"
    assert message.table_schema.column_definitions[0].part_of_pkey == 1
    assert message.table_schema.column_definitions[0].type_name == "integer"
    assert message.table_schema.column_definitions[0].optional is False

    assert message.table_schema.column_definitions[1].name == "json_data"
    assert message.table_schema.column_definitions[1].part_of_pkey == 0
    assert message.table_schema.column_definitions[1].type_name == "jsonb"
    assert message.table_schema.column_definitions[1].optional is True

    assert message.table_schema.column_definitions[2].name == "amount"
    assert message.table_schema.column_definitions[2].part_of_pkey == 0
    assert message.table_schema.column_definitions[2].type_name == "numeric(10,2)"
    assert message.table_schema.column_definitions[2].optional is True

    assert message.table_schema.column_definitions[3].name == "updated_at"
    assert message.table_schema.column_definitions[3].part_of_pkey == 0
    assert message.table_schema.column_definitions[3].type_name == "timestamp with time zone"
    assert message.table_schema.column_definitions[3].optional is False
    assert message.before is not None
    assert message.before["id"] == 10
    assert message.after is None
示例#3
0
 def execute(
     self,
     statement: str,
     vars: Union[list, tuple, dict, None],
     cur: psycopg2.extras.DictCursor,
 ) -> None:
     """Execute a database query."""
     cur.execute(statement, vars)
示例#4
0
 def queryone(
     self,
     statement: str,
     vars: Union[list, tuple, dict, None],
     cur: psycopg2.extras.DictCursor,
 ) -> Optional[psycopg2.extras.DictRow]:
     """Execute a datbase query and return the first result."""
     cur.execute(statement, vars)
     return cur.fetchone()
示例#5
0
def table(cursor: psycopg2.extras.DictCursor) -> None:
    query = """
    DROP TABLE IF EXISTS public.utils_test CASCADE;
    CREATE TABLE public.utils (
        c0 integer primary key,
        c1 timestamptz,
        c2 text not null
    );"""
    cursor.execute(query)
示例#6
0
def test_source_db_handler_column_type(
    cursor: psycopg2.extras.DictCursor, table: typing.Callable[[None], None]
) -> None:
    cursor.execute("SELECT oid FROM pg_type WHERE typname='timestamptz'")
    oid = cursor.fetchone()
    handler = pypgoutput.SourceDBHandler(dsn=DSN)
    handler.connect()
    result = handler.fetch_column_type(type_id=oid["oid"], atttypmod=-1)
    assert result == "timestamp with time zone"
    handler.close()
示例#7
0
def test_002_update(
    cursor: psycopg2.extras.DictCursor, configure_test_db: typing.Generator[pypgoutput.ChangeEvent, None, None]
) -> None:
    """with default replica identity"""
    cdc_reader = configure_test_db
    cursor.execute("UPDATE public.integration SET updated_at = '2020-02-01 00:00:00+00' WHERE id = 10;")
    message = next(cdc_reader)
    assert message.op == "U"
    assert message.table_schema.db == "test_db"
    assert message.table_schema.schema_name == "public"
    assert message.table_schema.table == "integration"

    assert message.table_schema.column_definitions[0].name == "id"
    assert message.table_schema.column_definitions[0].part_of_pkey == 1
    assert message.table_schema.column_definitions[0].type_name == "integer"
    assert message.table_schema.column_definitions[0].optional is False

    assert message.table_schema.column_definitions[1].name == "json_data"
    assert message.table_schema.column_definitions[1].part_of_pkey == 0
    assert message.table_schema.column_definitions[1].type_name == "jsonb"
    assert message.table_schema.column_definitions[1].optional is True

    assert message.table_schema.column_definitions[2].name == "amount"
    assert message.table_schema.column_definitions[2].part_of_pkey == 0
    assert message.table_schema.column_definitions[2].type_name == "numeric(10,2)"
    assert message.table_schema.column_definitions[2].optional is True

    assert message.table_schema.column_definitions[3].name == "updated_at"
    assert message.table_schema.column_definitions[3].part_of_pkey == 0
    assert message.table_schema.column_definitions[3].type_name == "timestamp with time zone"
    assert message.table_schema.column_definitions[3].optional is False
    # TODO check what happens with replica identity full?
    assert message.before is None
    assert message.after is not None
    assert list(message.after.keys()) == ["id", "json_data", "amount", "updated_at"]
    assert message.after["id"] == 10
    assert message.after["json_data"] == {"data": 10}
    assert message.after["amount"] == 10.2
    assert message.after["updated_at"] == datetime.strptime(
        "2020-02-01 00:00:00+00".split("+")[0], "%Y-%m-%d %H:%M:%S"
    ).replace(tzinfo=timezone.utc)
示例#8
0
def test_004_truncate(
    cursor: psycopg2.extras.DictCursor, configure_test_db: typing.Generator[pypgoutput.ChangeEvent, None, None]
) -> None:
    cdc_reader = configure_test_db
    cursor.execute("INSERT INTO public.integration (id, updated_at) VALUES (11, '2020-01-01 00:00:00+00');")
    cursor.execute("SELECT COUNT(*) AS n FROM public.integration;")
    assert cursor.fetchone()["n"] == 1
    insert_msg = next(cdc_reader)
    assert insert_msg.op == "I"
    cursor.execute("TRUNCATE public.integration;")
    message = next(cdc_reader)
    assert message.op == "T"
    assert message.table_schema.db == "test_db"
    assert message.table_schema.schema_name == "public"
    assert message.table_schema.table == "integration"

    assert message.table_schema.column_definitions[0].name == "id"
    assert message.table_schema.column_definitions[0].part_of_pkey == 1
    assert message.table_schema.column_definitions[0].type_name == "integer"
    assert message.table_schema.column_definitions[0].optional is False

    assert message.table_schema.column_definitions[1].name == "json_data"
    assert message.table_schema.column_definitions[1].part_of_pkey == 0
    assert message.table_schema.column_definitions[1].type_name == "jsonb"
    assert message.table_schema.column_definitions[1].optional is True

    assert message.table_schema.column_definitions[2].name == "amount"
    assert message.table_schema.column_definitions[2].part_of_pkey == 0
    assert message.table_schema.column_definitions[2].type_name == "numeric(10,2)"
    assert message.table_schema.column_definitions[2].optional is True

    assert message.table_schema.column_definitions[3].name == "updated_at"
    assert message.table_schema.column_definitions[3].part_of_pkey == 0
    assert message.table_schema.column_definitions[3].type_name == "timestamp with time zone"
    assert message.table_schema.column_definitions[3].optional is False
    assert message.before is None
    assert message.after is None
示例#9
0
def test_001_insert(
    cursor: psycopg2.extras.DictCursor, configure_test_db: typing.Generator[pypgoutput.ChangeEvent, None, None]
) -> None:
    """with default replica identity"""
    cdc_reader = configure_test_db
    cursor.execute(
        """INSERT INTO public.integration (id, json_data, amount, updated_at) VALUES (10, '{"data": 10}', 10.20, '2020-01-01 00:00:00+00');"""
    )
    cursor.execute("SELECT COUNT(*) AS n FROM public.integration;")
    assert cursor.fetchone()["n"] == 1

    message = next(cdc_reader)
    assert message.op == "I"
    assert message.table_schema.db == "test_db"
    assert message.table_schema.schema_name == "public"
    assert message.table_schema.table == "integration"

    assert message.table_schema.column_definitions[0].name == "id"
    assert message.table_schema.column_definitions[0].part_of_pkey == 1
    assert message.table_schema.column_definitions[0].type_name == "integer"
    assert message.table_schema.column_definitions[0].optional is False

    assert message.table_schema.column_definitions[1].name == "json_data"
    assert message.table_schema.column_definitions[1].part_of_pkey == 0
    assert message.table_schema.column_definitions[1].type_name == "jsonb"
    assert message.table_schema.column_definitions[1].optional is True

    assert message.table_schema.column_definitions[2].name == "amount"
    assert message.table_schema.column_definitions[2].part_of_pkey == 0
    assert message.table_schema.column_definitions[2].type_name == "numeric(10,2)"
    assert message.table_schema.column_definitions[2].optional is True

    assert message.table_schema.column_definitions[3].name == "updated_at"
    assert message.table_schema.column_definitions[3].part_of_pkey == 0
    assert message.table_schema.column_definitions[3].type_name == "timestamp with time zone"
    assert message.table_schema.column_definitions[3].optional is False

    query = "SELECT oid, typname FROM pg_type WHERE oid = %s::oid;"
    cursor.execute(query, vars=(message.table_schema.column_definitions[0].type_id,))
    result = cursor.fetchone()
    assert result["oid"] == message.table_schema.column_definitions[0].type_id
    assert result["typname"] == "int4"  # integer

    cursor.execute(query, vars=(message.table_schema.column_definitions[1].type_id,))
    result = cursor.fetchone()
    assert result["oid"] == message.table_schema.column_definitions[1].type_id
    assert result["typname"] == "jsonb"

    cursor.execute(query, vars=(message.table_schema.column_definitions[2].type_id,))
    result = cursor.fetchone()
    assert result["oid"] == message.table_schema.column_definitions[2].type_id
    assert result["typname"] == "numeric"

    cursor.execute(query, vars=(message.table_schema.column_definitions[3].type_id,))
    result = cursor.fetchone()
    assert result["oid"] == message.table_schema.column_definitions[3].type_id
    assert result["typname"] == "timestamptz"

    assert message.before is None
    assert message.after is not None
    assert list(message.after.keys()) == ["id", "json_data", "amount", "updated_at"]
    assert message.after["id"] == 10
    assert message.after["json_data"] == {"data": 10}
    assert message.after["amount"] == 10.2
    assert message.after["updated_at"] == datetime.strptime(
        "2020-01-01 00:00:00+00".split("+")[0], "%Y-%m-%d %H:%M:%S"
    ).replace(tzinfo=timezone.utc)
示例#10
0
def test_000_dummy_test(cursor: psycopg2.extras.DictCursor) -> None:
    """make sure connection/cursor and DB is operational for tests"""
    cursor.execute("SELECT 1 as n;")
    result = cursor.fetchone()
    assert result["n"] == 1