aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/clickhouse/src/Interpreters/TableOverrideUtils.cpp
blob: afff5ac011181ce3ce0885a13286eae826183120 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <Interpreters/TableOverrideUtils.h>

#include <Common/quoteString.h>
#include <DataTypes/IDataType.h>
#include <DataTypes/DataTypeFactory.h>
#include <IO/WriteBufferFromString.h>
#include <Interpreters/InDepthNodeVisitor.h>
#include <Interpreters/RequiredSourceColumnsVisitor.h>
#include <Parsers/ASTColumnDeclaration.h>
#include <Parsers/ASTCreateQuery.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTTableOverrides.h>
#include <Parsers/IAST.h>
#include <Storages/ColumnDefault.h>
#include <Storages/StorageInMemoryMetadata.h>

namespace DB
{

namespace ErrorCodes
{
    extern const int INVALID_TABLE_OVERRIDE;
}

namespace
{

class MaybeNullableColumnsMatcher
{
public:
    using Visitor = ConstInDepthNodeVisitor<MaybeNullableColumnsMatcher, false>;
    using Data = RequiredSourceColumnsData;

    static bool needChildVisit(const ASTPtr & node, const ASTPtr & child)
    {
        if (const auto * f = node->as<ASTFunction>(); f && f->name == "assumeNotNull")
            return false;
        return RequiredSourceColumnsMatcher::needChildVisit(node, child);
    }

    static void visit(const ASTPtr & ast, Data & data)
    {
        RequiredSourceColumnsMatcher::visit(ast, data);
    }
};

using MaybeNullableColumnsVisitor = MaybeNullableColumnsMatcher::Visitor;

}

static void checkRequiredColumns(const IAST * ast, const NameToTypeMap & existing_types, NamesAndTypes & used_columns, const String & what, bool allow_nulls = false)
{
    if (!ast)
        return;
    RequiredSourceColumnsData columns_data;
    RequiredSourceColumnsVisitor(columns_data).visit(ast->clone());
    auto required_columns = columns_data.requiredColumns();
    for (const auto & column : required_columns)
    {
        auto type = existing_types.find(column);
        if (type == existing_types.end())
            throw Exception(ErrorCodes::INVALID_TABLE_OVERRIDE, "{} override refers to unknown column {}", what, backQuote(column));
    }
    if (!allow_nulls)
    {
        RequiredSourceColumnsData nullable_data;
        MaybeNullableColumnsVisitor(nullable_data).visit(ast->clone());
        for (const auto & column : nullable_data.requiredColumns())
        {
            if (existing_types.find(column)->second->isNullable())
                throw Exception(
                    ErrorCodes::INVALID_TABLE_OVERRIDE,
                    "{} override refers to nullable column {} (use assumeNotNull() if the column does not in fact contain NULL values)",
                    what,
                    backQuote(column));
        }
    }
    for (const auto & col : required_columns)
    {
        used_columns.push_back({col, existing_types.find(col)->second});
    }
}

void TableOverrideAnalyzer::analyze(const StorageInMemoryMetadata & metadata, Result & result) const
{
    for (const auto & column : metadata.columns)
        result.existing_types[column.name] = column.type;
    checkRequiredColumns(override->storage->order_by, result.existing_types, result.order_by_columns, "ORDER BY");
    checkRequiredColumns(override->storage->primary_key, result.existing_types, result.primary_key_columns, "PRIMARY KEY");
    checkRequiredColumns(override->storage->partition_by, result.existing_types, result.partition_by_columns, "PARTITION BY");
    checkRequiredColumns(override->storage->sample_by, result.existing_types, result.sample_by_columns, "SAMPLE BY");
    checkRequiredColumns(override->storage->ttl_table, result.existing_types, result.ttl_columns, "TTL");
    if (override->columns && override->columns->columns)
    {
        for (const auto & column_ast : override->columns->columns->children)
        {
            auto * override_column = column_ast->as<ASTColumnDeclaration>();
            auto override_type = DataTypeFactory::instance().get(override_column->type);
            auto found = metadata.columns.tryGetColumnOrSubcolumn(GetColumnsOptions::All, override_column->name);
            std::optional<ColumnDefaultKind> override_default_kind;
            if (!override_column->default_specifier.empty())
                override_default_kind = columnDefaultKindFromString(override_column->default_specifier);
            if (found)
            {
                std::optional<ColumnDefaultKind> existing_default_kind;
                if (auto col_default = metadata.columns.getDefault(found->name))
                    existing_default_kind = col_default->kind;
                if (existing_default_kind != override_default_kind)
                    throw Exception(ErrorCodes::INVALID_TABLE_OVERRIDE,
                                    "column {}: modifying default specifier is not allowed",
                                    backQuote(override_column->name));
                result.modified_columns.push_back({found->name, override_type});
                /// TODO: validate that the original type can be converted to the overridden type
            }
            else
            {
                if (override_default_kind && *override_default_kind == ColumnDefaultKind::Alias)
                    result.added_columns.push_back({override_column->name, override_type});
                else
                    throw Exception(ErrorCodes::INVALID_TABLE_OVERRIDE, "column {}: can only add ALIAS columns", backQuote(override_column->name));
            }
            /// TODO: validate default and materialized expressions (use checkRequiredColumns, allowing nulls)
        }
    }
}

void TableOverrideAnalyzer::Result::appendTo(WriteBuffer & ostr)
{
    const auto & format_names = [&](const NamesAndTypes & names) -> String
    {
        WriteBufferFromOwnString buf;
        bool first = true;
        for (const auto & name : names)
        {
            if (!first)
                buf << ", ";
            first = false;
            buf << backQuote(name.name) << " ";
            auto old_type = existing_types.find(name.name);
            if (old_type != existing_types.end() && old_type->second != name.type)
                buf << old_type->second->getName() << " -> ";
            buf << name.type->getName();
        }
        return buf.str();
    };
    if (!modified_columns.empty())
    {
        ostr << "Modified columns: " << format_names(modified_columns) << "\n";
    }
    if (!added_columns.empty())
    {
        ostr << "Added columns: " << format_names(added_columns) << "\n";
    }
    if (!order_by_columns.empty())
    {
        ostr << "ORDER BY uses columns: " << format_names(order_by_columns) << "\n";
    }
    if (!primary_key_columns.empty())
    {
        ostr << "PRIMARY KEY uses columns: " << format_names(primary_key_columns) << "\n";
    }
    if (!partition_by_columns.empty())
    {
        ostr << "PARTITION BY uses columns: " << format_names(partition_by_columns) << "\n";
    }
    if (!sample_by_columns.empty())
    {
        ostr << "SAMPLE BY uses columns: " << format_names(sample_by_columns) << "\n";
    }
    if (!ttl_columns.empty())
    {
        ostr << "TTL uses columns: " << format_names(ttl_columns) << "\n";
    }
}

}