Replace node-sql-parser with sqlparser-ts - #61111

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs
Feb 16, 2026
Merged

Replace node-sql-parser with sqlparser-ts#61111
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs

Conversation

@guan404ming

@guan404mingguan404ming commented Jan 27, 2026

Copy link
Copy Markdown
Member

TL;DR

Replaced node-sql-parser with sqlparser-ts
This new one is 14x smaller, 5x faster and is a library I actively maintain.

Why

  • node-sql-parser adds a massive ~88 MB footprint to the package just to handle SQL string detection.
  • The existing implementation lacked the broad SQL dialect coverage needed for diverse user queries.

How

  • Switched to sqlparser-ts, a library I develop and maintain, which brings the power of Rust’s sqlparser from Apache DataFusion to JS/TS via WebAssembly. It offers superior performance with zero JS/TS dependencies.
  • Refactored detectLanguage.ts to use sqlparser-ts's validate() function instead of node-sql-parser's parse() for SQL detection
  • Added comprehensive unit tests for detectLanguage covering JSON, SQL, Bash, YAML and plain text detection

Benchmark

This optimization slashed the package size by 93% and boosted validation speed by 5.4x, while delivering superior SQL compatibility and maintaining stable load times compare to pure js implementation.

MetricBeforeAfterChange
Package size87 MB6.4 MB-93%
JS bundle7,846 KB5,248 KB-33%
Validate speed70.6 μs/call12.86 μs/call5.48x faster
Build time~44s~27s-39%
Load time~459.6ms~463.9ms+0.94%
Compatibilityfail in complex SQL in SQLitenear fully support and update actively-

More detailed benchmark between sqlparser-ts and node-sql-parser --> https://github.com/guan404ming/sqlparser-ts/tree/main/benchmark

Bundle Analyzer Treemap

before

image

after

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code with Opus 4.5 following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 27, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 3 times, most recently from fd75111 to 63e11deCompareJanuary 27, 2026 11:01
@guan404ming

guan404ming commented Jan 27, 2026

Copy link
Copy Markdown
MemberAuthor

Load Time Benchmark

Result

Metricmain (node-sql-parser)this pr (sqlparser-ts)
Avg Page Load459.6ms463.9ms
Median Page Load446.3ms450.2ms
Min382.0ms359.1ms
Max549.0ms643.2ms
Avg DOM Content Loaded456.4ms460.3ms
Individual Values549, 439, 382, 390, 433, 438, 453, 519, 469, 524443, 452, 368, 359, 406, 449, 480, 574, 643, 467

No measurable performance difference. Both branches are within noise margin (~4ms difference in median)


Config

  • Browser: Playwright Chromium (Default)
  • Runs: 10 (with 1 warmup run excluded)
  • Wait Condition: networkidle
  • test page: TI render template tab with sql inside
image

Playwright Script

async(page)=>{consturl='http://localhost:28080/dags/long_log_dag/runs/manual__2026-01-27T09:58:47.311305+00:00/tasks/execute_query/rendered_templates';construns=10;constresults=[];// Warm upawaitpage.goto(url,{waitUntil: 'networkidle'});for(leti=0;i<runs;i++){awaitpage.goto(url,{waitUntil: 'networkidle'});consttiming=awaitpage.evaluate(()=>{constnav=performance.getEntriesByType('navigation')[0];return{pageLoad: nav.loadEventEnd-nav.startTime,domContentLoaded: nav.domContentLoadedEventEnd-nav.startTime,};});results.push(timing);}constavg=(arr)=>(arr.reduce((a,b)=>a+b,0)/arr.length).toFixed(1);constmedian=(arr)=>{consts=[...arr].sort((a,b)=>a-b);constm=Math.floor(s.length/2);returns.length%2 ? s[m] : (s[m-1]+s[m])/2;};constp=results.map(r=>r.pageLoad);constd=results.map(r=>r.domContentLoaded);return{
runs,pageLoad: {avg: avg(p),median: median(p).toFixed(1),min: Math.min(...p).toFixed(1),max: Math.max(...p).toFixed(1),values: p.map(v=>v.toFixed(1))},domContentLoaded: {avg: avg(d),median: median(d).toFixed(1),min: Math.min(...d).toFixed(1),max: Math.max(...d).toFixed(1)},};}

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guan404ming for this proposal, indeed such optimization would be a good addition I believe.

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Do you know if there are more popular alternatives? If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package?

Just exploring our options here.

@guan404ming

guan404ming commented Jan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback! Totally valid concern regarding the new dependency.

Do you know if there are more popular alternatives?

Here is some alternatives but most of them are either support specific dialects or lack of maintenance. After this investigation, I then decide to start to build by myself.

  • node-sql-parser --> current pkg, large and have some compat issue for different dialects
  • pgsql-parser --> only support pg
  • dt-sql-parser --> build for flink / spark
  • js-sql-parser --> outdated, last release is 2 years ago
  • antlr4ts --> only provide engine. we need to built from scratch, which is high maintenance needed

If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package

I've also consider this option before. But since the core logic of datafusion-sqlparser-rs is written on Rust, embedding it directly would force Airflow to introduce a Rust toolchain and WASM build steps (like wasm-pack) into the CI/CD pipeline, which increases the barrier and lots code. So packaging it separately to abstracts this complexity away, keeping the Airflow focus on Python/JS is kind of better solution if we want to take speed and size advantage of the datafusion-sqlparser-rs

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Technically, I think the package is robust. I’ve ported the test suite directly from the upstream Rust repo to typescript to ensure the behavior matches the source of truth 1:1. On top of that, I’ve included comprehensive unit tests in this PR to guarantee it integrates with the Airflow UI without causing regressions.

I completely understand the hesitation with relying on a fresh personal repo. I will continue promoting this package to other projects to build a stronger track record. My ideal target is try to donate and transfer this package to the Apache Datafusion upstream. This would make it into an community-governed artifact, removing the dependency on a single individual. However I hope we can still keep this conversation open. I’d really appreciate it if you could give the pkg a spin to see the value. Thanks!

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 5 times, most recently from 206a3fb to 49d1485CompareFebruary 7, 2026 12:25
@pierrejeambrun

pierrejeambrun commented Feb 10, 2026

Copy link
Copy Markdown
Member

I checked with Jarek and we think it's a great opportunity to make this solution more known and used across the community.

We already trusted similar packages in the past such as https://github.com/kaxil/boring-cyborg from kaxil

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit, looking good to me.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Comment threadairflow-core/src/airflow/ui/src/components/SqlParserProvider.tsx Outdated
@pierrejeambrunpierrejeambrun added this to the Airflow 3.2.0 milestone Feb 10, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from ac0bb70 to 578e1faCompareFebruary 11, 2026 02:25
@guan404ming

guan404ming commented Feb 11, 2026

Copy link
Copy Markdown
MemberAuthor

I'm glad to hear that! Thank you and Jarek for the trust. I will keep maintaining this pkg and ensuring it continues to provide value to the community and also be reliable.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Sure, just rebased.

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from 73c062f to 57a036fCompareFebruary 12, 2026 13:57
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch from 57a036f to 4699c7cCompareFebruary 15, 2026 07:03
@pierrejeambrunpierrejeambrun mentioned this pull request Feb 16, 2026
@pierrejeambrun
pierrejeambrun merged commit c5206f5 into apache:mainFeb 16, 2026
78 checks passed
OscarLigthart pushed a commit to OscarLigthart/airflow that referenced this pull request Feb 17, 2026
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@pierrejeambrun
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Replace node-sql-parser with sqlparser-ts - #61111

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs
Feb 16, 2026
Merged

