From b009ac1fde8d35a2a03ae23fd582d95557df0bd4 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Mon, 12 Oct 2026 18:50:29 -0400 Subject: [PATCH 1/4] v2 spec: an expression the INSERT evaluates never sees the schema's own settings A DEFAULT or MATERIALIZED fill, a CHECK, a TTL, a partition key the server rebuilds at INSERT, a projection and any aggregate function's creator in them run under the call's own settings, the server profile and chs_set_defaults only, with type gates at the build's defaults unless one of those sets them; the sorting key and skip indices under the server profile and chs_set_defaults only. A server's CREATE-time settings never reach its INSERT, so a gated CAST or aggregate function there, gated only in the schema's own settings, is refused as the server refuses it (455, 44, 63). Parsing keeps all five layers, and session_timezone keeps the zone path. An evaluation's settings now say they govern the INSERT's own expressions too. Prose only: the fingerprint is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/reference/abi-v2.md | 6 +++--- include/v2/chtypes.h | 46 +++++++++++++++++++++++++++------------- spec/abi-v2/docs.md | 8 +++---- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/docs/reference/abi-v2.md b/docs/reference/abi-v2.md index f15cda09..f4c0508a 100644 --- a/docs/reference/abi-v2.md +++ b/docs/reference/abi-v2.md @@ -2018,7 +2018,7 @@ On a schema whose `filter_declined_settings` is not empty (`chs_schema_describe` **Changed (public issue #588).** A declined setting from the `chs_set_defaults` or the schema layer moves from evaluation time to create time: it was named at evaluation (`filter_result`'s `unsupported_settings`), and it now declines this call. A server-profile setting already declined it. -A key of this call's own `settings` other than `session_timezone` is never `CHS_DECLINED` here, and it is never in `schema_description`. A key in this build's honor set is applied to the WHERE; a key passed through is accepted and ignored; and a key this build's filters do not honor is reported at evaluation (`filter_result`'s `unsupported_settings`, every verdict `d`, and `chs_preview_batch`'s). The WHERE runs under this call's layers only (the defaults, the server profile, the schema's own settings and this call's), as a subscriber's SELECT in its own session would. An evaluation's `settings` (`chs_filter_eval_body`, `chs_preview_batch` with a filter, `chs_block_create`) are the INSERT session's: they govern parsing the body and never reach the WHERE, so they are never named as WHERE settings. So a filter evaluated in the INSERT's own session, a materialized view's WHERE for example, is created with that session's WHERE-tier settings in its own `settings`. Every key still meets the server's own SET check first, so a name the server does not know is `CHS_REJECTED` with ch_code 115. +A key of this call's own `settings` other than `session_timezone` is never `CHS_DECLINED` here, and it is never in `schema_description`. A key in this build's honor set is applied to the WHERE; a key passed through is accepted and ignored; and a key this build's filters do not honor is reported at evaluation (`filter_result`'s `unsupported_settings`, every verdict `d`, and `chs_preview_batch`'s). The WHERE runs under this call's layers only (the defaults, the server profile, the schema's own settings and this call's), as a subscriber's SELECT in its own session would. An evaluation's `settings` (`chs_filter_eval_body`, `chs_preview_batch` with a filter, `chs_block_create`) are the INSERT session's: they govern parsing the body and the expressions the INSERT itself evaluates (Settings, above), and never reach the WHERE, so they are never named as WHERE settings. So a filter evaluated in the INSERT's own session, a materialized view's WHERE for example, is created with that session's WHERE-tier settings in its own `settings`. Every key still meets the server's own SET check first, so a name the server does not know is `CHS_REJECTED` with ch_code 115. A filter under a per-call `session_timezone` answers as the artifact producer has measured against live servers, including how the zone in an evaluation's settings combines with the zone the filter was compiled under. Until a path is measured to match a server it declines, which is never a wrong answer. The filter holds a counted reference to the schema. @@ -2048,7 +2048,7 @@ CHS_API chs_status chs_filter_eval_body(const chs_filter *filter, chs_format for - `out`: out_handle, `chs_buf`, `document:filter_result`. - `err`: out_error. -Evaluates the filter over every row of a body, returning one verdict per row. `settings` governs parsing the body, and `session_timezone` there is this call's zone. When a server would not accept an INSERT of the body, because `chs_preview_batch`'s verdict over the same body and settings, with no filter, is not `accepted`, every verdict is `d` and the outcome mirrors that verdict (`filter_outcome`). The call reads an accepted body once, for its verdicts and that batch verdict together. +Evaluates the filter over every row of a body, returning one verdict per row. `settings` is the INSERT session's: it governs parsing the body and the expressions the INSERT evaluates (Settings, above), and `session_timezone` there is this call's zone. When a server would not accept an INSERT of the body, because `chs_preview_batch`'s verdict over the same body and settings, with no filter, is not `accepted`, every verdict is `d` and the outcome mirrors that verdict (`filter_outcome`). The call reads an accepted body once, for its verdicts and that batch verdict together. #### `chs_block_create` @@ -2066,7 +2066,7 @@ CHS_API chs_status chs_block_create(const chs_schema *schema, chs_format format, - `out`: out_handle, `chs_block`. - `err`: out_error. -Parses a body once under the schema, for evaluating many filters over it. `settings` governs the parse, `session_timezone` there included, and `columns` is read as `chs_preview_row` reads it. The block holds a counted reference to the schema. It also records `chs_preview_batch`'s verdict over the same body, settings and columns, with no filter, which `chs_filter_eval_block` answers by. An accepted body is read once, for the block and that verdict together. +Parses a body once under the schema, for evaluating many filters over it. `settings` is the INSERT session's: it governs the parse and the expressions the INSERT evaluates (Settings, above), `session_timezone` there included, and `columns` is read as `chs_preview_row` reads it. The block holds a counted reference to the schema. It also records `chs_preview_batch`'s verdict over the same body, settings and columns, with no filter, which `chs_filter_eval_block` answers by. An accepted body is read once, for the block and that verdict together. #### `chs_block_free` diff --git a/include/v2/chtypes.h b/include/v2/chtypes.h index a7d54e6a..a5d80ce5 100644 --- a/include/v2/chtypes.h +++ b/include/v2/chtypes.h @@ -71,12 +71,25 @@ * the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own * (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time * zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and - * a WHERE's CAST validates under the session layers, every layer but the schema's own. Each setting - * that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or - * declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, - * e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that - * is resource policy, not row access, and the library does not model it. A server's settings are - * the ones its profile applies to every query, and they never change how a schema compiles. No call + * a WHERE's CAST validates under the session layers, every layer but the schema's own. An + * expression the INSERT itself evaluates never sees the schema's own settings either, as a server's + * CREATE-time settings never reach its INSERT. A `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a + * TTL, a partition key the server rebuilds at INSERT (one with a modulo), a projection, and the + * creator of any aggregate function in them run under the call's own settings, its server's + * `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's + * default unless one of those three sets it. The sorting key and the skip indices, which a server + * builds under its global context, run under its server's `settings` and `chs_set_defaults` only, + * though an aggregate function's creator there still reads the call's own settings. So a gated + * `CAST` or a gated aggregate function in such an expression, with its gate only in the schema's + * own settings, is refused as the server refuses it (455, 44 or 63); a caller whose real INSERT + * session carries the gate passes it in the call's settings or the server profile, as that INSERT + * does. Parsing a body keeps all five layers, unchanged, the expressions a `Values` body evaluates + * while it is read included, and `session_timezone` keeps the zone path. Each setting that reaches + * a WHERE is honored (applied to it), passed through (accepted and ignored), or declined + * (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a + * small `max_columns_to_read`) can make the server refuse a query the library answers; that is + * resource policy, not row access, and the library does not model it. A server's settings are the + * ones its profile applies to every query, and they never change how a schema compiles. No call * reads a server's version or its changed settings itself: that discovery is a recorded gap in this * generation, so a caller that wants a server's settings passes them explicitly, in a server * profile, through `chs_set_defaults` or in each call's settings. @@ -1097,11 +1110,12 @@ CHS_API chs_status chs_preview_batch( * `chs_preview_batch`'s). The WHERE runs under this call's layers only (the defaults, the server * profile, the schema's own settings and this call's), as a subscriber's SELECT in its own session * would. An evaluation's `settings` (`chs_filter_eval_body`, `chs_preview_batch` with a filter, - * `chs_block_create`) are the INSERT session's: they govern parsing the body and never reach the - * WHERE, so they are never named as WHERE settings. So a filter evaluated in the INSERT's own - * session, a materialized view's WHERE for example, is created with that session's WHERE-tier - * settings in its own `settings`. Every key still meets the server's own SET check first, so a name - * the server does not know is `CHS_REJECTED` with ch_code 115. + * `chs_block_create`) are the INSERT session's: they govern parsing the body and the expressions + * the INSERT itself evaluates (Settings, above), and never reach the WHERE, so they are never named + * as WHERE settings. So a filter evaluated in the INSERT's own session, a materialized view's WHERE + * for example, is created with that session's WHERE-tier settings in its own `settings`. Every key + * still meets the server's own SET check first, so a name the server does not know is + * `CHS_REJECTED` with ch_code 115. * * A filter under a per-call `session_timezone` answers as the artifact producer has measured * against live servers, including how the zone in an evaluation's settings combines with the zone @@ -1143,8 +1157,9 @@ CHS_API void chs_filter_free(chs_filter *filter); /* * chs_filter_eval_body: Evaluates the filter over every row of a body, returning one verdict per - * row. `settings` governs parsing the body, and `session_timezone` there is this call's zone. When - * a server would not accept an INSERT of the body, because `chs_preview_batch`'s verdict over the + * row. `settings` is the INSERT session's: it governs parsing the body and the expressions the + * INSERT evaluates (Settings, above), and `session_timezone` there is this call's zone. When a + * server would not accept an INSERT of the body, because `chs_preview_batch`'s verdict over the * same body and settings, with no filter, is not `accepted`, every verdict is `d` and the outcome * mirrors that verdict (`filter_outcome`). The call reads an accepted body once, for its verdicts * and that batch verdict together. @@ -1173,8 +1188,9 @@ CHS_API chs_status chs_filter_eval_body( /* * chs_block_create: Parses a body once under the schema, for evaluating many filters over it. - * `settings` governs the parse, `session_timezone` there included, and `columns` is read as - * `chs_preview_row` reads it. The block holds a counted reference to the schema. It also records + * `settings` is the INSERT session's: it governs the parse and the expressions the INSERT evaluates + * (Settings, above), `session_timezone` there included, and `columns` is read as `chs_preview_row` + * reads it. The block holds a counted reference to the schema. It also records * `chs_preview_batch`'s verdict over the same body, settings and columns, with no filter, which * `chs_filter_eval_block` answers by. An accepted body is read once, for the block and that verdict * together. diff --git a/spec/abi-v2/docs.md b/spec/abi-v2/docs.md index b8584c32..df713efa 100644 --- a/spec/abi-v2/docs.md +++ b/spec/abi-v2/docs.md @@ -78,7 +78,7 @@ Stacks. A call's answer doesn't depend on the calling thread's stack. Every call Time zones. A library image has one zone of its own, set once by `chs_initialize`: the image zone. A schema has a home zone, its server's `timezone` (`chs_server_create`), or else the image zone. The schema's types bind its home zone, as a server's table binds the server's zone after a restart, and so do the functions its DEFAULT, MATERIALIZED, ALIAS and CHECK expressions apply to its columns, its PARTITION BY expressions, and a Date-valued TTL under a merge. Each call runs in a zone too, the first of these that is set: the call's own `session_timezone` (a key in its settings, which ClickHouse applies through its own query context); the `session_timezone` in the schema's own settings; the one in its server's `settings`; the one set by `chs_set_defaults`; its server's `timezone`; the image zone. An empty `session_timezone` at any of these means the server's zone, the last two. The call's zone governs parsing and rendering a zone-less `DateTime` or `DateTime64`, export, a DEFAULT literal's fold, `toDateTimeOrZero` and a filter's literals. A `Date` partition id is the civil date in every zone. Every zone name is validated by ClickHouse's own `DateLUT`, which reads the library host's time zone database, on every platform. On darwin the file system is case-insensitive, so `DateLUT` there accepts a spelling such as `utc` that a Linux server refuses; that difference is documented, not patched. -Settings. Every settings input is a JSON object whose values are JSON strings, and a binding never rewrites one. A call's settings are layered, highest first: the call's own `settings`; the schema's own (`chs_schema_create`); its server's `settings` (`chs_server_create`); the defaults set by `chs_set_defaults`; the build's own. A filter's WHERE takes the same layers, lowest first: the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and a WHERE's CAST validates under the session layers, every layer but the schema's own. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that is resource policy, not row access, and the library does not model it. A server's settings are the ones its profile applies to every query, and they never change how a schema compiles. No call reads a server's version or its changed settings itself: that discovery is a recorded gap in this generation, so a caller that wants a server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. +Settings. Every settings input is a JSON object whose values are JSON strings, and a binding never rewrites one. A call's settings are layered, highest first: the call's own `settings`; the schema's own (`chs_schema_create`); its server's `settings` (`chs_server_create`); the defaults set by `chs_set_defaults`; the build's own. A filter's WHERE takes the same layers, lowest first: the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and a WHERE's CAST validates under the session layers, every layer but the schema's own. An expression the INSERT itself evaluates never sees the schema's own settings either, as a server's CREATE-time settings never reach its INSERT. A `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a TTL, a partition key the server rebuilds at INSERT (one with a modulo), a projection, and the creator of any aggregate function in them run under the call's own settings, its server's `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's default unless one of those three sets it. The sorting key and the skip indices, which a server builds under its global context, run under its server's `settings` and `chs_set_defaults` only, though an aggregate function's creator there still reads the call's own settings. So a gated `CAST` or a gated aggregate function in such an expression, with its gate only in the schema's own settings, is refused as the server refuses it (455, 44 or 63); a caller whose real INSERT session carries the gate passes it in the call's settings or the server profile, as that INSERT does. Parsing a body keeps all five layers, unchanged, the expressions a `Values` body evaluates while it is read included, and `session_timezone` keeps the zone path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that is resource policy, not row access, and the library does not model it. A server's settings are the ones its profile applies to every query, and they never change how a schema compiles. No call reads a server's version or its changed settings itself: that discovery is a recorded gap in this generation, so a caller that wants a server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. Documents. Every output document is valid JSON that a stock parser reads: every ClickHouse rendering (a stored value, an input value, an engine's row) is a JSON string, a number written bare is within plus or minus 2^53 (anything larger is a string), and there is never a bare `inf`, `-inf` or `nan`. Every column entry of a row carries `null`, the library's verdict that the stored value is NULL, poisoned cells included. @@ -499,7 +499,7 @@ On a schema whose `filter_declined_settings` is not empty (`chs_schema_describe` **Changed (public issue #588).** A declined setting from the `chs_set_defaults` or the schema layer moves from evaluation time to create time: it was named at evaluation (`filter_result`'s `unsupported_settings`), and it now declines this call. A server-profile setting already declined it. -A key of this call's own `settings` other than `session_timezone` is never `CHS_DECLINED` here, and it is never in `schema_description`. A key in this build's honor set is applied to the WHERE; a key passed through is accepted and ignored; and a key this build's filters do not honor is reported at evaluation (`filter_result`'s `unsupported_settings`, every verdict `d`, and `chs_preview_batch`'s). The WHERE runs under this call's layers only (the defaults, the server profile, the schema's own settings and this call's), as a subscriber's SELECT in its own session would. An evaluation's `settings` (`chs_filter_eval_body`, `chs_preview_batch` with a filter, `chs_block_create`) are the INSERT session's: they govern parsing the body and never reach the WHERE, so they are never named as WHERE settings. So a filter evaluated in the INSERT's own session, a materialized view's WHERE for example, is created with that session's WHERE-tier settings in its own `settings`. Every key still meets the server's own SET check first, so a name the server does not know is `CHS_REJECTED` with ch_code 115. +A key of this call's own `settings` other than `session_timezone` is never `CHS_DECLINED` here, and it is never in `schema_description`. A key in this build's honor set is applied to the WHERE; a key passed through is accepted and ignored; and a key this build's filters do not honor is reported at evaluation (`filter_result`'s `unsupported_settings`, every verdict `d`, and `chs_preview_batch`'s). The WHERE runs under this call's layers only (the defaults, the server profile, the schema's own settings and this call's), as a subscriber's SELECT in its own session would. An evaluation's `settings` (`chs_filter_eval_body`, `chs_preview_batch` with a filter, `chs_block_create`) are the INSERT session's: they govern parsing the body and the expressions the INSERT itself evaluates (Settings, above), and never reach the WHERE, so they are never named as WHERE settings. So a filter evaluated in the INSERT's own session, a materialized view's WHERE for example, is created with that session's WHERE-tier settings in its own `settings`. Every key still meets the server's own SET check first, so a name the server does not know is `CHS_REJECTED` with ch_code 115. A filter under a per-call `session_timezone` answers as the artifact producer has measured against live servers, including how the zone in an evaluation's settings combines with the zone the filter was compiled under. Until a path is measured to match a server it declines, which is never a wrong answer. The filter holds a counted reference to the schema. @@ -509,11 +509,11 @@ Releases the caller's reference to a filter. Freeing NULL does nothing. ### chs_filter_eval_body -Evaluates the filter over every row of a body, returning one verdict per row. `settings` governs parsing the body, and `session_timezone` there is this call's zone. When a server would not accept an INSERT of the body, because `chs_preview_batch`'s verdict over the same body and settings, with no filter, is not `accepted`, every verdict is `d` and the outcome mirrors that verdict (`filter_outcome`). The call reads an accepted body once, for its verdicts and that batch verdict together. +Evaluates the filter over every row of a body, returning one verdict per row. `settings` is the INSERT session's: it governs parsing the body and the expressions the INSERT evaluates (Settings, above), and `session_timezone` there is this call's zone. When a server would not accept an INSERT of the body, because `chs_preview_batch`'s verdict over the same body and settings, with no filter, is not `accepted`, every verdict is `d` and the outcome mirrors that verdict (`filter_outcome`). The call reads an accepted body once, for its verdicts and that batch verdict together. ### chs_block_create -Parses a body once under the schema, for evaluating many filters over it. `settings` governs the parse, `session_timezone` there included, and `columns` is read as `chs_preview_row` reads it. The block holds a counted reference to the schema. It also records `chs_preview_batch`'s verdict over the same body, settings and columns, with no filter, which `chs_filter_eval_block` answers by. An accepted body is read once, for the block and that verdict together. +Parses a body once under the schema, for evaluating many filters over it. `settings` is the INSERT session's: it governs the parse and the expressions the INSERT evaluates (Settings, above), `session_timezone` there included, and `columns` is read as `chs_preview_row` reads it. The block holds a counted reference to the schema. It also records `chs_preview_batch`'s verdict over the same body, settings and columns, with no filter, which `chs_filter_eval_block` answers by. An accepted body is read once, for the block and that verdict together. ### chs_block_free From 7b30fae3e176cc61009a80e94dedb7299da061e6 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Mon, 12 Oct 2026 18:51:57 -0400 Subject: [PATCH 2/4] v2 spec: hold the Values-body clause until it is measured Whether the expressions a Values body evaluates while it is read keep the schema's own settings is still being measured against the server, so the Settings paragraph no longer says either way. Parsing keeps all five layers. Co-Authored-By: Claude Opus 5.5 (1M context) --- include/v2/chtypes.h | 19 +++++++++---------- spec/abi-v2/docs.md | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/include/v2/chtypes.h b/include/v2/chtypes.h index a5d80ce5..4a56e678 100644 --- a/include/v2/chtypes.h +++ b/include/v2/chtypes.h @@ -83,16 +83,15 @@ * `CAST` or a gated aggregate function in such an expression, with its gate only in the schema's * own settings, is refused as the server refuses it (455, 44 or 63); a caller whose real INSERT * session carries the gate passes it in the call's settings or the server profile, as that INSERT - * does. Parsing a body keeps all five layers, unchanged, the expressions a `Values` body evaluates - * while it is read included, and `session_timezone` keeps the zone path. Each setting that reaches - * a WHERE is honored (applied to it), passed through (accepted and ignored), or declined - * (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a - * small `max_columns_to_read`) can make the server refuse a query the library answers; that is - * resource policy, not row access, and the library does not model it. A server's settings are the - * ones its profile applies to every query, and they never change how a schema compiles. No call - * reads a server's version or its changed settings itself: that discovery is a recorded gap in this - * generation, so a caller that wants a server's settings passes them explicitly, in a server - * profile, through `chs_set_defaults` or in each call's settings. + * does. Parsing a body keeps all five layers, unchanged, and `session_timezone` keeps the zone + * path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and + * ignored), or declined (`chs_filter_create` says how). A server resource limit (an + * `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query + * the library answers; that is resource policy, not row access, and the library does not model it. + * A server's settings are the ones its profile applies to every query, and they never change how a + * schema compiles. No call reads a server's version or its changed settings itself: that discovery + * is a recorded gap in this generation, so a caller that wants a server's settings passes them + * explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. * * Documents. Every output document is valid JSON that a stock parser reads: every ClickHouse * rendering (a stored value, an input value, an engine's row) is a JSON string, a number written diff --git a/spec/abi-v2/docs.md b/spec/abi-v2/docs.md index df713efa..da0b597a 100644 --- a/spec/abi-v2/docs.md +++ b/spec/abi-v2/docs.md @@ -78,7 +78,7 @@ Stacks. A call's answer doesn't depend on the calling thread's stack. Every call Time zones. A library image has one zone of its own, set once by `chs_initialize`: the image zone. A schema has a home zone, its server's `timezone` (`chs_server_create`), or else the image zone. The schema's types bind its home zone, as a server's table binds the server's zone after a restart, and so do the functions its DEFAULT, MATERIALIZED, ALIAS and CHECK expressions apply to its columns, its PARTITION BY expressions, and a Date-valued TTL under a merge. Each call runs in a zone too, the first of these that is set: the call's own `session_timezone` (a key in its settings, which ClickHouse applies through its own query context); the `session_timezone` in the schema's own settings; the one in its server's `settings`; the one set by `chs_set_defaults`; its server's `timezone`; the image zone. An empty `session_timezone` at any of these means the server's zone, the last two. The call's zone governs parsing and rendering a zone-less `DateTime` or `DateTime64`, export, a DEFAULT literal's fold, `toDateTimeOrZero` and a filter's literals. A `Date` partition id is the civil date in every zone. Every zone name is validated by ClickHouse's own `DateLUT`, which reads the library host's time zone database, on every platform. On darwin the file system is case-insensitive, so `DateLUT` there accepts a spelling such as `utc` that a Linux server refuses; that difference is documented, not patched. -Settings. Every settings input is a JSON object whose values are JSON strings, and a binding never rewrites one. A call's settings are layered, highest first: the call's own `settings`; the schema's own (`chs_schema_create`); its server's `settings` (`chs_server_create`); the defaults set by `chs_set_defaults`; the build's own. A filter's WHERE takes the same layers, lowest first: the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and a WHERE's CAST validates under the session layers, every layer but the schema's own. An expression the INSERT itself evaluates never sees the schema's own settings either, as a server's CREATE-time settings never reach its INSERT. A `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a TTL, a partition key the server rebuilds at INSERT (one with a modulo), a projection, and the creator of any aggregate function in them run under the call's own settings, its server's `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's default unless one of those three sets it. The sorting key and the skip indices, which a server builds under its global context, run under its server's `settings` and `chs_set_defaults` only, though an aggregate function's creator there still reads the call's own settings. So a gated `CAST` or a gated aggregate function in such an expression, with its gate only in the schema's own settings, is refused as the server refuses it (455, 44 or 63); a caller whose real INSERT session carries the gate passes it in the call's settings or the server profile, as that INSERT does. Parsing a body keeps all five layers, unchanged, the expressions a `Values` body evaluates while it is read included, and `session_timezone` keeps the zone path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that is resource policy, not row access, and the library does not model it. A server's settings are the ones its profile applies to every query, and they never change how a schema compiles. No call reads a server's version or its changed settings itself: that discovery is a recorded gap in this generation, so a caller that wants a server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. +Settings. Every settings input is a JSON object whose values are JSON strings, and a binding never rewrites one. A call's settings are layered, highest first: the call's own `settings`; the schema's own (`chs_schema_create`); its server's `settings` (`chs_server_create`); the defaults set by `chs_set_defaults`; the build's own. A filter's WHERE takes the same layers, lowest first: the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and a WHERE's CAST validates under the session layers, every layer but the schema's own. An expression the INSERT itself evaluates never sees the schema's own settings either, as a server's CREATE-time settings never reach its INSERT. A `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a TTL, a partition key the server rebuilds at INSERT (one with a modulo), a projection, and the creator of any aggregate function in them run under the call's own settings, its server's `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's default unless one of those three sets it. The sorting key and the skip indices, which a server builds under its global context, run under its server's `settings` and `chs_set_defaults` only, though an aggregate function's creator there still reads the call's own settings. So a gated `CAST` or a gated aggregate function in such an expression, with its gate only in the schema's own settings, is refused as the server refuses it (455, 44 or 63); a caller whose real INSERT session carries the gate passes it in the call's settings or the server profile, as that INSERT does. Parsing a body keeps all five layers, unchanged, and `session_timezone` keeps the zone path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that is resource policy, not row access, and the library does not model it. A server's settings are the ones its profile applies to every query, and they never change how a schema compiles. No call reads a server's version or its changed settings itself: that discovery is a recorded gap in this generation, so a caller that wants a server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. Documents. Every output document is valid JSON that a stock parser reads: every ClickHouse rendering (a stored value, an input value, an engine's row) is a JSON string, a number written bare is within plus or minus 2^53 (anything larger is a string), and there is never a bare `inf`, `-inf` or `nan`. Every column entry of a row carries `null`, the library's verdict that the stored value is NULL, poisoned cells included. From aef3fa5766ed41df5f0f09652331d8e290e56c85 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Mon, 12 Oct 2026 19:31:45 -0400 Subject: [PATCH 3/4] v2 spec: at the INSERT, the schema's own settings reach only the parse Measured against the server: a Values body's CAST does not get the schema's type gates (455), and a schema-level partition limit does not reach the INSERT. So only the settings FormatFactory::getFormatSettings reads keep the schema's layer, a type gate among them excepted; every INSERT-time evaluation and every INSERT limit runs under the call's settings, the server profile and chs_set_defaults. Co-Authored-By: Claude Opus 5.5 (1M context) --- include/v2/chtypes.h | 48 +++++++++++++++++++++++++------------------- spec/abi-v2/docs.md | 2 +- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/include/v2/chtypes.h b/include/v2/chtypes.h index 4a56e678..959139b2 100644 --- a/include/v2/chtypes.h +++ b/include/v2/chtypes.h @@ -71,27 +71,33 @@ * the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own * (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time * zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and - * a WHERE's CAST validates under the session layers, every layer but the schema's own. An - * expression the INSERT itself evaluates never sees the schema's own settings either, as a server's - * CREATE-time settings never reach its INSERT. A `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a - * TTL, a partition key the server rebuilds at INSERT (one with a modulo), a projection, and the - * creator of any aggregate function in them run under the call's own settings, its server's - * `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's - * default unless one of those three sets it. The sorting key and the skip indices, which a server - * builds under its global context, run under its server's `settings` and `chs_set_defaults` only, - * though an aggregate function's creator there still reads the call's own settings. So a gated - * `CAST` or a gated aggregate function in such an expression, with its gate only in the schema's - * own settings, is refused as the server refuses it (455, 44 or 63); a caller whose real INSERT - * session carries the gate passes it in the call's settings or the server profile, as that INSERT - * does. Parsing a body keeps all five layers, unchanged, and `session_timezone` keeps the zone - * path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and - * ignored), or declined (`chs_filter_create` says how). A server resource limit (an - * `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query - * the library answers; that is resource policy, not row access, and the library does not model it. - * A server's settings are the ones its profile applies to every query, and they never change how a - * schema compiles. No call reads a server's version or its changed settings itself: that discovery - * is a recorded gap in this generation, so a caller that wants a server's settings passes them - * explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. + * a WHERE's CAST validates under the session layers, every layer but the schema's own. At the + * INSERT, the schema's own settings reach only the parse, as a server's CREATE-time settings never + * reach its INSERT: the settings ClickHouse's own `FormatFactory::getFormatSettings` reads, which + * shape how a body is read, keep all five layers, except a type gate among them + * (`Nullable(Tuple)`'s, for one), which binds the CREATE only. Everything else the INSERT does runs + * under the call's own settings, its server's `settings` and `chs_set_defaults` only, over the + * build's own, with every type gate at the build's default unless one of those three sets it. That + * covers every expression it evaluates (a `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a TTL, a + * partition key the server rebuilds at INSERT, one with a modulo, a projection, an expression a + * `Values` body evaluates while it is read, and the creator of any aggregate function in them) and + * its limits, `max_partitions_per_insert_block` and `throw_on_max_partitions_per_insert_block` + * among them. The sorting key and the skip indices, which a server builds under its global context, + * run under its server's `settings` and `chs_set_defaults` only, though an aggregate function's + * creator there still reads the call's own settings. So a gated `CAST` or a gated aggregate + * function in such an expression, a `Values` body's included, with its gate only in the schema's + * own settings, is refused as the server refuses it (455, 44 or 63), and a partition limit in the + * schema's own settings does not reach the INSERT; a caller whose real INSERT session carries such + * a setting passes it in the call's settings or the server profile, as that INSERT does. + * `session_timezone` keeps the zone path. Each setting that reaches a WHERE is honored (applied to + * it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server + * resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the + * server refuse a query the library answers; that is resource policy, not row access, and the + * library does not model it. A server's settings are the ones its profile applies to every query, + * and they never change how a schema compiles. No call reads a server's version or its changed + * settings itself: that discovery is a recorded gap in this generation, so a caller that wants a + * server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in + * each call's settings. * * Documents. Every output document is valid JSON that a stock parser reads: every ClickHouse * rendering (a stored value, an input value, an engine's row) is a JSON string, a number written diff --git a/spec/abi-v2/docs.md b/spec/abi-v2/docs.md index da0b597a..c9ee6288 100644 --- a/spec/abi-v2/docs.md +++ b/spec/abi-v2/docs.md @@ -78,7 +78,7 @@ Stacks. A call's answer doesn't depend on the calling thread's stack. Every call Time zones. A library image has one zone of its own, set once by `chs_initialize`: the image zone. A schema has a home zone, its server's `timezone` (`chs_server_create`), or else the image zone. The schema's types bind its home zone, as a server's table binds the server's zone after a restart, and so do the functions its DEFAULT, MATERIALIZED, ALIAS and CHECK expressions apply to its columns, its PARTITION BY expressions, and a Date-valued TTL under a merge. Each call runs in a zone too, the first of these that is set: the call's own `session_timezone` (a key in its settings, which ClickHouse applies through its own query context); the `session_timezone` in the schema's own settings; the one in its server's `settings`; the one set by `chs_set_defaults`; its server's `timezone`; the image zone. An empty `session_timezone` at any of these means the server's zone, the last two. The call's zone governs parsing and rendering a zone-less `DateTime` or `DateTime64`, export, a DEFAULT literal's fold, `toDateTimeOrZero` and a filter's literals. A `Date` partition id is the civil date in every zone. Every zone name is validated by ClickHouse's own `DateLUT`, which reads the library host's time zone database, on every platform. On darwin the file system is case-insensitive, so `DateLUT` there accepts a spelling such as `utc` that a Linux server refuses; that difference is documented, not patched. -Settings. Every settings input is a JSON object whose values are JSON strings, and a binding never rewrites one. A call's settings are layered, highest first: the call's own `settings`; the schema's own (`chs_schema_create`); its server's `settings` (`chs_server_create`); the defaults set by `chs_set_defaults`; the build's own. A filter's WHERE takes the same layers, lowest first: the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and a WHERE's CAST validates under the session layers, every layer but the schema's own. An expression the INSERT itself evaluates never sees the schema's own settings either, as a server's CREATE-time settings never reach its INSERT. A `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a TTL, a partition key the server rebuilds at INSERT (one with a modulo), a projection, and the creator of any aggregate function in them run under the call's own settings, its server's `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's default unless one of those three sets it. The sorting key and the skip indices, which a server builds under its global context, run under its server's `settings` and `chs_set_defaults` only, though an aggregate function's creator there still reads the call's own settings. So a gated `CAST` or a gated aggregate function in such an expression, with its gate only in the schema's own settings, is refused as the server refuses it (455, 44 or 63); a caller whose real INSERT session carries the gate passes it in the call's settings or the server profile, as that INSERT does. Parsing a body keeps all five layers, unchanged, and `session_timezone` keeps the zone path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that is resource policy, not row access, and the library does not model it. A server's settings are the ones its profile applies to every query, and they never change how a schema compiles. No call reads a server's version or its changed settings itself: that discovery is a recorded gap in this generation, so a caller that wants a server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. +Settings. Every settings input is a JSON object whose values are JSON strings, and a binding never rewrites one. A call's settings are layered, highest first: the call's own `settings`; the schema's own (`chs_schema_create`); its server's `settings` (`chs_server_create`); the defaults set by `chs_set_defaults`; the build's own. A filter's WHERE takes the same layers, lowest first: the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and a WHERE's CAST validates under the session layers, every layer but the schema's own. At the INSERT, the schema's own settings reach only the parse, as a server's CREATE-time settings never reach its INSERT: the settings ClickHouse's own `FormatFactory::getFormatSettings` reads, which shape how a body is read, keep all five layers, except a type gate among them (`Nullable(Tuple)`'s, for one), which binds the CREATE only. Everything else the INSERT does runs under the call's own settings, its server's `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's default unless one of those three sets it. That covers every expression it evaluates (a `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a TTL, a partition key the server rebuilds at INSERT, one with a modulo, a projection, an expression a `Values` body evaluates while it is read, and the creator of any aggregate function in them) and its limits, `max_partitions_per_insert_block` and `throw_on_max_partitions_per_insert_block` among them. The sorting key and the skip indices, which a server builds under its global context, run under its server's `settings` and `chs_set_defaults` only, though an aggregate function's creator there still reads the call's own settings. So a gated `CAST` or a gated aggregate function in such an expression, a `Values` body's included, with its gate only in the schema's own settings, is refused as the server refuses it (455, 44 or 63), and a partition limit in the schema's own settings does not reach the INSERT; a caller whose real INSERT session carries such a setting passes it in the call's settings or the server profile, as that INSERT does. `session_timezone` keeps the zone path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that is resource policy, not row access, and the library does not model it. A server's settings are the ones its profile applies to every query, and they never change how a schema compiles. No call reads a server's version or its changed settings itself: that discovery is a recorded gap in this generation, so a caller that wants a server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. Documents. Every output document is valid JSON that a stock parser reads: every ClickHouse rendering (a stored value, an input value, an engine's row) is a JSON string, a number written bare is within plus or minus 2^53 (anything larger is a string), and there is never a bare `inf`, `-inf` or `nan`. Every column entry of a row carries `null`, the library's verdict that the stored value is NULL, poisoned cells included. From c4d5dfdb248e6ee018fe4d0b12766f176a66d814 Mon Sep 17 00:00:00 2001 From: Eric Andrechek Date: Wed, 21 Oct 2026 03:38:41 -0400 Subject: [PATCH 4/4] spec (v2): a server profile shapes the compile at posture create The spec said a server's settings "never change how a schema compiles" and that chs_schema_create succeeds on a server "because a profile never changes a schema compile". Since build 20261021.025722 the library runs the CREATE at posture create in a session carrying chs_set_defaults' settings and then the server profile, both under the schema's own, so every profile setting reaches the CREATE: flatten_nested, the type gates and the aggregate gates among them. At posture load the stored definition is taken as written, as a server attaches it at startup. Both sentences now say so; the second keeps its rule (the setting is listed in filter_declined_settings at layer server) without the false reason. Measured by the artifact producer in that change's proof and in the release gate on the published build. Prose only: regenerated, header diff is comment lines only, ABI v2 fingerprint unchanged. Co-Authored-By: Claude Opus 5.5 --- docs/reference/abi-v2.md | 2 +- include/v2/chtypes.h | 12 +++++++----- spec/abi-v2/docs.md | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/reference/abi-v2.md b/docs/reference/abi-v2.md index 1db146bb..ba139a1f 100644 --- a/docs/reference/abi-v2.md +++ b/docs/reference/abi-v2.md @@ -2262,7 +2262,7 @@ Everything is validated here, once, and the server never changes after it. A key The macros are the server's whole `system.macros` when `macros` is present, even as `{}`: a macro a Replicated engine's arguments name that the set lacks is then the server's own refusal (`CHS_REJECTED`, 139, NO_ELEMENTS_IN_CONFIG). When `macros` is absent the server's macros are unknown, and a schema whose engine reads one is declined, as it is without a server, except under `schema_options`' `posture` `load`, where a stored Replicated definition compiles without expanding them (`input:schema_options`). A build that validates a profile's macros but does not yet apply them declines such a schema in both cases. -A setting this build's filters do not honor in a WHERE is no decline here, and the call succeeds. So does `chs_schema_create` on the server, because a profile never changes a schema compile: it lists the setting in the schema's `filter_declined_settings` (`schema_description`), at layer `server`. +A setting this build's filters do not honor in a WHERE is no decline here, and the call succeeds. So does `chs_schema_create` on the server: it lists the setting in the schema's `filter_declined_settings` (`schema_description`), at layer `server`. The call reads no defaults and leaves `chs_set_defaults`' latch open. Its init class is `required`, so before `chs_initialize` it refuses (Initialization, in the Preamble). diff --git a/include/v2/chtypes.h b/include/v2/chtypes.h index 78e12eee..50de43c0 100644 --- a/include/v2/chtypes.h +++ b/include/v2/chtypes.h @@ -141,8 +141,11 @@ * or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, * e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that * is resource policy, not row access, and the library does not model it. A server's settings are - * the ones its profile applies to every query, and they never change how a schema compiles. No call - * reads a server's version or its changed settings itself: that discovery is a recorded gap in this + * the ones its profile applies to every query. At posture `create`, they are layered under the + * schema's own settings for the whole CREATE, so they shape how the schema compiles + * (`flatten_nested`, the type gates and the aggregate gates among them). At posture `load`, the + * stored definition is taken as written, as a server attaches it at startup. No call reads a + * server's version or its changed settings itself: that discovery is a recorded gap in this * generation, so a caller that wants a server's settings passes them explicitly, in a server * profile, through `chs_set_defaults` or in each call's settings. * @@ -1230,9 +1233,8 @@ CHS_API chs_status chs_quote_string( * yet apply them declines such a schema in both cases. * * A setting this build's filters do not honor in a WHERE is no decline here, and the call succeeds. - * So does `chs_schema_create` on the server, because a profile never changes a schema compile: it - * lists the setting in the schema's `filter_declined_settings` (`schema_description`), at layer - * `server`. + * So does `chs_schema_create` on the server: it lists the setting in the schema's + * `filter_declined_settings` (`schema_description`), at layer `server`. * * The call reads no defaults and leaves `chs_set_defaults`' latch open. Its init class is * `required`, so before `chs_initialize` it refuses (Initialization, in the Preamble). diff --git a/spec/abi-v2/docs.md b/spec/abi-v2/docs.md index 00177a69..91cc5059 100644 --- a/spec/abi-v2/docs.md +++ b/spec/abi-v2/docs.md @@ -94,7 +94,7 @@ Stacks. A call's answer doesn't depend on the calling thread's stack. Every call Time zones. A library image has one zone of its own, set once by `chs_initialize`: the image zone. A schema has a home zone, its server's `timezone` (`chs_server_create`), or else the image zone. The schema's types bind its home zone, as a server's table binds the server's zone after a restart, and so do the functions its DEFAULT, MATERIALIZED, ALIAS and CHECK expressions apply to its columns, its PARTITION BY expressions, and a Date-valued TTL under a merge. Each call runs in a zone too, the first of these that is set: the call's own `session_timezone` (a key in its settings, which ClickHouse applies through its own query context); the `session_timezone` in the schema's own settings; the one in its server's `settings`; the one set by `chs_set_defaults`; its server's `timezone`; the image zone. An empty `session_timezone` at any of these means the server's zone, the last two. The call's zone governs parsing and rendering a zone-less `DateTime` or `DateTime64`, export, a DEFAULT literal's fold, `toDateTimeOrZero` and a filter's literals. A `Date` partition id is the civil date in every zone. Every zone name is validated by ClickHouse's own `DateLUT`, which reads the library host's time zone database, on every platform. On darwin the file system is case-insensitive, so `DateLUT` there accepts a spelling such as `utc` that a Linux server refuses; that difference is documented, not patched. -Settings. Every settings input is a JSON object whose values are JSON strings, or the object form for bytes (Input values, below), and a binding never rewrites one. A call's settings are layered, highest first: the call's own `settings`; the schema's own (`chs_schema_create`); its server's `settings` (`chs_server_create`); the defaults set by `chs_set_defaults`; the build's own. A filter's WHERE takes the same layers, lowest first: the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and a WHERE's CAST validates under the session layers, every layer but the schema's own. At the INSERT, the schema's own settings reach only the parse, as a server's CREATE-time settings never reach its INSERT: the settings ClickHouse's own `FormatFactory::getFormatSettings` reads, which shape how a body is read, keep all five layers, except a type gate among them (`Nullable(Tuple)`'s, for one), which binds the CREATE only. Everything else the INSERT does runs under the call's own settings, its server's `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's default unless one of those three sets it. That covers every expression it evaluates (a `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a TTL, a partition key the server rebuilds at INSERT, one with a modulo, a projection, an expression a `Values` body evaluates while it is read, and the creator of any aggregate function in them) and its limits, `max_partitions_per_insert_block` and `throw_on_max_partitions_per_insert_block` among them. The sorting key and the skip indices, which a server builds under its global context, run under its server's `settings` and `chs_set_defaults` only, though an aggregate function's creator there still reads the call's own settings. So a gated `CAST` or a gated aggregate function in such an expression, a `Values` body's included, with its gate only in the schema's own settings, is refused as the server refuses it (455, 44 or 63), and a partition limit in the schema's own settings does not reach the INSERT; a caller whose real INSERT session carries such a setting passes it in the call's settings or the server profile, as that INSERT does. `session_timezone` keeps the zone path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that is resource policy, not row access, and the library does not model it. A server's settings are the ones its profile applies to every query, and they never change how a schema compiles. No call reads a server's version or its changed settings itself: that discovery is a recorded gap in this generation, so a caller that wants a server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. +Settings. Every settings input is a JSON object whose values are JSON strings, or the object form for bytes (Input values, below), and a binding never rewrites one. A call's settings are layered, highest first: the call's own `settings`; the schema's own (`chs_schema_create`); its server's `settings` (`chs_server_create`); the defaults set by `chs_set_defaults`; the build's own. A filter's WHERE takes the same layers, lowest first: the build's own, `chs_set_defaults`, the server profile, the schema's own, and the filter's own (`chs_filter_create`), with `session_timezone` at every layer staying on the zone path (Time zones, above). One exception: a type gate in the schema's own settings binds the CREATE only, and a WHERE's CAST validates under the session layers, every layer but the schema's own. At the INSERT, the schema's own settings reach only the parse, as a server's CREATE-time settings never reach its INSERT: the settings ClickHouse's own `FormatFactory::getFormatSettings` reads, which shape how a body is read, keep all five layers, except a type gate among them (`Nullable(Tuple)`'s, for one), which binds the CREATE only. Everything else the INSERT does runs under the call's own settings, its server's `settings` and `chs_set_defaults` only, over the build's own, with every type gate at the build's default unless one of those three sets it. That covers every expression it evaluates (a `DEFAULT` or `MATERIALIZED` fill, a `CHECK`, a TTL, a partition key the server rebuilds at INSERT, one with a modulo, a projection, an expression a `Values` body evaluates while it is read, and the creator of any aggregate function in them) and its limits, `max_partitions_per_insert_block` and `throw_on_max_partitions_per_insert_block` among them. The sorting key and the skip indices, which a server builds under its global context, run under its server's `settings` and `chs_set_defaults` only, though an aggregate function's creator there still reads the call's own settings. So a gated `CAST` or a gated aggregate function in such an expression, a `Values` body's included, with its gate only in the schema's own settings, is refused as the server refuses it (455, 44 or 63), and a partition limit in the schema's own settings does not reach the INSERT; a caller whose real INSERT session carries such a setting passes it in the call's settings or the server profile, as that INSERT does. `session_timezone` keeps the zone path. Each setting that reaches a WHERE is honored (applied to it), passed through (accepted and ignored), or declined (`chs_filter_create` says how). A server resource limit (an `execution`-tier setting, e.g. a small `max_columns_to_read`) can make the server refuse a query the library answers; that is resource policy, not row access, and the library does not model it. A server's settings are the ones its profile applies to every query. At posture `create`, they are layered under the schema's own settings for the whole CREATE, so they shape how the schema compiles (`flatten_nested`, the type gates and the aggregate gates among them). At posture `load`, the stored definition is taken as written, as a server attaches it at startup. No call reads a server's version or its changed settings itself: that discovery is a recorded gap in this generation, so a caller that wants a server's settings passes them explicitly, in a server profile, through `chs_set_defaults` or in each call's settings. Documents. Every output document is valid JSON that a stock parser reads: every ClickHouse rendering (a stored value, an input value, an engine's row) is a JSON string, a number written bare is within plus or minus 2^53 (anything larger is a string), and there is never a bare `inf`, `-inf` or `nan`. Every column entry of a row carries `null`, the library's verdict that the stored value is NULL, poisoned cells included. @@ -521,7 +521,7 @@ Everything is validated here, once, and the server never changes after it. A key The macros are the server's whole `system.macros` when `macros` is present, even as `{}`: a macro a Replicated engine's arguments name that the set lacks is then the server's own refusal (`CHS_REJECTED`, 139, NO_ELEMENTS_IN_CONFIG). When `macros` is absent the server's macros are unknown, and a schema whose engine reads one is declined, as it is without a server, except under `schema_options`' `posture` `load`, where a stored Replicated definition compiles without expanding them (`input:schema_options`). A build that validates a profile's macros but does not yet apply them declines such a schema in both cases. -A setting this build's filters do not honor in a WHERE is no decline here, and the call succeeds. So does `chs_schema_create` on the server, because a profile never changes a schema compile: it lists the setting in the schema's `filter_declined_settings` (`schema_description`), at layer `server`. +A setting this build's filters do not honor in a WHERE is no decline here, and the call succeeds. So does `chs_schema_create` on the server: it lists the setting in the schema's `filter_declined_settings` (`schema_description`), at layer `server`. The call reads no defaults and leaves `chs_set_defaults`' latch open. Its init class is `required`, so before `chs_initialize` it refuses (Initialization, in the Preamble).