arel_extensions 1.7.0 → 2.0.0.rc3

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.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +2 -1
  3. data/.gitignore +6 -8
  4. data/.rubocop.yml +4 -194
  5. data/.travis/oracle/download.js +152 -0
  6. data/.travis/oracle/download.sh +30 -0
  7. data/.travis/oracle/download_ojdbc.js +116 -0
  8. data/.travis/oracle/install.sh +34 -0
  9. data/.travis/setup_accounts.sh +9 -0
  10. data/.travis/sqlite3/extension-functions.sh +6 -0
  11. data/.travis.yml +223 -0
  12. data/Gemfile +28 -2
  13. data/README.md +89 -265
  14. data/Rakefile +30 -50
  15. data/TODO +1 -0
  16. data/appveyor.yml +22 -60
  17. data/arel_extensions.gemspec +17 -14
  18. data/functions.html +3 -3
  19. data/gemfiles/rails3.gemfile +20 -0
  20. data/gemfiles/rails4.gemfile +29 -0
  21. data/gemfiles/rails5_0.gemfile +29 -0
  22. data/gemfiles/rails5_1_4.gemfile +17 -19
  23. data/gemfiles/rails5_2.gemfile +17 -26
  24. data/init/mssql.sql +4 -4
  25. data/init/mysql.sql +38 -38
  26. data/init/oracle.sql +0 -0
  27. data/init/postgresql.sql +25 -24
  28. data/init/sqlite.sql +0 -0
  29. data/lib/arel_extensions/attributes.rb +3 -9
  30. data/lib/arel_extensions/boolean_functions.rb +16 -57
  31. data/lib/arel_extensions/common_sql_functions.rb +20 -21
  32. data/lib/arel_extensions/comparators.rb +28 -31
  33. data/lib/arel_extensions/date_duration.rb +13 -19
  34. data/lib/arel_extensions/insert_manager.rb +15 -22
  35. data/lib/arel_extensions/math.rb +70 -68
  36. data/lib/arel_extensions/math_functions.rb +52 -57
  37. data/lib/arel_extensions/nodes/abs.rb +1 -0
  38. data/lib/arel_extensions/nodes/blank.rb +5 -6
  39. data/lib/arel_extensions/nodes/case.rb +27 -31
  40. data/lib/arel_extensions/nodes/cast.rb +12 -12
  41. data/lib/arel_extensions/nodes/ceil.rb +1 -1
  42. data/lib/arel_extensions/nodes/coalesce.rb +5 -4
  43. data/lib/arel_extensions/nodes/collate.rb +10 -9
  44. data/lib/arel_extensions/nodes/concat.rb +22 -13
  45. data/lib/arel_extensions/nodes/date_diff.rb +70 -77
  46. data/lib/arel_extensions/nodes/duration.rb +5 -2
  47. data/lib/arel_extensions/nodes/find_in_set.rb +1 -0
  48. data/lib/arel_extensions/nodes/floor.rb +1 -1
  49. data/lib/arel_extensions/nodes/format.rb +8 -35
  50. data/lib/arel_extensions/nodes/formatted_number.rb +23 -22
  51. data/lib/arel_extensions/nodes/function.rb +39 -50
  52. data/lib/arel_extensions/nodes/is_null.rb +0 -2
  53. data/lib/arel_extensions/nodes/json.rb +47 -72
  54. data/lib/arel_extensions/nodes/length.rb +0 -5
  55. data/lib/arel_extensions/nodes/levenshtein_distance.rb +2 -2
  56. data/lib/arel_extensions/nodes/locate.rb +3 -2
  57. data/lib/arel_extensions/nodes/log10.rb +2 -1
  58. data/lib/arel_extensions/nodes/matches.rb +8 -6
  59. data/lib/arel_extensions/nodes/md5.rb +1 -0
  60. data/lib/arel_extensions/nodes/power.rb +5 -5
  61. data/lib/arel_extensions/nodes/rand.rb +3 -2
  62. data/lib/arel_extensions/nodes/repeat.rb +6 -4
  63. data/lib/arel_extensions/nodes/replace.rb +8 -16
  64. data/lib/arel_extensions/nodes/round.rb +7 -6
  65. data/lib/arel_extensions/nodes/soundex.rb +15 -15
  66. data/lib/arel_extensions/nodes/std.rb +21 -18
  67. data/lib/arel_extensions/nodes/substring.rb +16 -58
  68. data/lib/arel_extensions/nodes/then.rb +2 -2
  69. data/lib/arel_extensions/nodes/trim.rb +7 -5
  70. data/lib/arel_extensions/nodes/union.rb +9 -6
  71. data/lib/arel_extensions/nodes/union_all.rb +8 -5
  72. data/lib/arel_extensions/nodes/wday.rb +6 -2
  73. data/lib/arel_extensions/nodes.rb +1 -1
  74. data/lib/arel_extensions/null_functions.rb +6 -18
  75. data/lib/arel_extensions/predications.rb +49 -54
  76. data/lib/arel_extensions/railtie.rb +5 -5
  77. data/lib/arel_extensions/set_functions.rb +7 -5
  78. data/lib/arel_extensions/string_functions.rb +55 -110
  79. data/lib/arel_extensions/tasks.rb +6 -6
  80. data/lib/arel_extensions/version.rb +1 -3
  81. data/lib/arel_extensions/visitors/ibm_db.rb +36 -29
  82. data/lib/arel_extensions/visitors/mssql.rb +248 -493
  83. data/lib/arel_extensions/visitors/mysql.rb +244 -420
  84. data/lib/arel_extensions/visitors/oracle.rb +258 -311
  85. data/lib/arel_extensions/visitors/oracle12.rb +34 -20
  86. data/lib/arel_extensions/visitors/postgresql.rb +239 -350
  87. data/lib/arel_extensions/visitors/sqlite.rb +169 -191
  88. data/lib/arel_extensions/visitors/to_sql.rb +299 -371
  89. data/lib/arel_extensions/visitors.rb +62 -83
  90. data/lib/arel_extensions.rb +32 -243
  91. data/test/database.yml +10 -20
  92. data/test/helper.rb +18 -0
  93. data/test/real_db_test.rb +120 -123
  94. data/test/support/fake_record.rb +3 -11
  95. data/test/test_comparators.rb +17 -14
  96. data/test/visitors/test_bulk_insert_oracle.rb +12 -12
  97. data/test/visitors/test_bulk_insert_sqlite.rb +14 -13
  98. data/test/visitors/test_bulk_insert_to_sql.rb +13 -11
  99. data/test/visitors/test_oracle.rb +55 -55
  100. data/test/visitors/test_to_sql.rb +226 -419
  101. data/test/with_ar/all_agnostic_test.rb +375 -912
  102. data/test/with_ar/insert_agnostic_test.rb +22 -28
  103. data/test/with_ar/test_bulk_sqlite.rb +17 -18
  104. data/test/with_ar/test_math_sqlite.rb +27 -27
  105. data/test/with_ar/test_string_mysql.rb +34 -32
  106. data/test/with_ar/test_string_sqlite.rb +35 -31
  107. metadata +64 -50
  108. data/.github/workflows/publish.yml +0 -30
  109. data/.github/workflows/release.yml +0 -30
  110. data/.github/workflows/ruby.yml +0 -452
  111. data/CONTRIBUTING.md +0 -102
  112. data/Makefile +0 -48
  113. data/NEWS.md +0 -131
  114. data/bin/build +0 -15
  115. data/bin/publish +0 -8
  116. data/dev/arelx.dockerfile +0 -41
  117. data/dev/compose.yaml +0 -69
  118. data/dev/postgres.dockerfile +0 -5
  119. data/dev/rbenv +0 -189
  120. data/gemfiles/rails5.gemfile +0 -36
  121. data/gemfiles/rails6.gemfile +0 -40
  122. data/gemfiles/rails6_1.gemfile +0 -48
  123. data/gemfiles/rails7.gemfile +0 -48
  124. data/gemfiles/rails7_1.gemfile +0 -48
  125. data/gemfiles/rails7_2.gemfile +0 -47
  126. data/gemfiles/rails8.gemfile +0 -40
  127. data/gemfiles/rails8_1.gemfile +0 -41
  128. data/gemspecs/arel_extensions-v1.gemspec +0 -25
  129. data/gemspecs/arel_extensions-v2.gemspec +0 -25
  130. data/generate_gems.sh +0 -15
  131. data/lib/arel_extensions/aliases.rb +0 -14
  132. data/lib/arel_extensions/constants.rb +0 -13
  133. data/lib/arel_extensions/helpers.rb +0 -61
  134. data/lib/arel_extensions/nodes/aggregate_function.rb +0 -13
  135. data/lib/arel_extensions/nodes/byte_size.rb +0 -11
  136. data/lib/arel_extensions/nodes/char_length.rb +0 -11
  137. data/lib/arel_extensions/nodes/formatted_date.rb +0 -42
  138. data/lib/arel_extensions/nodes/rollup.rb +0 -36
  139. data/lib/arel_extensions/nodes/select.rb +0 -10
  140. data/lib/arel_extensions/nodes/sum.rb +0 -7
  141. data/lib/arel_extensions/visitors/convert_format.rb +0 -37
  142. data/lib/arel_extensions/warning.rb +0 -42
  143. data/test/arelx_test_helper.rb +0 -94
  144. data/test/config_loader.rb +0 -9
  145. data/version_v1.rb +0 -5
  146. data/version_v2.rb +0 -5