Replace node-sql-parser with sqlparser-ts#61111
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs

Conversation

@guan404ming

@guan404mingguan404ming commented Jan 27, 2026

Copy link
Copy Markdown
Member

TL;DR

Replaced node-sql-parser with sqlparser-ts
This new one is 14x smaller, 5x faster and is a library I actively maintain.

Why

  • node-sql-parser adds a massive ~88 MB footprint to the package just to handle SQL string detection.
  • The existing implementation lacked the broad SQL dialect coverage needed for diverse user queries.

How

  • Switched to sqlparser-ts, a library I develop and maintain, which brings the power of Rust’s sqlparser from Apache DataFusion to JS/TS via WebAssembly. It offers superior performance with zero JS/TS dependencies.
  • Refactored detectLanguage.ts to use sqlparser-ts's validate() function instead of node-sql-parser's parse() for SQL detection
  • Added comprehensive unit tests for detectLanguage covering JSON, SQL, Bash, YAML and plain text detection

Benchmark

This optimization slashed the package size by 93% and boosted validation speed by 5.4x, while delivering superior SQL compatibility and maintaining stable load times compare to pure js implementation.

MetricBeforeAfterChange
Package size87 MB6.4 MB-93%
JS bundle7,846 KB5,248 KB-33%
Validate speed70.6 μs/call12.86 μs/call5.48x faster
Build time~44s~27s-39%
Load time~459.6ms~463.9ms+0.94%
Compatibilityfail in complex SQL in SQLitenear fully support and update actively-

More detailed benchmark between sqlparser-ts and node-sql-parser --> https://github.com/guan404ming/sqlparser-ts/tree/main/benchmark

Bundle Analyzer Treemap

before

image

after

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code with Opus 4.5 following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 27, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 3 times, most recently from fd75111 to 63e11deCompareJanuary 27, 2026 11:01
@guan404ming

guan404ming commented Jan 27, 2026

Copy link
Copy Markdown
MemberAuthor

Load Time Benchmark

Result

Metricmain (node-sql-parser)this pr (sqlparser-ts)
Avg Page Load459.6ms463.9ms
Median Page Load446.3ms450.2ms
Min382.0ms359.1ms
Max549.0ms643.2ms
Avg DOM Content Loaded456.4ms460.3ms
Individual Values549, 439, 382, 390, 433, 438, 453, 519, 469, 524443, 452, 368, 359, 406, 449, 480, 574, 643, 467

No measurable performance difference. Both branches are within noise margin (~4ms difference in median)


Config

  • Browser: Playwright Chromium (Default)
  • Runs: 10 (with 1 warmup run excluded)
  • Wait Condition: networkidle
  • test page: TI render template tab with sql inside
image

Playwright Script

async(page)=>{consturl='http://localhost:28080/dags/long_log_dag/runs/manual__2026-01-27T09:58:47.311305+00:00/tasks/execute_query/rendered_templates';construns=10;constresults=[];// Warm upawaitpage.goto(url,{waitUntil: 'networkidle'});for(leti=0;i<runs;i++){awaitpage.goto(url,{waitUntil: 'networkidle'});consttiming=awaitpage.evaluate(()=>{constnav=performance.getEntriesByType('navigation')[0];return{pageLoad: nav.loadEventEnd-nav.startTime,domContentLoaded: nav.domContentLoadedEventEnd-nav.startTime,};});results.push(timing);}constavg=(arr)=>(arr.reduce((a,b)=>a+b,0)/arr.length).toFixed(1);constmedian=(arr)=>{consts=[...arr].sort((a,b)=>a-b);constm=Math.floor(s.length/2);returns.length%2 ? s[m] : (s[m-1]+s[m])/2;};constp=results.map(r=>r.pageLoad);constd=results.map(r=>r.domContentLoaded);return{
runs,pageLoad: {avg: avg(p),median: median(p).toFixed(1),min: Math.min(...p).toFixed(1),max: Math.max(...p).toFixed(1),values: p.map(v=>v.toFixed(1))},domContentLoaded: {avg: avg(d),median: median(d).toFixed(1),min: Math.min(...d).toFixed(1),max: Math.max(...d).toFixed(1)},};}

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guan404ming for this proposal, indeed such optimization would be a good addition I believe.

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Do you know if there are more popular alternatives? If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package?

Just exploring our options here.

@guan404ming

guan404ming commented Jan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback! Totally valid concern regarding the new dependency.

Do you know if there are more popular alternatives?

Here is some alternatives but most of them are either support specific dialects or lack of maintenance. After this investigation, I then decide to start to build by myself.

  • node-sql-parser --> current pkg, large and have some compat issue for different dialects
  • pgsql-parser --> only support pg
  • dt-sql-parser --> build for flink / spark
  • js-sql-parser --> outdated, last release is 2 years ago
  • antlr4ts --> only provide engine. we need to built from scratch, which is high maintenance needed

If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package

I've also consider this option before. But since the core logic of datafusion-sqlparser-rs is written on Rust, embedding it directly would force Airflow to introduce a Rust toolchain and WASM build steps (like wasm-pack) into the CI/CD pipeline, which increases the barrier and lots code. So packaging it separately to abstracts this complexity away, keeping the Airflow focus on Python/JS is kind of better solution if we want to take speed and size advantage of the datafusion-sqlparser-rs

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Technically, I think the package is robust. I’ve ported the test suite directly from the upstream Rust repo to typescript to ensure the behavior matches the source of truth 1:1. On top of that, I’ve included comprehensive unit tests in this PR to guarantee it integrates with the Airflow UI without causing regressions.

I completely understand the hesitation with relying on a fresh personal repo. I will continue promoting this package to other projects to build a stronger track record. My ideal target is try to donate and transfer this package to the Apache Datafusion upstream. This would make it into an community-governed artifact, removing the dependency on a single individual. However I hope we can still keep this conversation open. I’d really appreciate it if you could give the pkg a spin to see the value. Thanks!

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 5 times, most recently from 206a3fb to 49d1485CompareFebruary 7, 2026 12:25
@pierrejeambrun

pierrejeambrun commented Feb 10, 2026

Copy link
Copy Markdown
Member

I checked with Jarek and we think it's a great opportunity to make this solution more known and used across the community.

We already trusted similar packages in the past such as https://github.com/kaxil/boring-cyborg from kaxil

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit, looking good to me.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Comment threadairflow-core/src/airflow/ui/src/components/SqlParserProvider.tsx Outdated
@pierrejeambrunpierrejeambrun added this to the Airflow 3.2.0 milestone Feb 10, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from ac0bb70 to 578e1faCompareFebruary 11, 2026 02:25
@guan404ming

guan404ming commented Feb 11, 2026

Copy link
Copy Markdown
MemberAuthor

I'm glad to hear that! Thank you and Jarek for the trust. I will keep maintaining this pkg and ensuring it continues to provide value to the community and also be reliable.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Sure, just rebased.

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from 73c062f to 57a036fCompareFebruary 12, 2026 13:57
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch from 57a036f to 4699c7cCompareFebruary 15, 2026 07:03
@pierrejeambrunpierrejeambrun mentioned this pull request Feb 16, 2026
@pierrejeambrun
pierrejeambrun merged commit c5206f5 into apache:mainFeb 16, 2026
78 checks passed
OscarLigthart pushed a commit to OscarLigthart/airflow that referenced this pull request Feb 17, 2026
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@pierrejeambrun
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Replace node-sql-parser with sqlparser-ts - #61111

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs
Feb 16, 2026
Merged

