codeant-ai-for-open-source[bot] commented on code in PR #41076: URL: https://github.com/apache/superset/pull/41076#discussion_r3500178524
########## scripts/seed_junction_load.py: ########## @@ -0,0 +1,679 @@ +#!/usr/bin/env python3 +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# ---------------------------------------------------------------------- +# Stress-test data generator for the composite-PK migration (sc-105349). +# +# Bulk-inserts synthetic parent rows and many-to-many junction rows for +# the eight association tables that the composite-PK migration touches. +# Useful for measuring migration runtime at varying scales — run this at +# 100K / 1M / 5M / 10M rows and time the migration at each scale to +# verify the O(N log N) extrapolation. +# +# Idempotent: rerunning with the same target is a no-op; rerunning with +# a higher target adds rows up to the new total. Batched bulk INSERTs +# (10K rows per statement) make it fast on Postgres, MySQL, and SQLite. +# +# Usage (inside the Superset container): +# +# docker exec superset-superset-1 \\ +# /app/.venv/bin/python /app/scripts/seed_junction_load.py \\ +# --dashboard-slices 1000000 \\ +# --slice-user 100000 \\ +# --dashboard-user 100000 +# +# Run with no flags for the defaults shown below. Use ``--dry-run`` to +# print the planned inserts without writing anything. +# +# The script connects via Superset's standard ``DATABASE_*`` env vars +# (or ``SUPERSET__SQLALCHEMY_DATABASE_URI`` if set), so it works +# automatically inside the Superset container regardless of which +# metadata DB backend is in use. + +from __future__ import annotations + +import argparse +import logging +import os +import sys +import time +from contextlib import contextmanager +from typing import Iterator +from uuid import uuid4 + +import sqlalchemy as sa +from sqlalchemy.engine import Connection, Engine + +logger = logging.getLogger("seed_junction_load") + +# Bulk INSERT batch size. Larger values = fewer statements but more memory. +BATCH = 10_000 + +# Default per-junction-table target row counts. Tuned to mimic the shape +# of a large multi-team Superset install. Override via CLI flags. +DEFAULTS: dict[str, int] = { + "dashboard_slices": 1_000_000, + "slice_user": 100_000, + "dashboard_user": 100_000, + "dashboard_roles": 10_000, +} + +# (junction_table, fk1_col, fk2_col, parent1_table, parent2_table) +# parents reference id columns; we generate (fk1, fk2) pairs by sampling +# from the parents' existing IDs. +JUNCTIONS: list[tuple[str, str, str, str, str]] = [ + ("dashboard_slices", "dashboard_id", "slice_id", "dashboards", "slices"), + ("slice_user", "user_id", "slice_id", "ab_user", "slices"), + ("dashboard_user", "user_id", "dashboard_id", "ab_user", "dashboards"), + ("dashboard_roles", "dashboard_id", "role_id", "dashboards", "ab_role"), +] + +# Junction tables that originally carried ``UNIQUE(fk1, fk2)`` and therefore +# cannot accept duplicate ``(fk1, fk2)`` pairs even on the pre-migration +# (downgrade) schema. The other JUNCTIONS allow duplicates pre-migration. +# Only ``dashboard_slices`` is listed: the migration's other UNIQUE table +# (``report_schedule_user``) is not in JUNCTIONS — this script doesn't seed +# it — so listing it here would imply coverage that doesn't exist. Add it +# alongside a JUNCTIONS entry if that table ever gets seeded. +JUNCTIONS_WITH_UNIQUE: set[str] = {"dashboard_slices"} + + +# ---------------------------------------------------------------------- +# Connection setup +# ---------------------------------------------------------------------- + + +def build_engine() -> Engine: + """Build a SQLAlchemy engine from Superset env vars.""" + if uri := os.environ.get("SUPERSET__SQLALCHEMY_DATABASE_URI"): + logger.info("Using SUPERSET__SQLALCHEMY_DATABASE_URI from env") + return sa.create_engine(uri) + + try: + dialect = os.environ["DATABASE_DIALECT"] + user = os.environ["DATABASE_USER"] + password = os.environ["DATABASE_PASSWORD"] + host = os.environ["DATABASE_HOST"] + port = os.environ["DATABASE_PORT"] + db = os.environ["DATABASE_DB"] + except KeyError as exc: + sys.exit( + f"Missing env var {exc}; either set DATABASE_DIALECT/USER/PASSWORD/" + f"HOST/PORT/DB or SUPERSET__SQLALCHEMY_DATABASE_URI before running." + ) + + uri = f"{dialect}://{user}:{password}@{host}:{port}/{db}" Review Comment: **Suggestion:** The connection URI is built via raw string interpolation, so special characters in credentials (for example `@`, `:`, `/`, `#`) will be parsed as URL delimiters and break authentication. Build the engine URL with SQLAlchemy URL helpers (or URL-encode each component) to avoid runtime connection failures in real environments. [api mismatch] <details> <summary><b>Severity Level:</b> Major ⚠️</summary> ```mdx - ❌ Seed script may fail to connect to metadata DB. - ⚠️ Large-scale migration load tests become impossible with complex passwords. - ⚠️ Operators may weaken passwords to make script work. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. In `scripts/seed_junction_load.py:101-124`, observe `build_engine()` builds a SQLAlchemy engine by first checking `SUPERSET__SQLALCHEMY_DATABASE_URI` and, if unset, reading `DATABASE_DIALECT`, `DATABASE_USER`, `DATABASE_PASSWORD`, `DATABASE_HOST`, `DATABASE_PORT`, and `DATABASE_DB` from the environment, then interpolating them into `uri = f"{dialect}://{user}:{password}@{host}:{port}/{db}"` at line 120 and passing that to `sa.create_engine(uri)`. 2. In the same file at `run()` (`scripts/seed_junction_load.py:196-211`), verify that this helper is the only engine constructor: `run()` calls `engine = build_engine()` and uses `with engine.begin() as conn:` to open a connection for all seeding work. 3. In `main()` (`scripts/seed_junction_load.py:218-280`), confirm that the CLI entrypoint ultimately calls `run(targets, dry_run=args.dry_run, dirty_duplicates_pct=args.dirty_duplicates_pct)` inside a `time_phase("total")` block, so any invocation of the script without `SUPERSET__SQLALCHEMY_DATABASE_URI` set will exercise the interpolated URI path. 4. Start the Superset container (or run the script in a similar environment) with `SUPERSET__SQLALCHEMY_DATABASE_URI` unset, set `DATABASE_DIALECT` (for example `postgresql`), `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_DB`, and set `DATABASE_PASSWORD` to a value containing URL-reserved characters such as `P@ss#word/1`; then run `/app/.venv/bin/python /app/scripts/seed_junction_load.py` as documented in the header (lines 32-38) and observe that SQLAlchemy’s `create_engine` fails to parse or authenticate against the database because the raw f-string in `build_engine()` treats `@`, `:`, `#`, or `/` as URI delimiters instead of as literal password characters. ``` </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=197aac905320400598312ed198e30ca0&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=197aac905320400598312ed198e30ca0&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** scripts/seed_junction_load.py **Line:** 120:120 **Comment:** *Api Mismatch: The connection URI is built via raw string interpolation, so special characters in credentials (for example `@`, `:`, `/`, `#`) will be parsed as URL delimiters and break authentication. Build the engine URL with SQLAlchemy URL helpers (or URL-encode each component) to avoid runtime connection failures in real environments. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix ``` </details> <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41076&comment_hash=53a2a41f5b67614626d4a9ad8a3de888c93e379f8d4724189efc43a78109c566&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41076&comment_hash=53a2a41f5b67614626d4a9ad8a3de888c93e379f8d4724189efc43a78109c566&reaction=dislike'>👎</a> ########## scripts/seed_junction_load.py: ########## @@ -0,0 +1,679 @@ +#!/usr/bin/env python3 +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# ---------------------------------------------------------------------- +# Stress-test data generator for the composite-PK migration (sc-105349). +# +# Bulk-inserts synthetic parent rows and many-to-many junction rows for +# the eight association tables that the composite-PK migration touches. Review Comment: **Suggestion:** The header comment states the script seeds eight association tables, but this implementation only defines four seeded junction tables in `JUNCTIONS`. Update the comment to match actual behavior so operators do not run incorrect capacity/performance tests based on wrong table coverage. [comment mismatch] <details> <summary><b>Severity Level:</b> Minor 🧹</summary> ```mdx - ⚠️ Header comment misstates junction-table coverage for operators. - ⚠️ Risk of overestimating migration test coverage from script. - ⚠️ Suggestion purely documentation; runtime behavior unaffected. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. Open `scripts/seed_junction_load.py` and look at the module header around lines 20-26: the comment at lines 22-23 states that the script “Bulk-inserts synthetic parent rows and many-to-many junction rows for the eight association tables that the composite-PK migration touches.” 2. Scroll down to the `JUNCTIONS` constant at lines 76-84, which defines the junctions the script actually seeds as a list of four tuples: `dashboard_slices`, `slice_user`, `dashboard_user`, and `dashboard_roles`. 3. Inspect `_seed_all_junctions` at lines 97-105 and verify that it iterates directly over `JUNCTIONS` and calls `seed_junction()` per entry, so only those four association tables are ever seeded by this script. 4. Examine `DEFAULTS` at lines 67-74 and confirm that the CLI only exposes target row counts for the same four junction tables; there is no configuration or code path seeding additional association tables, so the “eight association tables” statement in the header comment is inaccurate documentation rather than reflecting current behavior. ``` </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=28b46c357424420cbdeaed4b9edd0976&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=28b46c357424420cbdeaed4b9edd0976&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** scripts/seed_junction_load.py **Line:** 22:23 **Comment:** *Comment Mismatch: The header comment states the script seeds eight association tables, but this implementation only defines four seeded junction tables in `JUNCTIONS`. Update the comment to match actual behavior so operators do not run incorrect capacity/performance tests based on wrong table coverage. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix ``` </details> <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41076&comment_hash=41f007c3895afd927dc909c013ffbaf984acebbfa03b7a71dcb38e5a40135744&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41076&comment_hash=41f007c3895afd927dc909c013ffbaf984acebbfa03b7a71dcb38e5a40135744&reaction=dislike'>👎</a> ########## scripts/seed_junction_load.py: ########## @@ -0,0 +1,679 @@ +#!/usr/bin/env python3 +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# ---------------------------------------------------------------------- +# Stress-test data generator for the composite-PK migration (sc-105349). +# +# Bulk-inserts synthetic parent rows and many-to-many junction rows for +# the eight association tables that the composite-PK migration touches. +# Useful for measuring migration runtime at varying scales — run this at +# 100K / 1M / 5M / 10M rows and time the migration at each scale to +# verify the O(N log N) extrapolation. +# +# Idempotent: rerunning with the same target is a no-op; rerunning with +# a higher target adds rows up to the new total. Batched bulk INSERTs +# (10K rows per statement) make it fast on Postgres, MySQL, and SQLite. +# +# Usage (inside the Superset container): +# +# docker exec superset-superset-1 \\ +# /app/.venv/bin/python /app/scripts/seed_junction_load.py \\ +# --dashboard-slices 1000000 \\ +# --slice-user 100000 \\ +# --dashboard-user 100000 +# +# Run with no flags for the defaults shown below. Use ``--dry-run`` to +# print the planned inserts without writing anything. +# +# The script connects via Superset's standard ``DATABASE_*`` env vars +# (or ``SUPERSET__SQLALCHEMY_DATABASE_URI`` if set), so it works +# automatically inside the Superset container regardless of which +# metadata DB backend is in use. + +from __future__ import annotations + +import argparse +import logging +import os +import sys +import time +from contextlib import contextmanager +from typing import Iterator +from uuid import uuid4 + +import sqlalchemy as sa +from sqlalchemy.engine import Connection, Engine + +logger = logging.getLogger("seed_junction_load") + +# Bulk INSERT batch size. Larger values = fewer statements but more memory. +BATCH = 10_000 + +# Default per-junction-table target row counts. Tuned to mimic the shape +# of a large multi-team Superset install. Override via CLI flags. +DEFAULTS: dict[str, int] = { + "dashboard_slices": 1_000_000, + "slice_user": 100_000, + "dashboard_user": 100_000, + "dashboard_roles": 10_000, +} + +# (junction_table, fk1_col, fk2_col, parent1_table, parent2_table) +# parents reference id columns; we generate (fk1, fk2) pairs by sampling +# from the parents' existing IDs. +JUNCTIONS: list[tuple[str, str, str, str, str]] = [ + ("dashboard_slices", "dashboard_id", "slice_id", "dashboards", "slices"), + ("slice_user", "user_id", "slice_id", "ab_user", "slices"), + ("dashboard_user", "user_id", "dashboard_id", "ab_user", "dashboards"), + ("dashboard_roles", "dashboard_id", "role_id", "dashboards", "ab_role"), +] + +# Junction tables that originally carried ``UNIQUE(fk1, fk2)`` and therefore +# cannot accept duplicate ``(fk1, fk2)`` pairs even on the pre-migration +# (downgrade) schema. The other JUNCTIONS allow duplicates pre-migration. +# Only ``dashboard_slices`` is listed: the migration's other UNIQUE table +# (``report_schedule_user``) is not in JUNCTIONS — this script doesn't seed +# it — so listing it here would imply coverage that doesn't exist. Add it +# alongside a JUNCTIONS entry if that table ever gets seeded. +JUNCTIONS_WITH_UNIQUE: set[str] = {"dashboard_slices"} + + +# ---------------------------------------------------------------------- +# Connection setup +# ---------------------------------------------------------------------- + + +def build_engine() -> Engine: + """Build a SQLAlchemy engine from Superset env vars.""" + if uri := os.environ.get("SUPERSET__SQLALCHEMY_DATABASE_URI"): + logger.info("Using SUPERSET__SQLALCHEMY_DATABASE_URI from env") + return sa.create_engine(uri) + + try: + dialect = os.environ["DATABASE_DIALECT"] + user = os.environ["DATABASE_USER"] + password = os.environ["DATABASE_PASSWORD"] + host = os.environ["DATABASE_HOST"] + port = os.environ["DATABASE_PORT"] + db = os.environ["DATABASE_DB"] + except KeyError as exc: + sys.exit( + f"Missing env var {exc}; either set DATABASE_DIALECT/USER/PASSWORD/" + f"HOST/PORT/DB or SUPERSET__SQLALCHEMY_DATABASE_URI before running." + ) + + uri = f"{dialect}://{user}:{password}@{host}:{port}/{db}" + logger.info( + "Built URI from DATABASE_* env vars (dialect=%s, host=%s)", dialect, host + ) + return sa.create_engine(uri) + + +# ---------------------------------------------------------------------- +# Helpers +# ---------------------------------------------------------------------- + + +def uuid_value(dialect_name: str) -> bytes | str: + """Return a UUID in the form the active dialect expects. + + MySQL stores UUIDs as ``BINARY(16)`` (16 raw bytes); Postgres has a + native ``UUID`` type that accepts strings; SQLite stores them as + BLOB/TEXT and accepts either. Branching here keeps the seed script + backend-agnostic without depending on Superset's custom column types. + """ + if dialect_name.startswith("mysql"): + return uuid4().bytes + return str(uuid4()) + + +@contextmanager +def time_phase(name: str) -> Iterator[None]: + """Log elapsed wall time for a named phase.""" + start = time.monotonic() + logger.info("[%s] starting", name) + try: + yield + finally: + elapsed = time.monotonic() - start + logger.info("[%s] done in %.2fs", name, elapsed) + + +def count_rows(conn: Connection, table: str) -> int: + return conn.scalar(sa.text(f"SELECT COUNT(*) FROM {table}")) or 0 # noqa: S608 + + +def existing_ids(conn: Connection, table: str, limit: int | None = None) -> list[int]: + sql = f"SELECT id FROM {table} ORDER BY id" # noqa: S608 + if limit is not None: + sql += f" LIMIT {limit}" + return [row[0] for row in conn.execute(sa.text(sql))] + + +# ---------------------------------------------------------------------- +# Parent seeders +# +# Each function ensures the named parent table has at least ``target`` +# rows by inserting synthetic ones with minimal-but-valid columns. +# Returns nothing; subsequent code reads back IDs via ``existing_ids``. +# ---------------------------------------------------------------------- + + +def seed_dashboards(conn: Connection, target: int, dry_run: bool) -> None: + current = count_rows(conn, "dashboards") + if current >= target: + logger.info( + "dashboards: %d rows (target %d) — no insert needed", current, target Review Comment: **Suggestion:** This docstring says two UNIQUE junction tables are skipped, but the seeded set in this script only marks one (`dashboard_slices`) as UNIQUE. Correcting this avoids misleading operators about which seeded tables can receive duplicate rows during dirty-data runs. [comment mismatch] <details> <summary><b>Severity Level:</b> Minor 🧹</summary> ```mdx - ⚠️ Docstring overgeneralizes which UNIQUE tables are skipped. - ⚠️ Potential confusion when reasoning about dirty-data coverage. - ⚠️ Suggestion affects comments only; no runtime misbehavior. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. In `scripts/seed_junction_load.py` around lines 176-182, inspect the docstring for `_inject_dirty_data()`, which states: “Inject duplicate rows on every non-UNIQUE seeded junction. The two tables that originally carried ``UNIQUE(fk1, fk2)`` are skipped because their composite-PK successor (and their pre-migration UNIQUE constraint) both reject duplicate inserts.” 2. At lines 86-93, review the `JUNCTIONS_WITH_UNIQUE` constant, which is the set used to decide which seeded junction tables are skipped during dirty-data injection; it is defined as `{"dashboard_slices"}` with an inline comment explaining that the other UNIQUE table `report_schedule_user` is not included because this script does not seed it. 3. Inspect `_inject_dirty_data()`’s implementation at lines 176-193: it iterates `for junction, fk1, fk2, _, _ in JUNCTIONS`, checks `if junction in JUNCTIONS_WITH_UNIQUE`, and only skips those present in the set; because `JUNCTIONS` (lines 76-84) does not include `report_schedule_user`, the only junction ever skipped in practice is `dashboard_slices`. 4. From these code paths, conclude that the docstring’s phrase “The two tables that originally carried UNIQUE(fk1, fk2) are skipped” describes the broader migration context but does not match the script’s seeded set, where only one such table is actually considered; tightening this wording (for example, explicitly describing that only seeded UNIQUE tables—currently just `dashboard_slices`—are skipped) would align the documentation with the behavior implemented via `JUNCTIONS_WITH_UNIQUE`. ``` </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=98bed46753ef40008f78d644671ef95d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=98bed46753ef40008f78d644671ef95d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** scripts/seed_junction_load.py **Line:** 179:181 **Comment:** *Comment Mismatch: This docstring says two UNIQUE junction tables are skipped, but the seeded set in this script only marks one (`dashboard_slices`) as UNIQUE. Correcting this avoids misleading operators about which seeded tables can receive duplicate rows during dirty-data runs. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix ``` </details> <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41076&comment_hash=9cde39adf3c323075a2bfdab123fd2c05df2d0940c5cd994db27938abff83de4&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41076&comment_hash=9cde39adf3c323075a2bfdab123fd2c05df2d0940c5cd994db27938abff83de4&reaction=dislike'>👎</a> -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