@@ -1,106 +1,39 @@
1
1
  module ArelExtensions
2
2
  module Visitors
3
- class Arel::Visitors::MySQL
4
- DATE_MAPPING = {
5
- 'd' => 'DAY', 'm' => 'MONTH', 'w' => 'WEEK', 'y' => 'YEAR', 'wd' => 'WEEKDAY',
6
- 'h' => 'HOUR', 'mn' => 'MINUTE', 's' => 'SECOND'
7
- }.freeze
8
-
9
- DATE_FORMAT_DIRECTIVES = { # ISO C / POSIX
3
+ Arel::Visitors::MySQL.class_eval do
4
+ Arel::Visitors::MySQL::COMMA = ", "
5
+ Arel::Visitors::MySQL::DATE_MAPPING = {'d' => 'DAY', 'm' => 'MONTH', 'w' => 'WEEK', 'y' => 'YEAR', 'wd' => 'WEEKDAY', 'h' => 'HOUR', 'mn' => 'MINUTE', 's' => 'SECOND'}
6
+ Arel::Visitors::MySQL::DATE_FORMAT_DIRECTIVES = { # ISO C / POSIX
10
7
  '%Y' => '%Y', '%C' => '', '%y' => '%y', '%m' => '%m', '%B' => '%M', '%b' => '%b', '%^b' => '%b', # year, month
11
- '%V' => '%v', '%G' => '%x', # ISO week number and year of week
12
- '%d' => '%d', '%e' => '%e', '%j' => '%j', '%w' => '%w', '%a' => '%a', '%A' => '%W', # day, weekday
8
+ '%d' => '%d', '%e' => '%e', '%j' => '%j', '%w' => '%w', '%A' => '%W', # day, weekday
13
9
  '%H' => '%H', '%k' => '%k', '%I' => '%I', '%l' => '%l', '%P' => '%p', '%p' => '%p', # hours
14
10
  '%M' => '%i', '%S' => '%S', '%L' => '', '%N' => '%f', '%z' => ''
15
- }.freeze
16
-
17
- # This helper method did not exist in rails < 5.2
18
- if !Arel::Visitors::MySQL.method_defined?(:collect_nodes_for)
19
- def collect_nodes_for(nodes, collector, spacer, connector = ', ')
20
- if nodes&.any?
21
- collector << spacer
22
- inject_join nodes, collector, connector
23
- end
24
- end
25
- end
26
-
27
- if private_method_defined?(:visit_Arel_Nodes_SelectCore) || method_defined?(:visit_Arel_Nodes_SelectCore)
28
- alias old_visit_Arel_Nodes_SelectCore visit_Arel_Nodes_SelectCore
29
- end
30
- # The whole purpose of this override is to fix the behavior of RollUp.
31
- # All other databases treat RollUp sanely, execpt MySQL which requires
32
- # that it figures as the last element of a GROUP BY.
33
- def visit_Arel_Nodes_SelectCore(o, collector)
34
- collector << 'SELECT'
35
-
36
- collector = collect_optimizer_hints(o, collector) if respond_to?(:collect_optimizer_hints, true)
37
- collector = maybe_visit o.set_quantifier, collector
11
+ }
38
12
 