Replace node-sql-parser with sqlparser-ts#61111
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs

Conversation

@guan404ming

@guan404mingguan404ming commented Jan 27, 2026

Copy link
Copy Markdown
Member

TL;DR

Replaced node-sql-parser with sqlparser-ts
This new one is 14x smaller, 5x faster and is a library I actively maintain.

Why

  • node-sql-parser adds a massive ~88 MB footprint to the package just to handle SQL string detection.
  • The existing implementation lacked the broad SQL dialect coverage needed for diverse user queries.

How

  • Switched to sqlparser-ts, a library I develop and maintain, which brings the power of Rust’s sqlparser from Apache DataFusion to JS/TS via WebAssembly. It offers superior performance with zero JS/TS dependencies.
  • Refactored detectLanguage.ts to use sqlparser-ts's validate() function instead of node-sql-parser's parse() for SQL detection
  • Added comprehensive unit tests for detectLanguage covering JSON, SQL, Bash, YAML and plain text detection

Benchmark

This optimization slashed the package size by 93% and boosted validation speed by 5.4x, while delivering superior SQL compatibility and maintaining stable load times compare to pure js implementation.

MetricBeforeAfterChange
Package size87 MB6.4 MB-93%
JS bundle7,846 KB5,248 KB-33%
Validate speed70.6 μs/call12.86 μs/call5.48x faster
Build time~44s~27s-39%
Load time~459.6ms~463.9ms+0.94%
Compatibilityfail in complex SQL in SQLitenear fully support and update actively-

More detailed benchmark between sqlparser-ts and node-sql-parser --> https://github.com/guan404ming/sqlparser-ts/tree/main/benchmark

Bundle Analyzer Treemap

before

image

after

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code with Opus 4.5 following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 27, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 3 times, most recently from fd75111 to 63e11deCompareJanuary 27, 2026 11:01
@guan404ming

guan404ming commented Jan 27, 2026

Copy link
Copy Markdown
MemberAuthor

Load Time Benchmark

Result

Metricmain (node-sql-parser)this pr (sqlparser-ts)
Avg Page Load459.6ms463.9ms
Median Page Load446.3ms450.2ms
Min382.0ms359.1ms
Max549.0ms643.2ms
Avg DOM Content Loaded456.4ms460.3ms
Individual Values549, 439, 382, 390, 433, 438, 453, 519, 469, 524443, 452, 368, 359, 406, 449, 480, 574, 643, 467

No measurable performance difference. Both branches are within noise margin (~4ms difference in median)


Config

  • Browser: Playwright Chromium (Default)
  • Runs: 10 (with 1 warmup run excluded)
  • Wait Condition: networkidle
  • test page: TI render template tab with sql inside
image

Playwright Script

async(page)=>{consturl='http://localhost:28080/dags/long_log_dag/runs/manual__2026-01-27T09:58:47.311305+00:00/tasks/execute_query/rendered_templates';construns=10;constresults=[];// Warm upawaitpage.goto(url,{waitUntil: 'networkidle'});for(leti=0;i<runs;i++){awaitpage.goto(url,{waitUntil: 'networkidle'});consttiming=awaitpage.evaluate(()=>{constnav=performance.getEntriesByType('navigation')[0];return{pageLoad: nav.loadEventEnd-nav.startTime,domContentLoaded: nav.domContentLoadedEventEnd-nav.startTime,};});results.push(timing);}constavg=(arr)=>(arr.reduce((a,b)=>a+b,0)/arr.length).toFixed(1);constmedian=(arr)=>{consts=[...arr].sort((a,b)=>a-b);constm=Math.floor(s.length/2);returns.length%2 ? s[m] : (s[m-1]+s[m])/2;};constp=results.map(r=>r.pageLoad);constd=results.map(r=>r.domContentLoaded);return{
runs,pageLoad: {avg: avg(p),median: median(p).toFixed(1),min: Math.min(...p).toFixed(1),max: Math.max(...p).toFixed(1),values: p.map(v=>v.toFixed(1))},domContentLoaded: {avg: avg(d),median: median(d).toFixed(1),min: Math.min(...d).toFixed(1),max: Math.max(...d).toFixed(1)},};}

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guan404ming for this proposal, indeed such optimization would be a good addition I believe.

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Do you know if there are more popular alternatives? If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package?

Just exploring our options here.

@guan404ming

guan404ming commented Jan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback! Totally valid concern regarding the new dependency.

Do you know if there are more popular alternatives?

Here is some alternatives but most of them are either support specific dialects or lack of maintenance. After this investigation, I then decide to start to build by myself.

  • node-sql-parser --> current pkg, large and have some compat issue for different dialects
  • pgsql-parser --> only support pg
  • dt-sql-parser --> build for flink / spark
  • js-sql-parser --> outdated, last release is 2 years ago
  • antlr4ts --> only provide engine. we need to built from scratch, which is high maintenance needed

If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package

I've also consider this option before. But since the core logic of datafusion-sqlparser-rs is written on Rust, embedding it directly would force Airflow to introduce a Rust toolchain and WASM build steps (like wasm-pack) into the CI/CD pipeline, which increases the barrier and lots code. So packaging it separately to abstracts this complexity away, keeping the Airflow focus on Python/JS is kind of better solution if we want to take speed and size advantage of the datafusion-sqlparser-rs

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Technically, I think the package is robust. I’ve ported the test suite directly from the upstream Rust repo to typescript to ensure the behavior matches the source of truth 1:1. On top of that, I’ve included comprehensive unit tests in this PR to guarantee it integrates with the Airflow UI without causing regressions.

I completely understand the hesitation with relying on a fresh personal repo. I will continue promoting this package to other projects to build a stronger track record. My ideal target is try to donate and transfer this package to the Apache Datafusion upstream. This would make it into an community-governed artifact, removing the dependency on a single individual. However I hope we can still keep this conversation open. I’d really appreciate it if you could give the pkg a spin to see the value. Thanks!

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 5 times, most recently from 206a3fb to 49d1485CompareFebruary 7, 2026 12:25
@pierrejeambrun

pierrejeambrun commented Feb 10, 2026

Copy link
Copy Markdown
Member

I checked with Jarek and we think it's a great opportunity to make this solution more known and used across the community.

We already trusted similar packages in the past such as https://github.com/kaxil/boring-cyborg from kaxil

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit, looking good to me.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Comment threadairflow-core/src/airflow/ui/src/components/SqlParserProvider.tsx Outdated
@pierrejeambrunpierrejeambrun added this to the Airflow 3.2.0 milestone Feb 10, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from ac0bb70 to 578e1faCompareFebruary 11, 2026 02:25
@guan404ming

guan404ming commented Feb 11, 2026

Copy link
Copy Markdown
MemberAuthor

I'm glad to hear that! Thank you and Jarek for the trust. I will keep maintaining this pkg and ensuring it continues to provide value to the community and also be reliable.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Sure, just rebased.

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from 73c062f to 57a036fCompareFebruary 12, 2026 13:57
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch from 57a036f to 4699c7cCompareFebruary 15, 2026 07:03
@pierrejeambrunpierrejeambrun mentioned this pull request Feb 16, 2026
@pierrejeambrun
pierrejeambrun merged commit c5206f5 into apache:mainFeb 16, 2026
78 checks passed
OscarLigthart pushed a commit to OscarLigthart/airflow that referenced this pull request Feb 17, 2026
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@pierrejeambrun
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Replace node-sql-parser with sqlparser-ts - #61111

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs
Feb 16, 2026
Merged

Replace node-sql-parser with sqlparser-ts#61111
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs

Conversation

