srielau commented on code in PR #58224: URL: https://github.com/apache/spark/pull/58224#discussion_r4330579414
########## docs/sql-ref-character-string-types.md: ########## @@ -0,0 +1,375 @@ +--- +layout: global +title: Character String Types +displayTitle: Character String Types (Preview) +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +First-class `CHAR` / `VARCHAR` / `STRING` semantics are a preview feature in Spark 4.4 and may +change in future releases. They are off by default. This page describes that preview mode only. + +Enable the preview feature before using the behavior below: + +```sql +SET spark.sql.charVarchar.standardSemantics.enabled = true; +``` + +By default, `CHAR` and `VARCHAR` are table-schema overlays on `STRING`. DataFrame schemas +show `STRING`, `CAST(... AS CHAR(n))` is `STRING`, and function schemas raise +`UNSUPPORTED_CHAR_OR_VARCHAR_AS_STRING`. See [Data Types](sql-ref-datatypes.html). + +Spark SQL then has a family of three character string types: + +```text +CHAR(n) -> VARCHAR(n) -> STRING +``` + +The arrow denotes type precedence from the most constrained type to the least constrained type. +This is analogous to the precedence of integral numeric types such as +`TINYINT -> SMALLINT -> INT -> BIGINT`. `CHAR`, `VARCHAR`, and `STRING` are distinct SQL types in +the same family; `CHAR` and `VARCHAR` are not special uses of `STRING`. + +## Related settings + +The preview flag is persisted with view definitions, so a view continues to use the semantics +under which it was created. + +The older `spark.sql.preserveCharVarcharTypeInfo` setting is an experimental compatibility mode. +It preserves type information, but does not apply all the type precedence and expression-result +rules described here. Standard semantics take precedence over +`spark.sql.legacy.charVarcharAsString` when both settings are `true`. + +Two other settings still apply when standard semantics are on: + +* `spark.sql.charAsVarchar` still rewrites `CHAR` to `VARCHAR` in `CREATE`/`REPLACE`/`ALTER TABLE`. + `CAST(... AS CHAR(n))` stays `CHAR(n)`. +* `spark.sql.readSideCharPadding` is ignored. Read-side CHAR padding and length checks always run; + setting it to `false` logs a warning. + +## Type definitions + +| SQL type | Value constraint | Behavior | +|----------|------------------|----------| +| `CHAR(n)` | Exactly `n` characters | Values shorter than `n` characters are padded on the right with spaces. | +| `VARCHAR(n)` | At most `n` characters | Values retain their length and are not padded. | +| `STRING` | Unbounded length | Values have no declared length constraint. | + +The length is required for `CHAR` and `VARCHAR`. `CHARACTER(n)` is a synonym for `CHAR(n)`. +The length `n` counts characters, not UTF-8 bytes. All three types can have a +[collation](sql-ref-syntax-aux-show-collations.html), which controls comparison, ordering, and +other collation-sensitive operations. + +For example: + +```sql +SELECT typeof(CAST('ab' AS CHAR(5))); +-- char(5) + +SELECT concat('<', CAST('ab' AS CHAR(5)), '>'); +-- <ab > + +SELECT typeof(CAST('ab' AS VARCHAR(5))); +-- varchar(5) +``` + +## Type precedence and least common type + +Spark finds the least common type when an operation needs one type that can represent multiple +inputs. Within the character string family, Spark uses these rules: + +| Input types | Least common type | +|-------------|-------------------| +| `CHAR(n)`, `CHAR(m)` | `CHAR(max(n, m))` | +| `CHAR(n)`, `VARCHAR(m)` | `VARCHAR(max(n, m))` | +| `VARCHAR(n)`, `VARCHAR(m)` | `VARCHAR(max(n, m))` | +| Any character string type and `STRING` | `STRING` | +| Any character string type `T` and untyped `NULL` | `T` | + +These rules apply recursively to arrays, maps, and structs. They are used by expressions and +operations such as `CASE`, `COALESCE`, `IN`, `UNION`, `INTERSECT`, `EXCEPT`, multi-row `VALUES`, +and collection constructors. + +```sql +SELECT typeof(coalesce( + CAST('a' AS CHAR(2)), + CAST('bb' AS CHAR(4)))); +-- char(4) + +SELECT typeof(coalesce( + CAST('a' AS CHAR(2)), + CAST('bb' AS VARCHAR(4)))); +-- varchar(4) + +SELECT typeof(coalesce( + CAST('a' AS VARCHAR(4)), + CAST('unbounded' AS STRING))); +-- string +``` + +Collation is also part of a character string type. The least-common-type rules above apply after +Spark resolves the input collations. When the result remains `CHAR` or `VARCHAR`, it retains the +resolved collation. + +## Casts + +An explicit `CAST` or `TRY_CAST` can introduce a `CHAR` or `VARCHAR` value: + +```sql +SELECT CAST('abc' AS CHAR(5)); +-- 'abc ' + +SELECT CAST('abc' AS VARCHAR(5)); +-- 'abc' +``` + +For an explicit cast from one character string type to another: + +* Casting to `CHAR(n)` truncates values longer than `n` characters and pads values shorter than + `n` characters. +* Casting to `VARCHAR(n)` truncates values longer than `n` characters. Spaces already present in + a `CHAR` value are kept, so `CAST(CAST('ab' AS CHAR(4)) AS VARCHAR(4))` is `'ab '`. + +For a cast from a non-character value, Spark first converts the value to characters and then +checks the target length. `CAST` raises `EXCEED_LIMIT_LENGTH` if the value does not fit, while +`TRY_CAST` returns `NULL`. + +```sql +SELECT CAST('abcdef' AS VARCHAR(3)); +-- 'abc' + +SELECT TRY_CAST(12345 AS VARCHAR(4)); +-- NULL +``` + +## Assignment and reading + +Assignment to a declared `CHAR` or `VARCHAR` type is stricter than an explicit +character-to-character cast. Assignment occurs for operations such as table writes, routine +arguments and return values, session variables, schema-driven Dataset or DataFrame conversion, and +the output columns of a script `TRANSFORM` clause. + +* A value that fits is stored unchanged, except that `CHAR(n)` is padded on the right to `n` + characters. +* If only trailing spaces exceed the declared length, Spark removes only the number of trailing + spaces needed to fit. +* If non-space characters exceed the declared length, Spark raises `EXCEED_LIMIT_LENGTH`. + +Reads apply the same checks. In particular, a `CHAR(n)` value read from external or otherwise +untrusted storage is padded if short and rejected if its non-space content is too long. The same +`EXCEED_LIMIT_LENGTH` check applies to oversize partition values. + +```sql +CREATE TABLE contacts (code CHAR(4), name VARCHAR(10)) USING parquet; + +INSERT INTO contacts VALUES ('ab', 'Ada'); + +SELECT concat('<', code, '>'), name FROM contacts; +-- <ab > Ada +``` + +A script `TRANSFORM` clause (including `MAP` and `REDUCE`) exchanges rows with the external script +as unbounded `STRING`, so a `CHAR(n)` or `VARCHAR(n)` in its `AS` output list does not constrain +what the script emits. Spark instead applies the assignment above to the script output in a +projection: a declared `CHAR(n)` is padded and an over-length value raises `EXCEED_LIMIT_LENGTH`. +The declared type is kept only when these semantics (or `spark.sql.preserveCharVarcharTypeInfo`) +are enabled; otherwise an `AS` type such as `CHAR(n)` is treated as plain `STRING` with no padding +or length check. + +## Expression result types + +A reference to a `CHAR` or `VARCHAR` column preserves its declared type. Expressions that select +among values without changing their character content, such as `CASE` and `COALESCE`, use the +least common type rules above. + +An expression that transforms character content returns `STRING`, because its result length is +not constrained by an input declaration. This includes string functions and operators such as +`upper`, `lower`, `substring`, `concat`, `||`, `regexp_replace`, `trim`, `split`, and `mask`. + +```sql +SELECT typeof(upper(CAST('ab' AS CHAR(2)))); +-- string + +SELECT typeof(CAST('a' AS CHAR(1)) || CAST('b' AS VARCHAR(1))); +-- string +``` + +This rule prevents a length constraint from propagating to newly computed content. To constrain +the result, cast it explicitly to `CHAR(n)` or `VARCHAR(n)`. + +## Comparisons and trailing spaces + +Spark coerces character string operands to a least common type before comparing them. Casting to a +wider `CHAR` pads the value, while casting to `VARCHAR` or `STRING` preserves spaces already +present in a `CHAR` value. + +```sql +SELECT CAST('a' AS CHAR(2)) = CAST('a' AS CHAR(4)); +-- true + +SELECT CAST('a' AS CHAR(2)) = CAST('a' AS VARCHAR(2)); +-- false + +SELECT CAST('a' AS CHAR(2)) = CAST('a ' AS VARCHAR(2)); +-- true +``` + +A [string literal](sql-ref-literals.html#string-literal) is `STRING`, not `CHAR` or `VARCHAR`. Review Comment: Thanks. Documented that under the preview a CHAR column compared to 'ab' does not match stored 'ab ', while 'ab ' or an RTRIM collation does. The 4.4 flag bullet now calls this out. Locked it in with SPARK-58942 in BasicCharVarcharTestSuite (the suite that holds the other standard-semantics SQL cases) rather than regenerating SQLQueryTestSuite goldens. See dee7bd4d8a1. ########## docs/sql-migration-guide.md: ########## @@ -29,9 +29,12 @@ license: | ## Upgrading from Spark SQL 4.3 to 4.4 - Since Spark 4.4, `spark.sql.execution.arrow.pythonUDF.columnarInput.enabled` is `false` by default: Arrow-based Python UDFs read rows from an Arrow-backed upstream operator, such as a Data Source V2 scan or the Arrow cache, as before Spark 4.2. With columnar input, they kept the columns that they pass through until the results of their batch came back from Python, and failed or returned wrong results when the operator reused or released the vectors of the batch in the meantime. To enable columnar input again, set `spark.sql.execution.arrow.pythonUDF.columnarInput.enabled` to `true`. -- Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is true, `from_json` length-checks JSON object names used as `MAP<CHAR(n), _>` or `MAP<VARCHAR(n), _>` keys without padding or trimming them. A `CHAR(n)` key must already be exactly `n` characters, and a `VARCHAR(n)` key must already be at most `n` characters; duplicate names are kept, exactly as for STRING keys, and `spark.sql.mapKeyDedupPolicy` is not applied. A key that fails the check makes the enclosing map a bad record: in `PERMISSIVE` mode the nearest enclosing struct field becomes `null` (its sibling fields are preserved), and if the map is inside an array or another map, that whole array or map becomes `null`; in `FAILFAST` mode parsing fails with `UNSUPPORTED_JSON_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`) as the cause of `MALFORMED_RECORD_IN_PARSING`. The same parser-level check runs for any JSON scan whose reader schema carries a CHAR/VARCHAR map key, including catalog JSON tables, since under standa rd semantics those keys stay CHAR/VARCHAR all the way to the scan: an off-width key becomes a bad record there too, rather than being padded or raising `EXCEED_LIMIT_LENGTH`. Unlike `from_json`, keys that pass the check are then rebuilt read-side, so on the scan path `spark.sql.mapKeyDedupPolicy` does apply and a duplicate name can raise `DUPLICATED_MAP_KEY`. JSON file scans, including scans inside persisted views, read the flag from the executing session. +- Since Spark 4.4, `spark.sql.charVarchar.standardSemantics.enabled` (default `false`) is a preview feature that may change in future releases. When true, it enables first-class `CHAR(n)` and `VARCHAR(n)` semantics throughout schemas and query plans: `CHAR` -> `VARCHAR` -> `STRING` form a type-precedence family, explicit casts can return constrained types, least-common-type resolution for `COALESCE`/`CASE`/`UNION` can return `CHAR` or `VARCHAR`, and string expressions that transform their input return `STRING`. Assignment and reads enforce the declared length, padding `CHAR` to `n` characters and raising `EXCEED_LIMIT_LENGTH` when non-space content does not fit. The default remains the Spark 3.1 table-schema overlay on `STRING`. This opt-in mode supersedes the experimental `spark.sql.preserveCharVarcharTypeInfo`, which instead keeps constrained types through transforming expressions. See [Character String Types](sql-ref-character-string-types.html) for the preview semantics; the ent ries below note area-specific behavior that changed. Those later CHAR/VARCHAR bullets apply only when this preview flag is true, except where they name `preserveCharVarcharTypeInfo` alone. Review Comment: Thanks. The header now says the preview is preferred over preserveCharVarcharTypeInfo, and that later bullets that do not name preserve apply when the preview is on, except from_avro value assignment which also runs with preserve alone. See dee7bd4d8a1. ########## docs/sql-migration-guide.md: ########## @@ -29,9 +29,12 @@ license: | ## Upgrading from Spark SQL 4.3 to 4.4 - Since Spark 4.4, `spark.sql.execution.arrow.pythonUDF.columnarInput.enabled` is `false` by default: Arrow-based Python UDFs read rows from an Arrow-backed upstream operator, such as a Data Source V2 scan or the Arrow cache, as before Spark 4.2. With columnar input, they kept the columns that they pass through until the results of their batch came back from Python, and failed or returned wrong results when the operator reused or released the vectors of the batch in the meantime. To enable columnar input again, set `spark.sql.execution.arrow.pythonUDF.columnarInput.enabled` to `true`. -- Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is true, `from_json` length-checks JSON object names used as `MAP<CHAR(n), _>` or `MAP<VARCHAR(n), _>` keys without padding or trimming them. A `CHAR(n)` key must already be exactly `n` characters, and a `VARCHAR(n)` key must already be at most `n` characters; duplicate names are kept, exactly as for STRING keys, and `spark.sql.mapKeyDedupPolicy` is not applied. A key that fails the check makes the enclosing map a bad record: in `PERMISSIVE` mode the nearest enclosing struct field becomes `null` (its sibling fields are preserved), and if the map is inside an array or another map, that whole array or map becomes `null`; in `FAILFAST` mode parsing fails with `UNSUPPORTED_JSON_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`) as the cause of `MALFORMED_RECORD_IN_PARSING`. The same parser-level check runs for any JSON scan whose reader schema carries a CHAR/VARCHAR map key, including catalog JSON tables, since under standa rd semantics those keys stay CHAR/VARCHAR all the way to the scan: an off-width key becomes a bad record there too, rather than being padded or raising `EXCEED_LIMIT_LENGTH`. Unlike `from_json`, keys that pass the check are then rebuilt read-side, so on the scan path `spark.sql.mapKeyDedupPolicy` does apply and a duplicate name can raise `DUPLICATED_MAP_KEY`. JSON file scans, including scans inside persisted views, read the flag from the executing session. +- Since Spark 4.4, `spark.sql.charVarchar.standardSemantics.enabled` (default `false`) is a preview feature that may change in future releases. When true, it enables first-class `CHAR(n)` and `VARCHAR(n)` semantics throughout schemas and query plans: `CHAR` -> `VARCHAR` -> `STRING` form a type-precedence family, explicit casts can return constrained types, least-common-type resolution for `COALESCE`/`CASE`/`UNION` can return `CHAR` or `VARCHAR`, and string expressions that transform their input return `STRING`. Assignment and reads enforce the declared length, padding `CHAR` to `n` characters and raising `EXCEED_LIMIT_LENGTH` when non-space content does not fit. The default remains the Spark 3.1 table-schema overlay on `STRING`. This opt-in mode supersedes the experimental `spark.sql.preserveCharVarcharTypeInfo`, which instead keeps constrained types through transforming expressions. See [Character String Types](sql-ref-character-string-types.html) for the preview semantics; the ent ries below note area-specific behavior that changed. Those later CHAR/VARCHAR bullets apply only when this preview flag is true, except where they name `preserveCharVarcharTypeInfo` alone. +- Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is true, `from_json` length-checks JSON object names used as `MAP<CHAR(n), _>` or `MAP<VARCHAR(n), _>` keys without padding or trimming them. A `CHAR(n)` key must already be exactly `n` characters, and a `VARCHAR(n)` key must already be at most `n` characters; duplicate names are kept, exactly as for STRING keys, and `spark.sql.mapKeyDedupPolicy` is not applied. A key that fails the check makes the enclosing map a bad record: in `PERMISSIVE` mode the nearest enclosing struct field becomes `null` (its sibling fields are preserved), and if the map is inside an array or another map, that whole array or map becomes `null`; in `FAILFAST` mode parsing fails with `UNSUPPORTED_JSON_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`) as the cause of `MALFORMED_RECORD_IN_PARSING`. The same parser-level check runs for any JSON scan whose reader schema carries a CHAR/VARCHAR map key, including catalog JSON tables, since under standa rd semantics those keys stay CHAR/VARCHAR all the way to the scan: an off-width key becomes a bad record there too, rather than being padded or raising `EXCEED_LIMIT_LENGTH`. Unlike `from_json`, keys that pass the check are then rebuilt read-side, so on the scan path `spark.sql.mapKeyDedupPolicy` does apply and a duplicate name can raise `DUPLICATED_MAP_KEY`. JSON file scans, including scans inside persisted views, read the flag from the executing session. The optimizer no longer folds `from_json(to_json(m))` to `m` when that schema has a CHAR/VARCHAR map key, so the length check still runs on the round trip. +- Since Spark 4.4, `from_json`, `from_csv`, `from_xml`, and the corresponding `schema_of_*` functions do not rtrim a CHAR document argument. CHAR padding is part of the STRING value after CAST, so it is parsed as trailing spaces in the document. JSON and XML tolerate that trailing whitespace; CSV can tokenize it into extra fields or attach it to the last unquoted field (use `ignoreTrailingWhiteSpace` to restore a trimmed last-field parse). - Since Spark 4.4, when `spark.sql.preserveCharVarcharTypeInfo` is true and `spark.sql.charVarchar.standardSemantics.enabled` is false, ORC reads that apply a CHAR/VARCHAR schema over STRING storage return the stored values without ORC truncation, matching Parquet. Previously the ORC reader requested `char(n)`/`varchar(n)` and truncated STRING-stored values to `n`. Read-side length checks (`EXCEED_LIMIT_LENGTH`) apply only when `spark.sql.charVarchar.standardSemantics.enabled` is true. - Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is true, XML names used as `MAP<CHAR(n), _>` or `MAP<VARCHAR(n), _>` keys in `from_xml` and the XML datasource are length-checked without padding or trimming. The checked names are element names, `attributePrefix` plus attribute names (default prefix `_`), and the `valueTag` (default `_VALUE`) when mixed text is present. A `CHAR(n)` key must already be exactly `n` characters, and a `VARCHAR(n)` key must already be at most `n` characters. Mismatched keys fail with `UNSUPPORTED_XML_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`) and follow the XML parse mode (`PERMISSIVE` or `FAILFAST`). Exact repeated names last-win; `spark.sql.mapKeyDedupPolicy` is not applied. XML CHAR/VARCHAR values use the same pad and `EXCEED_LIMIT_LENGTH` checks as other parsed text. Empty, attribute-only, and text-only map elements stay SQL NULL or a malformed record; they do not become an empty map or a `valueTag` entry. In Spark 4.3, `from_xm l` and XML reader schemas rejected CHAR/VARCHAR with `UNSUPPORTED_CHAR_OR_VARCHAR_AS_STRING`, or replaced them with STRING when `spark.sql.legacy.charVarcharAsString` was true, so CHAR/VARCHAR map keys did not reach the parser. When the standard-semantics flag is false and `spark.sql.preserveCharVarcharTypeInfo` is true, short CHAR keys are padded and over-length CHAR or VARCHAR keys raise `EXCEED_LIMIT_LENGTH`. +- Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is true, `from_avro` assigns CHAR/VARCHAR on STRING values (pad, then trim-and-`EXCEED_LIMIT_LENGTH`; Avro ENUM symbols are not assigned). Avro map keys of CHAR/VARCHAR are length-checked without padding or trimming in `from_avro` and Avro file scans, matching JSON/XML object names; an off-width key fails with `UNSUPPORTED_AVRO_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`). `from_avro` PERMISSIVE nulls that malformed record; file scans do not wrap per-row PERMISSIVE around the error. `to_avro` does not pad or trim. Avro file scans still leave CHAR padding of STRING values to read-side CHAR padding rather than the deserializer. Review Comment: Thanks. The from_avro migration bullet now names first-class types (preview or preserve) for assignment, keeps map-key checks on the preview flag, and describes PERMISSIVE as a struct with every field NULL (NULL for a non-struct schema). Same wording on the reference page and function notes. See dee7bd4d8a1. ########## docs/sql-ref-character-string-types.md: ########## @@ -0,0 +1,375 @@ +--- +layout: global +title: Character String Types +displayTitle: Character String Types (Preview) +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +First-class `CHAR` / `VARCHAR` / `STRING` semantics are a preview feature in Spark 4.4 and may +change in future releases. They are off by default. This page describes that preview mode only. + +Enable the preview feature before using the behavior below: + +```sql +SET spark.sql.charVarchar.standardSemantics.enabled = true; +``` + +By default, `CHAR` and `VARCHAR` are table-schema overlays on `STRING`. DataFrame schemas +show `STRING`, `CAST(... AS CHAR(n))` is `STRING`, and function schemas raise +`UNSUPPORTED_CHAR_OR_VARCHAR_AS_STRING`. See [Data Types](sql-ref-datatypes.html). + +Spark SQL then has a family of three character string types: + +```text +CHAR(n) -> VARCHAR(n) -> STRING +``` + +The arrow denotes type precedence from the most constrained type to the least constrained type. +This is analogous to the precedence of integral numeric types such as +`TINYINT -> SMALLINT -> INT -> BIGINT`. `CHAR`, `VARCHAR`, and `STRING` are distinct SQL types in +the same family; `CHAR` and `VARCHAR` are not special uses of `STRING`. + +## Related settings + +The preview flag is persisted with view definitions, so a view continues to use the semantics +under which it was created. + +The older `spark.sql.preserveCharVarcharTypeInfo` setting is an experimental compatibility mode. +It preserves type information, but does not apply all the type precedence and expression-result +rules described here. Standard semantics take precedence over +`spark.sql.legacy.charVarcharAsString` when both settings are `true`. + +Two other settings still apply when standard semantics are on: + +* `spark.sql.charAsVarchar` still rewrites `CHAR` to `VARCHAR` in `CREATE`/`REPLACE`/`ALTER TABLE`. + `CAST(... AS CHAR(n))` stays `CHAR(n)`. +* `spark.sql.readSideCharPadding` is ignored. Read-side CHAR padding and length checks always run; + setting it to `false` logs a warning. + +## Type definitions + +| SQL type | Value constraint | Behavior | +|----------|------------------|----------| +| `CHAR(n)` | Exactly `n` characters | Values shorter than `n` characters are padded on the right with spaces. | +| `VARCHAR(n)` | At most `n` characters | Values retain their length and are not padded. | +| `STRING` | Unbounded length | Values have no declared length constraint. | + +The length is required for `CHAR` and `VARCHAR`. `CHARACTER(n)` is a synonym for `CHAR(n)`. +The length `n` counts characters, not UTF-8 bytes. All three types can have a +[collation](sql-ref-syntax-aux-show-collations.html), which controls comparison, ordering, and +other collation-sensitive operations. + +For example: + +```sql +SELECT typeof(CAST('ab' AS CHAR(5))); +-- char(5) + +SELECT concat('<', CAST('ab' AS CHAR(5)), '>'); +-- <ab > + +SELECT typeof(CAST('ab' AS VARCHAR(5))); +-- varchar(5) +``` + +## Type precedence and least common type + +Spark finds the least common type when an operation needs one type that can represent multiple +inputs. Within the character string family, Spark uses these rules: + +| Input types | Least common type | +|-------------|-------------------| +| `CHAR(n)`, `CHAR(m)` | `CHAR(max(n, m))` | +| `CHAR(n)`, `VARCHAR(m)` | `VARCHAR(max(n, m))` | +| `VARCHAR(n)`, `VARCHAR(m)` | `VARCHAR(max(n, m))` | +| Any character string type and `STRING` | `STRING` | +| Any character string type `T` and untyped `NULL` | `T` | + +These rules apply recursively to arrays, maps, and structs. They are used by expressions and +operations such as `CASE`, `COALESCE`, `IN`, `UNION`, `INTERSECT`, `EXCEPT`, multi-row `VALUES`, +and collection constructors. + +```sql +SELECT typeof(coalesce( + CAST('a' AS CHAR(2)), + CAST('bb' AS CHAR(4)))); +-- char(4) + +SELECT typeof(coalesce( + CAST('a' AS CHAR(2)), + CAST('bb' AS VARCHAR(4)))); +-- varchar(4) + +SELECT typeof(coalesce( + CAST('a' AS VARCHAR(4)), + CAST('unbounded' AS STRING))); +-- string +``` + +Collation is also part of a character string type. The least-common-type rules above apply after +Spark resolves the input collations. When the result remains `CHAR` or `VARCHAR`, it retains the +resolved collation. + +## Casts + +An explicit `CAST` or `TRY_CAST` can introduce a `CHAR` or `VARCHAR` value: + +```sql +SELECT CAST('abc' AS CHAR(5)); +-- 'abc ' + +SELECT CAST('abc' AS VARCHAR(5)); +-- 'abc' +``` + +For an explicit cast from one character string type to another: + +* Casting to `CHAR(n)` truncates values longer than `n` characters and pads values shorter than + `n` characters. +* Casting to `VARCHAR(n)` truncates values longer than `n` characters. Spaces already present in + a `CHAR` value are kept, so `CAST(CAST('ab' AS CHAR(4)) AS VARCHAR(4))` is `'ab '`. + +For a cast from a non-character value, Spark first converts the value to characters and then +checks the target length. `CAST` raises `EXCEED_LIMIT_LENGTH` if the value does not fit, while +`TRY_CAST` returns `NULL`. + +```sql +SELECT CAST('abcdef' AS VARCHAR(3)); +-- 'abc' + +SELECT TRY_CAST(12345 AS VARCHAR(4)); +-- NULL +``` + +## Assignment and reading + +Assignment to a declared `CHAR` or `VARCHAR` type is stricter than an explicit +character-to-character cast. Assignment occurs for operations such as table writes, routine +arguments and return values, session variables, schema-driven Dataset or DataFrame conversion, and +the output columns of a script `TRANSFORM` clause. + +* A value that fits is stored unchanged, except that `CHAR(n)` is padded on the right to `n` + characters. +* If only trailing spaces exceed the declared length, Spark removes only the number of trailing + spaces needed to fit. +* If non-space characters exceed the declared length, Spark raises `EXCEED_LIMIT_LENGTH`. + +Reads apply the same checks. In particular, a `CHAR(n)` value read from external or otherwise +untrusted storage is padded if short and rejected if its non-space content is too long. The same +`EXCEED_LIMIT_LENGTH` check applies to oversize partition values. + +```sql +CREATE TABLE contacts (code CHAR(4), name VARCHAR(10)) USING parquet; + +INSERT INTO contacts VALUES ('ab', 'Ada'); + +SELECT concat('<', code, '>'), name FROM contacts; +-- <ab > Ada +``` + +A script `TRANSFORM` clause (including `MAP` and `REDUCE`) exchanges rows with the external script +as unbounded `STRING`, so a `CHAR(n)` or `VARCHAR(n)` in its `AS` output list does not constrain +what the script emits. Spark instead applies the assignment above to the script output in a +projection: a declared `CHAR(n)` is padded and an over-length value raises `EXCEED_LIMIT_LENGTH`. +The declared type is kept only when these semantics (or `spark.sql.preserveCharVarcharTypeInfo`) +are enabled; otherwise an `AS` type such as `CHAR(n)` is treated as plain `STRING` with no padding +or length check. + +## Expression result types + +A reference to a `CHAR` or `VARCHAR` column preserves its declared type. Expressions that select +among values without changing their character content, such as `CASE` and `COALESCE`, use the +least common type rules above. + +An expression that transforms character content returns `STRING`, because its result length is +not constrained by an input declaration. This includes string functions and operators such as +`upper`, `lower`, `substring`, `concat`, `||`, `regexp_replace`, `trim`, `split`, and `mask`. + +```sql +SELECT typeof(upper(CAST('ab' AS CHAR(2)))); +-- string + +SELECT typeof(CAST('a' AS CHAR(1)) || CAST('b' AS VARCHAR(1))); +-- string +``` + +This rule prevents a length constraint from propagating to newly computed content. To constrain +the result, cast it explicitly to `CHAR(n)` or `VARCHAR(n)`. + +## Comparisons and trailing spaces + +Spark coerces character string operands to a least common type before comparing them. Casting to a +wider `CHAR` pads the value, while casting to `VARCHAR` or `STRING` preserves spaces already +present in a `CHAR` value. + +```sql +SELECT CAST('a' AS CHAR(2)) = CAST('a' AS CHAR(4)); +-- true + +SELECT CAST('a' AS CHAR(2)) = CAST('a' AS VARCHAR(2)); +-- false + +SELECT CAST('a' AS CHAR(2)) = CAST('a ' AS VARCHAR(2)); +-- true +``` + +A [string literal](sql-ref-literals.html#string-literal) is `STRING`, not `CHAR` or `VARCHAR`. +Comparing it with `CHAR` is the same widening: the `CHAR` pad is kept, and the comparison is +exact. + +```sql +SELECT CAST('a' AS CHAR(2)) = 'a'; +-- false + +SELECT CAST('a' AS CHAR(2)) = 'a '; +-- true +``` + +A [parameter marker](sql-ref-parameter-markers.html) bound to a string value is also `STRING`. +The bound value is not padded unless you cast it to `CHAR`. + +```sql +EXECUTE IMMEDIATE 'SELECT CAST(''a'' AS CHAR(2)) = :p' USING 'a' AS p; +-- false + +EXECUTE IMMEDIATE 'SELECT CAST(''a'' AS CHAR(2)) = :p' USING 'a ' AS p; +-- true + +EXECUTE IMMEDIATE + 'SELECT CAST(''a'' AS CHAR(2)) = CAST(:p AS CHAR(2))' USING 'a' AS p; +-- true +``` + +Ignoring trailing spaces is a collation property, not an implicit property of every `CHAR` +comparison. Use an `RTRIM` collation when comparisons should ignore trailing spaces: + +```sql +SELECT CAST('a' AS CHAR(2) COLLATE UTF8_BINARY_RTRIM) = + 'a' COLLATE UTF8_BINARY_RTRIM; +-- true +``` + +## Parsing functions and map keys + +The functions `from_json`, `from_csv`, `from_xml`, and `from_avro` have two CHAR/VARCHAR surfaces: +the document they parse, and the schema they parse into. File scans use the same schema rules as +the corresponding `from_*` function except where noted. + +### Document arguments + +`from_json`, `from_csv`, `from_xml`, and `schema_of_json` / `schema_of_csv` / `schema_of_xml` take a +STRING document. A `CHAR` argument is not rtrimmed. Padding is part of the value after CAST to +STRING, so it is parsed as trailing spaces in the document: + +```sql +SELECT from_json(CAST('{"a":1}' AS CHAR(12)), 'a INT'); +-- same as from_json('{"a":1} ', 'a INT') +``` + +JSON and XML ignore that trailing whitespace. CSV can tokenize it: with a space delimiter, padding +becomes extra empty fields; with the default comma delimiter, padding lands on the last unquoted +field. When CHAR is first-class, `schema_of_csv(CAST('1' AS CHAR(3)))` infers +`STRUCT<_c0: DOUBLE>` because the padded last field is not a clean integer. Under default +settings that CAST is STRING and the inferred field is INT. +`map('ignoreTrailingWhiteSpace', 'true')` restores a trimmed last-field parse. + +`from_avro` takes binary Avro, not a character document, so CHAR padding of a SQL string does not +apply there. + +### Parsed values + +With a reader schema that declares `CHAR`/`VARCHAR` fields, parsed string values are assigned +when CHAR/VARCHAR are first-class (this preview flag or +`spark.sql.preserveCharVarcharTypeInfo`): `CHAR(n)` is padded, and an over-length value is a +bad record. For `from_json`, `from_csv`, and `from_xml`, `FAILFAST` raises +`EXCEED_LIMIT_LENGTH` and `PERMISSIVE` nulls the failed field. CSV field overflow follows the Review Comment: Thanks. from_json/from_csv/from_xml FAILFAST is now MALFORMED_RECORD_IN_PARSING caused by EXCEED_LIMIT_LENGTH, matching the from_avro "as the cause" shape. See dee7bd4d8a1. ########## docs/sql-ref-character-string-types.md: ########## @@ -0,0 +1,375 @@ +--- +layout: global +title: Character String Types +displayTitle: Character String Types (Preview) +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +First-class `CHAR` / `VARCHAR` / `STRING` semantics are a preview feature in Spark 4.4 and may +change in future releases. They are off by default. This page describes that preview mode only. + +Enable the preview feature before using the behavior below: + +```sql +SET spark.sql.charVarchar.standardSemantics.enabled = true; +``` + +By default, `CHAR` and `VARCHAR` are table-schema overlays on `STRING`. DataFrame schemas +show `STRING`, `CAST(... AS CHAR(n))` is `STRING`, and function schemas raise +`UNSUPPORTED_CHAR_OR_VARCHAR_AS_STRING`. See [Data Types](sql-ref-datatypes.html). + +Spark SQL then has a family of three character string types: + +```text +CHAR(n) -> VARCHAR(n) -> STRING +``` + +The arrow denotes type precedence from the most constrained type to the least constrained type. +This is analogous to the precedence of integral numeric types such as +`TINYINT -> SMALLINT -> INT -> BIGINT`. `CHAR`, `VARCHAR`, and `STRING` are distinct SQL types in +the same family; `CHAR` and `VARCHAR` are not special uses of `STRING`. + +## Related settings + +The preview flag is persisted with view definitions, so a view continues to use the semantics +under which it was created. + +The older `spark.sql.preserveCharVarcharTypeInfo` setting is an experimental compatibility mode. +It preserves type information, but does not apply all the type precedence and expression-result +rules described here. Standard semantics take precedence over +`spark.sql.legacy.charVarcharAsString` when both settings are `true`. + +Two other settings still apply when standard semantics are on: + +* `spark.sql.charAsVarchar` still rewrites `CHAR` to `VARCHAR` in `CREATE`/`REPLACE`/`ALTER TABLE`. + `CAST(... AS CHAR(n))` stays `CHAR(n)`. +* `spark.sql.readSideCharPadding` is ignored. Read-side CHAR padding and length checks always run; + setting it to `false` logs a warning. + +## Type definitions + +| SQL type | Value constraint | Behavior | +|----------|------------------|----------| +| `CHAR(n)` | Exactly `n` characters | Values shorter than `n` characters are padded on the right with spaces. | +| `VARCHAR(n)` | At most `n` characters | Values retain their length and are not padded. | +| `STRING` | Unbounded length | Values have no declared length constraint. | + +The length is required for `CHAR` and `VARCHAR`. `CHARACTER(n)` is a synonym for `CHAR(n)`. +The length `n` counts characters, not UTF-8 bytes. All three types can have a +[collation](sql-ref-syntax-aux-show-collations.html), which controls comparison, ordering, and +other collation-sensitive operations. + +For example: + +```sql +SELECT typeof(CAST('ab' AS CHAR(5))); +-- char(5) + +SELECT concat('<', CAST('ab' AS CHAR(5)), '>'); +-- <ab > + +SELECT typeof(CAST('ab' AS VARCHAR(5))); +-- varchar(5) +``` + +## Type precedence and least common type + +Spark finds the least common type when an operation needs one type that can represent multiple +inputs. Within the character string family, Spark uses these rules: + +| Input types | Least common type | +|-------------|-------------------| +| `CHAR(n)`, `CHAR(m)` | `CHAR(max(n, m))` | +| `CHAR(n)`, `VARCHAR(m)` | `VARCHAR(max(n, m))` | +| `VARCHAR(n)`, `VARCHAR(m)` | `VARCHAR(max(n, m))` | +| Any character string type and `STRING` | `STRING` | +| Any character string type `T` and untyped `NULL` | `T` | + +These rules apply recursively to arrays, maps, and structs. They are used by expressions and +operations such as `CASE`, `COALESCE`, `IN`, `UNION`, `INTERSECT`, `EXCEPT`, multi-row `VALUES`, +and collection constructors. + +```sql +SELECT typeof(coalesce( + CAST('a' AS CHAR(2)), + CAST('bb' AS CHAR(4)))); +-- char(4) + +SELECT typeof(coalesce( + CAST('a' AS CHAR(2)), + CAST('bb' AS VARCHAR(4)))); +-- varchar(4) + +SELECT typeof(coalesce( + CAST('a' AS VARCHAR(4)), + CAST('unbounded' AS STRING))); +-- string +``` + +Collation is also part of a character string type. The least-common-type rules above apply after +Spark resolves the input collations. When the result remains `CHAR` or `VARCHAR`, it retains the +resolved collation. + +## Casts + +An explicit `CAST` or `TRY_CAST` can introduce a `CHAR` or `VARCHAR` value: + +```sql +SELECT CAST('abc' AS CHAR(5)); +-- 'abc ' + +SELECT CAST('abc' AS VARCHAR(5)); +-- 'abc' +``` + +For an explicit cast from one character string type to another: + +* Casting to `CHAR(n)` truncates values longer than `n` characters and pads values shorter than + `n` characters. +* Casting to `VARCHAR(n)` truncates values longer than `n` characters. Spaces already present in + a `CHAR` value are kept, so `CAST(CAST('ab' AS CHAR(4)) AS VARCHAR(4))` is `'ab '`. + +For a cast from a non-character value, Spark first converts the value to characters and then +checks the target length. `CAST` raises `EXCEED_LIMIT_LENGTH` if the value does not fit, while +`TRY_CAST` returns `NULL`. + +```sql +SELECT CAST('abcdef' AS VARCHAR(3)); +-- 'abc' + +SELECT TRY_CAST(12345 AS VARCHAR(4)); +-- NULL +``` + +## Assignment and reading + +Assignment to a declared `CHAR` or `VARCHAR` type is stricter than an explicit +character-to-character cast. Assignment occurs for operations such as table writes, routine +arguments and return values, session variables, schema-driven Dataset or DataFrame conversion, and +the output columns of a script `TRANSFORM` clause. + +* A value that fits is stored unchanged, except that `CHAR(n)` is padded on the right to `n` + characters. +* If only trailing spaces exceed the declared length, Spark removes only the number of trailing + spaces needed to fit. +* If non-space characters exceed the declared length, Spark raises `EXCEED_LIMIT_LENGTH`. + +Reads apply the same checks. In particular, a `CHAR(n)` value read from external or otherwise +untrusted storage is padded if short and rejected if its non-space content is too long. The same +`EXCEED_LIMIT_LENGTH` check applies to oversize partition values. + +```sql +CREATE TABLE contacts (code CHAR(4), name VARCHAR(10)) USING parquet; + +INSERT INTO contacts VALUES ('ab', 'Ada'); + +SELECT concat('<', code, '>'), name FROM contacts; +-- <ab > Ada +``` + +A script `TRANSFORM` clause (including `MAP` and `REDUCE`) exchanges rows with the external script +as unbounded `STRING`, so a `CHAR(n)` or `VARCHAR(n)` in its `AS` output list does not constrain +what the script emits. Spark instead applies the assignment above to the script output in a +projection: a declared `CHAR(n)` is padded and an over-length value raises `EXCEED_LIMIT_LENGTH`. +The declared type is kept only when these semantics (or `spark.sql.preserveCharVarcharTypeInfo`) +are enabled; otherwise an `AS` type such as `CHAR(n)` is treated as plain `STRING` with no padding +or length check. + +## Expression result types + +A reference to a `CHAR` or `VARCHAR` column preserves its declared type. Expressions that select +among values without changing their character content, such as `CASE` and `COALESCE`, use the +least common type rules above. + +An expression that transforms character content returns `STRING`, because its result length is +not constrained by an input declaration. This includes string functions and operators such as +`upper`, `lower`, `substring`, `concat`, `||`, `regexp_replace`, `trim`, `split`, and `mask`. + +```sql +SELECT typeof(upper(CAST('ab' AS CHAR(2)))); +-- string + +SELECT typeof(CAST('a' AS CHAR(1)) || CAST('b' AS VARCHAR(1))); +-- string +``` + +This rule prevents a length constraint from propagating to newly computed content. To constrain +the result, cast it explicitly to `CHAR(n)` or `VARCHAR(n)`. + +## Comparisons and trailing spaces + +Spark coerces character string operands to a least common type before comparing them. Casting to a +wider `CHAR` pads the value, while casting to `VARCHAR` or `STRING` preserves spaces already +present in a `CHAR` value. + +```sql +SELECT CAST('a' AS CHAR(2)) = CAST('a' AS CHAR(4)); +-- true + +SELECT CAST('a' AS CHAR(2)) = CAST('a' AS VARCHAR(2)); +-- false + +SELECT CAST('a' AS CHAR(2)) = CAST('a ' AS VARCHAR(2)); +-- true +``` + +A [string literal](sql-ref-literals.html#string-literal) is `STRING`, not `CHAR` or `VARCHAR`. +Comparing it with `CHAR` is the same widening: the `CHAR` pad is kept, and the comparison is +exact. + +```sql +SELECT CAST('a' AS CHAR(2)) = 'a'; +-- false + +SELECT CAST('a' AS CHAR(2)) = 'a '; +-- true +``` + +A [parameter marker](sql-ref-parameter-markers.html) bound to a string value is also `STRING`. +The bound value is not padded unless you cast it to `CHAR`. + +```sql +EXECUTE IMMEDIATE 'SELECT CAST(''a'' AS CHAR(2)) = :p' USING 'a' AS p; +-- false + +EXECUTE IMMEDIATE 'SELECT CAST(''a'' AS CHAR(2)) = :p' USING 'a ' AS p; +-- true + +EXECUTE IMMEDIATE + 'SELECT CAST(''a'' AS CHAR(2)) = CAST(:p AS CHAR(2))' USING 'a' AS p; +-- true +``` + +Ignoring trailing spaces is a collation property, not an implicit property of every `CHAR` +comparison. Use an `RTRIM` collation when comparisons should ignore trailing spaces: + +```sql +SELECT CAST('a' AS CHAR(2) COLLATE UTF8_BINARY_RTRIM) = + 'a' COLLATE UTF8_BINARY_RTRIM; +-- true +``` + +## Parsing functions and map keys + +The functions `from_json`, `from_csv`, `from_xml`, and `from_avro` have two CHAR/VARCHAR surfaces: +the document they parse, and the schema they parse into. File scans use the same schema rules as +the corresponding `from_*` function except where noted. + +### Document arguments + +`from_json`, `from_csv`, `from_xml`, and `schema_of_json` / `schema_of_csv` / `schema_of_xml` take a +STRING document. A `CHAR` argument is not rtrimmed. Padding is part of the value after CAST to +STRING, so it is parsed as trailing spaces in the document: + +```sql +SELECT from_json(CAST('{"a":1}' AS CHAR(12)), 'a INT'); +-- same as from_json('{"a":1} ', 'a INT') +``` + +JSON and XML ignore that trailing whitespace. CSV can tokenize it: with a space delimiter, padding +becomes extra empty fields; with the default comma delimiter, padding lands on the last unquoted +field. When CHAR is first-class, `schema_of_csv(CAST('1' AS CHAR(3)))` infers +`STRUCT<_c0: DOUBLE>` because the padded last field is not a clean integer. Under default +settings that CAST is STRING and the inferred field is INT. +`map('ignoreTrailingWhiteSpace', 'true')` restores a trimmed last-field parse. + +`from_avro` takes binary Avro, not a character document, so CHAR padding of a SQL string does not +apply there. + +### Parsed values + +With a reader schema that declares `CHAR`/`VARCHAR` fields, parsed string values are assigned +when CHAR/VARCHAR are first-class (this preview flag or +`spark.sql.preserveCharVarcharTypeInfo`): `CHAR(n)` is padded, and an over-length value is a +bad record. For `from_json`, `from_csv`, and `from_xml`, `FAILFAST` raises +`EXCEED_LIMIT_LENGTH` and `PERMISSIVE` nulls the failed field. CSV field overflow follows the +CSV parse mode the same way. + +`from_avro` assigns STRING values only. Avro ENUM symbols are not padded or length-checked, so a +padded CHAR cannot invent a symbol. Overflow is a malformed record handled by the `mode` +option: `FAILFAST` fails with `EXCEED_LIMIT_LENGTH` as the cause; `PERMISSIVE` returns a null +record. Avro file scans leave CHAR padding of STRING values to read-side CHAR padding rather +than assigning in the deserializer. `to_json`, `to_csv`, `to_xml`, and `to_avro` serialize the +current string content; `to_avro` does not itself pad or trim. + +### Map keys + +A `CHAR(n)` or `VARCHAR(n)` map key is length-checked but never padded or trimmed: a `CHAR(n)` key +must already be exactly `n` characters, and a `VARCHAR(n)` key at most `n`. Padding or trimming a +key would change its identity and could silently merge distinct keys. + +* JSON (`from_json` and JSON scans): `UNSUPPORTED_JSON_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`). + `from_json` captures the standard-semantics flag at analysis, so a view keeps the check from + when it was created. JSON file scans, including scans inside persisted views, read the flag + from the executing session. Duplicate names are kept, exactly as for STRING keys; + `spark.sql.mapKeyDedupPolicy` is not applied at parse time. Keys rebuilt read-side on a file + scan can still raise `DUPLICATED_MAP_KEY`. `from_json(to_json(m))` is not folded away when `m` + has a CHAR/VARCHAR map key. +* XML (`from_xml` and XML scans): `UNSUPPORTED_XML_CHAR_VARCHAR_MAP_KEY`. Checked names are + element names, `attributePrefix` plus attribute names (default prefix `_`), and the `valueTag` + (default `_VALUE`) when mixed text is present. Exact repeated names last-win. + `spark.sql.mapKeyDedupPolicy` is not applied. Empty, attribute-only, and text-only map elements + stay SQL NULL or a malformed record; they do not become an empty map or a `valueTag` entry. + When standard semantics are off and `spark.sql.preserveCharVarcharTypeInfo` is on, short CHAR + keys are padded and over-length keys raise `EXCEED_LIMIT_LENGTH`. +* Avro (`from_avro` and Avro scans): `UNSUPPORTED_AVRO_CHAR_VARCHAR_MAP_KEY`. `from_avro` + PERMISSIVE nulls that malformed record; Avro file scans do not wrap per-row PERMISSIVE around + the error. +* CSV has no object-name map keys; `from_csv` schemas are structs of fields. + +`PERMISSIVE` sets the nearest enclosing field to `null`; `FAILFAST` fails with the error above as Review Comment: Thanks. Scoped PERMISSIVE nearest-enclosing-field / FAILFAST MALFORMED_RECORD_IN_PARSING to JSON and XML, and noted that from_avro FAILFAST is a SparkException with no error class. See dee7bd4d8a1. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