39
- collect_nodes_for o.projections, collector, ' '
40
13
 
41
- if o.source && !o.source.empty?
42
- collector << ' FROM '
43
- collector = visit o.source, collector
44
- end
45
-
46
- # The actual work
47
- groups = o.groups
48
- rollup = groups.select { |g| g.expr.class == Arel::Nodes::RollUp }.map { |r| r.expr.value }
49
- if rollup && !rollup.empty?
50
- groups = o.groups.reject { |g| g.expr.class == Arel::Nodes::RollUp }
51
- groups << Arel::Nodes::RollUp.new(rollup)
52
- end
53
- # FIN
54
-
55
- collect_nodes_for o.wheres, collector, ' WHERE ', ' AND '
56
- collect_nodes_for groups, collector, ' GROUP BY '
57
- collect_nodes_for o.havings, collector, ' HAVING ', ' AND '
58
- collect_nodes_for o.windows, collector, ' WINDOW '
59
-
60
- if o.respond_to?(:comment)
61
- maybe_visit o.comment, collector
62
- else
14
+ #Math functions
15
+ def visit_ArelExtensions_Nodes_Log10 o, collector
16
+ collector << "LOG10("
17
+ o.expressions.each_with_index { |arg, i|
18
+ collector << Arel::Visitors::ToSql::COMMA unless i == 0
19
+ collector = visit arg, collector
20
+ }
21
+ collector << ")"
63
22
  collector
64
- end
65
- end
66
-
67
- # Math functions
68
- def visit_ArelExtensions_Nodes_Log10(o, collector)
69
- collector << 'LOG10('
70
- o.expressions.each_with_index { |arg, i|
71
- collector << COMMA if i != 0
72
- collector = visit arg, collector
73
- }
74
- collector << ')'
75
- collector
76
23
  end
77
24
 
78
- def visit_ArelExtensions_Nodes_Power(o, collector)
79
- collector << 'POW('
25
+ def visit_ArelExtensions_Nodes_Power o, collector
26
+ collector << "POW("
80
27
  o.expressions.each_with_index { |arg, i|
81
- collector << COMMA if i != 0
28
+ collector << Arel::Visitors::ToSql::COMMA unless i == 0
82
29
  collector = visit arg, collector
83
30
  }
84
- collector << ')'
85
- collector
86
- end
87
-
88
- # String functions
89
- def visit_ArelExtensions_Nodes_ByteSize(o, collector)
90
- collector << 'LENGTH('
91
- collector = visit o.expr.coalesce(''), collector
92
- collector << ')'
31
+ collector << ")"
93
32
  collector
94
33
  end
95
34
 
96
- def visit_ArelExtensions_Nodes_CharLength(o, collector)
97
- collector << 'CHAR_LENGTH('
98
- collector = visit o.expr.coalesce(''), collector
99
- collector << ')'
100
- collector
101
- end
102
-
103
- def visit_ArelExtensions_Nodes_IMatches(o, collector) # insensitive on ASCII
35
+ #String functions
36
+ def visit_ArelExtensions_Nodes_IMatches o, collector # insensitive on ASCII
104
37
  collector << 'LOWER('
105
38
  collector = visit o.left, collector
106
39
  collector << ') LIKE LOWER('
@@ -108,45 +41,49 @@ module ArelExtensions
108
41
  collector << ')'
109
42
  if o.escape
110
43
  collector << ' ESCAPE '
111
- collector = visit o.escape, collector
44
+ visit o.escape, collector
45
+ else
46
+ collector
112
47
  end
113
- collector
114
48
  end
115
49
 
116
- def visit_ArelExtensions_Nodes_AiMatches(o, collector)
50
+ def visit_ArelExtensions_Nodes_AiMatches o, collector
117
51
  collector = visit o.left.ai_collate, collector
118
52
  collector << ' LIKE '
119
53
  collector = visit o.right.ai_collate, collector
120
54
  if o.escape
121
55
  collector << ' ESCAPE '
122
- collector = visit o.escape, collector
56
+ visit o.escape, collector
57
+ else
58
+ collector
123
59
  end
124
- collector
125
60
  end
126
61
 
127
- def visit_ArelExtensions_Nodes_AiIMatches(o, collector)
62
+ def visit_ArelExtensions_Nodes_AiIMatches o, collector
128
63
  collector = visit o.left.ai_collate, collector
129
64
  collector << ' LIKE '
130
65
  collector = visit o.right.ai_collate, collector
131
66
  if o.escape
132
67
  collector << ' ESCAPE '
133
- collector = visit o.escape, collector
68
+ visit o.escape, collector
69
+ else
70
+ collector
134
71
  end
135
- collector
136
72
  end
137
73
 
138
- def visit_ArelExtensions_Nodes_SMatches(o, collector)
139
- collector = visit o.left.collate, collector
140
- collector << ' LIKE '
141
- collector = visit o.right.collate, collector
142
- if o.escape
143
- collector << ' ESCAPE '
144
- collector = visit o.escape, collector
145
- end
146
- collector
74
+ def visit_ArelExtensions_Nodes_SMatches o, collector
75
+ collector = visit o.left.collate, collector
76
+ collector << ' LIKE '
77
+ collector = visit o.right.collate, collector
78
+ if o.escape
79
+ collector << ' ESCAPE '
80
+ visit o.escape, collector
81
+ else
82
+ collector
83
+ end
147
84
  end
148
85
 
149
- def visit_ArelExtensions_Nodes_IDoesNotMatch(o, collector)
86
+ def visit_ArelExtensions_Nodes_IDoesNotMatch o, collector
150
87
  collector << 'LOWER('
151
88
  collector = visit o.left, collector
152
89
  collector << ') NOT LIKE LOWER('
@@ -154,189 +91,142 @@ module ArelExtensions
154
91
  collector << ')'
155
92
  if o.escape
156
93
  collector << ' ESCAPE '
157
- collector = visit o.escape, collector
94
+ visit o.escape, collector
95
+ else
96
+ collector
158
97
  end
159
- collector
160
98
  end
161
99
 