@guan404ming

@guan404mingguan404ming commented Jan 27, 2026

Copy link
Copy Markdown
Member

TL;DR

Replaced node-sql-parser with sqlparser-ts
This new one is 14x smaller, 5x faster and is a library I actively maintain.

Why

  • node-sql-parser adds a massive ~88 MB footprint to the package just to handle SQL string detection.
  • The existing implementation lacked the broad SQL dialect coverage needed for diverse user queries.

How

  • Switched to sqlparser-ts, a library I develop and maintain, which brings the power of Rust’s sqlparser from Apache DataFusion to JS/TS via WebAssembly. It offers superior performance with zero JS/TS dependencies.
  • Refactored detectLanguage.ts to use sqlparser-ts's validate() function instead of node-sql-parser's parse() for SQL detection
  • Added comprehensive unit tests for detectLanguage covering JSON, SQL, Bash, YAML and plain text detection

Benchmark

This optimization slashed the package size by 93% and boosted validation speed by 5.4x, while delivering superior SQL compatibility and maintaining stable load times compare to pure js implementation.

MetricBeforeAfterChange
Package size87 MB6.4 MB-93%
JS bundle7,846 KB5,248 KB-33%
Validate speed70.6 μs/call12.86 μs/call5.48x faster
Build time~44s~27s-39%
Load time~459.6ms~463.9ms+0.94%
Compatibilityfail in complex SQL in SQLitenear fully support and update actively-

More detailed benchmark between sqlparser-ts and node-sql-parser --> https://github.com/guan404ming/sqlparser-ts/tree/main/benchmark

Bundle Analyzer Treemap

before

image

after

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code with Opus 4.5 following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 27, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 3 times, most recently from fd75111 to 63e11deCompareJanuary 27, 2026 11:01
@guan404ming

guan404ming commented Jan 27, 2026

Copy link
Copy Markdown
MemberAuthor

Load Time Benchmark

Result

Metricmain (node-sql-parser)this pr (sqlparser-ts)
Avg Page Load459.6ms463.9ms
Median Page Load446.3ms450.2ms
Min382.0ms359.1ms
Max549.0ms643.2ms
Avg DOM Content Loaded456.4ms460.3ms
Individual Values549, 439, 382, 390, 433, 438, 453, 519, 469, 524443, 452, 368, 359, 406, 449, 480, 574, 643, 467

No measurable performance difference. Both branches are within noise margin (~4ms difference in median)


Config

  • Browser: Playwright Chromium (Default)
  • Runs: 10 (with 1 warmup run excluded)
  • Wait Condition: networkidle
  • test page: TI render template tab with sql inside
image

Playwright Script

async(page)=>{consturl='http://localhost:28080/dags/long_log_dag/runs/manual__2026-01-27T09:58:47.311305+00:00/tasks/execute_query/rendered_templates';construns=10;constresults=[];// Warm upawaitpage.goto(url,{waitUntil: 'networkidle'});for(leti=0;i<runs;i++){awaitpage.goto(url,{waitUntil: 'networkidle'});consttiming=awaitpage.evaluate(()=>{constnav=performance.getEntriesByType('navigation')[0];return{pageLoad: nav.loadEventEnd-nav.startTime,domContentLoaded: nav.domContentLoadedEventEnd-nav.startTime,};});results.push(timing);}constavg=(arr)=>(arr.reduce((a,b)=>a+b,0)/arr.length).toFixed(1);constmedian=(arr)=>{consts=[...arr].sort((a,b)=>a-b);constm=Math.floor(s.length/2);returns.length%2 ? s[m] : (s[m-1]+s[m])/2;};constp=results.map(r=>r.pageLoad);constd=results.map(r=>r.domContentLoaded);return{
runs,pageLoad: {avg: avg(p),median: median(p).toFixed(1),min: Math.min(...p).toFixed(1),max: Math.max(...p).toFixed(1),values: p.map(v=>v.toFixed(1))},domContentLoaded: {avg: avg(d),median: median(d).toFixed(1),min: Math.min(...d).toFixed(1),max: Math.max(...d).toFixed(1)},};}

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guan404ming for this proposal, indeed such optimization would be a good addition I believe.

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Do you know if there are more popular alternatives? If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package?

Just exploring our options here.

@guan404ming

guan404ming commented Jan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback! Totally valid concern regarding the new dependency.

Do you know if there are more popular alternatives?

Here is some alternatives but most of them are either support specific dialects or lack of maintenance. After this investigation, I then decide to start to build by myself.

  • node-sql-parser --> current pkg, large and have some compat issue for different dialects
  • pgsql-parser --> only support pg
  • dt-sql-parser --> build for flink / spark
  • js-sql-parser --> outdated, last release is 2 years ago
  • antlr4ts --> only provide engine. we need to built from scratch, which is high maintenance needed

If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package

I've also consider this option before. But since the core logic of datafusion-sqlparser-rs is written on Rust, embedding it directly would force Airflow to introduce a Rust toolchain and WASM build steps (like wasm-pack) into the CI/CD pipeline, which increases the barrier and lots code. So packaging it separately to abstracts this complexity away, keeping the Airflow focus on Python/JS is kind of better solution if we want to take speed and size advantage of the datafusion-sqlparser-rs

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Technically, I think the package is robust. I’ve ported the test suite directly from the upstream Rust repo to typescript to ensure the behavior matches the source of truth 1:1. On top of that, I’ve included comprehensive unit tests in this PR to guarantee it integrates with the Airflow UI without causing regressions.

I completely understand the hesitation with relying on a fresh personal repo. I will continue promoting this package to other projects to build a stronger track record. My ideal target is try to donate and transfer this package to the Apache Datafusion upstream. This would make it into an community-governed artifact, removing the dependency on a single individual. However I hope we can still keep this conversation open. I’d really appreciate it if you could give the pkg a spin to see the value. Thanks!

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 5 times, most recently from 206a3fb to 49d1485CompareFebruary 7, 2026 12:25
@pierrejeambrun

pierrejeambrun commented Feb 10, 2026

Copy link
Copy Markdown
Member

I checked with Jarek and we think it's a great opportunity to make this solution more known and used across the community.

We already trusted similar packages in the past such as https://github.com/kaxil/boring-cyborg from kaxil

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit, looking good to me.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Comment threadairflow-core/src/airflow/ui/src/components/SqlParserProvider.tsx Outdated
@pierrejeambrunpierrejeambrun added this to the Airflow 3.2.0 milestone Feb 10, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from ac0bb70 to 578e1faCompareFebruary 11, 2026 02:25
@guan404ming

guan404ming commented Feb 11, 2026

Copy link
Copy Markdown
MemberAuthor

I'm glad to hear that! Thank you and Jarek for the trust. I will keep maintaining this pkg and ensuring it continues to provide value to the community and also be reliable.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Sure, just rebased.

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from 73c062f to 57a036fCompareFebruary 12, 2026 13:57
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch from 57a036f to 4699c7cCompareFebruary 15, 2026 07:03
@pierrejeambrunpierrejeambrun mentioned this pull request Feb 16, 2026
@pierrejeambrun
pierrejeambrun merged commit c5206f5 into apache:mainFeb 16, 2026
78 checks passed
OscarLigthart pushed a commit to OscarLigthart/airflow that referenced this pull request Feb 17, 2026
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@pierrejeambrun
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Replace node-sql-parser with sqlparser-ts - #61111

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs
Feb 16, 2026
Merged

Replace node-sql-parser with sqlparser-ts#61111
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs

Conversation

@guan404ming

@guan404mingguan404ming commented Jan 27, 2026

