mrhhsg commented on code in PR #63528:
URL: https://github.com/apache/doris/pull/63528#discussion_r4227164743


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinition.java:
##########
@@ -630,16 +629,68 @@ private void validateInternal(boolean isOlap, Set<String> 
keysSet, Set<String> c
      * Validate non-null defaults for complex types before connector-specific 
validation.
      */
     public void validateComplexTypeDefaultValue() throws AnalysisException {
-        if (!defaultValue.isPresent() || defaultValue.get() == 
DefaultValue.NULL_DEFAULT_VALUE) {
-            return;
-        }
-        if (type.isMapType()) {
-            throw new AnalysisException("Map type column default value just 
support null");
+        if (type.isArrayType()) {
+            validateArrayDefaultValue();
+        } else if (type.isMapType()) {
+            validateMapDefaultValue();

Review Comment:
   Addressed in f44cc1901ac. The DDL parser keeps the text between the quotes 
of a default value verbatim (`LogicalPlanBuilder.toStringValue`; unlike 
comments it does not decode escapes), so `DEFAULT "{\"a\":10}"` would be 
replayed as `{\"a\":10}`. `Column.toSql()` now wraps a default that contains 
double quotes in single quotes (`DEFAULT '{"a":10}'`), which replays exactly; 
plain defaults keep the existing `DEFAULT "..."` rendering. Canonical complex 
defaults never contain single quotes (see the escaped-string thread). Covered 
by `ColumnTest.testToSqlQuotesDefaultValueForReplay` and the SHOW CREATE TABLE 
/ CREATE TABLE LIKE round trip in `test_complex_default_value` (`like_default`).



##########
be/src/core/data_type_serde/complex_type_deserialize_util.h:
##########
@@ -80,13 +81,37 @@ struct ComplexTypeDeserializeUtil {
 
     static bool is_null_string(const StringRef& str) {
         if (str.size == 4) {
-            // null
-            return str.data[0] == 'n' && str.data[1] == 'u' && str.data[2] == 
'l' &&
-                   str.data[3] == 'l';
+            // SQL NULL literal is case-insensitive.
+            return (str.data[0] == 'n' || str.data[0] == 'N') &&
+                   (str.data[1] == 'u' || str.data[1] == 'U') &&
+                   (str.data[2] == 'l' || str.data[2] == 'L') &&
+                   (str.data[3] == 'l' || str.data[3] == 'L');
         }
         return false;
     }
 
+    static Status process_field(const DataTypeSerDeSPtr& serde, StringRef str, 
Field& field) {
+        str = str.trim_whitespace();
+        auto nullable_serde = 
std::dynamic_pointer_cast<DataTypeNullableSerDe>(serde);
+        if (is_null_string(str)) {
+            if (nullable_serde == nullptr) {
+                return Status::InvalidArgument(
+                        "NULL default is not allowed for non-nullable complex 
field");
+            }
+            field = Field::create_field<TYPE_NULL>(Null {});
+            return Status::OK();
+        }
+        auto str_without_quote = str.trim_quote();

Review Comment:
   Addressed in f44cc1901ac. FE now parses the default as a SQL literal and 
stores a canonical text (`ComplexTypeDefaultValue.canonicalize`), so `"a""b"` 
is decoded to `a"b` on the FE side. Because neither BE parser of that text 
decodes escapes (the complex `from_fe_string` used here and the 
string-to-complex cast used by INSERT) and the DDL parser keeps default text 
verbatim when SHOW CREATE / LIKE output is replayed, a nested string value 
containing a quote or backslash cannot be stored unambiguously; it is rejected 
at DDL time with `must not contain quote or backslash` instead of being 
materialized as `a""b`. Covered by `ColumnDefinitionTest` and the 
`createTableRejects` cases in `test_complex_default_value`; accepted values 
with delimiters and brackets inside are read back through the default iterator 
in `alter_default` and pinned on the BE side by 
`canonicalComplexDefaultFromFeString`.



##########
be/src/core/data_type_serde/data_type_map_serde.cpp:
##########
@@ -756,6 +756,68 @@ Status DataTypeMapSerDe::_from_string(StringRef& str, 
IColumn& column,
     return Status::OK();
 }
 
+Status DataTypeMapSerDe::from_fe_string(const std::string& str, Field& field) 
const {
+    StringRef slice(str);
+    slice = slice.trim_whitespace();
+    if (slice.empty()) {
+        return Status::InvalidArgument("slice is empty!");
+    }
+    if (slice.front() != '{') {
+        std::stringstream ss;
+        ss << slice.front() << '\'';
+        return Status::InvalidArgument("Map does not start with '{' character, 
found '" + ss.str());
+    }
+    if (slice.back() != '}') {
+        std::stringstream ss;
+        ss << slice.back() << '\'';
+        return Status::InvalidArgument("Map does not end with '}' character, 
found '" + ss.str());
+    }
+
+    Array keys;
+    Array values;
+    if (slice.size > 2) {
+        slice = slice.substring(1, slice.size - 2);
+        slice = slice.trim_whitespace();
+        if (!slice.empty()) {
+            FormatOptions options;
+            auto split_result = 
ComplexTypeDeserializeUtil::split_by_delimiter(slice, [&](char c) {
+                return c == options.map_key_delim || c == 
options.collection_delim;
+            });
+            if (split_result.size() % 2 != 0) {
+                return Status::InvalidArgument("Map does not have even number 
of key-value pairs");
+            }
+            keys.reserve(split_result.size() / 2);
+            values.reserve(split_result.size() / 2);
+            for (int i = 0; i < split_result.size(); i += 2) {
+                if (split_result[i].delimiter != options.map_key_delim) {
+                    return Status::InvalidArgument(
+                            "Map key-value pair does not have map key 
delimiter");
+                }
+                if (i != 0 && split_result[i - 1].delimiter != 
options.collection_delim) {
+                    return Status::InvalidArgument(
+                            "Map key-value pair does not have collection 
delimiter");
+                }
+
+                Field key_field;
+                Field value_field;
+                RETURN_IF_ERROR(ComplexTypeDeserializeUtil::process_field(
+                        key_serde, split_result[i].element, key_field));
+                RETURN_IF_ERROR(ComplexTypeDeserializeUtil::process_field(
+                        value_serde, split_result[i + 1].element, 
value_field));
+                keys.push_back(std::move(key_field));

Review Comment:
   Addressed in f44cc1901ac. The canonical text is rendered from the parsed 
`MapLiteral`, which keeps the last value of a repeated key, so `{"a":1,"a":2}` 
is stored as `{"a":2}` and BE never receives repeated pairs. Covered by 
`ColumnDefinitionTest` and by `map_repeated_key` in 
`test_complex_default_value`, which reads the default through INSERT, the light 
schema change default iterator (`alter_default`) and direct schema change 
(`alter_direct_default`).



##########
be/src/core/data_type_serde/complex_type_deserialize_util.h:
##########
@@ -80,13 +81,37 @@ struct ComplexTypeDeserializeUtil {
 
     static bool is_null_string(const StringRef& str) {
         if (str.size == 4) {
-            // null
-            return str.data[0] == 'n' && str.data[1] == 'u' && str.data[2] == 
'l' &&
-                   str.data[3] == 'l';
+            // SQL NULL literal is case-insensitive.
+            return (str.data[0] == 'n' || str.data[0] == 'N') &&
+                   (str.data[1] == 'u' || str.data[1] == 'U') &&
+                   (str.data[2] == 'l' || str.data[2] == 'L') &&
+                   (str.data[3] == 'l' || str.data[3] == 'L');
         }
         return false;
     }
 
+    static Status process_field(const DataTypeSerDeSPtr& serde, StringRef str, 
Field& field) {
+        str = str.trim_whitespace();
+        auto nullable_serde = 
std::dynamic_pointer_cast<DataTypeNullableSerDe>(serde);
+        if (is_null_string(str)) {
+            if (nullable_serde == nullptr) {
+                return Status::InvalidArgument(
+                        "NULL default is not allowed for non-nullable complex 
field");
+            }
+            field = Field::create_field<TYPE_NULL>(Null {});
+            return Status::OK();
+        }
+        auto str_without_quote = str.trim_quote();
+        auto nested_serde = serde;
+        if (nullable_serde != nullptr) {
+            // Complex default values need strict nested conversion. 
DataTypeNullableSerDe
+            // converts nested parse failures to NULL, which would silently 
accept
+            // type-mismatched default literals.
+            nested_serde = nullable_serde->get_nested_serde();
+        }
+        return nested_serde->from_fe_string(str_without_quote.to_string(), 
field);

Review Comment:
   Addressed in f44cc1901ac. FE now casts every nested literal to the declared 
nested type with strict checking and stores the rendered value instead of the 
SQL token spelling: `[DATEV2 "2024-01-01"]` and `["2024-01-01"]` are both 
stored as `["2024-01-01"]`, `[1e3, "7"]` for `ARRAY<INT>` as `[1000, 7]`, 
booleans as `1`/`0`, decimals at the column scale, and a type mismatch such as 
`["bad"]` is rejected at CREATE/ALTER time. Nested types whose text format is 
not pinned down (TIMEV2, TIMESTAMP_NS, TIMESTAMP_TZ, UUID) are rejected 
explicitly rather than forwarded. The swallow in the date serde's 
`from_olap_string` is shared with the zonemap path and is left unchanged; it is 
no longer reachable from a default because the stored text is always the 
canonical `yyyy-MM-dd` form. Covered by `ColumnDefinitionTest`, BE UT 
`canonicalComplexDefaultFromFeString` (what BE must accept from the canonical 
text), and `arr_typed_date` / `arr_exponent` / `arr_bool` / `arr_decimal` in 
`test_complex_defaul
 t_value`, read through old rows in `alter_default` and `alter_direct_default`.



-- 
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]

Reply via email to