162
- def visit_ArelExtensions_Nodes_Collate(o, collector)
163
- charset =
164
- case o.expressions.first
165
- when Arel::Attributes::Attribute
166
- case o.option
167
- when 'latin1', 'utf8'
100
+ def visit_ArelExtensions_Nodes_Collate o, collector
101
+ case o.expressions.first
102
+ when Arel::Attributes::Attribute
103
+ charset = case o.option
104
+ when 'latin1','utf8'
168
105
  o.option
169
106
  else
170
107
  Arel::Table.engine.connection.charset || 'utf8'
171
108
  end
172
- else
173
- o.option == 'latin1' ? 'latin1' : 'utf8'
174
- end
109
+ else
110
+ charset = (o.option == 'latin1') ? 'latin1' : 'utf8'
111
+ end
175
112
  collector = visit o.expressions.first, collector
176
- collector <<
177
- if o.ai
178
- " COLLATE #{charset == 'latin1' ? 'latin1_general_ci' : "#{charset}_unicode_ci"}"
179
- # doesn't work in latin1
180
- elsif o.ci
181
- " COLLATE #{charset == 'latin1' ? 'latin1_general_ci' : "#{charset}_unicode_ci"}"
182
- else
183
- " COLLATE #{charset}_bin"
184
- end
113
+ if o.ai
114
+ collector << " COLLATE #{charset == 'latin1' ? 'latin1_general_ci' : 'utf8_unicode_ci' }"
115
+ #doesn't work in latin1
116
+ elsif o.ci
117
+ collector << " COLLATE #{charset == 'latin1' ? 'latin1_general_ci' : 'utf8_unicode_ci' }"
118
+ else
119
+ collector << " COLLATE #{charset}_bin"
120
+ end
185
121
  collector
186
122
  end
187
123
 
188
- def visit_ArelExtensions_Nodes_Concat(o, collector)
189
- collector << 'CONCAT('
124
+ def visit_ArelExtensions_Nodes_Concat o, collector
125
+ collector << "CONCAT("
190
126
  o.expressions.each_with_index { |arg, i|
191
- collector << COMMA if i != 0
192
- if arg.is_a?(Numeric) || arg.is_a?(Arel::Attributes::Attribute)
193
- collector << 'CAST('
127
+ collector << Arel::Visitors::MySQL::COMMA unless i == 0
128
+ if (arg.is_a?(Numeric)) || (arg.is_a?(Arel::Attributes::Attribute))
129
+ collector << "CAST("
194
130
  collector = visit arg, collector
195
- collector << ' AS char)'
131
+ collector << " AS char)"
196
132
  else
197
133
  collector = visit arg, collector
198
134
  end
199
135
  }
200
- collector << ')'
136
+ collector << ")"
201
137
  collector
202
138
  end
203
139
 
204
- def visit_Arel_Nodes_RollUp(o, collector)
205
- visit o.expr, collector
206
- collector << ' WITH ROLLUP'
207
- end
208
-
209
- def visit_ArelExtensions_Nodes_GroupConcat(o, collector)
210
- collector << 'GROUP_CONCAT('
140
+ def visit_ArelExtensions_Nodes_GroupConcat o, collector
141
+ collector << "GROUP_CONCAT("
211
142
  collector = visit o.left, collector
212
- if !o.order.blank?
143
+ if !o.orders.blank?
213
144
  collector << ' ORDER BY '
214
- o.order.each_with_index do |order, i|
215
- collector << COMMA if i != 0
145
+ o.orders.each_with_index do |order,i|
146
+ collector << Arel::Visitors::ToSql::COMMA unless i == 0
216
147
  collector = visit order, collector
217
148
  end
218
149
  end
219
- sep = o.separator.is_a?(Arel::Nodes::Quoted) ? o.separator.expr : o.separator
220
- if 'NULL' != sep
150
+ if o.right && o.right != 'NULL'
221
151
  collector << ' SEPARATOR '
222
- collector = visit o.separator, collector
152
+ collector = visit o.right, collector
223
153
  end
224
- collector << ')'
154
+ collector << ")"
225
155
  collector
226
156
  end
227
157
 
228
- def visit_ArelExtensions_Nodes_Trim(o, collector)
229
- collector << 'TRIM(' # BOTH
230
- collector = visit o.right, collector
231
- collector << ' FROM '
232
- collector = visit o.left, collector
233
- collector << ')'
234
- collector
158
+ def visit_ArelExtensions_Nodes_Trim o, collector
159
+ collector << 'TRIM(' # BOTH
160
+ collector = visit o.right, collector
161
+ collector << " FROM "
162
+ collector = visit o.left, collector
163
+ collector << ")"
164
+ collector
235
165
  end
236
166
 
237
- def visit_ArelExtensions_Nodes_Ltrim(o, collector)
238
- collector << 'TRIM(LEADING '
239
- collector = visit o.right, collector
240
- collector << ' FROM '
241
- collector = visit o.left, collector
242
- collector << ')'
243
- collector
167
+ def visit_ArelExtensions_Nodes_Ltrim o , collector
168
+ collector << 'TRIM(LEADING '
169
+ collector = visit o.right, collector
170
+ collector << " FROM "
171
+ collector = visit o.left, collector
172
+ collector << ")"
173
+ collector
244
174
  end
245
175
 
246
- def visit_ArelExtensions_Nodes_Rtrim(o, collector)
176
+ def visit_ArelExtensions_Nodes_Rtrim o , collector
247
177
  collector << 'TRIM(TRAILING '
248
178
  collector = visit o.right, collector
249
- collector << ' FROM '
179
+ collector << " FROM "
250
180
  collector = visit o.left, collector
251
- collector << ')'
181
+ collector << ")"
252
182
  collector
253
183
  end
254
184
 
255
- def visit_ArelExtensions_Nodes_Repeat(o, collector)
256
- collector << 'REPEAT('
185
+ def visit_ArelExtensions_Nodes_Repeat o, collector
186
+ collector << "REPEAT("
257
187
  o.expressions.each_with_index { |arg, i|
258
- collector << COMMA if i != 0
188
+ collector << Arel::Visitors::ToSql::COMMA unless i == 0
259
189
  collector = visit arg, collector
260
190
  }
261
- collector << ')'
191
+ collector << ")"
262
192
  collector
263
193
  end
264
194
 