Copy link
Copy Markdown
Member

TL;DR

Replaced node-sql-parser with sqlparser-ts
This new one is 14x smaller, 5x faster and is a library I actively maintain.

Why

  • node-sql-parser adds a massive ~88 MB footprint to the package just to handle SQL string detection.
  • The existing implementation lacked the broad SQL dialect coverage needed for diverse user queries.

How

  • Switched to sqlparser-ts, a library I develop and maintain, which brings the power of Rust’s sqlparser from Apache DataFusion to JS/TS via WebAssembly. It offers superior performance with zero JS/TS dependencies.
  • Refactored detectLanguage.ts to use sqlparser-ts's validate() function instead of node-sql-parser's parse() for SQL detection
  • Added comprehensive unit tests for detectLanguage covering JSON, SQL, Bash, YAML and plain text detection

Benchmark

This optimization slashed the package size by 93% and boosted validation speed by 5.4x, while delivering superior SQL compatibility and maintaining stable load times compare to pure js implementation.

MetricBeforeAfterChange
Package size87 MB6.4 MB-93%
JS bundle7,846 KB5,248 KB-33%
Validate speed70.6 μs/call12.86 μs/call5.48x faster
Build time~44s~27s-39%
Load time~459.6ms~463.9ms+0.94%
Compatibilityfail in complex SQL in SQLitenear fully support and update actively-

More detailed benchmark between sqlparser-ts and node-sql-parser --> https://github.com/guan404ming/sqlparser-ts/tree/main/benchmark

Bundle Analyzer Treemap

before

image

after

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code with Opus 4.5 following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 27, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 3 times, most recently from fd75111 to 63e11deCompareJanuary 27, 2026 11:01
@guan404ming

guan404ming commented Jan 27, 2026

Copy link
Copy Markdown
MemberAuthor

Load Time Benchmark

Result

Metricmain (node-sql-parser)this pr (sqlparser-ts)
Avg Page Load459.6ms463.9ms
Median Page Load446.3ms450.2ms
Min382.0ms359.1ms
Max549.0ms643.2ms
Avg DOM Content Loaded456.4ms460.3ms
Individual Values549, 439, 382, 390, 433, 438, 453, 519, 469, 524443, 452, 368, 359, 406, 449, 480, 574, 643, 467

No measurable performance difference. Both branches are within noise margin (~4ms difference in median)


Config

  • Browser: Playwright Chromium (Default)
  • Runs: 10 (with 1 warmup run excluded)
  • Wait Condition: networkidle
  • test page: TI render template tab with sql inside
image

Playwright Script

async(page)=>{consturl='http://localhost:28080/dags/long_log_dag/runs/manual__2026-01-27T09:58:47.311305+00:00/tasks/execute_query/rendered_templates';construns=10;constresults=[];// Warm upawaitpage.goto(url,{waitUntil: 'networkidle'});for(leti=0;i<runs;i++){awaitpage.goto(url,{waitUntil: 'networkidle'});consttiming=awaitpage.evaluate(()=>{constnav=performance.getEntriesByType('navigation')[0];return{pageLoad: nav.loadEventEnd-nav.startTime,domContentLoaded: nav.domContentLoadedEventEnd-nav.startTime,};});results.push(timing);}constavg=(arr)=>(arr.reduce((a,b)=>a+b,0)/arr.length).toFixed(1);constmedian=(arr)=>{consts=[...arr].sort((a,b)=>a-b);constm=Math.floor(s.length/2);returns.length%2 ? s[m] : (s[m-1]+s[m])/2;};constp=results.map(r=>r.pageLoad);constd=results.map(r=>r.domContentLoaded);return{
runs,pageLoad: {avg: avg(p),median: median(p).toFixed(1),min: Math.min(...p).toFixed(1),max: Math.max(...p).toFixed(1),values: p.map(v=>v.toFixed(1))},domContentLoaded: {avg: avg(d),median: median(d).toFixed(1),min: Math.min(...d).toFixed(1),max: Math.max(...d).toFixed(1)},};}

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guan404ming for this proposal, indeed such optimization would be a good addition I believe.

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Do you know if there are more popular alternatives? If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package?

Just exploring our options here.

@guan404ming

guan404ming commented Jan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback! Totally valid concern regarding the new dependency.

Do you know if there are more popular alternatives?

Here is some alternatives but most of them are either support specific dialects or lack of maintenance. After this investigation, I then decide to start to build by myself.

  • node-sql-parser --> current pkg, large and have some compat issue for different dialects
  • pgsql-parser --> only support pg
  • dt-sql-parser --> build for flink / spark
  • js-sql-parser --> outdated, last release is 2 years ago
  • antlr4ts --> only provide engine. we need to built from scratch, which is high maintenance needed

If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package

I've also consider this option before. But since the core logic of datafusion-sqlparser-rs is written on Rust, embedding it directly would force Airflow to introduce a Rust toolchain and WASM build steps (like wasm-pack) into the CI/CD pipeline, which increases the barrier and lots code. So packaging it separately to abstracts this complexity away, keeping the Airflow focus on Python/JS is kind of better solution if we want to take speed and size advantage of the datafusion-sqlparser-rs

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Technically, I think the package is robust. I’ve ported the test suite directly from the upstream Rust repo to typescript to ensure the behavior matches the source of truth 1:1. On top of that, I’ve included comprehensive unit tests in this PR to guarantee it integrates with the Airflow UI without causing regressions.

I completely understand the hesitation with relying on a fresh personal repo. I will continue promoting this package to other projects to build a stronger track record. My ideal target is try to donate and transfer this package to the Apache Datafusion upstream. This would make it into an community-governed artifact, removing the dependency on a single individual. However I hope we can still keep this conversation open. I’d really appreciate it if you could give the pkg a spin to see the value. Thanks!

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 5 times, most recently from 206a3fb to 49d1485CompareFebruary 7, 2026 12:25
@pierrejeambrun

pierrejeambrun commented Feb 10, 2026

Copy link
Copy Markdown
Member

I checked with Jarek and we think it's a great opportunity to make this solution more known and used across the community.

We already trusted similar packages in the past such as https://github.com/kaxil/boring-cyborg from kaxil

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit, looking good to me.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Comment threadairflow-core/src/airflow/ui/src/components/SqlParserProvider.tsx Outdated
@pierrejeambrunpierrejeambrun added this to the Airflow 3.2.0 milestone Feb 10, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from ac0bb70 to 578e1faCompareFebruary 11, 2026 02:25
@guan404ming

guan404ming commented Feb 11, 2026

Copy link
Copy Markdown
MemberAuthor

I'm glad to hear that! Thank you and Jarek for the trust. I will keep maintaining this pkg and ensuring it continues to provide value to the community and also be reliable.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Sure, just rebased.

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from 73c062f to 57a036fCompareFebruary 12, 2026 13:57
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch from 57a036f to 4699c7cCompareFebruary 15, 2026 07:03
@pierrejeambrunpierrejeambrun mentioned this pull request Feb 16, 2026
@pierrejeambrun
pierrejeambrun merged commit c5206f5 into apache:mainFeb 16, 2026
78 checks passed
OscarLigthart pushed a commit to OscarLigthart/airflow that referenced this pull request Feb 17, 2026
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@pierrejeambrun
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Replace node-sql-parser with sqlparser-ts - #61111

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs
Feb 16, 2026
Merged

Replace node-sql-parser with sqlparser-ts#61111
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs

Conversation

@guan404ming

@guan404mingguan404ming commented Jan 27, 2026

Copy link
Copy Markdown
Member

TL;DR

