[{"data":1,"prerenderedAt":847},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fengines\u002Fclickhouse-ai-user":3,"docs-nav":620},{"id":4,"title":5,"body":6,"description":612,"extension":613,"meta":614,"navigation":239,"path":616,"seo":617,"stem":618,"__hash__":619},"docs\u002Fdocs\u002Fengines\u002Fclickhouse-ai-user.md","A dedicated ClickHouse user for the AI assistant",{"type":7,"value":8,"toc":604},"minimark",[9,13,33,73,76,81,124,134,156,166,170,173,289,311,318,349,359,363,366,492,497,535,539,586,600],[10,11,5],"h1",{"id":12},"a-dedicated-clickhouse-user-for-the-ai-assistant",[14,15,16,17,22,23,27,28,32],"p",{},"The engine talks to the external ClickHouse with one account, and that account\ncan read everything on the server. The AI assistant (",[18,19,21],"a",{"href":20},"\u002Fdocs\u002Fintegrations\u002Fai","AI in Tabularia",") hands a free-form ",[24,25,26],"code",{},"SELECT","\nto every user with VIEW on a datasource, so its queries get ",[29,30,31],"strong",{},"two"," barriers:",[34,35,36,52],"ol",{},[37,38,39,40,43,44,47,48,51],"li",{},"the SQL node allow-list (",[24,41,42],{},"backend\u002Fapp\u002Fengine\u002Fsql_guard.py","): a query can name\nonly its own input, ",[24,45,46],{},"self"," \u002F ",[24,49,50],{},"input",";",[37,53,54,55,58,59,62,63,47,66,47,69,72],{},"a ",[29,56,57],{},"dedicated, read-only ClickHouse user",": if a query ever got past the\nfirst barrier, on the server it would find an account that cannot read\n",[24,60,61],{},"system",", cannot read any table, and cannot use ",[24,64,65],{},"url()",[24,67,68],{},"file()",[24,70,71],{},"remote()",".",[14,74,75],{},"The second barrier is optional and off until you configure it.",[77,78,80],"h2",{"id":79},"configuration","Configuration",[82,83,84,97],"table",{},[85,86,87],"thead",{},[88,89,90,94],"tr",{},[91,92,93],"th",{},"Variable",[91,95,96],{},"Meaning",[98,99,100,114],"tbody",{},[88,101,102,108],{},[103,104,105],"td",{},[24,106,107],{},"CLICKHOUSE_EXTERNAL__AI_USERNAME",[103,109,110,111],{},"the dedicated user, e.g. ",[24,112,113],{},"ai",[88,115,116,121],{},[103,117,118],{},[24,119,120],{},"CLICKHOUSE_EXTERNAL__AI_PASSWORD",[103,122,123],{},"its password (a secret)",[14,125,126,127,130,131,72],{},"Helm: ",[24,128,129],{},"externalServices.clickhouse.aiUsername"," and ",[24,132,133],{},"secrets.clickhouseAiPassword",[14,135,136,137,140,141,144,147,148,151,152,155],{},"The user is used only when ",[29,138,139],{},"both"," variables are set ",[29,142,143],{},"and",[24,145,146],{},"CLICKHOUSE_EXTERNAL__TRANSPORT=s3",". In every other case the assistant runs as\n",[24,149,150],{},"CLICKHOUSE_EXTERNAL__USERNAME",", exactly as before — a half-configured pair\nnever produces failed logins. With the ",[24,153,154],{},"push"," transport a preview creates\nstaging tables on the server, which a read-only user cannot do.",[14,157,158,159,162,163,165],{},"Set them on the ",[29,160,161],{},"backend and the workers"," (the gateway never talks to\nClickHouse). Flows, runs, the Viewer and the editor previews are not affected:\nonly the assistant's queries carry the ",[24,164,113],{}," identity.",[77,167,169],{"id":168},"creating-the-user","Creating the user",[14,171,172],{},"Run as an administrator of the ClickHouse service:",[174,175,180],"pre",{"className":176,"code":177,"language":178,"meta":179,"style":179},"language-sql shiki shiki-themes github-dark-dimmed github-light","CREATE USER ai IDENTIFIED WITH sha256_password BY '\u003Ca long random password>'\n  SETTINGS readonly = 2;\n\n-- reading the parquet files from object storage is a table function:\n-- it needs both grants, and nothing else\nGRANT CREATE TEMPORARY TABLE, S3 ON *.* TO ai;\n","sql","",[24,181,182,215,234,241,248,254],{"__ignoreMap":179},[183,184,187,191,194,198,202,205,208,211],"span",{"class":185,"line":186},"line",1,[183,188,190],{"class":189},"s6PUj","CREATE",[183,192,193],{"class":189}," USER",[183,195,197],{"class":196},"saVmf"," ai",[183,199,201],{"class":200},"ssh_m"," IDENTIFIED ",[183,203,204],{"class":189},"WITH",[183,206,207],{"class":200}," sha256_password ",[183,209,210],{"class":189},"BY",[183,212,214],{"class":213},"sXfbr"," '\u003Ca long random password>'\n",[183,216,218,221,224,227,231],{"class":185,"line":217},2,[183,219,220],{"class":200},"  SETTINGS ",[183,222,223],{"class":189},"readonly",[183,225,226],{"class":189}," =",[183,228,230],{"class":229},"swcJU"," 2",[183,232,233],{"class":200},";\n",[183,235,237],{"class":185,"line":236},3,[183,238,240],{"emptyLinePlaceholder":239},true,"\n",[183,242,244],{"class":185,"line":243},4,[183,245,247],{"class":246},"sgHix","-- reading the parquet files from object storage is a table function:\n",[183,249,251],{"class":185,"line":250},5,[183,252,253],{"class":246},"-- it needs both grants, and nothing else\n",[183,255,257,260,263,266,269,272,275,278,280,283,286],{"class":185,"line":256},6,[183,258,259],{"class":189},"GRANT",[183,261,262],{"class":189}," CREATE",[183,264,265],{"class":200}," TEMPORARY ",[183,267,268],{"class":189},"TABLE",[183,270,271],{"class":200},", S3 ",[183,273,274],{"class":189},"ON",[183,276,277],{"class":189}," *",[183,279,72],{"class":200},[183,281,282],{"class":189},"*",[183,284,285],{"class":189}," TO",[183,287,288],{"class":200}," ai;\n",[14,290,291,294,295,298,299,302,303,306,307,310],{},[24,292,293],{},"readonly = 2"," rather than ",[24,296,297],{},"1",": the engine raises ",[24,300,301],{},"max_threads"," on scans and\ntags every query with ",[24,304,305],{},"log_comment",", and ",[24,308,309],{},"readonly = 1"," forbids changing\nsettings.",[14,312,313,314,317],{},"If the engine uses a named collection for the S3 credentials\n(",[24,315,316],{},"CLICKHOUSE_EXTERNAL__S3_NAMED_COLLECTION","), also:",[174,319,321],{"className":176,"code":320,"language":178,"meta":179,"style":179},"GRANT NAMED COLLECTION ON \u003Ccollection_name> TO ai;\n",[24,322,323],{"__ignoreMap":179},[183,324,325,327,330,333,336,339,342,345,347],{"class":185,"line":186},[183,326,259],{"class":189},[183,328,329],{"class":200}," NAMED ",[183,331,332],{"class":189},"COLLECTION",[183,334,335],{"class":189}," ON",[183,337,338],{"class":189}," \u003C",[183,340,341],{"class":200},"collection_name",[183,343,344],{"class":189},">",[183,346,285],{"class":189},[183,348,288],{"class":200},[14,350,351,352,355,356,358],{},"Do ",[29,353,354],{},"not"," grant ",[24,357,26],{}," on any database. The assistant never reads the\nmaterialized copies of the Viewer (it reads the parquet), so it needs none.",[77,360,362],{"id":361},"what-the-user-can-and-cannot-do","What the user can and cannot do",[14,364,365],{},"Verified on ClickHouse 24.8 with exactly the grants above:",[82,367,368,378],{},[85,369,370],{},[88,371,372,375],{},[91,373,374],{},"Query",[91,376,377],{},"Result",[98,379,380,398,417,427,440,451,460,482],{},[88,381,382,395],{},[103,383,384,387,388,391,392],{},[24,385,386],{},"SELECT … FROM s3(\u003Cparquet>)",", in the engine's ",[24,389,390],{},"WITH input AS (…), self AS (…)"," form, with ",[24,393,394],{},"SETTINGS max_threads, log_comment",[103,396,397],{},"works",[88,399,400,412],{},[103,401,402,405,406,405,409],{},[24,403,404],{},"SELECT … FROM system.query_log",", ",[24,407,408],{},"system.processes",[24,410,411],{},"system.users",[103,413,414],{},[24,415,416],{},"ACCESS_DENIED",[88,418,419,424],{},[103,420,421],{},[24,422,423],{},"merge('system', '^query_log$')",[103,425,426],{},"finds no table",[88,428,429,437],{},[103,430,431,405,434],{},[24,432,433],{},"system.tables",[24,435,436],{},"SHOW TABLES",[103,438,439],{},"empty",[88,441,442,447],{},[103,443,444],{},[24,445,446],{},"information_schema.tables",[103,448,449],{},[24,450,416],{},[88,452,453,456],{},[103,454,455],{},"any table of any database",[103,457,458],{},[24,459,416],{},[88,461,462,470],{},[103,463,464,405,467],{},[24,465,466],{},"url(…)",[24,468,469],{},"file(…)",[103,471,472,474,475,47,478,481],{},[24,473,416],{}," (needs ",[24,476,477],{},"URL",[24,479,480],{},"FILE",")",[88,483,484,489],{},[103,485,486],{},[24,487,488],{},"system.settings",[103,490,491],{},"readable — harmless, and the client library needs it",[493,494,496],"h3",{"id":495},"one-thing-it-does-not-stop","One thing it does not stop",[14,498,499,502,503,506,507,509,510,513,514,517,518,521,522,526,527,530,531,534],{},[24,500,501],{},"INSERT INTO FUNCTION s3(…)"," ",[29,504,505],{},"succeeds"," even with ",[24,508,293],{},": on these\nClickHouse versions the ",[24,511,512],{},"S3"," grant covers reads and writes alike. It is not\nreachable from the assistant — the SQL node refuses ",[24,515,516],{},"INSERT",", refuses ",[24,519,520],{},"s3(",", and\nthe query never sees the storage credentials, which only the engine writes into\nthe SQL it builds — but it means this user is ",[523,524,525],"em",{},"read-only on the server",", not\n",[523,528,529],{},"read-only on the bucket",". From ClickHouse 25.7 the grant can be split\n(",[24,532,533],{},"GRANT READ ON S3","); use that form when the service offers it.",[77,536,538],{"id":537},"checking-it","Checking it",[174,540,542],{"className":176,"code":541,"language":178,"meta":179,"style":179},"-- as the ai user: must work\nSELECT 1;\n-- must fail with ACCESS_DENIED\nSELECT count() FROM system.query_log;\n",[24,543,544,549,558,563],{"__ignoreMap":179},[183,545,546],{"class":185,"line":186},[183,547,548],{"class":246},"-- as the ai user: must work\n",[183,550,551,553,556],{"class":185,"line":217},[183,552,26],{"class":189},[183,554,555],{"class":229}," 1",[183,557,233],{"class":200},[183,559,560],{"class":185,"line":236},[183,561,562],{"class":246},"-- must fail with ACCESS_DENIED\n",[183,564,565,567,570,573,576,579,581,584],{"class":185,"line":243},[183,566,26],{"class":189},[183,568,569],{"class":229}," count",[183,571,572],{"class":200},"() ",[183,574,575],{"class":189},"FROM",[183,577,578],{"class":229}," system",[183,580,72],{"class":200},[183,582,583],{"class":229},"query_log",[183,585,233],{"class":200},[14,587,588,589,592,593,596,597,72],{},"Then ask the assistant a question on a datasource with the ",[24,590,591],{},"clickhouse"," engine\nselected, and look at the server's ",[24,594,595],{},"system.query_log"," as an administrator: the\nassistant's queries must appear with ",[24,598,599],{},"user = 'ai'",[601,602,603],"style",{},"html pre.shiki code .s6PUj, html code.shiki .s6PUj{--shiki-default:#F47067;--shiki-light:#D73A49}html pre.shiki code .saVmf, html code.shiki .saVmf{--shiki-default:#DCBDFB;--shiki-light:#6F42C1}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":179,"searchDepth":217,"depth":236,"links":605},[606,607,608,611],{"id":79,"depth":217,"text":80},{"id":168,"depth":217,"text":169},{"id":361,"depth":217,"text":362,"children":609},[610],{"id":495,"depth":236,"text":496},{"id":537,"depth":217,"text":538},"The engine talks to the external ClickHouse with one account, and that account\ncan read everything on the server. The AI assistant (AI in Tabularia) hands a free-form SELECT\nto every user with VIEW on a datasource, so its queries get two barriers:","md",{"source":615},"docs\u002Fengines\u002Fclickhouse-ai-user.md","\u002Fdocs\u002Fengines\u002Fclickhouse-ai-user",{"title":5,"description":612},"docs\u002Fengines\u002Fclickhouse-ai-user","6FCKmdbaNInJTiE6NDiVwHTHrzIZuhGqpdm_z1OvoOQ",[621,640,696,728,760,788,813,839],{"title":622,"icon":623,"intro":624,"items":625},"Getting started","start","Install it on one machine, learn the few ideas everything else is built on, and run a first flow.",[626,628,631,634,637],{"path":627,"title":622},"\u002Fdocs\u002Fgetting-started",{"path":629,"title":630},"\u002Fdocs\u002Fgetting-started-docker-compose","Walkthrough with Docker Compose",{"path":632,"title":633},"\u002Fdocs\u002Fdeploy\u002Fminikube","Try it on your computer (minikube)",{"path":635,"title":636},"\u002Fdocs\u002Fconcepts","Concepts",{"path":638,"title":639},"\u002Fdocs\u002Foverview","Overview (README)",{"title":641,"icon":642,"intro":643,"items":644},"Capabilities","capabilities","What you can build: visual flows, analysis on any step, AI questions, pixel-perfect reports, dashboards, Python, R and SQL scripts, containers and the orchestration that ties them together.",[645,648,651,654,657,660,663,666,669,672,675,678,681,684,687,690,693],{"path":646,"title":647},"\u002Fdocs\u002Fcapabilities","All capabilities",{"path":649,"title":650},"\u002Fdocs\u002Fcapabilities\u002Fflows","Low-code flows",{"path":652,"title":653},"\u002Fdocs\u002Fcapabilities\u002Fpreview-and-analysis","Preview and analysis",{"path":655,"title":656},"\u002Fdocs\u002Fcapabilities\u002Fask-your-data","Ask your data with AI",{"path":658,"title":659},"\u002Fdocs\u002Fcapabilities\u002Freports","Pixel-perfect reports",{"path":661,"title":662},"\u002Fdocs\u002Fcapabilities\u002Freport-functions","Report functions",{"path":664,"title":665},"\u002Fdocs\u002Fcapabilities\u002Fdashboards","Dashboards",{"path":667,"title":668},"\u002Fdocs\u002Fscripting","Python and R",{"path":670,"title":671},"\u002Fdocs\u002Fsql-scripts","SQL scripts",{"path":673,"title":674},"\u002Fdocs\u002Fcapabilities\u002Fnotebooks","marimo notebooks as apps",{"path":676,"title":677},"\u002Fdocs\u002Fcontainer-node","Docker images in flows",{"path":679,"title":680},"\u002Fdocs\u002Fcapabilities\u002Fflow-groups","Flow groups: orchestration",{"path":682,"title":683},"\u002Fdocs\u002Fcapabilities\u002Fscheduling","Scheduling",{"path":685,"title":686},"\u002Fdocs\u002Fpushmail","Pushmail: datasources, reports and dashboards by email",{"path":688,"title":689},"\u002Fdocs\u002Fplaceholders","Dynamic dates",{"path":691,"title":692},"\u002Fdocs\u002Fcapabilities\u002Fengines","Engines",{"path":694,"title":695},"\u002Fdocs\u002Fengines\u002Fengine-differences","Engine differences",{"title":697,"icon":698,"intro":699,"items":700},"Sources","sources","Every way data comes in: files, databases, buckets, SFTP folders, HTTP APIs, SharePoint, Google Sheets, and the results of your own flows.",[701,704,707,710,713,716,719,722,725],{"path":702,"title":703},"\u002Fdocs\u002Fsources","All sources",{"path":705,"title":706},"\u002Fdocs\u002Fsources\u002Ffiles","Uploaded files",{"path":708,"title":709},"\u002Fdocs\u002Fsources\u002Fdatabases","Database tables and queries",{"path":711,"title":712},"\u002Fdocs\u002Fs3-datasource","S3 buckets",{"path":714,"title":715},"\u002Fdocs\u002Fsftp","SFTP folders",{"path":717,"title":718},"\u002Fdocs\u002Fapi-datasource","HTTP APIs",{"path":720,"title":721},"\u002Fdocs\u002Fsources\u002Fsharepoint","Excel on SharePoint",{"path":723,"title":724},"\u002Fdocs\u002Fsources\u002Fgoogle-sheets","Google Sheets",{"path":726,"title":727},"\u002Fdocs\u002Fsources\u002Fflow-outputs","Results of flows and scripts",{"title":729,"icon":730,"intro":731,"items":732},"Connections","connections","The systems Tabularia reads from and writes to, how each connection is set up, and who may use it.",[733,736,739,742,745,747,748,750,751,754,757],{"path":734,"title":735},"\u002Fdocs\u002Fconnections","All connections",{"path":737,"title":738},"\u002Fdocs\u002Fconnections\u002Fdatabases","Databases",{"path":740,"title":741},"\u002Fdocs\u002Fsqlserver","Microsoft SQL Server driver",{"path":743,"title":744},"\u002Fdocs\u002Fconnections\u002Fs3","S3 and compatible storage",{"path":714,"title":746},"SFTP servers",{"path":717,"title":718},{"path":720,"title":749},"SharePoint",{"path":723,"title":724},{"path":752,"title":753},"\u002Fdocs\u002Fconnections\u002Fsmtp","SMTP email",{"path":755,"title":756},"\u002Fdocs\u002Fcontainer-registries","Container registries",{"path":758,"title":759},"\u002Fdocs\u002Fconnections\u002Fdestinations","Writing data out",{"title":761,"icon":762,"intro":763,"items":764},"Integrations","integrations","How Tabularia fits your stack: AI providers, the REST API and webhooks, dbt, OpenLineage, single sign-on, SIEM and monitoring.",[765,768,770,773,776,779,782,785],{"path":766,"title":767},"\u002Fdocs\u002Fintegrations","All integrations",{"path":20,"title":769},"AI: where it is used",{"path":771,"title":772},"\u002Fdocs\u002Fapi","API, tokens and webhooks",{"path":774,"title":775},"\u002Fdocs\u002Fdbt-export","Export to dbt",{"path":777,"title":778},"\u002Fdocs\u002Fopenlineage","OpenLineage",{"path":780,"title":781},"\u002Fdocs\u002Fsso-keycloak","Single sign-on with Keycloak",{"path":783,"title":784},"\u002Fdocs\u002Fsiem-splunk","Audit log to Splunk",{"path":786,"title":787},"\u002Fdocs\u002Fintegrations\u002Fmonitoring","Monitoring",{"title":789,"icon":790,"intro":791,"items":792},"Security & governance","governance","Who may see and do what, what is recorded, and how data quality and personal data are kept in check.",[793,796,799,802,805,808,811],{"path":794,"title":795},"\u002Fdocs\u002Fgovernance","Overview",{"path":797,"title":798},"\u002Fdocs\u002Fgovernance\u002Fpermissions","Permissions",{"path":800,"title":801},"\u002Fdocs\u002Fgovernance\u002Faudit-log","Audit log",{"path":803,"title":804},"\u002Fdocs\u002Fgovernance\u002Fdata-contracts","Data contracts",{"path":806,"title":807},"\u002Fdocs\u002Fdesign\u002Fsso-group-mapping","SSO and group mapping",{"path":809,"title":810},"\u002Fdocs\u002Fexamples\u002Fkeycloak","Keycloak example on a laptop",{"path":616,"title":812},"A ClickHouse user for the AI assistant",{"title":814,"icon":815,"intro":816,"items":817},"Install & operate","operate","Run it in production on Kubernetes, configure it, upgrade it and keep it healthy.",[818,821,824,827,830,833,836],{"path":819,"title":820},"\u002Fdocs\u002Fdeploy\u002Fkubernetes","Kubernetes and Helm",{"path":822,"title":823},"\u002Fdocs\u002Fdeploy\u002Fkind","A company-like cluster on one computer (kind)",{"path":825,"title":826},"\u002Fdocs\u002Fdeploy\u002Fupgrading","Upgrading, backups and restore",{"path":828,"title":829},"\u002Fdocs\u002Fdeploy\u002Frelease-checklist","Install and upgrade checklist",{"path":831,"title":832},"\u002Fdocs\u002Fconfiguration","Configuration reference",{"path":834,"title":835},"\u002Fdocs\u002Fengine-settings","Settings from the panel",{"path":837,"title":838},"\u002Fdocs\u002Fdatabase-migrations","Database migrations",{"title":840,"icon":841,"intro":842,"items":843},"Reference","reference","Release process and the remaining reference pages.",[844],{"path":845,"title":846},"\u002Fdocs\u002Frelease-process","Release process",1793656372218]