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.
@@ -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 = Arel::Table.engine.connection.schema_cache.columns_hash(element.relation.table_name)[element.name.to_s]
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
- collector << " AS \""
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
 
@@ -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
@@ -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: travis
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: travis
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
@@ -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.to_sql).first.name
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
@@ -1,3 +1,3 @@
1
1
  module ArelExtensions
2
- VERSION = "1.3.0".freeze
2
+ VERSION = "1.3.3".freeze
3
3
  end
data/version_v2.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ArelExtensions
2
- VERSION = "2.1.0".freeze
2
+ VERSION = "2.1.3".freeze
3
3
  end
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.0
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-02-04 00:00:00.000000000 Z
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
-