arel_extensions 1.3.0 → 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|