[{"data":1,"prerenderedAt":3325},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fdbt-export":3,"docs-nav":3101},{"id":4,"title":5,"body":6,"description":3093,"extension":3094,"meta":3095,"navigation":1922,"path":3097,"seo":3098,"stem":3099,"__hash__":3100},"docs\u002Fdocs\u002Fdbt-export.md","Export a flow as a dbt project",{"type":7,"value":8,"toc":3076},"minimark",[9,13,31,44,49,160,170,177,506,524,577,646,720,725,786,789,870,917,1020,1091,1095,1102,1109,1116,1306,1324,1337,1341,1344,1423,1430,1441,1444,1459,1469,1472,1505,1509,1517,1554,1558,1680,1712,1721,1731,1734,1822,1873,1877,1899,2256,2260,2298,2315,2319,2339,2519,2541,2545,2629,2688,2692,2695,2800,2815,2819,3072],[10,11,5],"h1",{"id":12},"export-a-flow-as-a-dbt-project",[14,15,16,17,21,22,26,27,30],"p",{},"A flow designed in Tabularia can leave as a ",[18,19,20],"strong",{},"dbt project",": a zip with one model per\noutput, the shared steps as their own models, ",[23,24,25],"code",{},"sources.yml",", ",[23,28,29],{},"schema.yml"," with the\ncolumns, their descriptions and the data contract as dbt tests, seeds for the files\nthe flow reads, and a README that says how to run it. Put it in a repository, run it\nin your CI, read it in your catalog: the flow is no longer locked in the tool.",[14,32,33,34,37,38,43],{},"It is an ",[18,35,36],{},"exporter, not a second engine",": Tabularia keeps running the flow as it\ndoes; the dbt project is a faithful, versionable copy of the same transformations\nthat you own from then on. The other integrations are listed in\n",[39,40,42],"a",{"href":41},"\u002Fdocs\u002Fintegrations","Integrations",".",[45,46,48],"h2",{"id":47},"three-targets","Three targets",[50,51,52,71],"table",{},[53,54,55],"thead",{},[56,57,58,62,65,68],"tr",{},[59,60,61],"th",{},"Target",[59,63,64],{},"Where the SQL runs",[59,66,67],{},"Needs",[59,69,70],{},"When",[72,73,74,109,133],"tbody",{},[56,75,76,82,85,106],{},[77,78,79],"td",{},[18,80,81],{},"ClickHouse",[77,83,84],{},"In your ClickHouse — by default the one Tabularia computes on. Postgres, MySQL and MariaDB sources are read live through ClickHouse's PostgreSQL\u002FMySQL table engines",[77,86,87,90,91,26,94,97,98,101,102,105],{},[23,88,89],{},"pip install dbt-clickhouse","; a ClickHouse user that can ",[23,92,93],{},"CREATE DATABASE",[23,95,96],{},"CREATE TABLE"," and ",[23,99,100],{},"DROP"," on the ",[23,103,104],{},"tab_src_*"," databases; ClickHouse must reach the source databases",[77,107,108],{},"The usual case: the ClickHouse engine, data in Postgres\u002FMySQL (any number of connections), files as seeds",[56,110,111,117,120,126],{},[77,112,113,116],{},[18,114,115],{},"dbt-duckdb"," (federated)",[77,118,119],{},"In DuckDB, which attaches the source databases and reads their tables live",[77,121,122,125],{},[23,123,124],{},"pip install dbt-duckdb","; network access to the sources",[77,127,128,129,132],{},"Sources on PostgreSQL, MySQL or MariaDB, on any number of connections; any operation of the editor, ",[23,130,131],{},"pivot"," included",[56,134,135,140,150,153],{},[77,136,137],{},[18,138,139],{},"Native warehouse",[77,141,142,143,146,147],{},"Inside the source database itself: ",[18,144,145],{},"dbt-postgres"," or ",[18,148,149],{},"dbt-clickhouse",[77,151,152],{},"The adapter of that database",[77,154,155,156,159],{},"All sources on the ",[18,157,158],{},"same"," PostgreSQL or ClickHouse connection; the result lands in that database, next to the data. There is no native target on MySQL or MariaDB: the federated and the ClickHouse targets read them live",[14,161,162,165,166,169],{},[18,163,164],{},"The project gives the data Tabularia publishes."," The first step of every model\ncasts the source columns to the types Tabularia read them with, so the project sees\nthe data as the flow does even where the warehouse types them differently (a column of\na type the export cannot write — an array, a uuid — passes through as the warehouse has\nit, and the others are still cast). A Postgres ",[23,167,168],{},"timestamptz",", which Tabularia reads as a\nUTC instant, becomes that instant in UTC in the first step, whatever the time zone of\nthe session that runs dbt; the dbt-duckdb profile also pins the session to UTC, as\nTabularia's engine does.",[14,171,172,173,176],{},"The federated target runs every operation with the very SQL functions of\nTabularia's DuckDB engine (the cast that trims text and truncates numbers, the\npredicates, the ",[23,174,175],{},"NULLS LAST"," of sorting, the join that fills the key of right\u002Ffull\njoins, the pivot labels) — imported, not copied. The native and ClickHouse targets\ntranslate to the warehouse's dialect with sqlglot and replace what the dialect lacks\nwith portable forms that give Tabularia's values:",[178,179,180,336,369,491],"ul",{},[181,182,183,189,190,193,194,197,198,201,202,205,206,208,209,212,213,26,216,26,219,26,222,225,226,229,230,26,233,97,236,239,240,26,243,246,247,250,251,250,254,250,257,250,260,263,264,250,267,250,270,250,273,250,276,279,280,263,283,286,287,26,290,293,294,296,297,300,301,304,305,308,309,312,313,316,317,250,320,323,324,327,328,331,332,335],"li",{},[18,184,185,186],{},"casts guarded by the value, not by ",[23,187,188],{},"TRY_CAST",": a text is converted where both of\nTabularia's in-process engines (Polars and DuckDB) convert it, with their value. A date\nis a real day (month lengths, 29 February in leap years) with a one- or two-digit month\nand day (",[23,191,192],{},"2024-1-5",") and a year of one to four digits (",[23,195,196],{},"24-01-05"," is the year 24, as both\nengines read it, not 2024). An instant has a ",[23,199,200],{},"T"," or any white space before the time,\nhours, minutes and seconds of one or two digits (",[23,203,204],{},"2024-01-05 1:3:5","), a fraction cut at\nthe microsecond, maybe a trailing ",[23,207,43],{},", and after the seconds maybe a ",[23,210,211],{},"Z"," or an offset\n(",[23,214,215],{},"+02",[23,217,218],{},"+0200",[23,220,221],{},"+02:00",[23,223,224],{},"-05:30","), which both engines drop: ",[23,227,228],{},"10:30:00+02"," is 10:30.\nA float takes ",[23,231,232],{},"inf",[23,234,235],{},"infinity",[23,237,238],{},"nan"," with a sign, in any case, and a number beyond\nthe double range is ±infinity or ±0 (",[23,241,242],{},"1e400",[23,244,245],{},"1e-400",") — it used to fail the whole\nPostgres model. A boolean takes DuckDB's words ",[23,248,249],{},"true","\u002F",[23,252,253],{},"t",[23,255,256],{},"1",[23,258,259],{},"yes",[23,261,262],{},"y"," and\n",[23,265,266],{},"false",[23,268,269],{},"f",[23,271,272],{},"0",[23,274,275],{},"no",[23,277,278],{},"n"," (Polars cannot cast text to a boolean). Where the two engines\ndisagree these targets give the DuckDB engine's value or NULL: ",[23,281,282],{},"2024\u002F01\u002F01",[23,284,285],{},"2024-01-01 10:00"," as a date, ",[23,288,289],{},"24:00:00",[23,291,292],{},"23:59:60",", a ",[23,295,211],{}," right after the minutes, a\nlowercase ",[23,298,299],{},"z",", an offset after a space, and ",[23,302,303],{},"1_000.5"," as a float are NULL. Text →\ninteger only within the 64-bit range, a float or a decimal → integer only within it\n(NaN and infinities give NULL). A bad value gives NULL, as in Tabularia — never a\nfailed model on Postgres, never a made-up value on ClickHouse (",[23,306,307],{},"2024-02-30"," used to\nbecome 1 March there). A float written as text keeps its ",[23,310,311],{},".0"," (",[23,314,315],{},"200.0","), as the\nengines write it; boolean ↔ number casts and ",[23,318,319],{},"min",[23,321,322],{},"max"," of a boolean run on Postgres;\na ",[23,325,326],{},"UInt64"," (a ClickHouse hash key, a MySQL ",[23,329,330],{},"BIGINT UNSIGNED",") stays unsigned\n(",[23,333,334],{},"numeric(20,0)"," on Postgres) and a value over 2^63 cast to integer is NULL;",[181,337,338,341,342,345,346,250,348,350,351,250,354,357,358,26,361,364,365,368],{},[18,339,340],{},"text in byte order on Postgres",", as Tabularia compares it: the first step gives the\nsource text columns ",[23,343,344],{},"COLLATE \"C\"",", and sort keys, ranges, ",[23,347,319],{},[23,349,322],{}," and the order of\n",[23,352,353],{},"first",[23,355,356],{},"last"," add it to text the flow makes. Equality, joins, ",[23,359,360],{},"GROUP BY",[23,362,363],{},"DISTINCT","\nand ",[23,366,367],{},"ORDER BY"," then agree with Tabularia whatever the database's collation (also a\ncase-insensitive one). ClickHouse already compares bytes;",[181,370,371,250,374,250,377,380,381,250,384,250,387,26,390,393,394,397,398,401,402,405,406,409,410,26,413,26,416,26,419,26,422,26,425,428,429,26,432,26,435,26,438,26,441,26,444,447,448,451,452,455,456,26,459,462,463,466,467,470,471,474,475,26,477,364,480,483,484,486,487,490],{},[23,372,373],{},"strpos",[23,375,376],{},"left",[23,378,379],{},"right"," instead of ",[23,382,383],{},"contains",[23,385,386],{},"starts_with",[23,388,389],{},"ends_with",[23,391,392],{},"EXISTS"," for\nsemi and anti joins, explicit column lists instead of DuckDB's\n",[23,395,396],{},"* EXCLUDE\u002FREPLACE\u002FRENAME"," and of ",[23,399,400],{},"UNION BY NAME",", an unpivot written as ",[23,403,404],{},"UNION ALL","\nwith the engine's supertype for the value, a cast on ",[23,407,408],{},"ROUND"," for PostgreSQL, DuckDB's\n",[23,411,412],{},"year(d)",[23,414,415],{},"month(d)",[23,417,418],{},"day(d)",[23,420,421],{},"dayofweek(d)",[23,423,424],{},"isodow(d)",[23,426,427],{},"dayofyear(d)",",\n",[23,430,431],{},"quarter(d)",[23,433,434],{},"week(d)",[23,436,437],{},"weekofyear(d)",[23,439,440],{},"hour(t)",[23,442,443],{},"minute(t)",[23,445,446],{},"second(t)"," as an\n",[23,449,450],{},"EXTRACT"," cast to an integer on PostgreSQL (as in DuckDB, a second without its\nfraction), the ",[23,453,454],{},"*UTF8"," string functions on ClickHouse (",[23,457,458],{},"upperUTF8",[23,460,461],{},"substringUTF8","…:\n",[23,464,465],{},"upper('città')"," is ",[23,468,469],{},"CITTÀ",", as in Tabularia), and ",[23,472,473],{},"positionUTF8"," for ",[23,476,373],{},[23,478,479],{},"instr",[23,481,482],{},"position",", which count characters as DuckDB does (ClickHouse's ",[23,485,482],{}," counts\nbytes; so does the Polars engine, whose text is flagged for review anyway). On\nPostgres sqlglot's SQL is not run at export time, and the README of a native Postgres\nproject says so: run ",[23,488,489],{},"dbt build"," once on a copy (ClickHouse models are read by\nClickHouse's parser, see below);",[181,492,493,496,497,500,501,97,503,505],{},[18,494,495],{},"aggregates with the engine's types",": a sum of integers or booleans is a 64-bit\ninteger (a boolean sum counts the true values, also on Postgres), a sum of a decimal\nkeeps its scale; the mean, deviation and variance of an integer, a decimal or a\nboolean are doubles (Postgres gave a ",[23,498,499],{},"numeric","), a boolean counting as 1 or 0 as in\nPolars (the DuckDB engine has no mean of a boolean); the mean and the median of dates\nor instants are a timestamp and run on Postgres and ClickHouse too — the mean over the\nmicroseconds since 1970, rounded to the microsecond, the median the midpoint of the two\nmiddle values; ",[23,502,353],{},[23,504,356],{}," follow the sort before them on every target.",[14,507,508,509,512,513,516,517,519,520,523],{},"To know the columns and types at every step, the export builds each model's steps, one\nat a time, as ",[18,510,511],{},"empty"," tables in an in-memory DuckDB, starting from empty tables typed\nlike the sources — no data, no file, no network — and reads each step's real column\nnames and types from it: a compute's type, a ",[23,514,515],{},"sql"," node's columns, an aggregate's type.\nThat is what the casts, the unpivot, the joins and the types recorded for models read by\nother models use. Each step binds against the table of the step before it alone, so the\ntime grows with the number of steps, not faster. It stops (and the export goes on with\nits own tracking) after a pivot and at a step that does not bind in DuckDB. A source\ncolumn of a type the export cannot name (a uuid, a bytea, an array) does not stop it:\nthe steps that do not read that column keep their types and casts, a ",[23,518,515],{}," node gets\nDuckDB's names, and only a cast of a column made from it is refused\n(",[23,521,522],{},"cast_type_unknown",").",[14,525,526,527,530,531,533,534,536,537,540,541,97,544,547,548,26,551,554,555,557,558,561,562,250,565,568,569,572,573,576],{},"Column names keep their case (",[23,528,529],{},"\"Città\""," stays ",[23,532,529],{},"). A ",[23,535,515],{}," node keeps its query,\nits own ",[23,538,539],{},"WITH"," included: ",[23,542,543],{},"self",[23,545,546],{},"input"," point at the previous step; an expression\nwithout an alias keeps the name DuckDB gives it (",[23,549,550],{},"(i + 1)",[23,552,553],{},"count_star()","), so the\nsteps after it find it. A ",[23,556,131],{}," has no portable SQL, so it is refused on the native\nand ClickHouse targets; after a pivot, on the federated target, a join keeps every\npivot column (a right-side column with the name of a left-side one gets ",[23,559,560],{},"_right",", as in\nTabularia; so does the index when the join reads it back with ",[23,563,564],{},"left_on",[23,566,567],{},"right_on",":\n",[23,570,571],{},"\u003Cindex>_right",") and a compute replaces a pivot column in place or adds one. ",[23,574,575],{},"foreach","\nis refused everywhere (dbt has no runtime loop).",[14,578,579,585,586,589,590,592,593,596,597,599,600,603,604,606,607,26,610,26,613,616,617,26,620,26,623,626,627,630,631,634,635,638,639,641,642,645],{},[18,580,581,582,584],{},"The text of a compute or of a ",[23,583,515],{}," node is read in the dialect of the flow's engine","\n(its production engine if it has one). DuckDB text is used as it is, except its escape\nstrings: an ",[23,587,588],{},"E'…'"," in a compute or a ",[23,591,515],{}," node is written as the standard string of the\nsame value (",[23,594,595],{},"'…'",", its line breaks raw) wherever the export rewrites the text with\nsqlglot — every native target, the ClickHouse target, and a ",[23,598,515],{}," node on the federated\ntarget — so its bytes are the value DuckDB gives (before, ",[23,601,602],{},"E'a\\\\ b'"," lost its backslash on\nnative Postgres and in a federated ",[23,605,515],{}," node, and ",[23,608,609],{},"\\q",[23,611,612],{},"\\101",[23,614,615],{},"\\x42"," stayed as written\non ClickHouse instead of ",[23,618,619],{},"q",[23,621,622],{},"A",[23,624,625],{},"B","). An escape DuckDB itself refuses (a byte outside\nASCII, ",[23,628,629],{},"\\u",") is left as written; a compute on the federated target keeps its text as\ntyped, which DuckDB reads the same way. ClickHouse and\nBigQuery text is translated by sqlglot, and the model's header and the README list the\nstep as \"Translated from ClickHouse SQL by sqlglot, review\"; text the translation\ncannot run is refused (",[23,632,633],{},"toYear(d)"," on the federated target: use the ClickHouse target,\nwhich runs ClickHouse text as the ClickHouse engine does). Polars text has no sqlglot\ndialect: it is kept as it is and listed as \"Written for the Polars engine, review\" —\n",[23,636,637],{},"CAST"," of a decimal to an integer, ",[23,640,408],{}," of a .5, ",[23,643,644],{},"%"," of a negative number, window\nframes with ties and the names of unaliased expressions can differ there.",[14,647,648,651,652,654,655,658,659,146,662,665,666,669,670,673,674,677,678,681,682,685,686,688,689,692,693,696,697,699,700,702,703,706,707,312,710,474,713,716,717,43],{},[18,649,650],{},"Text typed in the flow is data, never Jinja."," Values, literals, names, compute and\n",[23,653,515],{}," text, datasource queries, descriptions, contract rules and AI text reach the\nwarehouse as they were typed: a value ",[23,656,657],{},"{{ 7 * 7 }}"," stays that text, and\n",[23,660,661],{},"{{ env_var(…) }}",[23,663,664],{},"{% do run_query(…) %}"," in a flow neither reads a variable nor\nruns anything. Each Jinja opener is written as an expression that prints it\n(",[23,667,668],{},"{{ '{%' }}","); a text without openers is written unchanged. No flow is refused for it.\nA carriage return is written the same way, ",[23,671,672],{},"{{ '\\r' }}",": Jinja writes every line break\nof a template as ",[23,675,676],{},"\\n",", and dbt turned ",[23,679,680],{},"'a\\r\\nb'"," into ",[23,683,684],{},"'a\\nb'",". So a text on several\nlines — a value, a compute expression, a ",[23,687,515],{}," node's query, a column name — keeps its\nexact bytes on every target; the model indents only the line breaks outside its string\nliterals, quoted names and comments. dbt also renders every YAML value as a template, and\nJinja drops a template's final line break, so a value that ends with one (a column named\nlike a spreadsheet header that ends with Alt+Enter, ",[23,690,691],{},"Total⏎",") writes it as ",[23,694,695],{},"{{ '\\n' }}",":\nthe column keeps its name in ",[23,698,29],{}," and in its tests (before, the not_null test of\n",[23,701,691],{}," failed with «Referenced column \"Total\" not found»). A name in ",[23,704,705],{},"config()"," (a\nnative output's real table and schema) that holds a quote or a backslash is written as\npieces joined with ",[23,708,709],{},"~",[23,711,712],{},"alias='t' ~ '\\\\' ~ 'x'",[23,714,715],{},"t\\x","): dbt's static parser, which\ndbt uses for models by default, reads a string literal as written, escapes included,\nand gave dbt another table. A plain name stays ",[23,718,719],{},"alias='orders'",[721,722,724],"h3",{"id":723},"the-clickhouse-target","The ClickHouse target",[14,726,727,728,731,732,97,735,738,739,146,742,745,746,748,749,752,753,756,757,760,761,764,765,768,769,771,772,775,776,779,780,782,783,785],{},"The project names one ordinary ClickHouse database per (connection, schema),\n",[23,729,730],{},"tab_src_\u003Cconnection>_\u003Cschema>"," (a schema that is not a plain lowercase name gets a\nfingerprint, so ",[23,733,734],{},"Sales",[23,736,737],{},"sales",", or two Cyrillic schemas, never share one), and in\nit one table per source table the project reads — the tables of the sources and the\nones a datasource query reads — with the ",[23,740,741],{},"PostgreSQL",[23,743,744],{},"MySQL"," table engine.\n",[23,747,25],{}," points at them, so the models use plain ",[23,750,751],{},"source()"," and the dbt docs show\nthe lineage. Their columns are what ClickHouse infers, with the types that would lose\nvalues widened: a date is a ",[23,754,755],{},"Date32"," (ClickHouse's ",[23,758,759],{},"Date"," turned a 1955 birth date into\n2134), an instant a ",[23,762,763],{},"DateTime64(6)"," (its ",[23,766,767],{},"DateTime"," dropped the microseconds and wrapped\ndates before 1970), a Postgres ",[23,770,499],{}," without precision a ",[23,773,774],{},"Float64"," as Tabularia\nreads it (",[23,777,778],{},"Decimal(38, 19)"," failed on a value over 10^19). From there on everything\nruns in ClickHouse, with the native SQL translated to its dialect: every cast writes a\ndate as ",[23,781,755],{},", a timestamp as ",[23,784,763],{},", a time of day as text, as the\nClickHouse engine does.",[14,787,788],{},"The databases and tables are created by a macro in the project — once, and again when a\nsource table changes its columns:",[790,791,796],"pre",{"className":792,"code":793,"language":794,"meta":795,"style":795},"language-bash shiki shiki-themes github-dark-dimmed github-light","export CLICKHOUSE_USER=... CLICKHOUSE_PASSWORD=...   # dbt's own ClickHouse user\nexport TABULARIA_DB_1_PASSWORD=...                   # one per source connection\nDBT_PROFILES_DIR=. dbt run-operation create_tabularia_sources --log-level-file none\n","bash","",[23,797,798,826,842],{"__ignoreMap":795},[799,800,803,807,811,814,817,819,822],"span",{"class":801,"line":802},"line",1,[799,804,806],{"class":805},"s6PUj","export",[799,808,810],{"class":809},"ssh_m"," CLICKHOUSE_USER",[799,812,813],{"class":805},"=",[799,815,816],{"class":809},"... CLICKHOUSE_PASSWORD",[799,818,813],{"class":805},[799,820,821],{"class":809},"...   ",[799,823,825],{"class":824},"sgHix","# dbt's own ClickHouse user\n",[799,827,829,831,834,836,839],{"class":801,"line":828},2,[799,830,806],{"class":805},[799,832,833],{"class":809}," TABULARIA_DB_1_PASSWORD",[799,835,813],{"class":805},[799,837,838],{"class":809},"...                   ",[799,840,841],{"class":824},"# one per source connection\n",[799,843,845,848,850,853,857,860,863,867],{"class":801,"line":844},3,[799,846,847],{"class":809},"DBT_PROFILES_DIR",[799,849,813],{"class":805},[799,851,43],{"class":852},"sXfbr",[799,854,856],{"class":855},"sqRhv"," dbt",[799,858,859],{"class":852}," run-operation",[799,861,862],{"class":852}," create_tabularia_sources",[799,864,866],{"class":865},"swcJU"," --log-level-file",[799,868,869],{"class":852}," none\n",[14,871,872,873,875,876,879,880,883,884,887,888,890,891,894,895,898,899,901,902,906,907,428,910,97,913,916],{},"The source passwords are part of the ",[23,874,96],{}," it runs, and dbt writes the SQL it\nruns into ",[23,877,878],{},"logs\u002Fdbt.log"," at debug level: ",[23,881,882],{},"--log-level-file none"," keeps them out of it\n(the oracle ",[23,885,886],{},"lifecycle.py"," checks both ways: with it the password is in no file, without it\nit is in ",[23,889,878],{},"). ClickHouse keeps the password in\neach table's definition — ",[23,892,893],{},"SHOW CREATE"," prints ",[23,896,897],{},"[HIDDEN]"," — so give it a read-only user\non the sources. A ",[23,900,104],{}," database an earlier export created with the PostgreSQL or\nMySQL ",[903,904,905],"em",{},"database"," engine is dropped and made again (it holds no data). ",[23,908,909],{},"CLICKHOUSE_HOST",[23,911,912],{},"CLICKHOUSE_PORT",[23,914,915],{},"CLICKHOUSE_SECURE"," point the profile at another ClickHouse.",[178,918,919,942,948,963,987,993,998,1013],{},[181,920,921,922,925,926,928,929,932,933,935,936,250,938,941],{},"A ",[18,923,924],{},"datasource defined by a SQL query"," is translated with sqlglot from the\nsource's dialect to ClickHouse, its tables read from the ",[23,927,104],{}," databases; the\nREADME marks it \"review\". A Postgres query reads the names Postgres reads (unquoted\nnames folded to lowercase, quoted ones kept), a typed date literal in a comparison is\nwritten as its text (ClickHouse 24 sent ",[23,930,931],{},"DATE '1970-01-01'"," to Postgres as a number of\ndays), and the query gets the ",[23,934,454],{}," functions and the ",[23,937,755],{},[23,939,940],{},"DateTime64"," types as\nthe models do. A query that does not translate is refused.",[181,943,921,944,947],{},[18,945,946],{},"ClickHouse connection"," is read directly if it is the ClickHouse the project\nruns on; another ClickHouse server is refused (use the native target there).",[181,949,950,951,954,955,958,959,962],{},"The profile sets ",[23,952,953],{},"join_use_nulls = 1"," (with ClickHouse's default the missing side of\nan outer join is 0 or ",[23,956,957],{},"''"," on a non-Nullable column instead of NULL) and\n",[23,960,961],{},"max_query_size = 16777216"," (ClickHouse's 256 KiB refused wide, long flows). A model\nbigger than that, counted with the ephemeral models dbt puts inside it, is refused\nwith the reason: split the flow.",[181,964,965,968,969,26,972,428,975,978,979,982,983,986],{},[18,966,967],{},"The SQL is read by a real ClickHouse parser before it is written."," sqlglot rarely\nfails: it translates constructs ClickHouse does not have (",[23,970,971],{},"SIMILAR TO",[23,973,974],{},"LATERAL",[23,976,977],{},"TABLESAMPLE",") without a word, and the error would surface only at ",[23,980,981],{},"dbt run",". Every\nClickHouse model (this target and the native one on ClickHouse) goes through\n",[23,984,985],{},"EXPLAIN AST"," — no execution, no table needed — on chDB if the engine has it,\notherwise on the engine's ClickHouse, which gets 10 seconds; if neither answers the\ncheck is skipped for the rest of the export and the README says so. A syntax error\nthere is a model that does not translate: the export says so, or, with the AI option,\nasks the AI for a translation. It does not catch functions ClickHouse does not have —\nthat would need the tables.",[181,988,989,990,523],{},"The names of the CTEs are unique across the whole project: an ephemeral model dbt\nputs inside another never shadows one of its steps (ClickHouse 24 failed with\n",[23,991,992],{},"UNKNOWN_TABLE",[181,994,995,997],{},[23,996,131],{}," is refused, as in native.",[181,999,921,1000,146,1002,1005,1006,1009,1010,1012],{},[23,1001,50],{},[23,1003,1004],{},"incremental"," model with a column whose name holds a double quote is\nrefused (",[23,1007,1008],{},"column_name_unsupported","): dbt-clickhouse writes a table's column list\nwithout escaping it, and ",[23,1011,981],{}," failed with a syntax error. Rename the column in the\nflow, or materialize the model as a view (a view builds). The same holds on the native\nClickHouse target.",[181,1014,1015,1016,1019],{},"The result is in the ClickHouse database ",[23,1017,1018],{},"dbt_tabularia","; a database output of the\nflow becomes a model there, not a write into the source database.",[14,1021,1022,1023,1026,1027,263,1030,1033,1034,1037,1038,1042,1043,1046,1047,1050,1051,1054,1055,1058,1059,1062,1063,1066,1067,1070,1071,1074,1075,1078,1079,1082,1083,1086,1087,1090],{},"On the ",[18,1024,1025],{},"native ClickHouse"," target the profile sets the same ",[23,1028,1029],{},"join_use_nulls",[23,1031,1032],{},"max_query_size",", and ",[23,1035,1036],{},"secure"," follows the connection's\n",[39,1039,1041],{"href":1040},"\u002Fdocs\u002Fconnections\u002Fdatabases#encryption-tls","encryption",": on with verify or trust (trust\nadds ",[23,1044,1045],{},"verify: false","), off with none; a connection saved before the option keeps the rule\nTabularia's own client follows, TLS on ports 8443 and 443 and not on the others\n(",[23,1048,1049],{},"CLICKHOUSE_SECURE=true|false"," overrides it; the README says which). On the ",[18,1052,1053],{},"native\nPostgreSQL"," target the profile's ",[23,1056,1057],{},"sslmode"," is the connection's: ",[23,1060,1061],{},"verify-full"," for verify\n(with ",[23,1064,1065],{},"sslrootcert: system",", the system's authorities), ",[23,1068,1069],{},"require"," for trust, ",[23,1072,1073],{},"disable"," for\nnone, nothing for a connection saved before the option. A company authority comes with\nthe project as ",[23,1076,1077],{},"certs\u002Fca.pem",": dbt-postgres names it in ",[23,1080,1081],{},"sslrootcert",", dbt-clickhouse\nreads it with ",[23,1084,1085],{},"SSL_CERT_FILE=certs\u002Fca.pem",". On the federated targets the profile names\nhost, port, database and user only: the README says how to encrypt an encrypted\nconnection there (",[23,1088,1089],{},"PGSSLMODE"," for DuckDB's PostgreSQL, the ClickHouse server's own\nsettings for its table engines).",[721,1092,1094],{"id":1093},"download-options","Download options",[14,1096,1097,1098,1101],{},"In the Flows page, ",[18,1099,1100],{},"Export → dbt"," opens a dialog that shapes the project for the\ndata team's repository. The choices are remembered in the browser, per flow, as they\nwere typed (an empty folder stays empty: its default follows the flow's name).",[14,1103,1104],{},[1105,1106],"img",{"alt":1107,"src":1108},"The dialog Export as a dbt project for Top 20 Products by Revenue: where it runs (ClickHouse, DuckDB federated, native warehouse), complete project or folder, folder, model prefix and schema, the source names of Sample ERP, the materialization and what to include","\u002Fmedia\u002Fguide\u002Fdbt\u002Fdbt-export-dialog.png",[14,1110,1111],{},[903,1112,1113,1115],{},[18,1114,1100],{}," on the sample flow «Top 20 Products by Revenue»: its two sources on the sample ERP get a source name each, its output a materialization.",[50,1117,1118,1128],{},[53,1119,1120],{},[56,1121,1122,1125],{},[59,1123,1124],{},"Option",[59,1126,1127],{},"What it does",[72,1129,1130,1147,1164,1205,1223,1244,1261,1278,1296],{},[56,1131,1132,1137],{},[77,1133,1134],{},[18,1135,1136],{},"Where it runs",[77,1138,1139,1140,1142,1143,1146],{},"ClickHouse, DuckDB (federated) or native. A target that cannot work for the flow is disabled, with the reason: a ",[23,1141,131],{}," outside DuckDB, sources on several connections or on MySQL\u002FMariaDB for native, an Oracle, SQL Server or Trino source, a seed over 50 000 rows, a step saved half done (a join without its right input or its keys, a filter without a column or a value, an aggregation without a column…), a source or output name with a character the target's dbt adapter cannot write (see Limits). ClickHouse with an Oracle, SQL Server or Trino table becomes available in ",[18,1144,1145],{},"Folder for an existing project",": those sources are then ticked «already declared» and cannot be unticked (the team's project must declare them; type the source name it has there); in the complete project ClickHouse stays disabled with the reason",[56,1148,1149,1154],{},[77,1150,1151],{},[18,1152,1153],{},"Complete project",[77,1155,1156,1157,26,1160,1163],{},"Ready to run: ",[23,1158,1159],{},"profiles.yml",[23,1161,1162],{},"dbt_project.yml",", README — as described above",[56,1165,1166,1170],{},[77,1167,1168],{},[18,1169,1145],{},[77,1171,1172,26,1175,26,1178,26,1181,1184,1185,1188,1189,1192,1193,1196,1197,1200,1201,1204],{},[23,1173,1174],{},"models\u002F\u003Cfolder>\u002F",[23,1176,1177],{},"seeds\u002F\u003Cfolder>\u002F",[23,1179,1180],{},"tests\u002F\u003Cfolder>\u002F",[23,1182,1183],{},"macros\u002F\u003Cfolder>\u002F"," to copy into the team's repository, and an ",[23,1186,1187],{},"INTEGRATION.md"," that says what their profile needs (the DuckDB secrets and attachments and the UTC session, ClickHouse's ",[23,1190,1191],{},"custom_settings","), which sources they must already declare, and how to run it: ",[23,1194,1195],{},"dbt build -s +tag:\u003Cflow>,tag:tabularia"," — the flow's models and everything they read, with their tests, but only what came from Tabularia, so a model of the team tagged like the flow is not built, nor what it reads. No ",[23,1198,1199],{},"generate_schema_name"," macro in a folder: it would rename the schemas of their whole project. The default folder is the flow's name as an ASCII slug (accents dropped, at most 40 characters, ",[23,1202,1203],{},"tabularia"," when nothing is left)",[56,1206,1207,1212],{},[77,1208,1209],{},[18,1210,1211],{},"Folder, model prefix",[77,1213,1214,1215,1218,1219,1222],{},"Models and seeds named ",[23,1216,1217],{},"\u003Cprefix>\u003Cname>"," (e.g. ",[23,1220,1221],{},"tab_","), away from the team's own names; the name is the one the dialog shows, with the prefix in front",[56,1224,1225,1230],{},[77,1226,1227],{},[18,1228,1229],{},"Layers",[77,1231,1232,1233,1236,1237,1240,1241],{},"Query models in ",[23,1234,1235],{},"staging\u002F",", shared steps in ",[23,1238,1239],{},"intermediate\u002F",", outputs in ",[23,1242,1243],{},"marts\u002F",[56,1245,1246,1251],{},[77,1247,1248],{},[18,1249,1250],{},"Schema",[77,1252,1253,1254,1256,1257,1260],{},"Complete project only (in a folder the team's profile decides it): where the models land, the profile's schema — ",[23,1255,1018],{}," by default on the native and ClickHouse targets, ",[23,1258,1259],{},"main"," on dbt-duckdb (the profile names no schema)",[56,1262,1263,1268],{},[77,1264,1265],{},[18,1266,1267],{},"Sources",[77,1269,1270,1271,1274,1275,1277],{},"For each (connection, schema), the source name of the team's project. Two sources may share a name only if they have no table in common (dbt could not tell them apart). Folder only: ",[18,1272,1273],{},"already declared"," — no entry in our ",[23,1276,25],{}," (dbt rejects a source declared twice) and, on ClickHouse, no federated database for it; the complete project always declares every source",[56,1279,1280,1285],{},[77,1281,1282],{},[18,1283,1284],{},"Materialization of each output",[77,1286,1287,146,1289,1292,1293,1295],{},[23,1288,50],{},[23,1290,1291],{},"view","; ",[23,1294,1004],{}," too for an append output",[56,1297,1298,1303],{},[77,1299,1300],{},[18,1301,1302],{},"Include",[77,1304,1305],{},"The data contract as dbt tests (off: the declared types stay in the descriptions); who built and exported the flow (off: the emails leave the metadata, the flow id and version stay)",[14,1307,1308,1309,26,1312,1315,1316,1319,1320,1323],{},"Each invalid field says the rule it breaks (first character, allowed characters,\nlength) next to itself, also to a screen reader. A source with no name typed shows the\nname it will get, as the export gives it: when a typed name takes a source's default,\nthat source moves aside (",[23,1310,1311],{},"_2",[23,1313,1314],{},"_3","). Closing the dialog — Esc, ×, Cancel, outside — or\nleaving the page while it prepares the zip cancels the download. The file keeps the\nletters of the flow's name (accented, Greek, Cyrillic…): ",[23,1317,1318],{},"\u003Cflow>_dbt_\u003Ctarget>.zip",", with\n",[23,1321,1322],{},"_folder"," for the folder package.",[14,1325,1326,1327,1330,1331,1333,1334,1336],{},"The oracle ",[23,1328,1329],{},"folder_package.py"," checks the folder package with a \"team project\" that has its\nown model, tagged like the flow, and its own ",[23,1332,25],{}," already declaring the sales\nsource: the folder copied in, with the profile blocks ",[23,1335,1187],{}," gives, must build\nwith the command it gives, give the data Tabularia publishes and leave their model\nalone; without \"already declared\" dbt must reject the duplicate source.",[721,1338,1340],{"id":1339},"ai-optional","AI (optional)",[14,1342,1343],{},"When the assistant is configured and the administrator has enabled a model, the dialog\noffers two more choices, both off by default:",[178,1345,1346,1361],{},[181,1347,1348,1351,1352,1355,1356,1360],{},[18,1349,1350],{},"Descriptions written by AI where they are missing",": a paragraph \"what the flow\ndoes\" for every flow (in the README, and after the description of its outputs), and\na sentence for every column of the outputs, the source tables and the seeds that has\nno description. In English, marked ",[23,1353,1354],{},"(AI)",". The AI sees the structure of the flow —\nnames, types, steps, the descriptions already written — and at most 3 short sample\nvalues per column, none with ",[39,1357,1359],{"href":1358},"\u002Fdocs\u002Fintegrations\u002Fai#metadata-only-mode","metadata-only mode","\non. Characters YAML cannot carry (control characters, DEL,\nU+FFFE\u002FFFFF) are removed from what it writes before it is kept.",[181,1362,1363,1366,1367,1370,1371,1374,1375,26,1378,26,1381,26,1384,26,1387,26,1390,428,1393,1292,1396,26,1399,26,1402,26,1405,26,1408,428,1411,1414,1415,1418,1419,1422],{},[18,1364,1365],{},"An AI translation where a query does not translate",": when the engine cannot\ntranslate a model or a datasource query to the target's SQL, the AI proposes one,\nand the engine uses it only if it passes Tabularia's checks — one ",[23,1368,1369],{},"SELECT",", nothing\nthat writes, no ",[23,1372,1373],{},"SETTINGS",", no table function, no function that reads data or\nsettings outside the model (",[23,1376,1377],{},"dictGet",[23,1379,1380],{},"joinGet",[23,1382,1383],{},"file",[23,1385,1386],{},"url",[23,1388,1389],{},"s3",[23,1391,1392],{},"remote",[23,1394,1395],{},"getSetting",[23,1397,1398],{},"dblink",[23,1400,1401],{},"pg_*",[23,1403,1404],{},"lo_*",[23,1406,1407],{},"current_setting",[23,1409,1410],{},"set_config",[23,1412,1413],{},"nextval","…), only the tables the model reads (on the native and ClickHouse targets,\nonly that model's own sources; a table hidden behind a CTE that is not visible where\nit is read, ",[23,1416,1417],{},"IN db.table",", or a qualified placeholder count as other tables), the same\noutput columns in the same order, and, on ClickHouse, read by ClickHouse's own parser.\nA query over 20 000 characters is not sent (it would arrive cut): the export says so.\nIf a proposal does not pass, the AI gets one more attempt with the reason in front of\nit (a model sometimes sends the query back unchanged); if that fails too, the export\nsays why. A model so translated says it in its header and in its ",[23,1420,1421],{},"meta",", and the\nREADME lists it.",[14,1424,1425,1426,1429],{},"What the AI writes is ",[18,1427,1428],{},"kept per version of the flow",", \"no description\" included:\nitems the AI leaves out, or answers with nothing, are asked once more in the same\nexport, then kept without a description. Re-exporting the same version reuses what is\nkept, without a call — even with the AI switched off since — and gives the same zip; the\nREADME names the model(s) that wrote the texts, not the one configured now. Two exports\nof the same version at once (on any gateway replica) ask the AI once, and the other uses\nthe same texts. A new version asks again. An empty translation is never kept, and a\ntranslation the engine's checks reject is forgotten (unless another export has stored a\nnew one since), so the next export asks again.",[14,1431,1432,1433,312,1436,1440],{},"The instructions the AI gets for both are editable in Administration → AI → ",[903,1434,1435],{},"Agent\ninstructions",[39,1437,1439],{"href":1438},"\u002Fdocs\u002Fconfiguration#the-agents-instructions","configuration","); by default the\ndescriptions are written in English.",[14,1442,1443],{},"With the AI switched off, the dialog still offers what the AI kept for the flow's\ncurrent version: «Use the AI descriptions kept for this version» and «Use the AI\ntranslations kept for this version», each only when the version has such texts. Ticked,\nthe export reuses them without any call and gives the same zip as before. The hint under\nthe AI options names the model(s) that wrote the kept texts (with the AI on as well); for\na kind nothing was kept for, it names the model configured now.",[14,1445,1446,1447,1450,1451,1454,1455,1458],{},"The calls go through the assistant's model, accounting and caps: every AI call of one\nexport shares a time budget, ",[23,1448,1449],{},"AI__DBT_EXPORT_TIMEOUT_SECONDS"," (180 s by default, under\nthe ingress' 300 s: raise both together), and each call has the assistant's per-turn\nlimits (",[23,1452,1453],{},"AI__MAX_COST_PER_TURN_USD",", with a model of no known price counted at an assumed\none, and ",[23,1456,1457],{},"AI__MAX_INPUT_TOKENS_PER_TURN","). What a failed call cost is\nrecorded and counts against the daily caps too. A cap reached, the budget spent or a\nprovider that does not answer stops the export with the reason; the budget also stops\nthe engine's check of a new AI translation once it is spent.",[14,1460,1461,1462,1465,1466,1468],{},"The engine builds the project inside the download request. The gateway waits for it at\nmost ",[23,1463,1464],{},"ENGINE__DBT_EXPORT_TIMEOUT_SECONDS"," (100 s by default) per engine call — the first\none, and the one after each AI translation it checks. Past it the download stops with\n«The engine did not build the dbt project within 100 s …» (HTTP 504); an engine that\ncannot be reached is «The engine cannot be reached (…)» (HTTP 502). The ingress cuts the\nrequest at 300 s: with the AI on, up to ",[23,1467,1449],{}," (180 s) go by\nbefore the last engine call starts, so keep the two together under the ingress' proxy\nread timeout, and raise them together.",[14,1470,1471],{},"Why a target is not available, and why a download was refused, is written in the\nlanguage of the interface (English, Italian, German, Spanish, French). A flow definition\nthe editor would never save (written through the API: a node without an id, text that is\nnot JSON) is a reason in the plan and a refused download, never a server error.",[14,1473,1474,1475,1478,1479,26,1482,1033,1485,1488,1489,1492,1493,1496,1497,1500,1501,1504],{},"The API: ",[23,1476,1477],{},"GET \u002Fflows\u002F{id}\u002Fexport\u002Fdbt\u002Fplan"," (what the dialog needs: targets and why\nnot, sources with their default names, outputs with the allowed materializations, the\ndefault folder, the AI options (",[23,1480,1481],{},"ai.available",[23,1483,1484],{},"ai.model",[23,1486,1487],{},"ai.stored",": whether\ndescriptions and translations are kept for the current version and the models that\nwrote them), and for a target that works only in the folder package, ",[23,1490,1491],{},"requires"," (the\nsources that must be already declared)), ",[23,1494,1495],{},"POST \u002Fflows\u002F{id}\u002Fexport\u002Fdbt"," with the options\nas JSON, and\n",[23,1498,1499],{},"GET \u002Fflows\u002F{id}\u002Fexport\u002Fdbt?target=clickhouse|duckdb|native"," for the complete project\nwith the defaults. All three are for administrators only. ",[18,1502,1503],{},"Only administrators can export","\n(the personal flag or an admin group — give the data team a group): the project is\nself-contained, so it carries the upstream flows, the text of SQL queries and, as\nseeds, the data of sources that are not in a database, including those of folders\nthe person who views the flow cannot see. Observers and ordinary users see only the\nOpenLineage export in the dialog. The download is in the audit log, with the AI models\nthat wrote what it carries.",[45,1506,1508],{"id":1507},"what-the-project-contains","What the project contains",[790,1510,1515],{"className":1511,"code":1513,"language":1514},[1512],"language-text","dbt_project.yml          profile, paths; models materialized as table by default\nprofiles.yml             the connection(s): passwords via env_var, never in the file\nmodels\u002F\n  sources.yml            one source per (connection, schema): tables, descriptions, columns\n  schema.yml             every model: description, columns, descriptions, not_null tests\n  \u003Coutput>.sql           one model per output node of the flow\n  int_\u003Cstep>.sql         the steps shared by several outputs (ephemeral)\n  src_\u003Cdatasource>.sql   a datasource defined by a SQL query (ephemeral)\nseeds\u002F\u003Cname>.csv         the files the flow reads, as CSV (with seeds\u002Fschema.yml)\ntests\u002F\u003Cmodel>__\u003Crule>.sql   the contract rules dbt has no faithful generic test for\nmacros\u002Fgenerate_schema_name.sql   native only: a model with a `schema` lands in that schema\nmacros\u002Fcreate_tabularia_sources.sql   ClickHouse only: the source databases and tables\nREADME.md                what is in it, where it lands, how to run it, what to know\n","text",[23,1516,1513],{"__ignoreMap":795},[14,1518,1519,1520,26,1523,26,1525,26,1528,1530,1531,1534,1535,146,1538,1541,1542,1545,1546,1549,1550,1553],{},"Names that YAML would read as something else — ",[23,1521,1522],{},"on",[23,1524,259],{},[23,1526,1527],{},"null",[23,1529,249],{}," in any case,\nor with YAML punctuation — are quoted; characters YAML does not allow raw (DEL, C1\ncontrols, line and paragraph separators) are written as ",[23,1532,1533],{},"\\uXXXX"," escapes and read back\nexactly. A flow named ",[23,1536,1537],{},"dbt",[23,1539,1540],{},"dbt_\u003Canything>"," gets the project name ",[23,1543,1544],{},"tabularia_\u003Cslug>","\n(dbt refuses a project named like one of its packages). Model names fit Postgres'\n63-byte identifiers whatever the prefix: past 43 bytes (31 for an ephemeral model, which\ndbt-postgres names ",[23,1547,1548],{},"__dbt__cte__\u003Cname>",") a name keeps its start and ends in ",[23,1551,1552],{},"_\u003C8 hex>","\nof the full name.",[721,1555,1557],{"id":1556},"models-and-materializations","Models and materializations",[50,1559,1560,1570],{},[53,1561,1562],{},[56,1563,1564,1567],{},[59,1565,1566],{},"In the flow",[59,1568,1569],{},"In dbt",[72,1571,1572,1581,1605,1620,1632,1642,1650,1669],{},[56,1573,1574,1577],{},[77,1575,1576],{},"Output → datasource",[77,1578,1579],{},[23,1580,50],{},[56,1582,1583,1589],{},[77,1584,1585,1586],{},"Output → database table, ",[903,1587,1588],{},"replace",[77,1590,1591,1593,1594,97,1597,1600,1601,1604],{},[23,1592,50],{},". Native, on the connection dbt runs on: with the ",[23,1595,1596],{},"schema",[23,1598,1599],{},"alias"," of that table, so dbt writes the very table Tabularia writes (a one-part name is in the connection's schema, else ",[23,1602,1603],{},"public"," on Postgres — see below — or the connection's database on ClickHouse)",[56,1606,1607,1612],{},[77,1608,1585,1609],{},[903,1610,1611],{},"append",[77,1613,1614,26,1616,1619],{},[23,1615,1004],{},[23,1617,1618],{},"incremental_strategy='append'"," (native, on the connection dbt runs on: into that same table)",[56,1621,1622,1629],{},[77,1623,1624,1625,1628],{},"Output → database table on ",[18,1626,1627],{},"another"," connection (native)",[77,1630,1631],{},"an ordinary model in the project's schema, named like any other: dbt does not write into another database, and the README says that loading it there is the data team's job",[56,1633,1634,1637],{},[77,1635,1636],{},"Output → S3\u002FGCS file",[77,1638,1639,1641],{},[23,1640,50],{}," (export it from the warehouse)",[56,1643,1644,1647],{},[77,1645,1646],{},"Output → email",[77,1648,1649],{},"no model: an email is not a dataset",[56,1651,1652,1655],{},[77,1653,1654],{},"Steps shared by several outputs",[77,1656,1657,1660,1661,1664,1665,1668],{},[23,1658,1659],{},"int_\u003Cstep>"," model, ",[23,1662,1663],{},"ephemeral",", referenced with ",[23,1666,1667],{},"ref()"," — one definition, as in the editor",[56,1670,1671,1674],{},[77,1672,1673],{},"Publish sort keys",[77,1675,1676,1677,1679],{},"an ",[23,1678,367],{}," at the end of the model, as the app does",[14,1681,1682,1683,474,1685,26,1687,1689,1690,1692,1693,1695,1696,1698,1699,1701,1702,1704,1705,97,1708,1711],{},"On the connection dbt runs on, an output keeps its real table only when its model is\nmaterialized the way Tabularia writes it: ",[23,1684,50],{},[903,1686,1588],{},[23,1688,1004],{}," for\n",[903,1691,1611],{},". Another choice (a ",[23,1694,50],{}," or a ",[23,1697,1291],{}," over an append, a ",[23,1700,1291],{}," over a replace)\nbuilds the model in the project's schema, and the README says why: dbt would rebuild\nTabularia's accumulated table with the last batch only, or drop it for a view. On\nPostgres the alias and the schema are the first 63 bytes of the names, the table Postgres\ncreated for Tabularia; a long name with accented letters that dbt-postgres cannot rebuild\nas a ",[23,1703,50],{}," (it cuts the names of its ",[23,1706,1707],{},"__dbt_tmp",[23,1709,1710],{},"__dbt_backup"," tables by\ncharacters, Postgres cuts names by bytes, and they collide) is built in the project's\nschema with a README note, while an incremental keeps the real table.",[14,1713,1714,1715,1717,1718,1720],{},"A Postgres connection without a Schema: Tabularia reads and writes a one-part table name\nthrough the search path of the database role (a schema named like the role when there is\none, then ",[23,1716,1603],{},"), while the project names it in ",[23,1719,1603],{},". They are the same table\nunless the role has a schema of its own or another search path; the README says so and\nnames the tables. Set the connection's Schema in Tabularia and export again to remove\nthe doubt.",[14,1722,921,1723,1726,1727,1730],{},[903,1724,1725],{},"post-SQL"," on a database output is not carried over: the README tells you to add it\nas a ",[23,1728,1729],{},"post-hook"," if you need it.",[721,1732,1267],{"id":1733},"sources",[50,1735,1736,1745],{},[53,1737,1738],{},[56,1739,1740,1743],{},[59,1741,1742],{},"Datasource",[59,1744,1569],{},[72,1746,1747,1764,1782,1796,1811],{},[56,1748,1749,1752],{},[77,1750,1751],{},"A database table",[77,1753,1754,1757,1758,1760,1761,1763],{},[23,1755,1756],{},"{{ source('\u003Cconnection>_\u003Cschema>', '\u003Ctable>') }}"," in ",[23,1759,25],{},", with the table's and columns' descriptions. A one-part table name is read from the connection's schema, else ",[23,1762,1603],{}," (Postgres, see above for a role with another search path) or the connection's database",[56,1765,1766,1769],{},[77,1767,1768],{},"A SQL query on a database",[77,1770,1771,1774,1775,250,1778,1781],{},[23,1772,1773],{},"src_\u003Cname>",", an ephemeral model with the query. Federated: it runs in the source database through DuckDB's ",[23,1776,1777],{},"postgres_query",[23,1779,1780],{},"mysql_query","; native: the query as written; ClickHouse: translated (see above)",[56,1783,1784,1787],{},[77,1785,1786],{},"The output of another flow",[77,1788,1789,1792,1793,1795],{},[18,1790,1791],{},"only the branch read"," comes in: the model of the Output that publishes the datasource (in the flow that last published it), with its own upstream steps, referenced with ",[23,1794,1667],{},". That flow's other outputs are not in the project, and an upstream model never writes into that flow's destination tables. Recursive, with a cycle check on outputs: two flows feeding each other through different outputs export fine. A datasource that two Outputs of the upstream flow publish is refused (which one wrote it last depends on the order they ran in): keep one Output per datasource",[56,1797,1798,1801],{},[77,1799,1800],{},"A file (uploaded in the editor, imported, SharePoint, an SFTP or S3 folder) or an HTTP API call",[77,1802,1803,1804,1807,1808,1810],{},"a ",[18,1805,1806],{},"seed",": the data as CSV at export time, up to 50 000 rows (beyond that the plan disables every target: import the file into a database). Seeds are tagged ",[23,1809,1203],{}," too",[56,1812,1813,1816],{},[77,1814,1815],{},"A table or query on Oracle, SQL Server or Trino",[77,1817,1818,1819,1821],{},"not read by any target: DuckDB does not federate them, ClickHouse does not read them, and there is no native dbt target for them. The plan says so, with the connection's name, and suggests copying the data into a PostgreSQL or MySQL table. A ",[18,1820,50],{}," can still go, with the ClickHouse target, into an existing dbt project that already declares it as a source: the folder package with that source «already declared» (the plan offers it there). A SQL-query datasource on such a database is exported by no target",[14,1823,1824,1825,1828,1829,26,1831,1833,1834,1837,1838,1840,1841,1844,1845,1847,1848,1850,1851,1853,1854,26,1857,26,1860,26,1863,1866,1867,1869,1870,1872],{},"dbt's seed loaders change some texts: dbt-duckdb reads ",[23,1826,1827],{},"\"\""," as NULL; dbt-postgres and\ndbt-clickhouse read ",[23,1830,957],{},[23,1832,1527],{}," in any case and blank text as NULL and turn ",[23,1835,1836],{},"\\r"," into\n",[23,1839,676],{},"; ClickHouse cuts spaces and tabs at the ends and reads ",[23,1842,1843],{},"\\N"," as NULL. Such a value is\nwritten in the seed CSV as ",[23,1846,709],{}," plus the text, with ",[23,1849,709],{},", space, tab and ",[23,1852,1836],{}," written\n",[23,1855,1856],{},"~0",[23,1858,1859],{},"~1",[23,1861,1862],{},"~2",[23,1864,1865],{},"~3"," (a value that starts with ",[23,1868,709],{}," too), only in the columns that hold\none; the first step of every model that reads the seed gives the exact text back, and\nthe README names the seed and the columns. The seed ",[18,1871,50],{}," itself holds the written\nform: read it through the models.",[721,1874,1876],{"id":1875},"reading-a-model","Reading a model",[14,1878,1879,1880,1882,1883,26,1886,26,1889,428,1892,26,1895,1898],{},"A model is one ",[23,1881,539],{}," list, one step per node, in the order of the flow. Each step\nis named after what it does (",[23,1884,1885],{},"source_orders",[23,1887,1888],{},"filter_canale",[23,1890,1891],{},"compute_netto",[23,1893,1894],{},"aggregate_by_canale",[23,1896,1897],{},"left_join_customers",") and opens with a comment in plain\nwords, so a reviewer reads the flow in the SQL without opening the app:",[790,1900,1903],{"className":1901,"code":1902,"language":515,"meta":795,"style":795},"language-sql shiki shiki-themes github-dark-dimmed github-light","{{ config(materialized='table') }}\n\n-- Exported from Tabularia: flow «Margin flow» (id 12, version 7 saved 2026-10-09 18:00 UTC), output «Margin» (datasource).\n-- Built by anna@example.com. Re-exporting the same version gives this same file.\n\nWITH\nsource_orders AS (\n    -- source: orders, with the column types Tabularia reads\n    SELECT\n        CAST(\"id\" AS BIGINT) AS \"id\",\n        CAST(\"canale\" AS VARCHAR) AS \"canale\",\n        CAST(\"importo\" AS DOUBLE) AS \"importo\"\n    FROM {{ source('db_1_vendite', 'ordini') }}\n),\nfilter_canale AS (\n    -- keep rows where canale = 'web'\n    SELECT *\n    FROM source_orders\n    WHERE \"canale\" = 'web'\n),\ncompute_netto AS (\n    -- netto = importo * 0.8\n    SELECT *, (importo * 0.8) AS \"netto\"\n    FROM filter_canale\n),\naggregate_by_canale AS (\n    -- aggregate by canale: sum(netto) as tot\n    SELECT \"canale\", sum(\"netto\") AS \"tot\"\n    FROM compute_netto\n    GROUP BY \"canale\"\n)\nSELECT *\nFROM aggregate_by_canale\n",[23,1904,1905,1918,1924,1929,1935,1940,1946,1958,1964,1970,1998,2022,2042,2061,2067,2077,2083,2092,2100,2114,2119,2129,2135,2164,2172,2177,2187,2193,2217,2225,2234,2240,2247],{"__ignoreMap":795},[799,1906,1907,1910,1912,1915],{"class":801,"line":802},[799,1908,1909],{"class":809},"{{ config(materialized",[799,1911,813],{"class":805},[799,1913,1914],{"class":852},"'table'",[799,1916,1917],{"class":809},") }}\n",[799,1919,1920],{"class":801,"line":828},[799,1921,1923],{"emptyLinePlaceholder":1922},true,"\n",[799,1925,1926],{"class":801,"line":844},[799,1927,1928],{"class":824},"-- Exported from Tabularia: flow «Margin flow» (id 12, version 7 saved 2026-10-09 18:00 UTC), output «Margin» (datasource).\n",[799,1930,1932],{"class":801,"line":1931},4,[799,1933,1934],{"class":824},"-- Built by anna@example.com. Re-exporting the same version gives this same file.\n",[799,1936,1938],{"class":801,"line":1937},5,[799,1939,1923],{"emptyLinePlaceholder":1922},[799,1941,1943],{"class":801,"line":1942},6,[799,1944,1945],{"class":805},"WITH\n",[799,1947,1949,1952,1955],{"class":801,"line":1948},7,[799,1950,1951],{"class":809},"source_orders ",[799,1953,1954],{"class":805},"AS",[799,1956,1957],{"class":809}," (\n",[799,1959,1961],{"class":801,"line":1960},8,[799,1962,1963],{"class":824},"    -- source: orders, with the column types Tabularia reads\n",[799,1965,1967],{"class":801,"line":1966},9,[799,1968,1969],{"class":805},"    SELECT\n",[799,1971,1973,1976,1979,1982,1985,1988,1991,1993,1996],{"class":801,"line":1972},10,[799,1974,1975],{"class":865},"        CAST",[799,1977,1978],{"class":809},"(",[799,1980,1981],{"class":852},"\"id\"",[799,1983,1984],{"class":805}," AS",[799,1986,1987],{"class":805}," BIGINT",[799,1989,1990],{"class":809},") ",[799,1992,1954],{"class":805},[799,1994,1995],{"class":852}," \"id\"",[799,1997,428],{"class":809},[799,1999,2001,2003,2005,2008,2010,2013,2015,2017,2020],{"class":801,"line":2000},11,[799,2002,1975],{"class":865},[799,2004,1978],{"class":809},[799,2006,2007],{"class":852},"\"canale\"",[799,2009,1984],{"class":805},[799,2011,2012],{"class":805}," VARCHAR",[799,2014,1990],{"class":809},[799,2016,1954],{"class":805},[799,2018,2019],{"class":852}," \"canale\"",[799,2021,428],{"class":809},[799,2023,2025,2027,2029,2032,2034,2037,2039],{"class":801,"line":2024},12,[799,2026,1975],{"class":865},[799,2028,1978],{"class":809},[799,2030,2031],{"class":852},"\"importo\"",[799,2033,1984],{"class":805},[799,2035,2036],{"class":809}," DOUBLE) ",[799,2038,1954],{"class":805},[799,2040,2041],{"class":852}," \"importo\"\n",[799,2043,2045,2048,2051,2054,2056,2059],{"class":801,"line":2044},13,[799,2046,2047],{"class":805},"    FROM",[799,2049,2050],{"class":809}," {{ source(",[799,2052,2053],{"class":852},"'db_1_vendite'",[799,2055,26],{"class":809},[799,2057,2058],{"class":852},"'ordini'",[799,2060,1917],{"class":809},[799,2062,2064],{"class":801,"line":2063},14,[799,2065,2066],{"class":809},"),\n",[799,2068,2070,2073,2075],{"class":801,"line":2069},15,[799,2071,2072],{"class":809},"filter_canale ",[799,2074,1954],{"class":805},[799,2076,1957],{"class":809},[799,2078,2080],{"class":801,"line":2079},16,[799,2081,2082],{"class":824},"    -- keep rows where canale = 'web'\n",[799,2084,2086,2089],{"class":801,"line":2085},17,[799,2087,2088],{"class":805},"    SELECT",[799,2090,2091],{"class":805}," *\n",[799,2093,2095,2097],{"class":801,"line":2094},18,[799,2096,2047],{"class":805},[799,2098,2099],{"class":809}," source_orders\n",[799,2101,2103,2106,2108,2111],{"class":801,"line":2102},19,[799,2104,2105],{"class":805},"    WHERE",[799,2107,2019],{"class":852},[799,2109,2110],{"class":805}," =",[799,2112,2113],{"class":852}," 'web'\n",[799,2115,2117],{"class":801,"line":2116},20,[799,2118,2066],{"class":809},[799,2120,2122,2125,2127],{"class":801,"line":2121},21,[799,2123,2124],{"class":809},"compute_netto ",[799,2126,1954],{"class":805},[799,2128,1957],{"class":809},[799,2130,2132],{"class":801,"line":2131},22,[799,2133,2134],{"class":824},"    -- netto = importo * 0.8\n",[799,2136,2138,2140,2143,2146,2149,2152,2154,2157,2159,2161],{"class":801,"line":2137},23,[799,2139,2088],{"class":805},[799,2141,2142],{"class":805}," *",[799,2144,2145],{"class":809},", (importo ",[799,2147,2148],{"class":805},"*",[799,2150,2151],{"class":865}," 0",[799,2153,43],{"class":809},[799,2155,2156],{"class":865},"8",[799,2158,1990],{"class":809},[799,2160,1954],{"class":805},[799,2162,2163],{"class":852}," \"netto\"\n",[799,2165,2167,2169],{"class":801,"line":2166},24,[799,2168,2047],{"class":805},[799,2170,2171],{"class":809}," filter_canale\n",[799,2173,2175],{"class":801,"line":2174},25,[799,2176,2066],{"class":809},[799,2178,2180,2183,2185],{"class":801,"line":2179},26,[799,2181,2182],{"class":809},"aggregate_by_canale ",[799,2184,1954],{"class":805},[799,2186,1957],{"class":809},[799,2188,2190],{"class":801,"line":2189},27,[799,2191,2192],{"class":824},"    -- aggregate by canale: sum(netto) as tot\n",[799,2194,2196,2198,2200,2202,2205,2207,2210,2212,2214],{"class":801,"line":2195},28,[799,2197,2088],{"class":805},[799,2199,2019],{"class":852},[799,2201,26],{"class":809},[799,2203,2204],{"class":865},"sum",[799,2206,1978],{"class":809},[799,2208,2209],{"class":852},"\"netto\"",[799,2211,1990],{"class":809},[799,2213,1954],{"class":805},[799,2215,2216],{"class":852}," \"tot\"\n",[799,2218,2220,2222],{"class":801,"line":2219},29,[799,2221,2047],{"class":805},[799,2223,2224],{"class":809}," compute_netto\n",[799,2226,2228,2231],{"class":801,"line":2227},30,[799,2229,2230],{"class":805},"    GROUP BY",[799,2232,2233],{"class":852}," \"canale\"\n",[799,2235,2237],{"class":801,"line":2236},31,[799,2238,2239],{"class":809},")\n",[799,2241,2243,2245],{"class":801,"line":2242},32,[799,2244,1369],{"class":805},[799,2246,2091],{"class":805},[799,2248,2250,2253],{"class":801,"line":2249},33,[799,2251,2252],{"class":805},"FROM",[799,2254,2255],{"class":809}," aggregate_by_canale\n",[721,2257,2259],{"id":2258},"where-a-model-comes-from","Where a model comes from",[14,2261,2262,2263,2265,2266,2269,2270,26,2273,428,2276,26,2279,26,2282,26,2285,26,2288,26,2291,2294,2295,2297],{},"The header of every model says which flow it comes from (id, the version that was\nexported and when that version was saved), which output, and who built the flow. The\nsame facts sit in ",[23,2264,29],{}," under ",[23,2267,2268],{},"config.meta.tabularia"," — ",[23,2271,2272],{},"flow",[23,2274,2275],{},"flow_id",[23,2277,2278],{},"flow_version",[23,2280,2281],{},"version_saved_at",[23,2283,2284],{},"folder",[23,2286,2287],{},"owner",[23,2289,2290],{},"output",[23,2292,2293],{},"node"," (the output\nnode's id) — and so in the dbt docs and the manifest, where a catalog can read them.\nWho exported the project is written in the README (or ",[23,2296,1187],{},") only, unless\nthe emails option is off.",[14,2299,2300,2303,2304,2306,2307,2310,2311,2314],{},[18,2301,2302],{},"The export is deterministic."," The same version of a flow with the same options\ngives the same zip, byte for byte — whenever, from whichever gateway or engine process:\nno export time in the files, entries in order with a fixed date, nothing that depends\non the order of a Python set. Whoever exports it, the models, the YAML, the tests and\nthe seeds are the same bytes; only the README's \"Exported by\" line names the person who\nexported it, and with the emails option off the whole zip is the same. Re-exporting into\nthe team's repository shows, in the diff, only what changed in the flow. Every model is\ntagged ",[23,2305,1203],{}," and with the flow's name: ",[23,2308,2309],{},"dbt run -s tag:tabularia"," runs everything\nthat came from the app, ",[23,2312,2313],{},"dbt run -s tag:margin_flow"," one flow. A model made from a\ndatasource's SQL query names that datasource instead of a flow. The README lists the\nflows the project was made from.",[721,2316,2318],{"id":2317},"descriptions-and-the-data-contract","Descriptions and the data contract",[14,2320,2321,2322,2325,2326,2329,2330,26,2333,2329,2336,2338],{},"The datasource an output publishes lends the model its columns and column\ndescriptions. If it has a ",[18,2323,2324],{},"data contract",", every rule becomes a dbt test with the\nrule's severity (",[23,2327,2328],{},"warning"," → ",[23,2331,2332],{},"warn",[23,2334,2335],{},"error",[23,2337,2335],{},"), written to give the verdict\nTabularia's contract check gives:",[50,2340,2341,2350],{},[53,2342,2343],{},[56,2344,2345,2348],{},[59,2346,2347],{},"Contract rule",[59,2349,1537],{},[72,2351,2352,2387,2407,2444,2475,2498,2509],{},[56,2353,2354,2359],{},[77,2355,2356],{},[23,2357,2358],{},"not_null",[77,2360,2361,2362,2364,2365,2368,2369,146,2372,2375,2376,2379,2380,2383,2384,2386],{},"a generic test on the column in ",[23,2363,29],{},", with ",[23,2366,2367],{},"quote: true"," (dbt's generic tests write the name as it is: ",[23,2370,2371],{},"Nome Cliente",[23,2373,2374],{},"select"," was a syntax error, and Postgres read ",[23,2377,2378],{},"Canale"," as ",[23,2381,2382],{},"canale","). Two ",[23,2385,2358],{}," rules on one column (a warning and an error) are one test with the stricter severity; on a column whose name holds a double quote it is a singular test",[56,2388,2389,2395],{},[77,2390,2391,2394],{},[23,2392,2393],{},"unique"," (one column)",[77,2396,2397,2398,293,2401,2403,2404,2406],{},"a singular test ",[23,2399,2400],{},"tests\u002F\u003Cmodel>__unique_\u003Ccolumn>.sql",[23,2402,360],{}," that counts NULLs: two NULLs are a duplicate, as in Tabularia (dbt's generic ",[23,2405,2393],{}," ignores them)",[56,2408,2409,2414],{},[77,2410,2411],{},[23,2412,2413],{},"accepted_values",[77,2415,2397,2416,2419,2420,2423,2424,2427,2428,26,2430,26,2432,26,2434,2436,2437,26,2440,2443],{},[23,2417,2418],{},"tests\u002F\u003Cmodel>__accepted_values_\u003Ccolumn>.sql"," that compares as Tabularia does: a number column as numbers (",[23,2421,2422],{},"3"," = ",[23,2425,2426],{},"3.0",", unless a value is not a number: then as text), a boolean column as booleans (",[23,2429,249],{},[23,2431,266],{},[23,2433,256],{},[23,2435,272],{},"; another value refuses the export), anything else by its text. Quotes and backslashes are escaped for each warehouse (",[23,2438,2439],{},"Sant'Antonio",[23,2441,2442],{},"L'Aquila",")",[56,2445,2446,2451],{},[77,2447,2448],{},[23,2449,2450],{},"pattern",[77,2452,2453,2454,2457,2458,2461,2462,2465,2466,2468,2469,2471,2472,2474],{},"a singular test: the ",[18,2455,2456],{},"whole"," value must match (",[23,2459,2460],{},"^(?:regex)$","), read as text as Tabularia reads it — a date ",[23,2463,2464],{},"2024-01-05",", a timestamp with six fraction digits, a boolean ",[23,2467,249],{},", a decimal with all its scale digits, a whole float with ",[23,2470,311],{},". ",[23,2473,43],{}," does not match a newline, as in Tabularia",[56,2476,2477,2491],{},[77,2478,2479,2481,2482,26,2485,26,2488],{},[23,2480,2393],{}," (several columns), ",[23,2483,2484],{},"range",[23,2486,2487],{},"row_count",[23,2489,2490],{},"expression",[77,2492,2493,2494,2497],{},"a singular test in ",[23,2495,2496],{},"tests\u002F",", a query that selects the violations",[56,2499,2500,2506],{},[77,2501,2502,2505],{},[23,2503,2504],{},"column"," (exists, type)",[77,2507,2508],{},"the column is listed, the declared type in its description",[56,2510,2511,2516],{},[77,2512,2513],{},[23,2514,2515],{},"freshness",[77,2517,2518],{},"a note in the README: dbt checks freshness on sources, not on models",[14,2520,2521,2522,312,2525,2528,2529,26,2531,2533,2534,2537,2538,2540],{},"A singular test is named ",[23,2523,2524],{},"\u003Cmodel>__\u003Ckind>_\u003Crule id>",[23,2526,2527],{},"\u003Cmodel>__\u003Ckind>_\u003Ccolumn>"," for\nthe column rules above); two that would share a file name get ",[23,2530,1311],{},[23,2532,1314],{},".\n",[23,2535,2536],{},"dbt test"," then fails or warns exactly where Tabularia's contract would (two ",[23,2539,2358],{},"\nrules of different severity on one column: the one test fails where the error rule\nfails).",[45,2542,2544],{"id":2543},"run-it","Run it",[790,2546,2548],{"className":792,"code":2547,"language":794,"meta":795,"style":795},"pip install dbt-duckdb          # or dbt-clickhouse, or dbt-postgres for the native target\nexport TABULARIA_DB_1_PASSWORD=...   # one per connection, named in profiles.yml\n# ClickHouse target only: CLICKHOUSE_USER \u002F CLICKHOUSE_PASSWORD, then once\n# DBT_PROFILES_DIR=. dbt run-operation create_tabularia_sources --log-level-file none\nDBT_PROFILES_DIR=. dbt seed     # only if the project has seeds\nDBT_PROFILES_DIR=. dbt run\nDBT_PROFILES_DIR=. dbt test\n",[23,2549,2550,2564,2577,2582,2587,2603,2616],{"__ignoreMap":795},[799,2551,2552,2555,2558,2561],{"class":801,"line":802},[799,2553,2554],{"class":855},"pip",[799,2556,2557],{"class":852}," install",[799,2559,2560],{"class":852}," dbt-duckdb",[799,2562,2563],{"class":824},"          # or dbt-clickhouse, or dbt-postgres for the native target\n",[799,2565,2566,2568,2570,2572,2574],{"class":801,"line":828},[799,2567,806],{"class":805},[799,2569,833],{"class":809},[799,2571,813],{"class":805},[799,2573,821],{"class":809},[799,2575,2576],{"class":824},"# one per connection, named in profiles.yml\n",[799,2578,2579],{"class":801,"line":844},[799,2580,2581],{"class":824},"# ClickHouse target only: CLICKHOUSE_USER \u002F CLICKHOUSE_PASSWORD, then once\n",[799,2583,2584],{"class":801,"line":1931},[799,2585,2586],{"class":824},"# DBT_PROFILES_DIR=. dbt run-operation create_tabularia_sources --log-level-file none\n",[799,2588,2589,2591,2593,2595,2597,2600],{"class":801,"line":1937},[799,2590,847],{"class":809},[799,2592,813],{"class":805},[799,2594,43],{"class":852},[799,2596,856],{"class":855},[799,2598,2599],{"class":852}," seed",[799,2601,2602],{"class":824},"     # only if the project has seeds\n",[799,2604,2605,2607,2609,2611,2613],{"class":801,"line":1942},[799,2606,847],{"class":809},[799,2608,813],{"class":805},[799,2610,43],{"class":852},[799,2612,856],{"class":855},[799,2614,2615],{"class":852}," run\n",[799,2617,2618,2620,2622,2624,2626],{"class":801,"line":1948},[799,2619,847],{"class":809},[799,2621,813],{"class":805},[799,2623,43],{"class":852},[799,2625,856],{"class":855},[799,2627,2628],{"class":852}," test\n",[14,2630,2631,2632,2635,2636,2638,2639,250,2642,2645,2646,312,2649,2652,2653,2656,2657,2660,2661,2664,2665,2668,2669,2672,2673,2675,2676,97,2678,2681,2682,2684,2685,2687],{},"Federated: the result is in ",[23,2633,2634],{},"\u003Cproject>.duckdb",", next to the project, in the schema\n",[23,2637,1259],{}," unless one was chosen (the README says which; the DuckDB ",[23,2640,2641],{},"postgres",[23,2643,2644],{},"mysql","\nextension is downloaded at first run). The source passwords go through DuckDB\n",[18,2647,2648],{},"secrets",[23,2650,2651],{},"secrets:"," in the profile, ",[23,2654,2655],{},"attach … secret:","), never in the ",[23,2658,2659],{},"ATTACH","\npath: a wrong or rotated password, or a source that does not answer, fails with an\nerror that names no connection string (",[23,2662,2663],{},"Unable to connect to Postgres at \"\"","), and the\npassword is not on the console, in ",[23,2666,2667],{},"logs\u002F"," or in ",[23,2670,2671],{},"target\u002F","; passwords with spaces,\nquotes and backslashes work. The folder package's ",[23,2674,1187],{}," gives the same\n",[23,2677,2651],{},[23,2679,2680],{},"attach:"," blocks, and the UTC session, for the team's profile. Native: the\nmodels are in the ",[23,2683,1018],{}," schema of the source database, and the database\noutputs on that connection in their own tables. ClickHouse: in the ",[23,2686,1018],{},"\ndatabase.",[45,2689,2691],{"id":2690},"how-it-is-checked","How it is checked",[14,2693,2694],{},"End-to-end oracles run a real dbt (1.12: dbt-duckdb, dbt-postgres, dbt-clickhouse)\nagainst real databases and compare every model with what Tabularia publishes for the\nsame flow: columns in order, the family of their types (integer, float and decimal apart;\ntext, date, datetime, bool), rows (an empty text is not NULL, a text is compared as\ntext), and order where the order is the point, with dbt running in a time zone other\nthan UTC. They check:",[178,2696,2697,2726,2732,2755,2769,2774,2794],{},[181,2698,2699,2702,2703,97,2705,2708,2709,2711,2712,250,2714,2716,2717,2719,2720,2722,2723,2725],{},[18,2700,2701],{},"every operation of the editor"," with its variants (60 cases: every cast, every\nfilter operator, every join kind, ",[23,2704,1522],{},[23,2706,2707],{},"left_on\u002Fright_on",", both unions,\nevery aggregation, compute that adds or replaces a column, unpivot, sql nodes with\nand without their own ",[23,2710,539],{},", names with spaces, accents, upper case and reserved\nwords, NULLs everywhere) on the three targets, plus pivot — then a join, then a\ncompute, then a join back on its index with ",[23,2713,564],{},[23,2715,567],{}," — on the federated\none; chains whose types the export must know (compute → cast, compute → unpivot, sort\n→ first\u002Flast, sums and min\u002Fmax of booleans, the mean, deviation, variance and median of\nan integer column, floats written as text); a text on several lines (LF, CRLF, a lone\nCR) in a compute, a filter value, a fill_null value, a ",[23,2718,515],{}," node and a column name; a\n",[23,2721,168],{},", an unbounded ",[23,2724,499],{}," up to 10^20, dates before 1970 and after 2149,\nmicroseconds, empty texts; a file read as a seed with the texts dbt's loaders change.\nA case Tabularia refuses must fail in dbt too;",[181,2727,2728,2731],{},[18,2729,2730],{},"the data contract",": for every rule, the dbt test must pass, warn or fail exactly\nwhen Tabularia's contract check does, with the same severity — whole-value patterns\non text, integer and date columns, accepted values with apostrophes and numbers,\nunique with NULLs, not_null on awkward names;",[181,2733,2734,2737,2738,26,2741,26,2744,2747,2748,2750,2751,2754],{},[18,2735,2736],{},"the dbt lifecycle",": ",[23,2739,2740],{},"parse",[23,2742,2743],{},"compile",[23,2745,2746],{},"docs generate","; a table written in append\ndoubles on the second ",[23,2749,981],{}," (as two Tabularia runs do) and ",[23,2752,2753],{},"--full-refresh"," starts\nover — natively, in the flow's own destination table; the source passwords in no file\ndbt writes and not on the console, also when a connection fails;",[181,2756,2757,2760,2761,2763,2764,2668,2766,2768],{},[18,2758,2759],{},"Jinja typed in a flow"," — in a filter value, a compute expression, a column name and\nthe output table's name — reaches the warehouse as text: with dbt's static parser on,\nthe model's alias is the table typed in the flow, ",[23,2762,981],{}," writes that very table with\nthe texts as typed, and nothing that only running Jinja would print shows on the\nconsole, in ",[23,2765,2667],{},[23,2767,2671],{},"; a single quote in a native output's table name\nmust be refused with the reason, by the plan before the download and by the download;",[181,2770,2771,2773],{},[18,2772,81],{}," as native warehouse (NULL on the missing side of an outer join) and\nas the ClickHouse target, and every Sample flow in every target its export plan allows;",[181,2775,2776,2779,2780,1292,2782,2785,2786,2789,2790,2793],{},[18,2777,2778],{},"the folder package"," copied into a team project with its own model and\n",[23,2781,25],{},[18,2783,2784],{},"determinism"," across exports, restarts and exporters; the ",[18,2787,2788],{},"dialog","\nin a browser — its options and the zip they give, the download cancelled when the\ndialog closes or the page is left, the focus back on the Export button, each field's\nrule said to a screen reader, the source names the export will use, a target offered\nonly into the team's project, the AI texts kept for the version; the ",[18,2791,2792],{},"AI"," with the\nreal provider;",[181,2795,2796,2799],{},[18,2797,2798],{},"the refusals",": every code the engine and the gateway raise has its sentence, with\nits parameters, in the five languages, and what the gateway mirrors of the engine to\nsay it in the plan before the download (the parameters each step needs, the seed cap,\nthe databases each target reads, the dialects, the characters refused in each kind of\nname) has not drifted apart — two checks that read the code and need no stack.",[14,2801,2802,2803,568,2811,2814],{},"The oracles are in the repository, in ",[39,2804,2808],{"href":2805,"rel":2806},"https:\u002F\u002Fgithub.com\u002FJumpAsAService\u002FTabularia\u002Fblob\u002Fmain\u002Fe2e\u002Fdbt_export\u002FREADME.md",[2807],"nofollow",[23,2809,2810],{},"e2e\u002Fdbt_export\u002F",[23,2812,2813],{},"python3 e2e\u002Fdbt_export\u002Frun_all.py all"," runs them against a running stack and says which\nare green. Run them after any change to the exporter or to the engines it mirrors, and\nbefore a release.",[45,2816,2818],{"id":2817},"limits","Limits",[178,2820,2821,2831,2846,2856,2859,2898,2920,2931,2954,2978,2995,3024,3038,3046,3052,3055,3062],{},[181,2822,2823,2824,2826,2827,2830],{},"After a ",[23,2825,515],{}," node the export reads its columns from DuckDB at export time. When it\ncannot (a query DuckDB does not bind), the following steps use ",[23,2828,2829],{},"SELECT *",", and on the\nnative targets the steps that need the column list (drop, rename, reorder, cast, fill\nnull, union by name, join) are refused with a message. On the ClickHouse target with\nClickHouse text, an expression without an alias is named by ClickHouse: alias it if a\nlater step needs the list. A cast whose source type cannot be known at all is refused.",[181,2832,2823,2833,2835,2836,2838,2839,2841,2842,2845],{},[23,2834,131],{}," its columns are decided by the data. When the pivot is on the ",[18,2837,376],{},"\nof a join and one of its columns has the name of a right-side column, the model stops\nat ",[23,2840,981],{}," (\"UNION (ALL) BY NAME … the name \"X\" occurs multiple times\") instead of\nrenaming it in silence: rename the right column before the join. A pivot label equal\nto another column but for its case is renamed ",[23,2843,2844],{},"B_1"," by DuckDB, as Tabularia's own\nDuckDB engine does.",[181,2847,2848,2849,428,2852,2855],{},"On the federated target two columns whose names differ only by case (",[23,2850,2851],{},"Codice",[23,2853,2854],{},"codice",") are refused: DuckDB reads them as one column. The native and ClickHouse\ntargets keep both.",[181,2857,2858],{},"On native Postgres a name keeps only its first 63 bytes: one long column name is cut\n(the README says what it becomes), two names equal in their first 63 bytes are refused.",[181,2860,2861,2862,2865,2866,2868,2869,2872,2873,2875,2876,97,2878,2880,2881,2883,2884,2887,2888,2891,2892,2894,2895,2897],{},"Names a dbt adapter cannot write are refused with the reason (",[23,2863,2864],{},"name_not_quotable","),\nnever a project that fails at ",[23,2867,981],{},". The adapters write names unescaped into their\nown SQL: between double quotes on dbt-postgres and dbt-duckdb, between backticks on\ndbt-clickhouse (where a backslash starts an escape), and between single quotes in the\nqueries that list a schema's tables and a table's columns. So: a double quote in any\ntable or schema name on dbt-postgres and dbt-duckdb; a backtick or a backslash on\ndbt-clickhouse; a single quote in a table or schema dbt writes (a native output's real\ntable, the project's schema) on dbt-postgres and dbt-clickhouse (on Postgres an\nincremental's second run and ",[23,2870,2871],{},"dbt docs generate"," failed, on ClickHouse the first ",[23,2874,981],{},"), and in a source's schema or table on all three adapters (dbt reads a source\nthrough ",[23,2877,751],{},[23,2879,489],{}," passes, but ",[23,2882,2871],{}," writes source schemas\nand tables between single quotes in its catalog query: ",[23,2885,2886],{},"upper('o'tab')"," failed); and a\nbackslash in a Postgres schema dbt writes (dbt-postgres compares schemas with ",[23,2889,2890],{},"ILIKE",",\nwhere a backslash escapes: the second ",[23,2893,981],{}," did not find the table). A backslash in\na Postgres source's schema or table, and a double quote in a ClickHouse source's table,\nare fine: real dbt built them twice and documented them. The plan says it before the\ndownload, except, for now, a single quote in a source's schema or table, which the\ndownload refuses when it builds the project. A column whose name holds a double\nquote cannot be in a dbt-clickhouse table (",[23,2896,1008],{},", see the\nClickHouse target).",[181,2899,2900,2901,2903,2904,2907,2908,2911,2912,2915,2916,2919],{},"A PostgreSQL ",[23,2902,499],{}," column is a number (a double) in Tabularia, and the first step\nof every model casts it the same way: exact decimals become doubles, as for a CSV. On\nthe federated target DuckDB's Postgres scanner builds that double itself: some values\ndiffer from Tabularia's in the last digit (",[23,2905,2906],{},"0.14285714285714288"," against ",[23,2909,2910],{},"…85","), and a\n",[23,2913,2914],{},"NaN"," in a ",[23,2917,2918],{},"numeric(p,s)"," becomes NULL. Totals rounded to cents agree; the native\ntarget converts inside Postgres and is exact.",[181,2921,2922,2923,2926,2927,2930],{},"A Postgres ",[23,2924,2925],{},"char(n)"," keeps its trailing blanks in Tabularia (",[23,2928,2929],{},"'ab   '","); the federated\nand native Postgres targets read it without them (the ClickHouse target keeps them),\nso group keys, equality filters and lengths can differ on such columns.",[181,2932,2933,2934,97,2936,2938,2939,2942,2943,26,2946,2949,2950,2953],{},"Casts on the native and ClickHouse targets, rare cases: on the ClickHouse target a\nyear outside 1900–2299 is NULL (",[23,2935,755],{},[23,2937,940],{}," hold no other; ClickHouse\nmoved it to 1900 or 2299, a made-up value); ClickHouse's own float parsing (the\nClickHouse engine's too) reads a subnormal (",[23,2940,2941],{},"4.9e-324",") as 0 and can differ in the last\ndigit for some writings (",[23,2944,2945],{},"123e-10",[23,2947,2948],{},"0.1e-5","); on Postgres a numeric text longer than\n1000 characters is NULL; a non-integer float between 10^15 and 10^16 written as text\nuses an exponent; ",[23,2951,2952],{},"'0000-01-01'"," is NULL (Polars and DuckDB read it differently).",[181,2955,2956,2957,2960,2961,2963,2964,2967,2968,2971,2972,2974,2975,2977],{},"On native Postgres a compute keeps the type Postgres gives its SQL: ",[23,2958,2959],{},"ROUND(x, 2)"," is a\n",[23,2962,499],{}," (Postgres rounds to decimals only a numeric, so the export casts ",[23,2965,2966],{},"x"," to one)\nand an ",[23,2969,2970],{},"EXTRACT(YEAR FROM d)"," written in the flow is a ",[23,2973,499],{},", where Tabularia has a\nfloat and an integer (DuckDB's ",[23,2976,412],{}," and its kin are cast to an integer). The\nvalues are the same.",[181,2979,2980,2981,97,2984,2987,2988,2907,2991,2994],{},"On native Postgres ",[23,2982,2983],{},"std",[23,2985,2986],{},"var"," can differ from Tabularia in the last digit\n(Postgres' own algorithm: ",[23,2989,2990],{},"0.5773502691896257",[23,2992,2993],{},"…58"," for 1, 1, 2); on the native\nand ClickHouse targets a mean or median of instants that falls between two\nmicroseconds may be rounded the other way (the Polars and DuckDB engines already\ndisagree there).",[181,2996,2997,2998,3000,3001,3004,3005,26,3008,26,3011,3013,3014,97,3017,3020,3021,3023],{},"The regex flavours of the ",[23,2999,2450],{}," rule are not identical: Tabularia uses Rust's\nregex, DuckDB and ClickHouse RE2, Postgres its own. RE2 has no ",[23,3002,3003],{},"(?x)","; Postgres reads\ninline flags only at the very start (a leading ",[23,3006,3007],{},"(?i)",[23,3009,3010],{},"(?s)",[23,3012,3003],{}," is moved there);\nRust's ",[23,3015,3016],{},"\\d",[23,3018,3019],{},"\\w"," are Unicode, RE2's ASCII, Postgres' depend on the locale. A float\nwritten as text differs from Tabularia's only for NaN (",[23,3022,238],{},") and some exponents.",[181,3025,3026,3027,3030,3031,3033,3034,3037],{},"On the ClickHouse target: dates and instants cover 1900-01-01 … 2299-12-31, as in the\nClickHouse engine; a time of day is text; a seed table loaded by an older export keeps\nits column types until ",[23,3028,3029],{},"dbt seed --full-refresh","; ClickHouse's PostgreSQL engine reads a\n",[23,3032,168],{}," in the time zone of its Postgres session, so the user ClickHouse connects\nwith needs ",[23,3035,3036],{},"ALTER ROLE \u003Cuser> SET timezone = 'UTC'"," (the README says so when the project\nreads one); a MySQL query must write column names with their exact case; two\ndatasource queries that define a CTE of the same name, read by one model, collide on\nClickHouse 24; planning a long chain of steps grows faster than the steps (150 columns ×\n40 steps: about 96 s on 24.8, under dbt-clickhouse's 300 s).",[181,3039,3040,3042,3043,3045],{},[23,3041,575],{}," cannot be exported; ",[23,3044,131],{}," only in the federated target.",[181,3047,921,3048,3051],{},[18,3049,3050],{},"Script"," node (Python or R) is not dbt: an Output that reads what a script writes,\nhowever far down, cannot be exported, and the export says which. A Script node that\nfeeds no exported Output is left out of the project, with a note: Tabularia keeps\nrunning it with the flow.",[181,3053,3054],{},"ClickHouse and Trino cannot be federated (DuckDB has no scanner for them). A\nClickHouse source exports natively (all sources on that connection) or with the\nClickHouse target when it is the server the project runs on; Oracle, SQL Server and\nTrino sources have no dbt target of their own: their tables go, with the ClickHouse\ntarget, only into a team project that already declares them (see Sources).",[181,3056,3057,3058,3061],{},"Seeds are snapshots: a flow that reads a file keeps reading the CSV exported that\nday until you run ",[23,3059,3060],{},"dbt seed"," with a new one.",[181,3063,3064,3065,3067,3068,3071],{},"The native target writes with the user of the connection, in ",[23,3066,1018],{}," (or the\nchosen schema) and in the schemas of the flow's outputs on that connection: that user\nneeds ",[23,3069,3070],{},"CREATE"," there.",[3073,3074,3075],"style",{},"html pre.shiki code .s6PUj, html code.shiki .s6PUj{--shiki-default:#F47067;--shiki-light:#D73A49}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}html pre.shiki code .sqRhv, html code.shiki .sqRhv{--shiki-default:#F69D50;--shiki-light:#6F42C1}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}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":795,"searchDepth":828,"depth":844,"links":3077},[3078,3083,3090,3091,3092],{"id":47,"depth":828,"text":48,"children":3079},[3080,3081,3082],{"id":723,"depth":844,"text":724},{"id":1093,"depth":844,"text":1094},{"id":1339,"depth":844,"text":1340},{"id":1507,"depth":828,"text":1508,"children":3084},[3085,3086,3087,3088,3089],{"id":1556,"depth":844,"text":1557},{"id":1733,"depth":844,"text":1267},{"id":1875,"depth":844,"text":1876},{"id":2258,"depth":844,"text":2259},{"id":2317,"depth":844,"text":2318},{"id":2543,"depth":828,"text":2544},{"id":2690,"depth":828,"text":2691},{"id":2817,"depth":828,"text":2818},"A flow designed in Tabularia can leave as a dbt project: a zip with one model per\noutput, the shared steps as their own models, sources.yml, schema.yml with the\ncolumns, their descriptions and the data contract as dbt tests, seeds for the files\nthe flow reads, and a README that says how to run it. Put it in a repository, run it\nin your CI, read it in your catalog: the flow is no longer locked in the tool.","md",{"source":3096},"docs\u002Fdbt-export.md","\u002Fdocs\u002Fdbt-export",{"title":5,"description":3093},"docs\u002Fdbt-export","iyYfTn1VRhd8R1LfA13grKIweIgIwY_9dzFLYYjUL6Y",[3102,3121,3177,3207,3239,3265,3291,3317],{"title":3103,"icon":3104,"intro":3105,"items":3106},"Getting started","start","Install it on one machine, learn the few ideas everything else is built on, and run a first flow.",[3107,3109,3112,3115,3118],{"path":3108,"title":3103},"\u002Fdocs\u002Fgetting-started",{"path":3110,"title":3111},"\u002Fdocs\u002Fgetting-started-docker-compose","Walkthrough with Docker Compose",{"path":3113,"title":3114},"\u002Fdocs\u002Fdeploy\u002Fminikube","Try it on your computer (minikube)",{"path":3116,"title":3117},"\u002Fdocs\u002Fconcepts","Concepts",{"path":3119,"title":3120},"\u002Fdocs\u002Foverview","Overview (README)",{"title":3122,"icon":3123,"intro":3124,"items":3125},"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.",[3126,3129,3132,3135,3138,3141,3144,3147,3150,3153,3156,3159,3162,3165,3168,3171,3174],{"path":3127,"title":3128},"\u002Fdocs\u002Fcapabilities","All capabilities",{"path":3130,"title":3131},"\u002Fdocs\u002Fcapabilities\u002Fflows","Low-code flows",{"path":3133,"title":3134},"\u002Fdocs\u002Fcapabilities\u002Fpreview-and-analysis","Preview and analysis",{"path":3136,"title":3137},"\u002Fdocs\u002Fcapabilities\u002Fask-your-data","Ask your data with AI",{"path":3139,"title":3140},"\u002Fdocs\u002Fcapabilities\u002Freports","Pixel-perfect reports",{"path":3142,"title":3143},"\u002Fdocs\u002Fcapabilities\u002Freport-functions","Report functions",{"path":3145,"title":3146},"\u002Fdocs\u002Fcapabilities\u002Fdashboards","Dashboards",{"path":3148,"title":3149},"\u002Fdocs\u002Fscripting","Python and R",{"path":3151,"title":3152},"\u002Fdocs\u002Fsql-scripts","SQL scripts",{"path":3154,"title":3155},"\u002Fdocs\u002Fcapabilities\u002Fnotebooks","marimo notebooks as apps",{"path":3157,"title":3158},"\u002Fdocs\u002Fcontainer-node","Docker images in flows",{"path":3160,"title":3161},"\u002Fdocs\u002Fcapabilities\u002Fflow-groups","Flow groups: orchestration",{"path":3163,"title":3164},"\u002Fdocs\u002Fcapabilities\u002Fscheduling","Scheduling",{"path":3166,"title":3167},"\u002Fdocs\u002Fpushmail","Pushmail: datasources, reports and dashboards by email",{"path":3169,"title":3170},"\u002Fdocs\u002Fplaceholders","Dynamic dates",{"path":3172,"title":3173},"\u002Fdocs\u002Fcapabilities\u002Fengines","Engines",{"path":3175,"title":3176},"\u002Fdocs\u002Fengines\u002Fengine-differences","Engine differences",{"title":1267,"icon":1733,"intro":3178,"items":3179},"Every way data comes in: files, databases, buckets, SFTP folders, HTTP APIs, SharePoint, Google Sheets, and the results of your own flows.",[3180,3183,3186,3189,3192,3195,3198,3201,3204],{"path":3181,"title":3182},"\u002Fdocs\u002Fsources","All sources",{"path":3184,"title":3185},"\u002Fdocs\u002Fsources\u002Ffiles","Uploaded files",{"path":3187,"title":3188},"\u002Fdocs\u002Fsources\u002Fdatabases","Database tables and queries",{"path":3190,"title":3191},"\u002Fdocs\u002Fs3-datasource","S3 buckets",{"path":3193,"title":3194},"\u002Fdocs\u002Fsftp","SFTP folders",{"path":3196,"title":3197},"\u002Fdocs\u002Fapi-datasource","HTTP APIs",{"path":3199,"title":3200},"\u002Fdocs\u002Fsources\u002Fsharepoint","Excel on SharePoint",{"path":3202,"title":3203},"\u002Fdocs\u002Fsources\u002Fgoogle-sheets","Google Sheets",{"path":3205,"title":3206},"\u002Fdocs\u002Fsources\u002Fflow-outputs","Results of flows and scripts",{"title":3208,"icon":3209,"intro":3210,"items":3211},"Connections","connections","The systems Tabularia reads from and writes to, how each connection is set up, and who may use it.",[3212,3215,3218,3221,3224,3226,3227,3229,3230,3233,3236],{"path":3213,"title":3214},"\u002Fdocs\u002Fconnections","All connections",{"path":3216,"title":3217},"\u002Fdocs\u002Fconnections\u002Fdatabases","Databases",{"path":3219,"title":3220},"\u002Fdocs\u002Fsqlserver","Microsoft SQL Server driver",{"path":3222,"title":3223},"\u002Fdocs\u002Fconnections\u002Fs3","S3 and compatible storage",{"path":3193,"title":3225},"SFTP servers",{"path":3196,"title":3197},{"path":3199,"title":3228},"SharePoint",{"path":3202,"title":3203},{"path":3231,"title":3232},"\u002Fdocs\u002Fconnections\u002Fsmtp","SMTP email",{"path":3234,"title":3235},"\u002Fdocs\u002Fcontainer-registries","Container registries",{"path":3237,"title":3238},"\u002Fdocs\u002Fconnections\u002Fdestinations","Writing data out",{"title":42,"icon":3240,"intro":3241,"items":3242},"integrations","How Tabularia fits your stack: AI providers, the REST API and webhooks, dbt, OpenLineage, single sign-on, SIEM and monitoring.",[3243,3245,3248,3251,3253,3256,3259,3262],{"path":41,"title":3244},"All integrations",{"path":3246,"title":3247},"\u002Fdocs\u002Fintegrations\u002Fai","AI: where it is used",{"path":3249,"title":3250},"\u002Fdocs\u002Fapi","API, tokens and webhooks",{"path":3097,"title":3252},"Export to dbt",{"path":3254,"title":3255},"\u002Fdocs\u002Fopenlineage","OpenLineage",{"path":3257,"title":3258},"\u002Fdocs\u002Fsso-keycloak","Single sign-on with Keycloak",{"path":3260,"title":3261},"\u002Fdocs\u002Fsiem-splunk","Audit log to Splunk",{"path":3263,"title":3264},"\u002Fdocs\u002Fintegrations\u002Fmonitoring","Monitoring",{"title":3266,"icon":3267,"intro":3268,"items":3269},"Security & governance","governance","Who may see and do what, what is recorded, and how data quality and personal data are kept in check.",[3270,3273,3276,3279,3282,3285,3288],{"path":3271,"title":3272},"\u002Fdocs\u002Fgovernance","Overview",{"path":3274,"title":3275},"\u002Fdocs\u002Fgovernance\u002Fpermissions","Permissions",{"path":3277,"title":3278},"\u002Fdocs\u002Fgovernance\u002Faudit-log","Audit log",{"path":3280,"title":3281},"\u002Fdocs\u002Fgovernance\u002Fdata-contracts","Data contracts",{"path":3283,"title":3284},"\u002Fdocs\u002Fdesign\u002Fsso-group-mapping","SSO and group mapping",{"path":3286,"title":3287},"\u002Fdocs\u002Fexamples\u002Fkeycloak","Keycloak example on a laptop",{"path":3289,"title":3290},"\u002Fdocs\u002Fengines\u002Fclickhouse-ai-user","A ClickHouse user for the AI assistant",{"title":3292,"icon":3293,"intro":3294,"items":3295},"Install & operate","operate","Run it in production on Kubernetes, configure it, upgrade it and keep it healthy.",[3296,3299,3302,3305,3308,3311,3314],{"path":3297,"title":3298},"\u002Fdocs\u002Fdeploy\u002Fkubernetes","Kubernetes and Helm",{"path":3300,"title":3301},"\u002Fdocs\u002Fdeploy\u002Fkind","A company-like cluster on one computer (kind)",{"path":3303,"title":3304},"\u002Fdocs\u002Fdeploy\u002Fupgrading","Upgrading, backups and restore",{"path":3306,"title":3307},"\u002Fdocs\u002Fdeploy\u002Frelease-checklist","Install and upgrade checklist",{"path":3309,"title":3310},"\u002Fdocs\u002Fconfiguration","Configuration reference",{"path":3312,"title":3313},"\u002Fdocs\u002Fengine-settings","Settings from the panel",{"path":3315,"title":3316},"\u002Fdocs\u002Fdatabase-migrations","Database migrations",{"title":3318,"icon":3319,"intro":3320,"items":3321},"Reference","reference","Release process and the remaining reference pages.",[3322],{"path":3323,"title":3324},"\u002Fdocs\u002Frelease-process","Release process",1793656371142]