diff --git a/README.md b/README.md index ad1e593aa..2fb56481f 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,11 @@ This macro expands to the ClickHouse `additional_table_filters` setting with the currently active ad-hoc filters. It should be placed in the `SETTINGS` clause of your query. -Example: +**Multiple tables are supported** by passing multiple table names as comma-separated +arguments: `$__adHocFilters('table1', 'table2', 'table3')`. This will apply the same +ad-hoc filters to all specified tables. + +Example (single table): ```sql SELECT * @@ -295,8 +299,17 @@ FROM ( SETTINGS $__adHocFilters('my_complex_table') ``` +Example (multiple tables): + +```sql +SELECT t1.*, t2.* +FROM table1 t1 +JOIN table2 t2 ON t1.id = t2.id +SETTINGS $__adHocFilters('table1', 'table2') +``` + When ad-hoc filters are active (e.g., `status = 'active'` and `region = 'us-west'`), -this expands to: +the single table example expands to: ```sql SELECT * @@ -307,6 +320,15 @@ FROM ( SETTINGS additional_table_filters={'my_complex_table': 'status = \'active\' AND region = \'us-west\''} ``` +And the multiple tables example expands to: + +```sql +SELECT t1.*, t2.* +FROM table1 t1 +JOIN table2 t2 ON t1.id = t2.id +SETTINGS additional_table_filters={'table1': 'status = \'active\' AND region = \'us-west\'', 'table2': 'status = \'active\' AND region = \'us-west\''} +``` + ## Learn more - Add [Annotations](https://grafana.com/docs/grafana/latest/dashboards/annotations/). diff --git a/package-lock.json b/package-lock.json index a28fc84b5..b98f9c6f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,6 +120,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -802,7 +803,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.18.tgz", "integrity": "sha512-EF77RqROHL+4LhMGW5NTeKqfUd/e4OOv6EDFQ/UQQiFyWuqkEKyEz0NDILxOFxWUEVdjT2GQ2cC7t12B6pESwg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-dart": { "version": "2.3.1", @@ -942,14 +944,16 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.13.tgz", "integrity": "sha512-vHzk2xfqQYPvoXtQtywa6ekIonPrUEwe2uftjry3UNRNl89TtzLJVSkiymKJ3WMb+W/DwKXKIb1tKzcIS8ccIg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.4.tgz", "integrity": "sha512-afea+0rGPDeOV9gdO06UW183Qg6wRhWVkgCFwiO3bDupAoyXRuvupbb5nUyqSTsLXIKL8u8uXQlJ9pkz07oVXw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-java": { "version": "5.0.12", @@ -1147,7 +1151,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", @@ -3034,7 +3039,8 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/@openfeature/core/-/core-1.9.1.tgz", "integrity": "sha512-YySPtH4s/rKKnHRU0xyFGrqMU8XA+OIPNWDrlEFxE6DCVWCIrxE5YpiB94YD2jMFn6SSdA0cwQ8vLkCkl8lm8A==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@openfeature/ofrep-core": { "version": "2.0.0", @@ -3082,6 +3088,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -3545,6 +3552,7 @@ "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.57.0" }, @@ -4254,6 +4262,7 @@ "integrity": "sha512-2r6cLcmdF6til66lx8esBYvBvsn7xCmLT50gw/n1rGGlTq/OxeNjBIh4c3VEaDGMa/5TybrZTia6sQUHdIWx1w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/utils": "^8.32.1", "eslint-visitor-keys": "^4.2.0", @@ -4286,6 +4295,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" @@ -4565,6 +4575,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4947,6 +4958,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.0.tgz", "integrity": "sha512-5x08bUtU8hfboMTrJ7mEO4CpepS9yBwAqcL52y86SWNmbPX8LVbNs3EP4cNrIZgdjk2NAlP2ahNihozpoZIxSg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.14.0" } @@ -4974,6 +4986,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4985,6 +4998,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -5196,6 +5210,7 @@ "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.0", @@ -5235,6 +5250,7 @@ "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", @@ -5628,6 +5644,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5705,6 +5722,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6286,6 +6304,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7572,6 +7591,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -8342,6 +8362,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8402,6 +8423,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8418,6 +8440,7 @@ "integrity": "sha512-hu3r9/6JBmPG6wTcqtYzgZAnjEG2eqRUATfkFscokESg1VDxZM21ZaMire0KjeMwfj+SXvgB4Rvh5LBuesj92w==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@es-joy/jsdoccomment": "~0.84.0", "@es-joy/resolve.exports": "1.2.0", @@ -8478,6 +8501,7 @@ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -8511,6 +8535,7 @@ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -9754,6 +9779,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -9844,7 +9870,8 @@ "version": "5.1.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/import-fresh": { "version": "3.3.1", @@ -12672,6 +12699,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz", "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -12880,7 +12908,8 @@ "version": "0.34.1", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz", "integrity": "sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/moo": { "version": "0.5.2", @@ -13646,6 +13675,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14063,6 +14093,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -14138,6 +14169,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -14320,6 +14352,7 @@ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -14535,7 +14568,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -14849,6 +14883,7 @@ "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", @@ -14960,6 +14995,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -15211,6 +15247,7 @@ "resolved": "https://registry.npmjs.org/slate/-/slate-0.47.9.tgz", "integrity": "sha512-EK4O6b7lGt+g5H9PGw9O5KCM4RrOvOgE9mPi3rzQ0zDRlgAb2ga4TdpS6XNQbrsJWsc8I1fjaSsUeCqCUhhi9A==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^3.1.0", "direction": "^0.1.5", @@ -16095,6 +16132,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16232,6 +16270,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -16299,7 +16338,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/type-check": { "version": "0.4.0", @@ -16419,6 +16459,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16720,6 +16761,7 @@ "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -16769,6 +16811,7 @@ "integrity": "sha512-dB0R4T+C/8YuvM+fabdvil6QE44/ChDXikV5lOOkrUeCkW5hTJv2pGLE3keh+D5hjYw8icBaJkZzpFoaHV4T+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^1.0.0", "commander": "^14.0.3", diff --git a/src/ch-parser/pluginMacros.ts b/src/ch-parser/pluginMacros.ts index efc5b1609..728d18888 100644 --- a/src/ch-parser/pluginMacros.ts +++ b/src/ch-parser/pluginMacros.ts @@ -96,7 +96,7 @@ export const pluginMacros: PluginMacro[] = [ name: '$__adHocFilters', isFunction: true, documentation: - 'Manually applies ad-hoc filters to a specific table. Useful for complex queries where automatic filter detection fails. Use in SETTINGS clause to specify the target table for ad-hoc filters', - example: "additional_table_filters={'table_name': 'column = \\'value\\' AND column2 = \\'value2\\'}", + 'Manually applies ad-hoc filters to specific table(s). Useful for complex queries where automatic filter detection fails. Supports multiple tables by passing comma-separated table names. Use in SETTINGS clause to specify the target table(s) for ad-hoc filters', + example: "additional_table_filters={'table1': 'column = \\'value\\'', 'table2': 'column = \\'value\\''} (for multiple tables)", }, ]; diff --git a/src/data/CHDatasource.test.ts b/src/data/CHDatasource.test.ts index ebaca902b..94babbfcd 100644 --- a/src/data/CHDatasource.test.ts +++ b/src/data/CHDatasource.test.ts @@ -281,6 +281,49 @@ describe('ClickHouseDatasource', () => { "SELECT * FROM complex_table settings additional_table_filters={'my_table': ' key = \\'val\\' '}" ); }); + + it('should expand $__adHocFilters macro with multiple tables', async () => { + const query = { + rawSql: "SELECT * FROM complex_table settings $__adHocFilters('table1', 'table2')", + editorType: EditorType.SQL, + } as CHQuery; + + const adHocFilters = [ + { key: 'key', operator: '=', value: 'val' }, + { key: 'keyNum', operator: '=', value: '123' }, + ]; + + const spyOnReplace = jest.spyOn(templateSrvMock, 'replace').mockImplementation((x) => x); + const spyOnGetVars = jest.spyOn(templateSrvMock, 'getVariables').mockImplementation(() => []); + + const result = createInstance({}).applyTemplateVariables(query, {}, adHocFilters); + + expect(spyOnReplace).toHaveBeenCalled(); + expect(spyOnGetVars).toHaveBeenCalled(); + expect(result.rawSql).toEqual( + "SELECT * FROM complex_table settings additional_table_filters={'table1': ' key = \\'val\\' AND keyNum = \\'123\\' ', 'table2': ' key = \\'val\\' AND keyNum = \\'123\\' '}" + ); + }); + + it('should expand $__adHocFilters macro with multiple tables using double quotes', async () => { + const query = { + rawSql: 'SELECT * FROM complex_table settings $__adHocFilters("table1", "table2", "table3")', + editorType: EditorType.SQL, + } as CHQuery; + + const adHocFilters = [{ key: 'key', operator: '=', value: 'val' }]; + + const spyOnReplace = jest.spyOn(templateSrvMock, 'replace').mockImplementation((x) => x); + const spyOnGetVars = jest.spyOn(templateSrvMock, 'getVariables').mockImplementation(() => []); + + const result = createInstance({}).applyTemplateVariables(query, {}, adHocFilters); + + expect(spyOnReplace).toHaveBeenCalled(); + expect(spyOnGetVars).toHaveBeenCalled(); + expect(result.rawSql).toEqual( + "SELECT * FROM complex_table settings additional_table_filters={'table1': ' key = \\'val\\' ', 'table2': ' key = \\'val\\' ', 'table3': ' key = \\'val\\' '}" + ); + }); }); describe('Tag Keys', () => { diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index 512d8d628..a8ff51a5c 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -306,15 +306,31 @@ export class Datasource return rawQuery; } - // Match $__adHocFilters('table_name') or $__adHocFilters("table_name") - const regex = /\$__adHocFilters\s*\(\s*['"](.+?)['"]\s*\)/g; + // Match $__adHocFilters('table_name') or $__adHocFilters("table_name") or multiple tables + const regex = /\$__adHocFilters\s*\(([^)]+)\)/g; + + return rawQuery.replace(regex, (match, args) => { + // Extract all table names from comma-separated quoted strings + const tableNameRegex = /['"]([^'"]+)['"]/g; + const tableNames: string[] = []; + let tableMatch; + + while ((tableMatch = tableNameRegex.exec(args)) !== null) { + tableNames.push(tableMatch[1]); + } + + if (tableNames.length === 0) { + return match; // Return original if no valid table names found + } - return rawQuery.replace(regex, (match, tableName) => { const filterStr = this.adHocFilter.buildFilterString(filters, useJSON); if (filterStr === '') { return 'additional_table_filters={}'; } - return `additional_table_filters={'${tableName}': '${filterStr}'}`; + + // Build filter entries for all tables + const tableFilters = tableNames.map(tableName => `'${tableName}': '${filterStr}'`).join(', '); + return `additional_table_filters={${tableFilters}}`; }); }