You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/09/22 21:12:05 UTC
(datafusion-sqlparser-rs) branch main updated: Add dialect labeler (#2517)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 0cb62a18c Add dialect labeler (#2517)
0cb62a18c is described below
commit 0cb62a18cba029ec00b27bfdf41b1a00b79266b1
Author: Luca Cappelletti <ca...@gmail.com>
AuthorDate: Tue Sep 22 21:08:05 2026 +0000
Add dialect labeler (#2517)
---
.github/PULL_REQUEST_TEMPLATE.md | 2 +
.github/workflows/labeler.yml | 50 ++++++++++
.github/workflows/labeler/label_dialects.js | 136 ++++++++++++++++++++++++++++
dev/release/rat_exclude_files.txt | 3 +-
4 files changed, 190 insertions(+), 1 deletion(-)
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000..984786743
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,2 @@
+<!-- comma separated, or `none` -->
+Dialects:
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 000000000..f8bf5a73a
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,50 @@
+# 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.
+
+name: labeler
+
+# pull_request_target runs on the base branch, so the script below is never taken from the PR head.
+on:
+ pull_request_target:
+ types: [opened, edited]
+ issues:
+ types: [opened, edited]
+ issue_comment:
+ types: [created]
+
+jobs:
+ label-dialects:
+ name: Label dialects
+ # Bot edits and comments arrive in bursts and never carry a dialect.
+ if: >-
+ github.event.sender.type != 'Bot' &&
+ (github.event_name != 'issue_comment' || contains(github.event.comment.body, 'dialect'))
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ sparse-checkout: .github/workflows/labeler
+ persist-credentials: false
+ - uses: actions/github-script@v9
+ with:
+ script: |
+ const label = require('./.github/workflows/labeler/label_dialects.js');
+ await label({ github, context });
diff --git a/.github/workflows/labeler/label_dialects.js b/.github/workflows/labeler/label_dialects.js
new file mode 100644
index 000000000..ef752ef2d
--- /dev/null
+++ b/.github/workflows/labeler/label_dialects.js
@@ -0,0 +1,136 @@
+// 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.
+
+const DIALECTS = [
+ { label: "BigQuery", stems: ["bigquery"], pattern: /\bbig\s?query\b/i },
+ { label: "ClickHouse", stems: ["clickhouse"], pattern: /\bclick\s?house\b/i },
+ { label: "Databricks", stems: ["databricks"], pattern: /\bdatabricks\b/i },
+ { label: "DuckDB", stems: ["duckdb"], pattern: /\bduck\s?db\b/i },
+ { label: "Hive", stems: ["hive"], pattern: /\bhive\b/i },
+ { label: "MySQL", stems: ["mysql"], pattern: /\b(mysql|maria\s?db)\b/i },
+ { label: "Oracle", stems: ["oracle"], pattern: /\boracle\b/i },
+ { label: "PostgreSQL", stems: ["postgresql", "postgres"], pattern: /\bpostgres/i },
+ { label: "Redshift", stems: ["redshift"], pattern: /\bredshift\b/i },
+ { label: "Snowflake", stems: ["snowflake"], pattern: /\bsnowflake\b/i },
+ { label: "Spark", stems: ["spark"], pattern: /\bspark\b/i },
+ { label: "SQL Server", stems: ["mssql"], pattern: /\b(ms\s?sql|sql server|t-?sql)\b/i },
+ { label: "SQLite", stems: ["sqlite"], pattern: /\bsqlite\b/i },
+ { label: "Teradata", stems: ["teradata"], pattern: /\bteradata\b/i },
+];
+
+const DECLARATION = /^[ \t]*dialects?[ \t]*:[ \t]*(.*?)[ \t]*$/im;
+const MARKER = "<!-- dialect-labeler -->";
+const TRUSTED_ASSOCIATIONS = ["OWNER", "MEMBER", "COLLABORATOR"];
+
+function matchDialects(text) {
+ return DIALECTS.filter((d) => d.pattern.test(text)).map((d) => d.label);
+}
+
+// `undefined` when no `Dialects:` line exists, otherwise the labels it names (possibly none).
+function declaredDialects(text) {
+ const value = DECLARATION.exec(text ?? "")?.[1];
+ return value ? matchDialects(value) : undefined;
+}
+
+function stripMarkup(text) {
+ return text.replace(/<!--[\s\S]*?-->/g, "").replace(/```[\s\S]*?```/g, "");
+}
+
+function resolve({ title, body }) {
+ body ??= "";
+ const labels = new Set(matchDialects(title));
+ const declared = declaredDialects(body);
+ for (const label of declared ?? []) labels.add(label);
+ if (labels.size === 0 && declared === undefined) {
+ for (const label of matchDialects(stripMarkup(body))) labels.add(label);
+ }
+ return { labels: [...labels], answered: labels.size > 0 || declared !== undefined };
+}
+
+function touchedDialects(paths) {
+ const stems = new Set();
+ for (const path of paths) {
+ const stem = /^src\/dialect\/(\w+)\.rs$|^tests\/sqlparser_(\w+)\.rs$/.exec(path);
+ if (stem) stems.add(stem[1] ?? stem[2]);
+ }
+ return DIALECTS.filter((d) => d.stems.some((s) => stems.has(s))).map((d) => d.label);
+}
+
+function isCodeChange(paths) {
+ return paths.some((path) => path.startsWith("src/") || path.startsWith("tests/"));
+}
+
+function askComment(touched) {
+ const lines = [
+ MARKER,
+ "No dialect label was applied because the title does not name a SQL dialect.",
+ "If this change targets one or more dialects, edit the title to `<Dialect>[, <Dialect>]: <description>`, add a `Dialects: <Dialect>[, <Dialect>]` line to the description, or reply with one. Reply `Dialects: none` if the change is dialect-agnostic.",
+ ];
+ if (touched.length) lines.push(`Changed files touch ${touched.join(", ")}.`);
+ return lines.join("\n\n");
+}
+
+function trustedComment(comment, issue) {
+ if (comment.user.type === "Bot") return false;
+ return comment.user.id === issue.user.id || TRUSTED_ASSOCIATIONS.includes(comment.author_association);
+}
+
+async function run({ github, context }) {
+ const { owner, repo } = context.repo;
+ const { payload } = context;
+ const issue = payload.pull_request ?? payload.issue;
+ const issue_number = issue.number;
+ let labels = [];
+ let comment;
+
+ if (context.eventName === "issue_comment") {
+ if (!trustedComment(payload.comment, issue)) return;
+ labels = declaredDialects(payload.comment.body) ?? [];
+ } else {
+ const resolved = resolve(issue);
+ labels = resolved.labels;
+ if (!resolved.answered && payload.pull_request && payload.action === "opened") {
+ const files = await github.paginate(github.rest.pulls.listFiles, {
+ owner,
+ repo,
+ pull_number: issue_number,
+ per_page: 100,
+ });
+ const paths = files.map((f) => f.filename);
+ if (isCodeChange(paths)) comment = askComment(touchedDialects(paths));
+ }
+ }
+
+ const existing = new Set(issue.labels.map((l) => l.name));
+ const missing = labels.filter((l) => !existing.has(l));
+ if (missing.length) {
+ await github.rest.issues.addLabels({ owner, repo, issue_number, labels: missing });
+ }
+
+ if (comment) {
+ const comments = await github.paginate(github.rest.issues.listComments, {
+ owner,
+ repo,
+ issue_number,
+ per_page: 100,
+ });
+ if (comments.some((c) => c.body?.includes(MARKER))) return;
+ await github.rest.issues.createComment({ owner, repo, issue_number, body: comment });
+ }
+}
+
+module.exports = run;
diff --git a/dev/release/rat_exclude_files.txt b/dev/release/rat_exclude_files.txt
index 48f19ab33..54f041eec 100644
--- a/dev/release/rat_exclude_files.txt
+++ b/dev/release/rat_exclude_files.txt
@@ -7,4 +7,5 @@ sqlparser_bench/img/flamegraph.svg
**Cargo.lock
filtered_rat.txt
AGENTS.md
-fuzz/fuzz_seeds/*
\ No newline at end of file
+fuzz/fuzz_seeds/*
+.github/PULL_REQUEST_TEMPLATE.md
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org