Replaced node-sql-parser with sqlparser-ts
This new one is 14x smaller, 5x faster and is a library I actively maintain.

Why

  • node-sql-parser adds a massive ~88 MB footprint to the package just to handle SQL string detection.
  • The existing implementation lacked the broad SQL dialect coverage needed for diverse user queries.

How

  • Switched to sqlparser-ts, a library I develop and maintain, which brings the power of Rust’s sqlparser from Apache DataFusion to JS/TS via WebAssembly. It offers superior performance with zero JS/TS dependencies.
  • Refactored detectLanguage.ts to use sqlparser-ts's validate() function instead of node-sql-parser's parse() for SQL detection
  • Added comprehensive unit tests for detectLanguage covering JSON, SQL, Bash, YAML and plain text detection

Benchmark

This optimization slashed the package size by 93% and boosted validation speed by 5.4x, while delivering superior SQL compatibility and maintaining stable load times compare to pure js implementation.

MetricBeforeAfterChange
Package size87 MB6.4 MB-93%
JS bundle7,846 KB5,248 KB-33%
Validate speed70.6 μs/call12.86 μs/call5.48x faster
Build time~44s~27s-39%
Load time~459.6ms~463.9ms+0.94%
Compatibilityfail in complex SQL in SQLitenear fully support and update actively-

More detailed benchmark between sqlparser-ts and node-sql-parser --> https://github.com/guan404ming/sqlparser-ts/tree/main/benchmark

Bundle Analyzer Treemap

before

image

after

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code with Opus 4.5 following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 27, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 3 times, most recently from fd75111 to 63e11deCompareJanuary 27, 2026 11:01
@guan404ming

guan404ming commented Jan 27, 2026

Copy link
Copy Markdown
MemberAuthor

Load Time Benchmark

Result

Metricmain (node-sql-parser)this pr (sqlparser-ts)
Avg Page Load459.6ms463.9ms
Median Page Load446.3ms450.2ms
Min382.0ms359.1ms
Max549.0ms643.2ms
Avg DOM Content Loaded456.4ms460.3ms
Individual Values549, 439, 382, 390, 433, 438, 453, 519, 469, 524443, 452, 368, 359, 406, 449, 480, 574, 643, 467

No measurable performance difference. Both branches are within noise margin (~4ms difference in median)


Config

  • Browser: Playwright Chromium (Default)
  • Runs: 10 (with 1 warmup run excluded)
  • Wait Condition: networkidle
  • test page: TI render template tab with sql inside
image

Playwright Script

async(page)=>{consturl='http://localhost:28080/dags/long_log_dag/runs/manual__2026-01-27T09:58:47.311305+00:00/tasks/execute_query/rendered_templates';construns=10;constresults=[];// Warm upawaitpage.goto(url,{waitUntil: 'networkidle'});for(leti=0;i<runs;i++){awaitpage.goto(url,{waitUntil: 'networkidle'});consttiming=awaitpage.evaluate(()=>{constnav=performance.getEntriesByType('navigation')[0];return{pageLoad: nav.loadEventEnd-nav.startTime,domContentLoaded: nav.domContentLoadedEventEnd-nav.startTime,};});results.push(timing);}constavg=(arr)=>(arr.reduce((a,b)=>a+b,0)/arr.length).toFixed(1);constmedian=(arr)=>{consts=[...arr].sort((a,b)=>a-b);constm=Math.floor(s.length/2);returns.length%2 ? s[m] : (s[m-1]+s[m])/2;};constp=results.map(r=>r.pageLoad);constd=results.map(r=>r.domContentLoaded);return{
runs,pageLoad: {avg: avg(p),median: median(p).toFixed(1),min: Math.min(...p).toFixed(1),max: Math.max(...p).toFixed(1),values: p.map(v=>v.toFixed(1))},domContentLoaded: {avg: avg(d),median: median(d).toFixed(1),min: Math.min(...d).toFixed(1),max: Math.max(...d).toFixed(1)},};}

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guan404ming for this proposal, indeed such optimization would be a good addition I believe.

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Do you know if there are more popular alternatives? If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package?

Just exploring our options here.

@guan404ming

guan404ming commented Jan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback! Totally valid concern regarding the new dependency.

Do you know if there are more popular alternatives?

Here is some alternatives but most of them are either support specific dialects or lack of maintenance. After this investigation, I then decide to start to build by myself.

  • node-sql-parser --> current pkg, large and have some compat issue for different dialects
  • pgsql-parser --> only support pg
  • dt-sql-parser --> build for flink / spark
  • js-sql-parser --> outdated, last release is 2 years ago
  • antlr4ts --> only provide engine. we need to built from scratch, which is high maintenance needed

If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package

I've also consider this option before. But since the core logic of datafusion-sqlparser-rs is written on Rust, embedding it directly would force Airflow to introduce a Rust toolchain and WASM build steps (like wasm-pack) into the CI/CD pipeline, which increases the barrier and lots code. So packaging it separately to abstracts this complexity away, keeping the Airflow focus on Python/JS is kind of better solution if we want to take speed and size advantage of the datafusion-sqlparser-rs

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Technically, I think the package is robust. I’ve ported the test suite directly from the upstream Rust repo to typescript to ensure the behavior matches the source of truth 1:1. On top of that, I’ve included comprehensive unit tests in this PR to guarantee it integrates with the Airflow UI without causing regressions.

I completely understand the hesitation with relying on a fresh personal repo. I will continue promoting this package to other projects to build a stronger track record. My ideal target is try to donate and transfer this package to the Apache Datafusion upstream. This would make it into an community-governed artifact, removing the dependency on a single individual. However I hope we can still keep this conversation open. I’d really appreciate it if you could give the pkg a spin to see the value. Thanks!

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 5 times, most recently from 206a3fb to 49d1485CompareFebruary 7, 2026 12:25
@pierrejeambrun

pierrejeambrun commented Feb 10, 2026

Copy link
Copy Markdown
Member

I checked with Jarek and we think it's a great opportunity to make this solution more known and used across the community.

We already trusted similar packages in the past such as https://github.com/kaxil/boring-cyborg from kaxil

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit, looking good to me.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Comment threadairflow-core/src/airflow/ui/src/components/SqlParserProvider.tsx Outdated
@pierrejeambrunpierrejeambrun added this to the Airflow 3.2.0 milestone Feb 10, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from ac0bb70 to 578e1faCompareFebruary 11, 2026 02:25
@guan404ming

guan404ming commented Feb 11, 2026

Copy link
Copy Markdown
MemberAuthor

I'm glad to hear that! Thank you and Jarek for the trust. I will keep maintaining this pkg and ensuring it continues to provide value to the community and also be reliable.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Sure, just rebased.

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from 73c062f to 57a036fCompareFebruary 12, 2026 13:57
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch from 57a036f to 4699c7cCompareFebruary 15, 2026 07:03
@pierrejeambrunpierrejeambrun mentioned this pull request Feb 16, 2026
@pierrejeambrun
pierrejeambrun merged commit c5206f5 into apache:mainFeb 16, 2026
78 checks passed
OscarLigthart pushed a commit to OscarLigthart/airflow that referenced this pull request Feb 17, 2026
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@pierrejeambrun
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Replace node-sql-parser with sqlparser-ts - #61111

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs
Feb 16, 2026
Merged

Replace node-sql-parser with sqlparser-ts#61111
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs

Conversation

@guan404ming

@guan404mingguan404ming commented Jan 27, 2026

Copy link
Copy Markdown
Member

TL;DR

