activerecord-clickhouse-adapter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE +21 -0
- data/README.md +313 -0
- data/lib/active_record/connection_adapters/clickhouse/database_statements.rb +464 -0
- data/lib/active_record/connection_adapters/clickhouse/error_translation.rb +52 -0
- data/lib/active_record/connection_adapters/clickhouse/gem_version.rb +9 -0
- data/lib/active_record/connection_adapters/clickhouse/http_connection.rb +233 -0
- data/lib/active_record/connection_adapters/clickhouse/querying.rb +417 -0
- data/lib/active_record/connection_adapters/clickhouse/quoting.rb +92 -0
- data/lib/active_record/connection_adapters/clickhouse/row_binary.rb +190 -0
- data/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb +146 -0
- data/lib/active_record/connection_adapters/clickhouse/schema_dumper.rb +201 -0
- data/lib/active_record/connection_adapters/clickhouse/schema_statements.rb +722 -0
- data/lib/active_record/connection_adapters/clickhouse/type_parser.rb +207 -0
- data/lib/active_record/connection_adapters/clickhouse/types.rb +238 -0
- data/lib/active_record/connection_adapters/clickhouse_adapter.rb +126 -0
- data/lib/active_record/tasks/clickhouse_database_tasks.rb +91 -0
- data/lib/activerecord-clickhouse-adapter.rb +21 -0
- data/lib/arel/visitors/clickhouse.rb +172 -0
- metadata +82 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arel
|
|
4
|
+
module Visitors
|
|
5
|
+
# Rendering for the adapter's own dialect nodes (WITH FILL, ROLLUP, ARRAY JOIN and
|
|
6
|
+
# the LIMIT BY/SETTINGS suffix) — split from the statement-shape overrides below.
|
|
7
|
+
module ClickHouseDialectRendering
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# LIMIT BY renders before LIMIT (probed: the reverse is a syntax error) and
|
|
11
|
+
# SETTINGS renders last; both ride in the unused lock slot.
|
|
12
|
+
def visit_Arel_Nodes_SelectOptions(o, collector)
|
|
13
|
+
suffix = o.lock if o.lock.is_a?(ActiveRecord::ConnectionAdapters::ClickHouse::Nodes::DialectSuffix)
|
|
14
|
+
return super unless suffix
|
|
15
|
+
|
|
16
|
+
collector << " LIMIT #{limit_by_clause(suffix.limit_by)}" if suffix.limit_by
|
|
17
|
+
collector = maybe_visit o.limit, collector
|
|
18
|
+
collector = maybe_visit o.offset, collector
|
|
19
|
+
collector << " SETTINGS #{settings_clause(suffix.settings)}" if suffix.settings.present?
|
|
20
|
+
collector
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def limit_by_clause(limit_by)
|
|
24
|
+
count, columns = limit_by
|
|
25
|
+
"#{count} BY #{columns.map { |column| quote_column_name(column) }.join(", ")}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def settings_clause(settings)
|
|
29
|
+
settings.map do |name, value|
|
|
30
|
+
unless /\A[a-zA-Z_][a-zA-Z0-9_]*\z/.match?(name.to_s)
|
|
31
|
+
raise ArgumentError, "invalid ClickHouse setting name: #{name.inspect}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
"#{name} = #{quote(value)}"
|
|
35
|
+
end.join(", ")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def visit_ActiveRecord_ConnectionAdapters_ClickHouse_Nodes_OrderWithFill(o, collector)
|
|
39
|
+
collector = visit o.expr, collector
|
|
40
|
+
collector << " WITH FILL"
|
|
41
|
+
collector << " STEP #{o.step}" if o.step
|
|
42
|
+
collector
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Arel ships RollUp for grouping sets but only the PostgreSQL visitor renders it;
|
|
46
|
+
# ClickHouse shares the GROUP BY ROLLUP(a, b) spelling.
|
|
47
|
+
def visit_Arel_Nodes_RollUp(o, collector)
|
|
48
|
+
collector << "ROLLUP("
|
|
49
|
+
collector = inject_join o.expr, collector, ", "
|
|
50
|
+
collector << ")"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def visit_ActiveRecord_ConnectionAdapters_ClickHouse_Nodes_ArrayJoin(o, collector)
|
|
54
|
+
collector << "LEFT " if o.left
|
|
55
|
+
collector << "ARRAY JOIN "
|
|
56
|
+
collector = inject_join o.columns, collector, ", "
|
|
57
|
+
collector << " AS #{quote_column_name(o.alias_name)}" if o.alias_name
|
|
58
|
+
collector
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class ClickHouse < ToSql
|
|
63
|
+
include ClickHouseDialectRendering
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# ClickHouse's mutation machinery (lightweight DELETE, ALTER UPDATE) resolves WHERE
|
|
68
|
+
# against an internal projection where table-qualified column names raise
|
|
69
|
+
# UNKNOWN_IDENTIFIER (code 47), and it requires an explicit WHERE clause — so
|
|
70
|
+
# mutation statements compile with bare column names and `WHERE 1` when unscoped.
|
|
71
|
+
def visit_Arel_Nodes_DeleteStatement(o, collector)
|
|
72
|
+
o.wheres = [Arel::Nodes::SqlLiteral.new("1")] if o.wheres.empty?
|
|
73
|
+
unqualifying_columns { super }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# No UPDATE statement in ClickHouse 25.8 without opt-in table settings; the
|
|
77
|
+
# portable write path is the ALTER TABLE ... UPDATE mutation.
|
|
78
|
+
def visit_Arel_Nodes_UpdateStatement(o, collector)
|
|
79
|
+
o = prepare_update_statement(o)
|
|
80
|
+
|
|
81
|
+
unqualifying_columns do
|
|
82
|
+
collector << "ALTER TABLE "
|
|
83
|
+
collector = visit o.relation, collector
|
|
84
|
+
collector << " UPDATE "
|
|
85
|
+
collector = inject_join o.values, collector, ", "
|
|
86
|
+
collect_update_wheres(o, collector)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def collect_update_wheres(o, collector)
|
|
91
|
+
if o.wheres.empty?
|
|
92
|
+
collector << " WHERE 1"
|
|
93
|
+
else
|
|
94
|
+
collector << " WHERE "
|
|
95
|
+
inject_join o.wheres, collector, " AND "
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def visit_ActiveRecord_ConnectionAdapters_ClickHouse_Nodes_TableWithModifiers(o, collector)
|
|
100
|
+
collector = visit o.expr, collector
|
|
101
|
+
collector << " FINAL" if o.final
|
|
102
|
+
collector << " SAMPLE #{o.sample}" if o.sample
|
|
103
|
+
collector
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def visit_ActiveRecord_ConnectionAdapters_ClickHouse_Nodes_Prewhere(o, collector)
|
|
107
|
+
visit o.expr, collector
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Prewhere nodes ride through SelectCore#wheres; grammar order is
|
|
111
|
+
# FROM ... [FINAL] [SAMPLE] PREWHERE ... WHERE ...
|
|
112
|
+
def visit_Arel_Nodes_SelectCore(o, collector)
|
|
113
|
+
prewheres, plain_wheres = o.wheres.partition do |node|
|
|
114
|
+
node.is_a?(ActiveRecord::ConnectionAdapters::ClickHouse::Nodes::Prewhere)
|
|
115
|
+
end
|
|
116
|
+
return super if prewheres.empty?
|
|
117
|
+
|
|
118
|
+
o = o.dup
|
|
119
|
+
o.wheres = plain_wheres
|
|
120
|
+
|
|
121
|
+
with_prewhere_hook(prewheres) { super(o, collector) }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# SelectCore has no PREWHERE slot, so rendering is spliced in right after the
|
|
125
|
+
# FROM source via visit_Arel_Nodes_JoinSource.
|
|
126
|
+
def visit_Arel_Nodes_JoinSource(o, collector)
|
|
127
|
+
collected = super
|
|
128
|
+
if @pending_prewheres
|
|
129
|
+
collected << " PREWHERE "
|
|
130
|
+
collected = inject_join @pending_prewheres, collected, " AND "
|
|
131
|
+
@pending_prewheres = nil
|
|
132
|
+
end
|
|
133
|
+
collected
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def with_prewhere_hook(prewheres)
|
|
137
|
+
@pending_prewheres = prewheres
|
|
138
|
+
yield
|
|
139
|
+
ensure
|
|
140
|
+
@pending_prewheres = nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def visit_Arel_Attributes_Attribute(o, collector)
|
|
144
|
+
return super unless @unqualify_columns
|
|
145
|
+
|
|
146
|
+
collector << quote_column_name(o.name)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Only the mutation's own WHERE needs bare names; a nested SELECT (the
|
|
150
|
+
# WHERE key IN (subquery) rewrite for ordered/limited/joined mutations) is a
|
|
151
|
+
# regular query where stripping qualifiers makes JOIN columns ambiguous
|
|
152
|
+
# (AMBIGUOUS_COLUMN_NAME, code 352).
|
|
153
|
+
def visit_Arel_Nodes_SelectStatement(o, collector)
|
|
154
|
+
return super unless @unqualify_columns
|
|
155
|
+
|
|
156
|
+
@unqualify_columns = false
|
|
157
|
+
begin
|
|
158
|
+
super
|
|
159
|
+
ensure
|
|
160
|
+
@unqualify_columns = true
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def unqualifying_columns
|
|
165
|
+
@unqualify_columns = true
|
|
166
|
+
yield
|
|
167
|
+
ensure
|
|
168
|
+
@unqualify_columns = false
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: activerecord-clickhouse-adapter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ikraam Ghoor
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '8.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9.0'
|
|
32
|
+
description: 'A fully featured Active Record adapter for ClickHouse: native types,
|
|
33
|
+
server-side bind parameters, MergeTree-aware migrations, and real instrumentation
|
|
34
|
+
(read rows/bytes, server elapsed time) on every query.'
|
|
35
|
+
executables: []
|
|
36
|
+
extensions: []
|
|
37
|
+
extra_rdoc_files: []
|
|
38
|
+
files:
|
|
39
|
+
- CHANGELOG.md
|
|
40
|
+
- LICENSE
|
|
41
|
+
- README.md
|
|
42
|
+
- lib/active_record/connection_adapters/clickhouse/database_statements.rb
|
|
43
|
+
- lib/active_record/connection_adapters/clickhouse/error_translation.rb
|
|
44
|
+
- lib/active_record/connection_adapters/clickhouse/gem_version.rb
|
|
45
|
+
- lib/active_record/connection_adapters/clickhouse/http_connection.rb
|
|
46
|
+
- lib/active_record/connection_adapters/clickhouse/querying.rb
|
|
47
|
+
- lib/active_record/connection_adapters/clickhouse/quoting.rb
|
|
48
|
+
- lib/active_record/connection_adapters/clickhouse/row_binary.rb
|
|
49
|
+
- lib/active_record/connection_adapters/clickhouse/schema_definitions.rb
|
|
50
|
+
- lib/active_record/connection_adapters/clickhouse/schema_dumper.rb
|
|
51
|
+
- lib/active_record/connection_adapters/clickhouse/schema_statements.rb
|
|
52
|
+
- lib/active_record/connection_adapters/clickhouse/type_parser.rb
|
|
53
|
+
- lib/active_record/connection_adapters/clickhouse/types.rb
|
|
54
|
+
- lib/active_record/connection_adapters/clickhouse_adapter.rb
|
|
55
|
+
- lib/active_record/tasks/clickhouse_database_tasks.rb
|
|
56
|
+
- lib/activerecord-clickhouse-adapter.rb
|
|
57
|
+
- lib/arel/visitors/clickhouse.rb
|
|
58
|
+
homepage: https://github.com/ikraamg/activerecord-clickhouse-adapter
|
|
59
|
+
licenses:
|
|
60
|
+
- MIT
|
|
61
|
+
metadata:
|
|
62
|
+
rubygems_mfa_required: 'true'
|
|
63
|
+
changelog_uri: https://github.com/ikraamg/activerecord-clickhouse-adapter/blob/main/CHANGELOG.md
|
|
64
|
+
source_code_uri: https://github.com/ikraamg/activerecord-clickhouse-adapter
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '3.2'
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 4.0.10
|
|
80
|
+
specification_version: 4
|
|
81
|
+
summary: ClickHouse database adapter for Active Record
|
|
82
|
+
test_files: []
|