Skip to content

Commit

Permalink
support window named functions
Browse files Browse the repository at this point in the history
  • Loading branch information
PNixx committed Aug 27, 2024
1 parent 1454a79 commit d7c37af
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 15 deletions.
4 changes: 2 additions & 2 deletions lib/active_record/connection_adapters/clickhouse/quoting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ module Quoting

module ClassMethods # :nodoc:
def quote_column_name(name)
name
name.to_s.include?('.') ? "`#{name}`" : name.to_s
end

def quote_table_name(name)
name
name.to_s
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ def schema_migration
def migration_context
pool.migration_context
end

def internal_metadata
pool.internal_metadata
end
end

def assume_migrated_upto_version(version, migrations_paths = nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def is_view

module ModelSchema
module ClassMethods
delegate :final, :final!, :settings, :settings!, to: :all
delegate :final, :final!, :settings, :settings!, :window, :window!, to: :all

def is_view
@is_view || false
Expand Down
8 changes: 8 additions & 0 deletions lib/arel/visitors/clickhouse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ def visit_Arel_Nodes_DoesNotMatch(o, collector)
infix_value o, collector, op
end

def visit_Arel_Nodes_Rows(o, collector)
if o.expr.is_a?(String)
collector << "ROWS #{o.expr}"
else
super
end
end

def sanitize_as_setting_value(value)
if value == :default
'DEFAULT'
Expand Down
2 changes: 1 addition & 1 deletion lib/clickhouse-activerecord/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module ClickhouseActiverecord
VERSION = '1.1.1'
VERSION = '1.1.2'
end
18 changes: 18 additions & 0 deletions lib/core_extensions/active_record/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ def using!(*opts)
self
end

# Windows functions let you perform calculations across a set of rows that are related to the current row. For example:
#
# users = User.window('x', order: 'date', partition: 'name', rows: 'UNBOUNDED PRECEDING').select('sum(value) OVER x')
# # SELECT sum(value) OVER x FROM users WINDOW x AS (PARTITION BY name ORDER BY date ROWS UNBOUNDED PRECEDING)
#
# @param [String] name
# @param [Hash] opts
def window(name, **opts)
spawn.window!(name, **opts)
end

def window!(name, **opts)
@values[:windows] = [] unless @values[:windows]
@values[:windows] << [name, opts]
self
end

private

def check_command(cmd)
Expand All @@ -80,6 +97,7 @@ def build_arel(connection_or_aliases = nil, aliases = nil)
arel.final! if @values[:final].present?
arel.settings(@values[:settings]) if @values[:settings].present?
arel.using(@values[:using]) if @values[:using].present?
arel.windows(@values[:windows]) if @values[:windows].present?

arel
end
Expand Down
12 changes: 12 additions & 0 deletions lib/core_extensions/arel/select_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ def settings(values)
self
end

# @param [Array] windows
def windows(windows)
@ctx.windows = windows.map do |name, opts|
# https://github.com/rails/rails/blob/main/activerecord/test/cases/arel/select_manager_test.rb#L790
window = ::Arel::Nodes::NamedWindow.new(name)
opts.each do |key, value|
window.send(key, value)
end
window
end
end

def using(*exprs)
@ctx.source.right.last.right = ::Arel::Nodes::Using.new(::Arel.sql(exprs.join(',')))
self
Expand Down
2 changes: 1 addition & 1 deletion spec/single/migration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
end
let(:directory) { raise 'NotImplemented' }
let(:migrations_dir) { File.join(FIXTURES_PATH, 'migrations', directory) }
let(:migration_context) { ActiveRecord::MigrationContext.new(migrations_dir) }
let(:migration_context) { ActiveRecord::MigrationContext.new(migrations_dir, model.connection.schema_migration, model.connection.internal_metadata) }

connection_config = ActiveRecord::Base.connection_db_config.configuration_hash

Expand Down
12 changes: 12 additions & 0 deletions spec/single/model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,18 @@ class ModelPk < ActiveRecord::Base
end
end

describe '#window' do
it 'works' do
sql = Model.window('x', order: 'date', partition: 'name', rows: 'UNBOUNDED PRECEDING').select('sum(event_value) OVER x').to_sql
expect(sql).to eq('SELECT sum(event_value) OVER x FROM sample WINDOW x AS (PARTITION BY name ORDER BY date ROWS UNBOUNDED PRECEDING)')
end

it 'empty' do
sql = Model.window('x').select('sum(event_value) OVER x').to_sql
expect(sql).to eq('SELECT sum(event_value) OVER x FROM sample WINDOW x AS ()')
end
end

describe 'arel predicates' do
describe '#matches' do
it 'uses ilike for case insensitive matches' do
Expand Down
11 changes: 1 addition & 10 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,7 @@ def schema(model)
end

def clear_db
cluster = ActiveRecord::Base.connection_db_config.configuration_hash[:cluster_name]
pattern = if cluster
normalized_cluster_name = cluster.start_with?('{') ? "'#{cluster}'" : cluster

"DROP TABLE %s ON CLUSTER #{normalized_cluster_name} SYNC"
else
'DROP TABLE %s'
end

ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.execute(pattern % table) }
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table, sync: true) }
rescue ActiveRecord::NoDatabaseError
# Ignored
end
Expand Down

0 comments on commit d7c37af

Please sign in to comment.