Replaced node-sql-parser with sqlparser-ts
This new one is 14x smaller, 5x faster and is a library I actively maintain.

Why

  • node-sql-parser adds a massive ~88 MB footprint to the package just to handle SQL string detection.
  • The existing implementation lacked the broad SQL dialect coverage needed for diverse user queries.

How

  • Switched to sqlparser-ts, a library I develop and maintain, which brings the power of Rust’s sqlparser from Apache DataFusion to JS/TS via WebAssembly. It offers superior performance with zero JS/TS dependencies.
  • Refactored detectLanguage.ts to use sqlparser-ts's validate() function instead of node-sql-parser's parse() for SQL detection
  • Added comprehensive unit tests for detectLanguage covering JSON, SQL, Bash, YAML and plain text detection

Benchmark

This optimization slashed the package size by 93% and boosted validation speed by 5.4x, while delivering superior SQL compatibility and maintaining stable load times compare to pure js implementation.

MetricBeforeAfterChange
Package size87 MB6.4 MB-93%
JS bundle7,846 KB5,248 KB-33%
Validate speed70.6 μs/call12.86 μs/call5.48x faster
Build time~44s~27s-39%
Load time~459.6ms~463.9ms+0.94%
Compatibilityfail in complex SQL in SQLitenear fully support and update actively-

More detailed benchmark between sqlparser-ts and node-sql-parser --> https://github.com/guan404ming/sqlparser-ts/tree/main/benchmark

Bundle Analyzer Treemap

before

image

after

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code with Opus 4.5 following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 27, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 3 times, most recently from fd75111 to 63e11deCompareJanuary 27, 2026 11:01
@guan404ming

guan404ming commented Jan 27, 2026

Copy link
Copy Markdown
MemberAuthor

Load Time Benchmark

Result

Metricmain (node-sql-parser)this pr (sqlparser-ts)
Avg Page Load459.6ms463.9ms
Median Page Load446.3ms450.2ms
Min382.0ms359.1ms
Max549.0ms643.2ms
Avg DOM Content Loaded456.4ms460.3ms
Individual Values549, 439, 382, 390, 433, 438, 453, 519, 469, 524443, 452, 368, 359, 406, 449, 480, 574, 643, 467

No measurable performance difference. Both branches are within noise margin (~4ms difference in median)


Config

  • Browser: Playwright Chromium (Default)
  • Runs: 10 (with 1 warmup run excluded)
  • Wait Condition: networkidle
  • test page: TI render template tab with sql inside
image

Playwright Script

async(page)=>{consturl='http://localhost:28080/dags/long_log_dag/runs/manual__2026-01-27T09:58:47.311305+00:00/tasks/execute_query/rendered_templates';construns=10;constresults=[];// Warm upawaitpage.goto(url,{waitUntil: 'networkidle'});for(leti=0;i<runs;i++){awaitpage.goto(url,{waitUntil: 'networkidle'});consttiming=awaitpage.evaluate(()=>{constnav=performance.getEntriesByType('navigation')[0];return{pageLoad: nav.loadEventEnd-nav.startTime,domContentLoaded: nav.domContentLoadedEventEnd-nav.startTime,};});results.push(timing);}constavg=(arr)=>(arr.reduce((a,b)=>a+b,0)/arr.length).toFixed(1);constmedian=(arr)=>{consts=[...arr].sort((a,b)=>a-b);constm=Math.floor(s.length/2);returns.length%2 ? s[m] : (s[m-1]+s[m])/2;};constp=results.map(r=>r.pageLoad);constd=results.map(r=>r.domContentLoaded);return{
runs,pageLoad: {avg: avg(p),median: median(p).toFixed(1),min: Math.min(...p).toFixed(1),max: Math.max(...p).toFixed(1),values: p.map(v=>v.toFixed(1))},domContentLoaded: {avg: avg(d),median: median(d).toFixed(1),min: Math.min(...d).toFixed(1),max: Math.max(...d).toFixed(1)},};}

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guan404ming for this proposal, indeed such optimization would be a good addition I believe.

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Do you know if there are more popular alternatives? If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package?

Just exploring our options here.

@guan404ming

guan404ming commented Jan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback! Totally valid concern regarding the new dependency.

Do you know if there are more popular alternatives?

Here is some alternatives but most of them are either support specific dialects or lack of maintenance. After this investigation, I then decide to start to build by myself.

  • node-sql-parser --> current pkg, large and have some compat issue for different dialects
  • pgsql-parser --> only support pg
  • dt-sql-parser --> build for flink / spark
  • js-sql-parser --> outdated, last release is 2 years ago
  • antlr4ts --> only provide engine. we need to built from scratch, which is high maintenance needed

If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package

I've also consider this option before. But since the core logic of datafusion-sqlparser-rs is written on Rust, embedding it directly would force Airflow to introduce a Rust toolchain and WASM build steps (like wasm-pack) into the CI/CD pipeline, which increases the barrier and lots code. So packaging it separately to abstracts this complexity away, keeping the Airflow focus on Python/JS is kind of better solution if we want to take speed and size advantage of the datafusion-sqlparser-rs

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Technically, I think the package is robust. I’ve ported the test suite directly from the upstream Rust repo to typescript to ensure the behavior matches the source of truth 1:1. On top of that, I’ve included comprehensive unit tests in this PR to guarantee it integrates with the Airflow UI without causing regressions.

I completely understand the hesitation with relying on a fresh personal repo. I will continue promoting this package to other projects to build a stronger track record. My ideal target is try to donate and transfer this package to the Apache Datafusion upstream. This would make it into an community-governed artifact, removing the dependency on a single individual. However I hope we can still keep this conversation open. I’d really appreciate it if you could give the pkg a spin to see the value. Thanks!

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 5 times, most recently from 206a3fb to 49d1485CompareFebruary 7, 2026 12:25
@pierrejeambrun

pierrejeambrun commented Feb 10, 2026

Copy link
Copy Markdown
Member

I checked with Jarek and we think it's a great opportunity to make this solution more known and used across the community.

We already trusted similar packages in the past such as https://github.com/kaxil/boring-cyborg from kaxil

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit, looking good to me.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Comment threadairflow-core/src/airflow/ui/src/components/SqlParserProvider.tsx Outdated
@pierrejeambrunpierrejeambrun added this to the Airflow 3.2.0 milestone Feb 10, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from ac0bb70 to 578e1faCompareFebruary 11, 2026 02:25
@guan404ming

guan404ming commented Feb 11, 2026

Copy link
Copy Markdown
MemberAuthor

I'm glad to hear that! Thank you and Jarek for the trust. I will keep maintaining this pkg and ensuring it continues to provide value to the community and also be reliable.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Sure, just rebased.

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from 73c062f to 57a036fCompareFebruary 12, 2026 13:57
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch from 57a036f to 4699c7cCompareFebruary 15, 2026 07:03
@pierrejeambrunpierrejeambrun mentioned this pull request Feb 16, 2026
@pierrejeambrun
pierrejeambrun merged commit c5206f5 into apache:mainFeb 16, 2026
78 checks passed
OscarLigthart pushed a commit to OscarLigthart/airflow that referenced this pull request Feb 17, 2026
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@pierrejeambrun
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Replace node-sql-parser with sqlparser-ts - #61111

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs
Feb 16, 2026
Merged

Replace node-sql-parser with sqlparser-ts#61111
pierrejeambrun merged 1 commit into
apache:mainfrom
guan404ming:replace-with-sqlparser-rs

Conversation

@guan404ming

@guan404mingguan404ming commented Jan 27, 2026

Copy link
Copy Markdown
Member

TL;DR

