Skip to content
Draft
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
11 changes: 11 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ name: build
on: [push, pull_request]
jobs:
build:
services:
mssql:
image: mcr.microsoft.com/mssql/server:2022-latest
env:
SA_PASSWORD: yourStrongPassword123
ACCEPT_EULA: Y
ports:
- 1433:1433

strategy:
fail-fast: false
matrix:
Expand All @@ -18,6 +27,8 @@ jobs:
env:
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
steps:
- run: sudo apt install freetds-dev freetds-bin
- run: echo "CREATE DATABASE groupdate_test" | tsql -H localhost -p 1433 -U sa -P yourStrongPassword123
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ gem "mysql2"
gem "trilogy"
gem "sqlite3", "< 2"
gem "ruby-prof", require: false
gem "activerecord-sqlserver-adapter"
gem "tiny_tds"
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require "bundler/gem_tasks"
require "rake/testtask"

ADAPTERS = %w(postgresql mysql trilogy sqlite enumerable redshift)
ADAPTERS = %w(postgresql mysql trilogy sqlite enumerable redshift sqlserver)

ADAPTERS.each do |adapter|
namespace :test do
Expand Down
2 changes: 2 additions & 0 deletions gemfiles/activerecord61.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ gem "pg"
gem "mysql2"
gem "activerecord-trilogy-adapter"
gem "sqlite3", "< 2"
gem "activerecord-sqlserver-adapter"
gem "tiny_tds"
2 changes: 2 additions & 0 deletions gemfiles/activerecord70.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ gem "pg"
gem "mysql2"
gem "activerecord-trilogy-adapter"
gem "sqlite3", "< 2"
gem "activerecord-sqlserver-adapter"
gem "tiny_tds"
2 changes: 2 additions & 0 deletions lib/groupdate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
require_relative "groupdate/adapters/mysql_adapter"
require_relative "groupdate/adapters/postgresql_adapter"
require_relative "groupdate/adapters/sqlite_adapter"
require_relative "groupdate/adapters/sqlserver_adapter"

module Groupdate
class Error < RuntimeError; end
Expand Down Expand Up @@ -46,6 +47,7 @@ def self.register_adapter(name, adapter)
Groupdate.register_adapter ["Mysql2", "Mysql2Spatial", "Mysql2Rgeo", "Trilogy"], Groupdate::Adapters::MySQLAdapter
Groupdate.register_adapter ["PostgreSQL", "PostGIS", "Redshift"], Groupdate::Adapters::PostgreSQLAdapter
Groupdate.register_adapter "SQLite", Groupdate::Adapters::SQLiteAdapter
Groupdate.register_adapter "SQLServer", Groupdate::Adapters::SqlServerAdapter

require_relative "groupdate/enumerable"

Expand Down
48 changes: 48 additions & 0 deletions lib/groupdate/adapters/sqlserver_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module Groupdate
module Adapters
class SqlServerAdapter < BaseAdapter
def group_clause
raise Groupdate::Error, "Time zones not supported for SQLServer" unless @time_zone.utc_offset.zero?
raise Groupdate::Error, "day_start not supported for SQLServer" unless day_start.zero?

