Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ def assign_database_to_subquery!(subquery)
# If you do not specify a database explicitly, ClickHouse will use the "default" database.
return unless subquery

match = subquery.match(/(?<=from)[^.\w]+(?<database>\w+(?=\.))?(?<table_name>[.\w]+)/i)
# Match FROM as a keyword (with word boundary), not as part of a column name
# \b ensures we only match 'from' as a whole word
match = subquery.match(/\bfrom\s+(?<database>\w+(?=\.))?(?<table_name>[.\w]+)/i)
return unless match
return if match[:database]

Expand Down
80 changes: 80 additions & 0 deletions spec/single/schema_loading_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require 'spec_helper'

RSpec.describe 'Schema Loading', :migrations do
let(:model) { ActiveRecord::Base }
let(:connection) { model.connection }
let(:database) { connection.instance_variable_get(:@config)[:database] }

describe 'assign_database_to_subquery' do
after do
connection.execute('DROP VIEW IF EXISTS test_view')
connection.execute('DROP TABLE IF EXISTS test_target')
connection.execute('DROP TABLE IF EXISTS test_source')
end

context 'when column name contains "from"' do
it 'does not mistake column name for FROM keyword' do
# Bug: The regex /(?<=from)/ matches "from" anywhere in the query,
# including in column names like "sourced_from". This causes the next
# identifier (often a function name) to be incorrectly prefixed with
# the database name.
#
# Example bug:
# SELECT sourced_from, now() FROM table
# Would incorrectly become:
# SELECT sourced_from, default.now() FROM default.table
# Causing: Function with name 'default.now' does not exist

connection.execute(<<~SQL)
CREATE TABLE test_source (
id UInt64,
sourced_from String
) ENGINE = MergeTree ORDER BY id
SQL

connection.execute(<<~SQL)
CREATE TABLE test_target (
id UInt64,
sourced_from String,
created_at DateTime
) ENGINE = MergeTree ORDER BY id
SQL

# This query should work: column ending in "from" followed by a function
expect {
connection.create_table :test_view, view: true, materialized: true, to: 'test_target',
as: 'SELECT id, sourced_from, now() AS created_at FROM test_source' do |t|
end
}.not_to raise_error
end

it 'correctly adds database prefix only to table name' do
connection.execute(<<~SQL)
CREATE TABLE test_source (
id UInt64
) ENGINE = MergeTree ORDER BY id
SQL

connection.execute(<<~SQL)
CREATE TABLE test_target (
id UInt64,
created_at DateTime
) ENGINE = MergeTree ORDER BY id
SQL

connection.create_table :test_view, view: true, materialized: true, to: 'test_target',
as: 'SELECT id, now() AS created_at FROM test_source' do |t|
end

# Verify the view was created successfully
# Note: show_create_table strips database prefixes, so we just verify
# that the view exists and the function wasn't broken by incorrect prefixing
show_create = connection.show_create_table('test_view')

# Should NOT have database prefix on function name
expect(show_create).not_to include("#{database}.now")
expect(show_create).to include('now()')
end
end
end
end