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.
@@ -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
-