query =
case period
when :minute_of_hour
["CAST(DATEPART(MINUTE, #{column}) AS INT)"]
when :hour_of_day
["CAST(DATEPART(HOUR, #{column}) AS INT)"]
when :day_of_week
["CAST((DATEPART(WEEKDAY, #{column}) + @@DATEFIRST - 1) %% 7 AS INT)"]
when :day_of_month
["CAST(DATEPART(DAY, #{column}) AS INT)"]
when :day_of_year
["CAST(DATEPART(DAYOFYEAR, #{column}) AS INT)"]
when :month_of_year
["CAST(DATEPART(MONTH, #{column}) AS INT)"]
when :week
["CAST(DATEADD(DAY, -((DATEPART(WEEKDAY, #{column}) - 1 + @@DATEFIRST - ? + 7) % 7), #{column}) AS DATE)", week_start + 1]
when :quarter
raise Groupdate::Error, "Quarter not supported for SQLServer"
when :day
["CAST(DATETRUNC(DAY, #{column}) AS DATE)"]
when :month
["CAST(DATETRUNC(MONTH, #{column}) AS DATE)"]
when :year
["CAST(DATETRUNC(YEAR, #{column}) AS DATE)"]
when :custom
["DATEADD(SECOND, CAST(LEFT(FLOOR(DATEDIFF(SECOND, '1970-01-01', #{column}) / ?) * ?, 10) AS INT), '1970-01-01')", n_seconds, n_seconds]
when :second
["DATETRUNC(SECOND, #{column})"]
when :minute
["DATETRUNC(MINUTE, #{column})"]
when :hour
["DATETRUNC(HOUR, #{column})"]
else
raise Groupdate::Error, "'#{period}' not supported for SQL Server"
end

@relation.send(:sanitize_sql_array, query)
end
end
end
end
8 changes: 8 additions & 0 deletions test/adapters/sqlserver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ActiveRecord::Base.establish_connection(
adapter: "sqlserver",
database: "groupdate_test",
host: "localhost",
port: 1433,
username: "SA",
password: "yourStrongPassword123"
)
10 changes: 9 additions & 1 deletion test/column_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ def test_string_function
end

def test_string_function_arel
assert_empty User.joins(:posts).group_by_day(Arel.sql(now_function)).count
if sql_server?
assert_raises(ActiveRecord::StatementInvalid) do
User.joins(:posts).group_by_day(Arel.sql(now_function)).count
end
else
assert_empty User.joins(:posts).group_by_day(Arel.sql(now_function)).count
end
end

def test_symbol_with_join
Expand Down Expand Up @@ -89,6 +95,8 @@ def now_function
"datetime('now')"
elsif redshift?
"GETDATE()"
elsif sql_server?
"GETDATE()"
else
"NOW()"
end
Expand Down
8 changes: 4 additions & 4 deletions test/day_start_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,14 @@ def test_decimal_start_of_day
end

def test_decimal_hour_of_day
skip if sqlite?
skip if sqlite? || sql_server?
assert_result :hour_of_day, 23, "2013-05-04 02:29:59", false, day_start: 2.5
end

# invalid

def test_too_small
skip "call_method expects different error message" if sqlite?
skip "call_method expects different error message" if sqlite? || sql_server?

error = assert_raises(ArgumentError) do
call_method(:day, :created_at, day_start: -1)
Expand All @@ -190,7 +190,7 @@ def test_too_small
end

def test_too_large
skip "call_method expects different error message" if sqlite?
skip "call_method expects different error message" if sqlite? || sql_server?

error = assert_raises(ArgumentError) do
call_method(:day, :created_at, day_start: 24)
Expand All @@ -199,7 +199,7 @@ def test_too_large
end

def test_bad_method
skip "call_method expects different error message" if sqlite?
skip "call_method expects different error message" if sqlite? || sql_server?

error = assert_raises(ArgumentError) do
call_method(:minute, :created_at, day_start: 24)
Expand Down
8 changes: 8 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ def redshift?
ENV["ADAPTER"] == "redshift"
end

def sql_server?
ENV["ADAPTER"] == "sqlserver"
end

def create_user(created_at, score = 1)
created_at = created_at.utc.to_s if created_at.is_a?(Time)

Expand Down Expand Up @@ -105,6 +109,10 @@ def call_method(method, field, options)
error = assert_raises(Groupdate::Error) { User.group_by_period(method, field, **options).count }
assert_includes error.message, "not supported for SQLite"
skip
elsif sql_server? && (method == :quarter || (options[:time_zone] && options[:time_zone] != "bad") || options[:day_start] || (Time.zone && options[:time_zone] != false))
error = assert_raises(Groupdate::Error) { User.group_by_period(method, field, **options).count }
assert_includes error.message, "not supported for SQLServer"
skip
else
User.group_by_period(method, field, **options).count
end
Expand Down