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.
@@ -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: []