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
4 changes: 2 additions & 2 deletions .github/workflows/gem-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Set up Ruby 3.3
- name: Set up Ruby 3.4
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
ruby-version: 3.4

- name: Publish to RubyGems
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['3.2', '3.3']
ruby-version: ['3.3', '3.4']

steps:
- uses: actions/checkout@v2
Expand All @@ -30,7 +30,7 @@ jobs:
- name: Install Snowflake ODBC driver
run: curl ${SNOWFLAKE_DRIVER_URL} -o snowflake_driver.deb && sudo dpkg -i snowflake_driver.deb
env:
SNOWFLAKE_DRIVER_URL: https://sfc-repo.snowflakecomputing.com/odbc/linux/3.4.1/snowflake-odbc-3.4.1.x86_64.deb
SNOWFLAKE_DRIVER_URL: https://sfc-repo.snowflakecomputing.com/odbc/linux/3.12.0/snowflake-odbc-3.12.0.x86_64.deb
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## 2.3.0 / 2025-11-17
* Add support for `GROUP CUBE`
* Add support for `GROUP ROLLUP`
* Add support for `GROUPING SETS`

## 2.2.0 / 2023-10-17
* Add support for `MERGE` (credit: @benalavi)
* Add requirement for `sequel` v5.58.0 or newer (to support the new MERGE methods).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ be taken down either via the `after(:each)` blocks or when the connection is clo

We have two workflows included in this project:

* Ruby (`ruby.yml`): This runs the specs for this gem against Ruby 3.0 and 3.1. Note
* Ruby (`ruby.yml`): This runs the specs for this gem against Ruby 3.3 and 3.4. Note
that this requires the secret `SNOWFLAKE_CONN_STR` to be set (see above for example connection string),
as we need to connect to Snowflake to run tests. These specs will be run for every pull request,
and is run after every commit to those branches.
Expand Down
2 changes: 1 addition & 1 deletion lib/sequel-snowflake/version.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Sequel
module Snowflake
# sequel-snowflake version
VERSION = "2.2.0"
VERSION = "2.3.0"
end
end
19 changes: 16 additions & 3 deletions lib/sequel/adapters/snowflake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,22 @@ def fetch_rows(sql)
self
end

# Whether the MERGE statement is supported:
# https://github.com/jeremyevans/sequel/blob/master/lib/sequel/dataset/features.rb#L129
# Snowflake reference: https://docs.snowflake.com/en/sql-reference/sql/merge
# https://docs.snowflake.com/en/sql-reference/constructs/group-by-cube
def supports_group_cube?
true
end

# https://docs.snowflake.com/en/sql-reference/constructs/group-by-rollup
def supports_group_rollup?
true
end

# https://docs.snowflake.com/en/sql-reference/constructs/group-by-grouping-sets
def supports_grouping_sets?
true
end

# https://docs.snowflake.com/en/sql-reference/sql/merge
def supports_merge?
true
end
Expand Down
2 changes: 2 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
ruby = "latest"
171 changes: 152 additions & 19 deletions spec/sequel/adapters/snowflake_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
require 'securerandom'

describe Sequel::Snowflake::Dataset do
let(:db) { Sequel.connect(adapter: :snowflake, drvconnect: ENV['SNOWFLAKE_CONN_STR']) }
let(:db) { @db ||= Sequel.connect(adapter: :snowflake, drvconnect: ENV['SNOWFLAKE_CONN_STR']) }

before(:all) do
@db = Sequel.connect(adapter: :snowflake, drvconnect: ENV['SNOWFLAKE_CONN_STR'])
end

after(:all) do
@db.disconnect if @db
end

describe 'Converting Snowflake data types' do
# Create a test table with a reasonably-random suffix
Expand Down Expand Up @@ -70,33 +78,157 @@
end
end

describe 'GROUP BY features' do
before(:all) do
@products = "SEQUEL_SNOWFLAKE_SPECS_#{SecureRandom.hex(10)}".to_sym
@sales = "SEQUEL_SNOWFLAKE_SPECS_#{SecureRandom.hex(10)}".to_sym