265
- def visit_ArelExtensions_Nodes_RegexpReplace(o, collector)
266
- if !regexp_replace_supported?
267
- warn('Warning: ArelExtensions: REGEXP_REPLACE does not seem to be available in the current version of the DBMS, it might crash')
268
- end
269
- super
270
- end
271
-
272
- def visit_ArelExtensions_Nodes_Format(o, collector)
273
- # One use case we met is
274
- # `case…when…then(valid_date).else(Arel.null).format(…)`.
275
- #
276
- # In this case, `o.col_type` is `nil` but we have a legitimate type in
277
- # the expression to be formatted. The following is a best effort to
278
- # infer the proper type.
279
- first = o.expressions[0]
280
- type =
281
- if o.col_type.nil? \
282
- && first.respond_to?(:return_type) && !first&.return_type.nil?
283
- first&.return_type
284
- else
285
- o.col_type
286
- end
287
-
288
- case type
289
- when :date, :datetime, :time
290
- visit_ArelExtensions_Nodes_FormattedDate o, collector
195
+ def visit_ArelExtensions_Nodes_Format o, collector
196
+ case o.col_type
197
+ when :date, :datetime
198
+ collector << "DATE_FORMAT("
199
+ collector = visit o.left, collector
200
+ collector << Arel::Visitors::MySQL::COMMA
201
+ f = o.iso_format.dup
202
+ Arel::Visitors::MySQL::DATE_FORMAT_DIRECTIVES.each { |d, r| f.gsub!(d, r) }
203
+ collector = visit Arel::Nodes.build_quoted(f), collector
204
+ collector << ")"
291
205
  when :integer, :float, :decimal
292
- collector << 'FORMAT('
206
+ collector << "FORMAT("
293
207
  collector = visit o.left, collector
294
- collector << COMMA
208
+ collector << Arel::Visitors::ToSql::COMMA
295
209
  collector << '2'
296
- collector << COMMA
210
+ collector << Arel::Visitors::ToSql::COMMA
297
211
  collector = visit o.right, collector
298
- collector << ')'
212
+ collector << ")"
299
213
  else
300
214
  collector = visit o.left, collector
301
215
  end
302
216
  collector
303
217
  end
304
218
 
305
- def visit_ArelExtensions_Nodes_FormattedDate(o, collector)
306
- fmt = ArelExtensions::Visitors.strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
307
- collector << 'DATE_FORMAT('
308
- collector << 'CONVERT_TZ(' if o.time_zone
309
- collector = visit o.left, collector
310
- case o.time_zone
311
- when Hash
312
- src_tz, dst_tz = o.time_zone.first
313
- collector << COMMA
314
- collector = visit Arel.quoted(src_tz), collector
315
- collector << COMMA
316
- collector = visit Arel.quoted(dst_tz), collector
317
- collector << ')'
318
- when String
319
- collector << COMMA << "'UTC'" << COMMA
320
- collector = visit Arel.quoted(o.time_zone), collector
321
- collector << ')'
322
- end
323
- collector << COMMA
324
- collector = visit Arel.quoted(fmt), collector
325
- collector << ')'
326
- end
327
-
328
- def visit_ArelExtensions_Nodes_DateDiff(o, collector)
329
- case o.right_node_type
330
- when :ruby_date, :ruby_time, :date, :datetime, :time
331
- collector <<
332
- case o.left_node_type
333
- when :ruby_time, :datetime, :time then 'TIMESTAMPDIFF(SECOND, '
334
- else 'DATEDIFF('
335
- end
219
+ def visit_ArelExtensions_Nodes_DateDiff o, collector
220
+ if o.right_node_type == :ruby_date || o.right_node_type == :ruby_time || o.right_node_type == :date || o.right_node_type == :datetime || o.right_node_type == :time
221
+ collector << if o.left_node_type == :ruby_time || o.left_node_type == :datetime || o.left_node_type == :time
222
+ 'TIMESTAMPDIFF(SECOND, '
223
+ else
224
+ 'DATEDIFF('
225
+ end
336
226
  collector = visit o.right, collector
337
- collector << COMMA
227
+ collector << Arel::Visitors::MySQL::COMMA
338
228
  collector = visit o.left, collector
339
- collector << ')'
229
+ collector << ")"
340
230
  else
341
231
  collector << '('
342
232
  collector = visit o.left, collector
@@ -355,94 +245,103 @@ module ArelExtensions
355
245
  collector
356
246
  end
357
247
 
358
- def visit_ArelExtensions_Nodes_DateAdd(o, collector)
359
- collector << 'DATE_ADD('
248
+ def visit_ArelExtensions_Nodes_DateAdd o, collector
249
+ collector << "DATE_ADD("
360
250
  collector = visit o.left, collector
361
- collector << COMMA
251
+ collector << Arel::Visitors::MySQL::COMMA
362
252
  collector = visit o.mysql_value(o.right), collector
363
- collector << ')'
253
+ collector << ")"
364
254
  collector
365
255
  end
366
256
 
367
- def visit_ArelExtensions_Nodes_Duration(o, collector)
257
+
258
+ def visit_ArelExtensions_Nodes_Duration o, collector
368
259
  if o.left == 'wd'
369
- collector << '(WEEKDAY('
260
+ collector << "(WEEKDAY("
370
261
  collector = visit o.right, collector
371
- collector << ') + 1) % 7'
262
+ collector << ") + 1) % 7"
372
263
  else
373
264
  if o.with_interval
374
- interval =
375
- case o.left
376
- when 'd', 'm', 'y'
377
- 'DAY'
378
- when 'h', 'mn', 's'
379
- 'SECOND'
380
- when /i\z/
381
- DATE_MAPPING[o.left[0..-2]]
382
- end
265
+ case o.left
266
+ when 'd','m','y'
267
+ interval = 'DAY'
268
+ when 'h','mn','s'
269
+ interval = 'SECOND'
270
+ when /i\z/
271
+ interval = Arel::Visitors::MySQL::DATE_MAPPING[o.left[0..-2]]
272
+ else
273
+ interval = nil
274
+ end
383
275
  end
384
- collector << ' INTERVAL ' if o.with_interval && interval
385
- collector << "#{DATE_MAPPING[o.left]}("
276
+ collector << " INTERVAL " if o.with_interval && interval
277
+ collector << "#{Arel::Visitors::MySQL::DATE_MAPPING[o.left]}("
386
278
  collector = visit o.right, collector