Replaced node-sql-parser with sqlparser-ts
This new one is 14x smaller, 5x faster and is a library I actively maintain.

Why

  • node-sql-parser adds a massive ~88 MB footprint to the package just to handle SQL string detection.
  • The existing implementation lacked the broad SQL dialect coverage needed for diverse user queries.

How

  • Switched to sqlparser-ts, a library I develop and maintain, which brings the power of Rust’s sqlparser from Apache DataFusion to JS/TS via WebAssembly. It offers superior performance with zero JS/TS dependencies.
  • Refactored detectLanguage.ts to use sqlparser-ts's validate() function instead of node-sql-parser's parse() for SQL detection
  • Added comprehensive unit tests for detectLanguage covering JSON, SQL, Bash, YAML and plain text detection

Benchmark

This optimization slashed the package size by 93% and boosted validation speed by 5.4x, while delivering superior SQL compatibility and maintaining stable load times compare to pure js implementation.

MetricBeforeAfterChange
Package size87 MB6.4 MB-93%
JS bundle7,846 KB5,248 KB-33%
Validate speed70.6 μs/call12.86 μs/call5.48x faster
Build time~44s~27s-39%
Load time~459.6ms~463.9ms+0.94%
Compatibilityfail in complex SQL in SQLitenear fully support and update actively-

More detailed benchmark between sqlparser-ts and node-sql-parser --> https://github.com/guan404ming/sqlparser-ts/tree/main/benchmark

Bundle Analyzer Treemap

before

image

after

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code with Opus 4.5 following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 27, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 3 times, most recently from fd75111 to 63e11deCompareJanuary 27, 2026 11:01
@guan404ming

guan404ming commented Jan 27, 2026

Copy link
Copy Markdown
MemberAuthor

Load Time Benchmark

Result

Metricmain (node-sql-parser)this pr (sqlparser-ts)
Avg Page Load459.6ms463.9ms
Median Page Load446.3ms450.2ms
Min382.0ms359.1ms
Max549.0ms643.2ms
Avg DOM Content Loaded456.4ms460.3ms
Individual Values549, 439, 382, 390, 433, 438, 453, 519, 469, 524443, 452, 368, 359, 406, 449, 480, 574, 643, 467

No measurable performance difference. Both branches are within noise margin (~4ms difference in median)


Config

  • Browser: Playwright Chromium (Default)
  • Runs: 10 (with 1 warmup run excluded)
  • Wait Condition: networkidle
  • test page: TI render template tab with sql inside
image

Playwright Script

async(page)=>{consturl='http://localhost:28080/dags/long_log_dag/runs/manual__2026-01-27T09:58:47.311305+00:00/tasks/execute_query/rendered_templates';construns=10;constresults=[];// Warm upawaitpage.goto(url,{waitUntil: 'networkidle'});for(leti=0;i<runs;i++){awaitpage.goto(url,{waitUntil: 'networkidle'});consttiming=awaitpage.evaluate(()=>{constnav=performance.getEntriesByType('navigation')[0];return{pageLoad: nav.loadEventEnd-nav.startTime,domContentLoaded: nav.domContentLoadedEventEnd-nav.startTime,};});results.push(timing);}constavg=(arr)=>(arr.reduce((a,b)=>a+b,0)/arr.length).toFixed(1);constmedian=(arr)=>{consts=[...arr].sort((a,b)=>a-b);constm=Math.floor(s.length/2);returns.length%2 ? s[m] : (s[m-1]+s[m])/2;};constp=results.map(r=>r.pageLoad);constd=results.map(r=>r.domContentLoaded);return{
runs,pageLoad: {avg: avg(p),median: median(p).toFixed(1),min: Math.min(...p).toFixed(1),max: Math.max(...p).toFixed(1),values: p.map(v=>v.toFixed(1))},domContentLoaded: {avg: avg(d),median: median(d).toFixed(1),min: Math.min(...d).toFixed(1),max: Math.max(...d).toFixed(1)},};}

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guan404ming for this proposal, indeed such optimization would be a good addition I believe.

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Do you know if there are more popular alternatives? If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package?

Just exploring our options here.

@guan404ming

guan404ming commented Jan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback! Totally valid concern regarding the new dependency.

Do you know if there are more popular alternatives?

Here is some alternatives but most of them are either support specific dialects or lack of maintenance. After this investigation, I then decide to start to build by myself.

  • node-sql-parser --> current pkg, large and have some compat issue for different dialects
  • pgsql-parser --> only support pg
  • dt-sql-parser --> build for flink / spark
  • js-sql-parser --> outdated, last release is 2 years ago
  • antlr4ts --> only provide engine. we need to built from scratch, which is high maintenance needed

If there is not what would it take to directly embed datafusion-sqlparser-rs within airflow without relying on an external package

I've also consider this option before. But since the core logic of datafusion-sqlparser-rs is written on Rust, embedding it directly would force Airflow to introduce a Rust toolchain and WASM build steps (like wasm-pack) into the CI/CD pipeline, which increases the barrier and lots code. So packaging it separately to abstracts this complexity away, keeping the Airflow focus on Python/JS is kind of better solution if we want to take speed and size advantage of the datafusion-sqlparser-rs

I don't feel super confident adding a third party dependency freshly created and that hasn't a track record of 'working' in production. (and being popular amongst the community).

Technically, I think the package is robust. I’ve ported the test suite directly from the upstream Rust repo to typescript to ensure the behavior matches the source of truth 1:1. On top of that, I’ve included comprehensive unit tests in this PR to guarantee it integrates with the Airflow UI without causing regressions.

I completely understand the hesitation with relying on a fresh personal repo. I will continue promoting this package to other projects to build a stronger track record. My ideal target is try to donate and transfer this package to the Apache Datafusion upstream. This would make it into an community-governed artifact, removing the dependency on a single individual. However I hope we can still keep this conversation open. I’d really appreciate it if you could give the pkg a spin to see the value. Thanks!

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 5 times, most recently from 206a3fb to 49d1485CompareFebruary 7, 2026 12:25
@pierrejeambrun

pierrejeambrun commented Feb 10, 2026

Copy link
Copy Markdown
Member

I checked with Jarek and we think it's a great opportunity to make this solution more known and used across the community.

We already trusted similar packages in the past such as https://github.com/kaxil/boring-cyborg from kaxil

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit, looking good to me.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Comment threadairflow-core/src/airflow/ui/src/components/SqlParserProvider.tsx Outdated
@pierrejeambrunpierrejeambrun added this to the Airflow 3.2.0 milestone Feb 10, 2026
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from ac0bb70 to 578e1faCompareFebruary 11, 2026 02:25
@guan404ming

guan404ming commented Feb 11, 2026

Copy link
Copy Markdown
MemberAuthor

I'm glad to hear that! Thank you and Jarek for the trust. I will keep maintaining this pkg and ensuring it continues to provide value to the community and also be reliable.

Do you mind rebasing the PR, this will make it easier for me to test locally.

Sure, just rebased.

@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch 2 times, most recently from 73c062f to 57a036fCompareFebruary 12, 2026 13:57
@guan404ming
guan404mingforce-pushed the replace-with-sqlparser-rs branch from 57a036f to 4699c7cCompareFebruary 15, 2026 07:03
@pierrejeambrunpierrejeambrun mentioned this pull request Feb 16, 2026
@pierrejeambrun
pierrejeambrun merged commit c5206f5 into apache:mainFeb 16, 2026
78 checks passed
OscarLigthart pushed a commit to OscarLigthart/airflow that referenced this pull request Feb 17, 2026
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@pierrejeambrun