@db.create_table(@products, :temp => true) do
Integer :product_id
Float :wholesale_price
end

@db.create_table(@sales, :temp => true) do
Integer :product_id
Float :retail_price
Integer :quantity
String :city
String :state
end

@db[@products].insert({ product_id: 1, wholesale_price: 1.00 })
@db[@products].insert({ product_id: 2, wholesale_price: 2.00 })
@db[@sales].insert({ product_id: 1, retail_price: 2.00, quantity: 1, city: 'SF', state: 'CA' })
@db[@sales].insert({ product_id: 1, retail_price: 2.00, quantity: 2, city: 'SJ', state: 'CA' })
@db[@sales].insert({ product_id: 2, retail_price: 5.00, quantity: 4, city: 'SF', state: 'CA' })
@db[@sales].insert({ product_id: 2, retail_price: 5.00, quantity: 8, city: 'SJ', state: 'CA' })
@db[@sales].insert({ product_id: 2, retail_price: 5.00, quantity: 16, city: 'Miami', state: 'FL' })
@db[@sales].insert({ product_id: 2, retail_price: 5.00, quantity: 32, city: 'Orlando', state: 'FL' })
@db[@sales].insert({ product_id: 2, retail_price: 5.00, quantity: 64, city: 'SJ', state: 'CA' })
end

after(:all) do
@db.drop_table(@products) if @products
@db.drop_table(@sales) if @sales
end

let(:products) { @products }
let(:sales) { @sales }

it 'can use GROUP CUBE' do
res = db.from(Sequel[products].as(:p)).
join(Sequel[sales].as(:s), Sequel[:p][:product_id] => Sequel[:s][:product_id]).
select(
Sequel[:s][:state],
Sequel[:s][:city],
Sequel.function(:sum, Sequel.*(Sequel.-(Sequel[:s][:retail_price], Sequel[:p][:wholesale_price]), Sequel[:s][:quantity])).as(:profit)
).
group(Sequel[:s][:state], Sequel[:s][:city]).
group_cube.
order(Sequel.asc(Sequel[:s][:state], nulls: :last)).
order_append(Sequel[:s][:city]).
all

expect(res).to match_array([
{ state: 'CA', city: 'SF', profit: 13 },
{ state: 'CA', city: 'SJ', profit: 218 },
{ state: 'CA', city: nil, profit: 231 },
{ state: 'FL', city: 'Miami', profit: 48 },
{ state: 'FL', city: 'Orlando', profit: 96 },
{ state: 'FL', city: nil, profit: 144 },
{ state: nil, city: 'Miami', profit: 48 },
{ state: nil, city: 'Orlando', profit: 96 },
{ state: nil, city: 'SF', profit: 13 },
{ state: nil, city: 'SJ', profit: 218 },
{ state: nil, city: nil, profit: 375 },
])
end

it 'can use GROUP ROLLUP' do
res = db.from(Sequel[products].as(:p)).
join(Sequel[sales].as(:s), Sequel[:p][:product_id] => Sequel[:s][:product_id]).
select(
Sequel[:s][:state],
Sequel[:s][:city],
Sequel.function(:sum, Sequel.*(Sequel.-(Sequel[:s][:retail_price], Sequel[:p][:wholesale_price]), Sequel[:s][:quantity])).as(:profit)
).
group(Sequel[:s][:state], Sequel[:s][:city]).
group_rollup.
order(Sequel.asc(Sequel[:s][:state], nulls: :last)).
order_append(Sequel[:s][:city]).
all

expect(res).to match_array([
{ state: 'CA', city: 'SF', profit: 13 },
{ state: 'CA', city: 'SJ', profit: 218 },
{ state: 'CA', city: nil, profit: 231 },
{ state: 'FL', city: 'Miami', profit: 48 },
{ state: 'FL', city: 'Orlando', profit: 96 },
{ state: 'FL', city: nil, profit: 144 },
{ state: nil, city: nil, profit: 375 },
])
end