387
- collector << ')'
279
+ collector << ")"
388
280
  collector << " #{interval} " if o.with_interval && interval
389
281
  end
390
282
  collector
391
283
  end
392
284
 
393
- def visit_ArelExtensions_Nodes_IsNull(o, collector)
394
- collector << 'ISNULL('
285
+
286
+ def visit_ArelExtensions_Nodes_IsNull o, collector
287
+ collector << "ISNULL("
395
288
  collector = visit o.expr, collector
396
- collector << ')'
289
+ collector << ")"
397
290
  collector
398
291
  end
399
292
 
400
- def visit_ArelExtensions_Nodes_IsNotNull(o, collector)
401
- collector << 'NOT ISNULL('
402
- collector = visit o.expr, collector
403
- collector << ')'
404
- collector
293
+ def visit_ArelExtensions_Nodes_IsNotNull o, collector
294
+ collector << "NOT ISNULL("
295
+ collector = visit o.expr, collector
296
+ collector << ")"
297
+ collector
405
298
  end
406
299
 
407
- def visit_ArelExtensions_Nodes_Wday(o, collector)
408
- collector << '(WEEKDAY('
300
+ def visit_ArelExtensions_Nodes_Wday o, collector
301
+ collector << "(WEEKDAY("
409
302
  collector = visit o.date, collector
410
- collector << ') + 1) % 7'
303
+ collector << ") + 1) % 7"
411
304
  collector
412
305
  end
413
306
 
414
- def visit_ArelExtensions_Nodes_Cast(o, collector)
415
- as_attr =
416
- case o.as_attr
417
- when :binary then 'binary'
418
- when :date then 'date'
419
- when :datetime then 'datetime'
420
- when :int then 'signed'
421
- when :number, :decimal then 'decimal(20,6)'
422
- when :string then 'char'
423
- when :time then 'time'
424
- else o.as_attr.to_s
425
- end
426
-
427
- collector << 'CAST('
307
+ def visit_ArelExtensions_Nodes_Cast o, collector
308
+ collector << "CAST("
428
309
  collector = visit o.left, collector
429
- collector << ' AS '
430
- collector = visit Arel::Nodes::SqlLiteral.new(as_attr), collector
431
- collector << ')'
310
+ collector << " AS "
311
+ case o.as_attr
312
+ when :string
313
+ as_attr = Arel::Nodes::SqlLiteral.new('char')
314
+ when :time
315
+ as_attr = Arel::Nodes::SqlLiteral.new('time')
316
+ when :int
317
+ as_attr = Arel::Nodes::SqlLiteral.new('signed')
318
+ when :number, :decimal
319
+ as_attr = Arel::Nodes::SqlLiteral.new('decimal(20,6)')
320
+ when :datetime
321
+ as_attr = Arel::Nodes::SqlLiteral.new('datetime')
322
+ when :date
323
+ as_attr = Arel::Nodes::SqlLiteral.new('date')
324
+ when :binary
325
+ as_attr = Arel::Nodes::SqlLiteral.new('binary')
326
+ else
327
+ as_attr = Arel::Nodes::SqlLiteral.new(o.as_attr.to_s)
328
+ end
329
+ collector = visit as_attr, collector
330
+ collector << ")"
432
331
  collector
433
332
  end
434
333
 
435
- alias old_visit_Arel_Nodes_SelectStatement visit_Arel_Nodes_SelectStatement rescue nil
436
- def visit_Arel_Nodes_SelectStatement(o, collector)
334
+ alias_method :old_visit_Arel_Nodes_SelectStatement, :visit_Arel_Nodes_SelectStatement
335
+ def visit_Arel_Nodes_SelectStatement o, collector
437
336
  if !(collector.value.blank? || (collector.value.is_a?(Array) && collector.value[0].blank?)) && o.limit.blank? && o.offset.blank?
438
337
  o = o.dup
439
338
  o.orders = []
440
339
  end
441
- old_visit_Arel_Nodes_SelectStatement(o, collector)
340
+ old_visit_Arel_Nodes_SelectStatement(o,collector)
442
341
  end
443
342
 
444
- alias old_visit_Arel_Nodes_As visit_Arel_Nodes_As rescue nil
445
- def visit_Arel_Nodes_As(o, collector)
343
+ alias_method :old_visit_Arel_Nodes_As, :visit_Arel_Nodes_As
344
+ def visit_Arel_Nodes_As o, collector
446
345
  if o.left.is_a?(Arel::Nodes::Binary)
447
346
  collector << '('
448
347
  collector = visit o.left, collector
@@ -450,56 +349,44 @@ module ArelExtensions
450
349
  else
451
350
  collector = visit o.left, collector
452
351
  end
453
- collector << ' AS '
454
-
455
- # sometimes these values are already quoted, if they are, don't double quote it
456
- quote = o.right.is_a?(Arel::Nodes::SqlLiteral) && o.right[0] != '`' && o.right[-1] != '`'
457
-
458
- collector << '`' if quote
352
+ collector << " AS `"
459
353
  collector = visit o.right, collector
460
- collector << '`' if quote
461
-
354
+ collector << "`"
462
355
  collector
463
356
  end
464
357
 
465
- def visit_ArelExtensions_Nodes_FormattedNumber(o, collector)
358
+ def visit_ArelExtensions_Nodes_FormattedNumber o, collector
466
359
  col = o.left.coalesce(0)
467
- params = o.locale ? [o.precision, Arel.quoted(o.locale)] : [o.precision]
468
- sign = Arel.when(col < 0).
360
+ params = o.locale ? [o.precision,Arel::Nodes.build_quoted(o.locale)] : [o.precision]
361
+ sign = ArelExtensions::Nodes::Case.new.when(col<0).
469
362
  then('-').
470
363
  else(o.flags.include?('+') ? '+' : (o.flags.include?(' ') ? ' ' : ''))
471
364
  sign_length = ArelExtensions::Nodes::Length.new([sign])
472
365
 
