arel_extensions 1.3.0 → 1.3.3
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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +258 -52
- data/Gemfile +8 -8
- data/README.md +65 -0
- data/gemfiles/rails5_2.gemfile +3 -3
- data/gemfiles/rails6.gemfile +3 -4
- data/gemfiles/rails6_1.gemfile +1 -2
- data/gemfiles/rails7.gemfile +5 -13
- data/lib/arel_extensions/date_duration.rb +2 -2
- data/lib/arel_extensions/helpers.rb +48 -0
- data/lib/arel_extensions/math.rb +17 -27
- data/lib/arel_extensions/nodes/case.rb +3 -5
- data/lib/arel_extensions/nodes/date_diff.rb +23 -4
- data/lib/arel_extensions/nodes/format.rb +3 -2
- data/lib/arel_extensions/nodes/function.rb +1 -7
- data/lib/arel_extensions/version.rb +1 -1
- data/lib/arel_extensions/visitors/mssql.rb +126 -53
- data/lib/arel_extensions/visitors/mysql.rb +15 -0
- data/lib/arel_extensions/visitors/oracle.rb +14 -1
- data/lib/arel_extensions/visitors/postgresql.rb +20 -7
- data/lib/arel_extensions/visitors/sqlite.rb +6 -3
- data/lib/arel_extensions.rb +11 -1
- data/test/arelx_test_helper.rb +45 -1
- data/test/database.yml +8 -2
- data/test/support/fake_record.rb +1 -1
- data/test/visitors/test_to_sql.rb +21 -0
- data/test/with_ar/all_agnostic_test.rb +103 -3
- data/version_v1.rb +1 -1
- data/version_v2.rb +1 -1
- metadata +3 -3
- data/appveyor.yml +0 -44
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'arel_extensions/helpers'
|
2
|
+
|
1
3
|
module ArelExtensions
|
2
4
|
module Visitors
|
3
5
|
class Arel::Visitors::SQLite
|
@@ -327,7 +329,7 @@ module ArelExtensions
|
|
327
329
|
if element.is_a?(Time)
|
328
330
|
return Arel::Nodes::NamedFunction.new('STRFTIME',[element, '%H:%M:%S'])
|
329
331
|
elsif element.is_a?(Arel::Attributes::Attribute)
|
330
|
-
col =
|
332
|
+
col = ArelExtensions::column_of(element.relation.table_name, element.name.to_s)
|
331
333
|
if col && (col.type == :time)
|
332
334
|
return Arel::Nodes::NamedFunction.new('STRFTIME',[element, '%H:%M:%S'])
|
333
335
|
else
|
@@ -379,9 +381,10 @@ module ArelExtensions
|
|
379
381
|
else
|
380
382
|
collector = visit o.left, collector
|
381
383
|
end
|
382
|
-
|
384
|
+
sep = o.right.size > 1 && o.right[0] == '"' && o.right[-1] == '"' ? '' : '"'
|
385
|
+
collector << " AS #{sep}"
|
383
386
|
collector = visit o.right, collector
|
384
|
-
collector << "
|
387
|
+
collector << "#{sep}"
|
385
388
|
collector
|
386
389
|
end
|
387
390
|
|
data/lib/arel_extensions.rb
CHANGED
@@ -77,7 +77,17 @@ require 'arel_extensions/nodes/soundex'
|
|
77
77
|
require 'arel_extensions/nodes/cast'
|
78
78
|
require 'arel_extensions/nodes/json'
|
79
79
|
|
80
|
-
|
80
|
+
# It seems like the code in lib/arel_extensions/visitors.rb that is supposed
|
81
|
+
# to inject ArelExtension is not enough. Different versions of the sqlserver
|
82
|
+
# adapter behave differently. It doesn't always proc, so we added this for
|
83
|
+
# coverage.
|
84
|
+
if defined?(Arel::Visitors::SQLServer)
|
85
|
+
Arel::Visitors.const_set('MSSQL', Arel::Visitors::SQLServer)
|
86
|
+
require 'arel_extensions/visitors/mssql'
|
87
|
+
class Arel::Visitors::SQLServer
|
88
|
+
include ArelExtensions::Visitors::MSSQL
|
89
|
+
end
|
90
|
+
end
|
81
91
|
|
82
92
|
module Arel
|
83
93
|
def self.rand
|
data/test/arelx_test_helper.rb
CHANGED
@@ -6,8 +6,52 @@ require 'active_record'
|
|
6
6
|
|
7
7
|
require 'support/fake_record'
|
8
8
|
|
9
|
+
def colored(color, msg)
|
10
|
+
ENV["TERM"] =~ /^xterm|-256color$/ ? "\x1b[#{color}m#{msg}\x1b[89m\x1b[0m" : "#{msg}"
|
11
|
+
end
|
12
|
+
|
13
|
+
YELLOW = "33"
|
14
|
+
|
15
|
+
def warn(msg)
|
16
|
+
$stderr.puts(colored(YELLOW, msg))
|
17
|
+
end
|
18
|
+
|
19
|
+
# Load gems specific to databases
|
20
|
+
# NOTE:
|
21
|
+
# It's strongly advised to test each database on its own. Loading multiple
|
22
|
+
# backend gems leads to undefined behavior according to tests; the backend
|
23
|
+
# might not recognize the correct DB visitor and will fallback to `ToSQL`
|
24
|
+
# and screw all tests.
|
25
|
+
#
|
26
|
+
# The issue also seems to be related to arel version: at some point, arel
|
27
|
+
# dropped its wide support for DBs and kept Postgres, MySQL and SQLite.
|
28
|
+
# Here, we're just trying to load the correct ones.
|
29
|
+
db_and_gem = if RUBY_ENGINE == 'jruby'
|
30
|
+
{
|
31
|
+
'oracle' => 'activerecord-oracle_enhanced-adapter',
|
32
|
+
'mssql' => 'activerecord-jdbcsqlserver-adapter'
|
33
|
+
}
|
34
|
+
else
|
35
|
+
{
|
36
|
+
'oracle' => 'activerecord-oracle_enhanced-adapter',
|
37
|
+
'mssql' => 'activerecord-sqlserver-adapter'
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_lib(gem)
|
42
|
+
if gem && (RUBY_ENGINE == 'jruby' || Arel::VERSION.to_i > 9)
|
43
|
+
begin
|
44
|
+
Gem::Specification.find_by_name(gem)
|
45
|
+
require gem
|
46
|
+
rescue Gem::MissingSpecError
|
47
|
+
warn "Warning: failed to load gem #{gem}. Are you sure it's installed?"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
load_lib(db_and_gem[ENV['DB']])
|
53
|
+
|
9
54
|
require 'arel_extensions'
|
10
|
-
Arel::Table.engine = FakeRecord::Base.new
|
11
55
|
|
12
56
|
$arel_silence_type_casting_deprecation = true
|
13
57
|
|
data/test/database.yml
CHANGED
@@ -9,23 +9,29 @@ jdbc-sqlite:
|
|
9
9
|
mysql:
|
10
10
|
adapter: mysql2
|
11
11
|
database: arext_test
|
12
|
-
username:
|
12
|
+
username: root
|
13
13
|
host: 127.0.0.1
|
14
14
|
port: 3306
|
15
15
|
encoding: utf8
|
16
16
|
jdbc-mysql:
|
17
17
|
adapter: jdbcmysql
|
18
18
|
database: arext_test
|
19
|
-
username:
|
19
|
+
username: root
|
20
20
|
encoding: utf8
|
21
21
|
postgresql:
|
22
22
|
adapter: postgresql
|
23
23
|
database: arext_test
|
24
24
|
username: postgres
|
25
|
+
password: secret
|
26
|
+
host: 127.0.0.1
|
27
|
+
port: 5432
|
25
28
|
jdbc-postgresql:
|
26
29
|
adapter: jdbcpostgresql
|
27
30
|
database: arext_test
|
28
31
|
username: postgres
|
32
|
+
password: secret
|
33
|
+
host: 127.0.0.1
|
34
|
+
port: 5432
|
29
35
|
oracle:
|
30
36
|
adapter: oracle_enhanced
|
31
37
|
database: xe
|
data/test/support/fake_record.rb
CHANGED
@@ -105,7 +105,7 @@ module FakeRecord
|
|
105
105
|
attr_reader :spec, :connection
|
106
106
|
|
107
107
|
def initialize
|
108
|
-
@spec = Spec.new(:adapter => 'america')
|
108
|
+
@spec = Spec.new({:adapter => 'america'})
|
109
109
|
@connection = Connection.new
|
110
110
|
@connection.visitor = Arel::Visitors::ToSql.new(connection)
|
111
111
|
end
|
@@ -5,6 +5,27 @@ module ArelExtensions
|
|
5
5
|
module VisitorToSql
|
6
6
|
describe 'the to_sql visitor' do
|
7
7
|
before do
|
8
|
+
if Arel::Table.engine.is_a?(ActiveRecord::Base)
|
9
|
+
puts "This is a hack."
|
10
|
+
# As a matter of fact, if the whole if-block is removed, the to_sql
|
11
|
+
# test become flaky.
|
12
|
+
#
|
13
|
+
# The first time `Arel::Table.engine` is called
|
14
|
+
# from `ArelExtenstions::column_of_via_arel_table(table_name, column_name)`
|
15
|
+
# in `lib/arel_extensions/helpers.rb`
|
16
|
+
# will almost always fail. It's important to note that when the test
|
17
|
+
# fails, it's always on 1 test case, and every subsequent test that
|
18
|
+
# calls to these methods passes.
|
19
|
+
#
|
20
|
+
# After investigation, it turned out that `Arel::Table.engine` will be
|
21
|
+
# of type `ActiveRecord::Base` instead of the expected `FakeRecord::Base`
|
22
|
+
# as set in this `before` block, in the `@conn` instance variable.
|
23
|
+
# Subsequent calls to `column_of_via_arel_table` will have
|
24
|
+
# `Arel::Table.engine` of the expected type.
|
25
|
+
#
|
26
|
+
# It is still unclear why the call in the condition of this if-block
|
27
|
+
# fixes this behavior.
|
28
|
+
end
|
8
29
|
@conn = FakeRecord::Base.new
|
9
30
|
Arel::Table.engine = @conn
|
10
31
|
@visitor = Arel::Visitors::ToSql.new @conn.connection
|
@@ -33,7 +33,7 @@ module ArelExtensions
|
|
33
33
|
t.column :name, :string
|
34
34
|
t.column :comments, :text
|
35
35
|
t.column :created_at, :date
|
36
|
-
t.column :updated_at, :datetime
|
36
|
+
t.column :updated_at, :datetime, precision: nil
|
37
37
|
t.column :duration, :time
|
38
38
|
t.column :other, :string
|
39
39
|
t.column :score, :decimal, :precision => 20, :scale => 10
|
@@ -171,7 +171,7 @@ module ArelExtensions
|
|
171
171
|
# This should works no matter which version of rails is used
|
172
172
|
assert User.group(:score).average(:id).values.all?{|e| !e.nil?}
|
173
173
|
|
174
|
-
# Since Rails 7, a patch to calculations.rb has tirggered a double
|
174
|
+
# Since Rails 7, a patch to calculations.rb has tirggered a double
|
175
175
|
# quoting of the alias name. See https://github.com/rails/rails/commit/7e6e9091e55c3357b0162d44b6ab955ed0c718d5
|
176
176
|
# Before the patch that fixed this the following error would occur:
|
177
177
|
# ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
|
@@ -370,6 +370,105 @@ module ArelExtensions
|
|
370
370
|
assert_equal '2016-05-23', t(@lucas, @created_at.format('%Y-%m-%d'))
|
371
371
|
assert_equal '2014/03/03 12:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S'))
|
372
372
|
assert_equal '12:42%', t(@lucas, @updated_at.format('%R%%'))
|
373
|
+
|
374
|
+
# The following tests will ensure proper conversion of timestamps to
|
375
|
+
# requested timezones.
|
376
|
+
#
|
377
|
+
# The names of the timezones is highly dependant on the underlying
|
378
|
+
# operating system, and this is why we need to handle each database
|
379
|
+
# separately: the images we're using to test these databases are
|
380
|
+
# different. So don't rely on the provided examples. Your setup is your
|
381
|
+
# reference.
|
382
|
+
#
|
383
|
+
# One could always have portable code if s/he uses standard
|
384
|
+
# abbreviations, like:
|
385
|
+
#
|
386
|
+
# 1. CET => Central European Time
|
387
|
+
# 2. CEST => Central European Summer Time
|
388
|
+
#
|
389
|
+
# Which implies that the caller should handle daylight saving detection.
|
390
|
+
# In fact, CET will handle daylight saving in MySQL but not Postgres.
|
391
|
+
#
|
392
|
+
# It looks like the posix convention is supported by mysql and
|
393
|
+
# postgresql, e.g.:
|
394
|
+
#
|
395
|
+
# posix/Europe/Paris
|
396
|
+
# posix/America/Nipigon
|
397
|
+
#
|
398
|
+
# so it looks like a more reliably portable way of specifying it.
|
399
|
+
time_zones = {
|
400
|
+
'mssql' => {
|
401
|
+
'utc' => 'UTC',
|
402
|
+
'sao_paulo' => 'Argentina Standard Time',
|
403
|
+
'tahiti' => 'Hawaiian Standard Time',
|
404
|
+
'paris' => 'Central European Standard Time'
|
405
|
+
},
|
406
|
+
'posix' => {
|
407
|
+
'utc' => 'UTC',
|
408
|
+
'sao_paulo' => 'America/Sao_Paulo',
|
409
|
+
'tahiti' => 'Pacific/Tahiti',
|
410
|
+
'paris' => 'Europe/Paris'
|
411
|
+
}
|
412
|
+
}
|
413
|
+
|
414
|
+
skip "Unsupported timezone conversion for DB=#{ENV['DB']}" if !['mssql', 'mysql', 'oracle', 'postgresql'].include?(ENV['DB'])
|
415
|
+
|
416
|
+
tz = ENV['DB'] == 'mssql' ? time_zones['mssql'] : time_zones['posix']
|
417
|
+
|
418
|
+
assert_equal '2014/03/03 12:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', tz['utc']))
|
419
|
+
assert_equal '2014/03/03 09:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', { tz['utc'] => tz['sao_paulo'] }))
|
420
|
+
assert_equal '2014/03/03 02:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', { tz['utc'] => tz['tahiti'] }))
|
421
|
+
|
422
|
+
# Skipping conversion from UTC to the desired timezones fails in SQL
|
423
|
+
# Server and Postgres. This is mainly due to the fact that timezone
|
424
|
+
# information is not preserved in the column itself.
|
425
|
+
#
|
426
|
+
# MySQL is happy to consider that times by default are in UTC.
|
427
|
+
assert_equal '2014/03/03 13:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', { tz['utc'] => tz['paris'] }))
|
428
|
+
refute_equal '2014/03/03 13:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', tz['paris'])) if !['mysql'].include?(ENV['DB'])
|
429
|
+
|
430
|
+
# Winter/Summer time
|
431
|
+
assert_equal '2014/08/03 14:42:00', t(@lucas, (@updated_at + 5.months).format('%Y/%m/%d %H:%M:%S', { tz['utc'] => tz['paris'] }))
|
432
|
+
if ENV['DB'] == 'mssql'
|
433
|
+
assert_equal '2022/02/01 11:42:00', t(@lucas, Arel::Nodes.build_quoted('2022-02-01 10:42:00').cast(:datetime).format('%Y/%m/%d %H:%M:%S', { tz['utc'] => tz['paris'] }))
|
434
|
+
assert_equal '2022/08/01 12:42:00', t(@lucas, Arel::Nodes.build_quoted('2022-08-01 10:42:00').cast(:datetime).format('%Y/%m/%d %H:%M:%S', { tz['utc'] => tz['paris'] }))
|
435
|
+
else
|
436
|
+
assert_equal '2022/02/01 11:42:00', t(@lucas, Arel::Nodes.build_quoted('2022-02-01 10:42:00').cast(:datetime).format('%Y/%m/%d %H:%M:%S', tz['paris']))
|
437
|
+
assert_equal '2022/08/01 12:42:00', t(@lucas, Arel::Nodes.build_quoted('2022-08-01 10:42:00').cast(:datetime).format('%Y/%m/%d %H:%M:%S', tz['paris']))
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def test_format_iso_week
|
442
|
+
skip "Unsupported ISO week number for DB=#{ENV['DB']}" if ['sqlite'].include?(ENV['DB'])
|
443
|
+
assert_equal '10', t(@lucas, @updated_at.format('%V'))
|
444
|
+
{
|
445
|
+
'2024-01-01 10:42:00' => '01', # Monday
|
446
|
+
'2030-01-01 10:42:00' => '01', # Tuesday
|
447
|
+
'2025-01-01 10:42:00' => '01', # Wednesday
|
448
|
+
'2026-01-01 10:42:00' => '01', # Thursday
|
449
|
+
'2027-01-01 10:42:00' => '53', # Friday
|
450
|
+
'2028-01-01 10:42:00' => '52', # Saturday
|
451
|
+
'2034-01-01 10:42:00' => '52', # Sunday
|
452
|
+
}.each do |date, exp|
|
453
|
+
assert_equal exp, t(@lucas, Arel::Nodes.build_quoted(date).cast(:datetime).format('%V'))
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def test_format_iso_year_of_week
|
458
|
+
skip "Unsupported ISO year of week for DB=#{ENV['DB']}" if ['mssql', 'sqlite'].include?(ENV['DB'])
|
459
|
+
assert_equal '2014', t(@lucas, @updated_at.format('%G'))
|
460
|
+
|
461
|
+
{
|
462
|
+
'2024-01-01 10:42:00' => '2024', # Monday
|
463
|
+
'2030-01-01 10:42:00' => '2030', # Tuesday
|
464
|
+
'2025-01-01 10:42:00' => '2025', # Wednesday
|
465
|
+
'2026-01-01 10:42:00' => '2026', # Thursday
|
466
|
+
'2027-01-01 10:42:00' => '2026', # Friday
|
467
|
+
'2028-01-01 10:42:00' => '2027', # Saturday
|
468
|
+
'2034-01-01 10:42:00' => '2033', # Sunday
|
469
|
+
}.each do |date, exp|
|
470
|
+
assert_equal exp, t(@lucas, Arel::Nodes.build_quoted(date).cast(:datetime).format('%G'))
|
471
|
+
end
|
373
472
|
end
|
374
473
|
|
375
474
|
def test_coalesce
|
@@ -525,7 +624,7 @@ module ArelExtensions
|
|
525
624
|
# puts @age.is_null.inspect
|
526
625
|
# puts @age.is_null.to_sql
|
527
626
|
# puts @age=='34'
|
528
|
-
assert_equal "Test", User.select(@name).where(@age.is_null
|
627
|
+
assert_equal "Test", User.select(@name).where(@age.is_null).first.name
|
529
628
|
end
|
530
629
|
|
531
630
|
def test_math_plus
|
@@ -673,6 +772,7 @@ module ArelExtensions
|
|
673
772
|
end
|
674
773
|
|
675
774
|
def test_subquery_with_order
|
775
|
+
skip if ['mssql'].include?(@env_db) && Arel::VERSION.to_i < 10
|
676
776
|
assert_equal 9, User.where(:name => User.select(:name).order(:name)).count
|
677
777
|
assert_equal 9, User.where(@ut[:name].in(@ut.project(@ut[:name]).order(@ut[:name]))).count
|
678
778
|
if !['mysql'].include?(@env_db) # MySql can't have limit in IN subquery
|
data/version_v1.rb
CHANGED
data/version_v2.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arel_extensions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yann Azoury
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2022-
|
13
|
+
date: 2022-03-07 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: arel
|
@@ -97,7 +97,6 @@ files:
|
|
97
97
|
- Rakefile
|
98
98
|
- SQL_Challenges.md
|
99
99
|
- TODO
|
100
|
-
- appveyor.yml
|
101
100
|
- arel_extensions.gemspec
|
102
101
|
- functions.html
|
103
102
|
- gemfiles/rails3.gemfile
|
@@ -123,6 +122,7 @@ files:
|
|
123
122
|
- lib/arel_extensions/common_sql_functions.rb
|
124
123
|
- lib/arel_extensions/comparators.rb
|
125
124
|
- lib/arel_extensions/date_duration.rb
|
125
|
+
- lib/arel_extensions/helpers.rb
|
126
126
|
- lib/arel_extensions/insert_manager.rb
|
127
127
|
- lib/arel_extensions/math.rb
|
128
128
|
- lib/arel_extensions/math_functions.rb
|
data/appveyor.yml
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
version: "{build}"
|
2
|
-
|
3
|
-
cache:
|
4
|
-
- vendor/bundle
|
5
|
-
|
6
|
-
branches:
|
7
|
-
only:
|
8
|
-
- master
|
9
|
-
|
10
|
-
services:
|
11
|
-
- mssql2014
|
12
|
-
|
13
|
-
|
14
|
-
install:
|
15
|
-
- set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH%
|
16
|
-
- bundle config --local path vendor/bundle
|
17
|
-
- bundle install
|
18
|
-
|
19
|
-
# Disable normal Windows builds in favor of our test script.
|
20
|
-
build: off
|
21
|
-
|
22
|
-
|
23
|
-
before_test:
|
24
|
-
- ruby -v
|
25
|
-
- gem -v
|
26
|
-
- bundle -v
|
27
|
-
|
28
|
-
test_script:
|
29
|
-
- ruby --version
|
30
|
-
- gem --version
|
31
|
-
- bundler --version
|
32
|
-
- bundle exec rake test
|
33
|
-
- ps: Start-Service 'MSSQL$SQL2014'
|
34
|
-
- timeout /t 4 /nobreak > NUL
|
35
|
-
- bundle exec rake test:mssql
|
36
|
-
|
37
|
-
environment:
|
38
|
-
matrix:
|
39
|
-
- RUBY_VERSION: 200
|
40
|
-
- RUBY_VERSION: 21
|
41
|
-
- RUBY_VERSION: 22
|
42
|
-
- RUBY_VERSION: 23
|
43
|
-
- RUBY_VERSION: 23-x64
|
44
|
-
|