it 'can use GROUPING SETS' do
res = db.from(Sequel[products].as(:p)).
join(Sequel[sales].as(:s), Sequel[:p][:product_id] => Sequel[:s][:product_id]).
select(
Sequel[:s][:state],
Sequel[:s][:city],
Sequel.function(:sum, Sequel.*(Sequel.-(Sequel[:s][:retail_price], Sequel[:p][:wholesale_price]), Sequel[:s][:quantity])).as(:profit)
).
group([Sequel[:s][:state]], [Sequel[:s][:city]]).
grouping_sets.
order(Sequel.asc(Sequel[:s][:state], nulls: :last)).
order_append(Sequel[:s][:city]).
all

expect(res).to match_array([
{ state: 'CA', city: nil, profit: 231 },
{ state: 'FL', city: nil, profit: 144 },
{ state: nil, city: 'Miami', profit: 48 },
{ state: nil, city: 'Orlando', profit: 96 },
{ state: nil, city: 'SF', profit: 13 },
{ state: nil, city: 'SJ', profit: 218 },
])
end
end

describe 'MERGE feature' do
let(:target_table) { "SEQUEL_SNOWFLAKE_SPECS_#{SecureRandom.hex(10)}".to_sym }
let(:source_table) { "SEQUEL_SNOWFLAKE_SPECS_#{SecureRandom.hex(10)}".to_sym }
before(:all) do
@target_table = "SEQUEL_SNOWFLAKE_SPECS_#{SecureRandom.hex(10)}".to_sym
@source_table = "SEQUEL_SNOWFLAKE_SPECS_#{SecureRandom.hex(10)}".to_sym

before(:each) do
db.create_table(target_table, :temp => true) do
@db.create_table(@target_table, :temp => true) do
String :str
String :str2
String :str3
end

db.create_table(source_table, :temp => true) do
@db.create_table(@source_table, :temp => true) do
String :from
String :to
String :whomst
end
end

db[target_table].insert({ str: 'foo', str2: 'foo', str3: 'phoo' })
db[target_table].insert({ str: 'baz', str2: 'foo', str3: 'buzz' })
db[source_table].insert({ from: 'foo', to: 'bar', whomst: 'me' })
after(:all) do
@db.drop_table(@target_table) if @target_table
@db.drop_table(@source_table) if @source_table
end

after(:each) do
db.drop_table(target_table)
db.drop_table(source_table)
before(:each) do
# Clear and repopulate data for each test since MERGE modifies data
db[@target_table].delete
db[@source_table].delete
db[@target_table].insert({ str: 'foo', str2: 'foo', str3: 'phoo' })
db[@target_table].insert({ str: 'baz', str2: 'foo', str3: 'buzz' })
db[@source_table].insert({ from: 'foo', to: 'bar', whomst: 'me' })
end

let(:target_table) { @target_table }
let(:source_table) { @source_table }

it 'can use MERGE' do
db[target_table].merge_using(source_table, str: :from).merge_update(str2: :to).merge

Expand All @@ -108,26 +240,27 @@
end

describe '#explain' do
# Create a test table with a reasonably-random suffix
let(:test_table) { "SEQUEL_SNOWFLAKE_SPECS_#{SecureRandom.hex(10)}".to_sym }
before(:all) do
@test_table = "SEQUEL_SNOWFLAKE_SPECS_#{SecureRandom.hex(10)}".to_sym

before(:each) do
db.create_table(test_table, :temp => true) do
@db.create_table(@test_table, :temp => true) do
Numeric :id
String :name
String :email
String :title
end

db[test_table].insert(
@db[@test_table].insert(
{ id: 1, name: 'John Null', email: 'j.null@example.com', title: 'Software Tester' }
)
end

after(:each) do
db.drop_table(test_table)
after(:all) do
@db.drop_table(@test_table) if @test_table
end

let(:test_table) { @test_table }

it "should have explain output" do
query = db.fetch("SELECT * FROM #{test_table} WHERE ID=1;")

Expand Down
Loading