473
- number =
474
- if o.scientific_notation
475
- ArelExtensions::Nodes::Concat.new([
476
- Arel::Nodes::NamedFunction.new('FORMAT', [
477
- col.abs / Arel.quoted(10).pow(col.abs.log10.floor)
478
- ] + params),
479
- o.type,
480
- Arel::Nodes::NamedFunction.new('FORMAT', [
481
- col.abs.log10.floor,
482
- 0
483
- ])
366
+ if o.scientific_notation
367
+ number = ArelExtensions::Nodes::Concat.new([
368
+ Arel::Nodes::NamedFunction.new('FORMAT',[
369
+ col.abs/Arel::Nodes.build_quoted(10).pow(col.abs.log10.floor)
370
+ ]+params),
371
+ o.type,
372
+ Arel::Nodes::NamedFunction.new('FORMAT',[
373
+ col.abs.log10.floor,
374
+ 0
484
375
  ])
485
- else
486
- Arel::Nodes::NamedFunction.new('FORMAT', [col.abs] + params)
487
- end
376
+ ])
377
+ else
378
+ number = Arel::Nodes::NamedFunction.new('FORMAT',[col.abs]+params)
379
+ end
488
380
 
489
- repeated_char =
490
- if o.width == 0
491
- Arel.quoted('')
492
- else
493
- Arel
494
- .when(Arel.quoted(o.width).abs - (number.length + sign_length) > 0)
495
- .then(Arel.quoted(
496
- o.flags.include?('-') ? ' ' : (o.flags.include?('0') ? '0' : ' ')
497
- ).repeat(Arel.quoted(o.width).abs - (number.length + sign_length))
498
- )
499
- .else('')
500
- end
501
- before = !o.flags.include?('0') && !o.flags.include?('-') ? repeated_char : ''
502
- middle = o.flags.include?('0') && !o.flags.include?('-') ? repeated_char : ''
381
+ repeated_char = (o.width == 0) ? Arel::Nodes.build_quoted('') : ArelExtensions::Nodes::Case.new().
382
+ when(Arel::Nodes.build_quoted(o.width).abs-(number.length+sign_length)>0).
383
+ then(Arel::Nodes.build_quoted(
384
+ o.flags.include?('-') ? ' ' : (o.flags.include?('0') ? '0' : ' ')
385
+ ).repeat(Arel::Nodes.build_quoted(o.width).abs-(number.length+sign_length))
386
+ ).
387
+ else('')
388
+ before = (!o.flags.include?('0'))&&(!o.flags.include?('-')) ? repeated_char : ''
389
+ middle = (o.flags.include?('0'))&&(!o.flags.include?('-')) ? repeated_char : ''
503
390
  after = o.flags.include?('-') ? repeated_char : ''
504
391
  full_number = ArelExtensions::Nodes::Concat.new([
505
392
  before,
@@ -508,129 +395,66 @@ module ArelExtensions
508
395
  number,
509
396
  after
510
397
  ])
511
- collector = visit ArelExtensions::Nodes::Concat.new([Arel.quoted(o.prefix), full_number, Arel.quoted(o.suffix)]), collector
398
+ collector = visit ArelExtensions::Nodes::Concat.new([Arel::Nodes.build_quoted(o.prefix),full_number,Arel::Nodes.build_quoted(o.suffix)]), collector
512
399
  collector
513
400
  end
514
401
 
515
- def visit_Aggregate_For_AggregateFunction(o, collector)
516
- if !window_supported?
517
- warn('Warning: ArelExtensions: Window Functions are not available in the current version of the DBMS.')
518
- return collector
519
- end
520
-
521
- if !o.order.empty? || !o.group.empty?
522
- collector << ' OVER ('
523
- if !o.group.empty?
524
- collector << ' PARTITION BY ('
525
- visit o.group, collector
526
- collector << ')'
527
- end
528
- if !o.order.empty?
529
- collector << ' ORDER BY ('
530
- visit o.order, collector
531
- collector << ')'
532
- end
533
- collector << ')'
534
- end
535
- collector
536
- end
537
-
538
- def visit_ArelExtensions_Nodes_Std(o, collector)
539
- collector << (o.unbiased_estimator ? 'STDDEV_SAMP(' : 'STDDEV_POP(')
402
+ def visit_ArelExtensions_Nodes_Std o, collector
403
+ collector << (o.unbiased_estimator ? "STDDEV_SAMP(" : "STDDEV_POP(")
540
404
  visit o.left, collector
541
- collector << ')'
542
- visit_Aggregate_For_AggregateFunction o, collector
405
+ collector << ")"
543
406
  collector
544
407
  end
545
408
 
546
- def visit_ArelExtensions_Nodes_Variance(o, collector)
547
- collector << (o.unbiased_estimator ? 'VAR_SAMP(' : 'VAR_POP(')
409
+ def visit_ArelExtensions_Nodes_Variance o, collector
410
+ collector << (o.unbiased_estimator ? "VAR_SAMP(" : "VAR_POP(")
548
411
  visit o.left, collector
549
- collector << ')'
550
- visit_Aggregate_For_AggregateFunction o, collector
412
+ collector << ")"
551
413
  collector
552
414
  end
553
415
 
554
- # JSON if implemented only after 10.2.3 (aggregations after 10.5.0) in MariaDb and 5.7 (aggregations after 5.7.22) in MySql
416
+ # JSON if implemented only after 10.2.3 in MariaDb and 5.7 in MySql
555
417
  def json_supported?
556
- version_supported?('10.5.0', '5.7.22')
557
- end
558
-
559
- def window_supported?
560
- version_supported?('10.2.3', '8.0')
561
- end
562
-
563
- def regexp_replace_supported?
564
- version_supported?('10.0.5', '8.0')
418
+ Arel::Table.engine.connection.send(:mariadb?) &&
419
+ Arel::Table.engine.connection.send(:version) >= '10.2.3' ||
420
+ !Arel::Table.engine.connection.send(:mariadb?) &&
421
+ Arel::Table.engine.connection.send(:version) >= '5.7.0'
565
422
  end
566
423
 
567
- def version_supported?(mariadb_v = '10.2.3', mysql_v = '5.7.0')
568
- conn = Arel::Table.engine.connection
569
- conn.send(:mariadb?) &&
570
- (conn.respond_to?(:get_database_version) && conn.send(:get_database_version) >= mariadb_v ||
571
- conn.respond_to?(:version) && conn.send(:version) >= mariadb_v ||
572
- conn.instance_variable_get(:@version) && conn.instance_variable_get(:@version) >= mariadb_v) ||
573
- !conn.send(:mariadb?) &&
574
- (conn.respond_to?(:get_database_version) && conn.send(:get_database_version) >= mysql_v ||
575
- conn.respond_to?(:version) && conn.send(:version) >= mysql_v ||
576
- conn.instance_variable_get(:@version) && conn.instance_variable_get(:@version) >= mysql_v)
577
- # ideally we should parse the instance_variable @full_version because @version contains only the supposedly
578
- # corresponding mysql version of the current mariadb version (which is not very helpful most of the time)
579
- end
580
-
581
- def visit_ArelExtensions_Nodes_Json(o, collector)
424
+ def visit_ArelExtensions_Nodes_Json o,collector
582
425
  return super if !json_supported?
583
-
584
426
  case o.dict
585
427
  when Array
586
428
  collector << 'JSON_ARRAY('
587
- o.dict.each.with_index do |v, i|
429
+ o.dict.each.with_index do |v,i|
588
430
  if i != 0
589
- collector << COMMA
431
+ collector << Arel::Visitors::MySQL::COMMA
590
432
  end
591
- collector = visit_json_value(o, v, collector)
433
+ collector = visit v, collector
592
434
  end
593
435
  collector << ')'
594
436
  when Hash
595
437
  collector << 'JSON_OBJECT('
596
- o.dict.each.with_index do |(k, v), i|
438
+ o.dict.each.with_index do |(k,v),i|
597
439
  if i != 0
598
- collector << COMMA
440
+ collector << Arel::Visitors::MySQL::COMMA
599
441
  end
600
442
  collector = visit k, collector
601
- collector << COMMA
602
- collector = visit_json_value(o, v, collector)
443
+ collector << Arel::Visitors::MySQL::COMMA
444
+ collector = visit v, collector
603
445
  end
604
446
  collector << ')'
605
447
  else
606
- collector = visit_json_value(o, o.dict, collector)
607
- end
608
- collector
609
- end
610
-
611
- # A JSON Boolean must render as `true`/`false`, not the integer `1`/`0` that MySQL
612
- # collapses `TRUE`/`FALSE` into inside JSON functions (MariaDB keeps them boolean).
613
- def visit_json_value(o, v, collector)
614
- if o.boolean_literal?(v)
615
- value = v.is_a?(Arel::Nodes::Quoted) ? v.expr : v
616
- collector << "JSON_EXTRACT('#{value}', '$')"
617
- elsif o.type_of_node(v) == :boolean
618
- collector << 'JSON_EXTRACT(CASE WHEN '
619
- collector = visit v, collector
620
- collector << " IS NULL THEN NULL WHEN "
621
- collector = visit v, collector
622
- collector << " THEN 'true' ELSE 'false' END, '$')"
623
- else
624
- collector = visit v, collector
448
+ collector = visit o.dict, collector
625
449
  end
626
450
  collector
627
451
  end
628
452
 
629
- def visit_ArelExtensions_Nodes_JsonMerge(o, collector)
453
+ def visit_ArelExtensions_Nodes_JsonMerge o,collector
630
454
  collector << 'JSON_MERGE_PATCH('
631
- o.expressions.each.with_index do |v, i|
455
+ o.expressions.each.with_index do |v,i|
632
456
  if i != 0
633
- collector << COMMA
457
+ collector << Arel::Visitors::MySQL::COMMA
634
458
  end
635
459
  collector = visit v, collector
636
460
  end
@@ -638,37 +462,36 @@ module ArelExtensions
638
462
  collector
639
463
  end
640
464
 
641
- def visit_ArelExtensions_Nodes_JsonGet(o, collector)
465
+ def visit_ArelExtensions_Nodes_JsonGet o,collector
642
466
  collector << 'JSON_EXTRACT('
643
467
  collector = visit o.dict, collector
644
- collector << COMMA
468
+ collector << Arel::Visitors::MySQL::COMMA
645
469
  if o.key.is_a?(Integer)
646
470
  collector << "\"$[#{o.key}]\""
647
471
  else
648
- collector = visit Arel.quoted('$.') + o.key, collector
472
+ collector = visit Arel::Nodes.build_quoted('$.')+o.key, collector
649
473
  end
650
474
  collector << ')'
651
475
  collector
652
476
  end
653
477
 
654
- def visit_ArelExtensions_Nodes_JsonSet(o, collector)
478
+ def visit_ArelExtensions_Nodes_JsonSet o,collector
655
479
  collector << 'JSON_SET('
656
480
  collector = visit o.dict, collector
657
- collector << COMMA
481
+ collector << Arel::Visitors::MySQL::COMMA
658
482
  if o.key.is_a?(Integer)
659
483
  collector << "\"$[#{o.key}]\""
660
484
  else
661
- collector = visit Arel.quoted('$.') + o.key, collector
485
+ collector = visit Arel::Nodes.build_quoted('$.')+o.key, collector
662
486
  end
663
- collector << COMMA
487
+ collector << Arel::Visitors::MySQL::COMMA
664
488
  collector = visit o.value, collector
665
489
  collector << ')'
666
490
  collector
667
491
  end
668
492
 
669
- def visit_ArelExtensions_Nodes_JsonGroup(o, collector)
493
+ def visit_ArelExtensions_Nodes_JsonGroup o, collector
670
494
  return super if !json_supported?
671
-
672
495
  if o.as_array
673
496
  collector << 'JSON_ARRAYAGG('
674
497
  collector = visit o.dict, collector
@@ -677,13 +500,13 @@ module ArelExtensions
677
500
  case o.dict
678
501
  when Hash
679
502
  collector << 'JSON_MERGE_PATCH(' if o.dict.length > 1
680
- o.dict.each.with_index do |(k, v), i|
503
+ o.dict.each.with_index do |(k,v),i|
681
504
  if i != 0
682
- collector << COMMA
505
+ collector << Arel::Visitors::MySQL::COMMA
683
506
  end
684
507
  collector << 'JSON_OBJECTAGG('
685
508
  collector = visit k, collector
686
- collector << COMMA
509
+ collector << Arel::Visitors::MySQL::COMMA
687
510
  collector = visit v, collector
688
511
  collector << ')'
689
512
  end
@@ -696,6 +519,7 @@ module ArelExtensions
696
519
  end
697
520
  collector
698
521
  end
522
+
699
523
  end
700
524
  end
701
525
  end