arel_extensions 0.8.4 → 0.8.5

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,6 +1,7 @@
1
1
  module ArelExtensions
2
2
  module Visitors
3
3
  Arel::Visitors::PostgreSQL.class_eval do
4
+ Arel::Visitors::PostgreSQL::DATE_MAPPING = {'d' => 'DAY', 'm' => 'MONTH', 'w' => 'WEEK', 'y' => 'YEAR', 'wd' => 'DOW'}
4
5
 
5
6
  def visit_ArelExtensions_Nodes_Rand o, collector
6
7
  collector << "RANDOM("
@@ -91,17 +92,7 @@ module ArelExtensions
91
92
  end
92
93
 
93
94
  def visit_ArelExtensions_Nodes_Duration o, collector
94
- #visit left for period
95
- if o.left == "d"
96
- collector << "EXTRACT(DAY FROM"
97
- elsif o.left == "m"
98
- collector << "EXTRACT(MONTH FROM "
99
- elsif (o.left == "w")
100
- collector << "EXTRACT(WEEK FROM"
101
- elsif (o.left == "y")
102
- collector << "EXTRACT(YEAR FROM"
103
- end
104
- #visit right
95
+ collector << "EXTRACT(#{Arel::Visitors::PostgreSQL::DATE_MAPPING[o.left]} FROM "
105
96
  collector = visit o.right, collector
106
97
  collector << ")"
107
98
  collector
@@ -130,7 +121,7 @@ module ArelExtensions
130
121
  end
131
122
 
132
123
  def visit_ArelExtensions_Nodes_Wday o, collector
133
- collector << "DATE_PART('dow', "
124
+ collector << "EXRTACT(DOW, "
134
125
  collector = visit o.date, collector
135
126
  collector << ")"
136
127
  collector
@@ -1,6 +1,7 @@
1
1
  module ArelExtensions
2
2
  module Visitors
3
3
  Arel::Visitors::SQLite.class_eval do
4
+ Arel::Visitors::SQLite::DATE_MAPPING = {'d' => '%d', 'm' => '%m', 'w' => '%W', 'y' => '%Y', 'wd' => '%w', 'M' => '%M'}
4
5
 
5
6
  #String functions
6
7
  def visit_ArelExtensions_Nodes_IMatches o, collector # insensitive on ASCII
@@ -47,17 +48,7 @@ module ArelExtensions
47
48
  end
48
49
 
49
50
  def visit_ArelExtensions_Nodes_Duration o, collector
50
- #visit left for period
51
- if(o.left == "d")
52
- collector << "strftime('%d',"
53
- elsif(o.left == "m")
54
- collector << "strftime('%m',"
55
- elsif (o.left == "w")
56
- collector << "strftime('%W',"
57
- elsif (o.left == "y")
58
- collector << "strftime('%Y',"
59
- end
60
- #visit right
51
+ collector << "strftime('#{Arel::Visitors::SQLite::DATE_MAPPING[o.left]}'#{Arel::Visitors::SQLite::COMMA}"
61
52
  collector = visit o.right, collector
62
53
  collector << ")"
63
54
  collector
@@ -55,10 +55,12 @@ module ArelExtensions
55
55
 
56
56
  # String functions
57
57
  def visit_ArelExtensions_Nodes_Concat o, collector
58
+ collector << '('
58
59
  o.expressions.each_with_index { |arg, i|
59
60
  collector = visit arg, collector
60
61
  collector << ' || ' unless i == o.expressions.length - 1
61
62
  }
63
+ collector << ")"
62
64
  collector
63
65
  end
64
66
 
@@ -150,6 +152,26 @@ module ArelExtensions
150
152
  collector
151
153
  end
152
154
 
155
+ def visit_ArelExtensions_Nodes_Format o, collector
156
+ case o.col_type
157
+ when :date, :datetime
158
+ collector << "STRFTIME("
159
+ collector = visit o.right, collector
160
+ collector << Arel::Visitors::ToSql::COMMA
161
+ collector = visit o.left, collector
162
+ collector << ")"
163
+ when :integer, :float, :decimal
164
+ collector << "FORMAT("
165
+ collector = visit o.left, collector
166
+ collector << Arel::Visitors::ToSql::COMMA
167
+ collector = visit o.right, collector
168
+ collector << ")"
169
+ else
170
+ collector = visit o.left, collector
171
+ end
172
+ collector
173
+ end
174
+
153
175
  #comparators
154
176
 
155
177
  def visit_ArelExtensions_Nodes_Coalesce o, collector
data/test/database.yml CHANGED
@@ -27,11 +27,13 @@ jdbc-postgresql:
27
27
  oracle:
28
28
  adapter: oracle_enhanced
29
29
  database: xe
30
- username: travis
30
+ username: ruby
31
+ password: oci8
31
32
  jdbc-oracle:
32
33
  adapter: oracle_enhanced
33
34
  database: xe
34
- username: travis
35
+ username: ruby
36
+ password: oci8
35
37
  ibm_db:
36
38
  adapter: ibm_db
37
39
  username: travis
@@ -0,0 +1,2 @@
1
+ alter user sys identified by admin;
2
+ alter user system identified by admin;
@@ -0,0 +1,29 @@
1
+ CREATE USER oracle_enhanced IDENTIFIED BY oracle_enhanced;
2
+
3
+ GRANT unlimited tablespace, create session, create table, create sequence,
4
+ create procedure, create trigger, create view, create materialized view,
5
+ create database link, create synonym, create type, ctxapp TO oracle_enhanced;
6
+
7
+ CREATE USER oracle_enhanced_schema IDENTIFIED BY oracle_enhanced_schema;
8
+
9
+ GRANT unlimited tablespace, create session, create table, create sequence,
10
+ create procedure, create trigger, create view, create materialized view,
11
+ create database link, create synonym, create type, ctxapp TO oracle_enhanced_schema;
12
+
13
+ CREATE USER arunit IDENTIFIED BY arunit;
14
+
15
+ GRANT unlimited tablespace, create session, create table, create sequence,
16
+ create procedure, create trigger, create view, create materialized view,
17
+ create database link, create synonym, create type, ctxapp TO arunit;
18
+
19
+ CREATE USER arunit2 IDENTIFIED BY arunit2;
20
+
21
+ GRANT unlimited tablespace, create session, create table, create sequence,
22
+ create procedure, create trigger, create view, create materialized view,
23
+ create database link, create synonym, create type, ctxapp TO arunit2;
24
+
25
+ CREATE USER ruby IDENTIFIED BY oci8;
26
+ GRANT connect, resource, create view,create synonym TO ruby;
27
+ GRANT EXECUTE ON dbms_lock TO ruby;
28
+ GRANT CREATE VIEW TO ruby;
29
+ GRANT unlimited tablespace to ruby;
@@ -46,8 +46,8 @@ module ArelExtensions
46
46
  # String Functions
47
47
  it "should accept functions on strings" do
48
48
  c = @table[:name]
49
- compile(c + 'test').must_be_like %{"users"."name" || 'test'}
50
- compile(c + 'test' + ' chain').must_be_like %{"users"."name" || 'test' || ' chain'}
49
+ compile(c + 'test').must_be_like %{("users"."name" || 'test')}
50
+ compile(c + 'test' + ' chain').must_be_like %{("users"."name" || 'test' || ' chain')}
51
51
  compile(c.length).must_be_like %{LENGTH("users"."name")}
52
52
  compile(c.length.round + 42).must_be_like %{(ROUND(LENGTH("users"."name")) + 42)}
53
53
  compile(c.locate('test')).must_be_like %{LOCATE('test', "users"."name")}
@@ -7,7 +7,13 @@ module ArelExtensions
7
7
  class ListTest < Minitest::Test
8
8
  def setup_db
9
9
  ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
10
- ActiveRecord::Base.establish_connection(ENV['DB'].try(:to_sym) || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
10
+ if ENV['DB'] == 'oracle' && ((defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx") || (RUBY_PLATFORM == 'java')) # not supported
11
+ @env_db = (RUBY_PLATFORM == 'java' ? "jdbc-sqlite" : 'sqlite')
12
+ skip "Platform not supported"
13
+ else
14
+ @env_db = ENV['DB']
15
+ end
16
+ ActiveRecord::Base.establish_connection(@env_db.try(:to_sym) || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
11
17
  ActiveRecord::Base.default_timezone = :utc
12
18
  @cnx = ActiveRecord::Base.connection
13
19
  $sqlite ||= false
@@ -31,12 +37,12 @@ module ArelExtensions
31
37
  end
32
38
  end
33
39
  end
34
- if File.exist?("init/#{ENV['DB']}.sql")
35
- sql = File.read("init/#{ENV['DB']}.sql")
40
+ if File.exist?("init/#{@env_db}.sql")
41
+ sql = File.read("init/#{@env_db}.sql")
36
42
  @cnx.execute(sql) unless sql.blank?
37
43
  end
38
- @cnx.drop_table(:users) rescue nil
39
- @cnx.create_table :users do |t|
44
+ @cnx.drop_table(:user_tests) rescue nil
45
+ @cnx.create_table :user_tests do |t|
40
46
  t.column :age, :integer
41
47
  t.column :name, :string
42
48
  t.column :comments, :text
@@ -44,23 +50,19 @@ module ArelExtensions
44
50
  t.column :updated_at, :datetime
45
51
  t.column :score, :decimal, :precision => 20, :scale => 10
46
52
  end
47
- @cnx.drop_table(:products) rescue nil
48
- @cnx.create_table :products do |t|
53
+ @cnx.drop_table(:product_tests) rescue nil
54
+ @cnx.create_table :product_tests do |t|
49
55
  t.column :price, :decimal, :precision => 20, :scale => 10
50
56
  end
51
57
  end
52
58
 
53
- def teardown_db
54
- @cnx.drop_table(:users)
55
- @cnx.drop_table(:products)
56
- end
57
-
58
59
  class User < ActiveRecord::Base
60
+ self.table_name = 'user_tests'
59
61
  end
60
62
  class Product < ActiveRecord::Base
63
+ self.table_name = 'product_tests'
61
64
  end
62
65
 
63
-
64
66
  def setup
65
67
  d = Date.new(2016,05,23)
66
68
  setup_db
@@ -90,7 +92,8 @@ module ArelExtensions
90
92
  end
91
93
 
92
94
  def teardown
93
- teardown_db
95
+ @cnx.drop_table(:user_tests)
96
+ @cnx.drop_table(:product_tests)
94
97
  end
95
98
 
96
99
  def t(scope, node)
@@ -109,17 +112,15 @@ module ArelExtensions
109
112
  end
110
113
 
111
114
  def test_ceil
112
- if !$sqlite || !$load_extension_disabled
113
- assert_equal 1, t(@neg, @score.ceil)
114
- assert_equal 63, t(@arthur, @age.ceil + 42)
115
- end
115
+ skip "Sqlite version can't load extension for ceil" if $sqlite && $load_extension_disabled
116
+ assert_equal 1, t(@neg, @score.ceil)
117
+ assert_equal 63, t(@arthur, @age.ceil + 42)
116
118
  end
117
119
 
118
120
  def test_floor
119
- if !$sqlite || !$load_extension_disabled
120
- assert_equal 0, t(@neg, @score.floor)
121
- assert_equal 42, t(@arthur, @score.floor - 23)
122
- end
121
+ skip "Sqlite version can't load extension for floor" if $sqlite && $load_extension_disabled
122
+ assert_equal 0, t(@neg, @score.floor)
123
+ assert_equal 42, t(@arthur, @score.floor - 23)
123
124
  end
124
125
 
125
126
  def test_rand
@@ -146,8 +147,8 @@ module ArelExtensions
146
147
  def test_concat
147
148
  assert_equal 'Camille Camille', t(@camille, @name + ' ' + @name)
148
149
  assert_equal 'Laure 2', t(@laure, @name + ' ' + 2)
149
- if ENV['DB'] == 'postgresql'
150
- assert_equal "Lucas Sophie", t(User.reorder(nil).from(User.select(:name).where(:name => ['Lucas', 'Sophie']).reorder(:name).as('users')), @name.group_concat(' '))
150
+ if @env_db == 'postgresql'
151
+ assert_equal "Lucas Sophie", t(User.reorder(nil).from(User.select(:name).where(:name => ['Lucas', 'Sophie']).reorder(:name).as('user_tests')), @name.group_concat(' '))
151
152
  else
152
153
  assert_equal "Lucas Sophie", t(User.where(:name => ['Lucas', 'Sophie']).reorder(:name), @name.group_concat(' '))
153
154
  end
@@ -160,32 +161,35 @@ module ArelExtensions
160
161
  end
161
162
 
162
163
  def test_locate
163
- if !$sqlite || !$load_extension_disabled
164
- assert_equal 1, t(@camille, @name.locate("C"))
165
- assert_equal 0, t(@lucas, @name.locate("z"))
166
- assert_equal 5, t(@lucas, @name.locate("s"))
167
- end
164
+ skip "Sqlite version can't load extension for locate" if $sqlite && $load_extension_disabled
165
+ assert_equal 1, t(@camille, @name.locate("C"))
166
+ assert_equal 0, t(@lucas, @name.locate("z"))
167
+ assert_equal 5, t(@lucas, @name.locate("s"))
168
168
  end
169
169
 
170
170
  def test_find_in_set
171
- if !$sqlite || !$load_extension_disabled
172
- assert 4, t(@neg, @comments & 2)
173
- assert 2, t(@neg, @comments & 6)
174
- end
171
+ skip "Sqlite version can't load extension for find_in_set" if $sqlite && $load_extension_disabled
172
+ assert_equal 5, t(@neg, @comments & 2)
173
+ assert_equal 0, t(@neg, @comments & 6) # not found
175
174
  end
176
175
 
177
176
  def test_string_comparators
178
- assert 1, t(@neg, @name >= 'test')
179
- assert 1, t(@neg, @name <= @comments)
177
+ skip "Oracle can't use math operators to compare strings, any function to do that?" if @env_db == 'oracle'
178
+ if @env_db == 'postgresql' # may return real boolean
179
+ assert t(@neg, @name >= 'Mest') == true || t(@neg, @name >= 'Mest') == 't' # depends of ar version
180
+ assert t(@neg, @name <= (@name + 'Z')) == true || t(@neg, @name <= (@name + 'Z')) == 't'
181
+ else
182
+ assert_equal 1, t(@neg, @name >= 'Mest')
183
+ assert_equal 1, t(@neg, @name <= (@name + 'Z'))
184
+ end
180
185
  end
181
186
 
182
- def test_regexp_not_regex
183
- if !$sqlite || !$load_extension_disabled
184
- assert_equal 1, User.where(@name =~ '^M').count
185
- assert_equal 6, User.where(@name !~ '^L').count
186
- assert_equal 1, User.where(@name =~ /^M/).count
187
- assert_equal 6, User.where(@name !~ /^L/).count
188
- end
187
+ def test_regexp_not_regexp
188
+ skip "Sqlite version can't load extension for regexp" if $sqlite && $load_extension_disabled
189
+ assert_equal 1, User.where(@name =~ '^M').count
190
+ assert_equal 6, User.where(@name !~ '^L').count
191
+ assert_equal 1, User.where(@name =~ /^M/).count
192
+ assert_equal 6, User.where(@name !~ /^L/).count
189
193
  end
190
194
 
191
195
  def test_imatches
@@ -195,26 +199,27 @@ module ArelExtensions
195
199
  end
196
200
 
197
201
  def test_replace
198
- assert_equal "LucaX", t(@lucas, @name.replace("s","X"))
199
- assert_equal "replace", t(@lucas, @name.replace(@name,"replace"))
202
+ assert_equal "LucaX", t(@lucas, @name.replace("s", "X"))
203
+ assert_equal "replace", t(@lucas, @name.replace(@name, "replace"))
200
204
  end
201
205
 
202
206
  def test_soundex
203
- if (!$sqlite || !$load_extension_disabled) && (ENV['DB'] != 'postgresql')
204
- assert_equal "C540", t(@camille, @name.soundex)
205
- assert_equal 8, User.where(@name.soundex.eq(@name.soundex)).count
206
- end
207
+ skip "Sqlite version can't load extension for soundex" if $sqlite && $load_extension_disabled
208
+ skip "PostgreSql version can't load extension for soundex" if @env_db == 'postgresql'
209
+ assert_equal "C540", t(@camille, @name.soundex)
210
+ assert_equal 8, User.where(@name.soundex.eq(@name.soundex)).count
207
211
  end
208
212
 
209
213
  def test_trim
210
214
  assert_equal "Myun", t(@myung, @name.rtrim("g"))
211
215
  assert_equal "yung", t(@myung, @name.ltrim("M"))
212
216
  assert_equal "yung", t(@myung, (@name + "M").trim("M"))
217
+ skip "Oracle does not accept multi char trim" if @env_db == 'oracle'
213
218
  assert_equal "", t(@myung, @name.rtrim(@name))
214
219
  end
215
220
 
216
221
  def test_coalesce
217
- if ENV['DB'] == 'postgresql'
222
+ if @env_db == 'postgresql'
218
223
  assert_equal 100, t(@test, @age.coalesce(100))
219
224
  assert_equal "Camille", t(@camille, @name.coalesce(nil, "default"))
220
225
  assert_equal 20, t(@test, @age.coalesce(nil, 20))
@@ -224,7 +229,10 @@ module ArelExtensions
224
229
  end
225
230
  end
226
231
 
227
- def test_comparator
232
+
233
+
234
+ # Comparators
235
+ def test_number_comparator
228
236
  assert_equal 2, User.where(@age < 6).count
229
237
  assert_equal 2, User.where(@age <= 10).count
230
238
  assert_equal 3, User.where(@age > 20).count
@@ -232,27 +240,31 @@ module ArelExtensions
232
240
  assert_equal 1, User.where(@age > 5).where(@age < 20).count
233
241
  end
234
242
 
243
+ def test_date_comparator
244
+ d = Date.new(2016,05,23)
245
+ assert_equal 0, User.where(@created_at < d).count
246
+ assert_equal 8, User.where(@created_at >= d).count
247
+ end
248
+
235
249
  def test_date_duration
236
250
  #Year
237
- assert_equal 2016, @lucas.select((User.arel_table[:created_at].year).as("res")).first.res.to_i
251
+ assert_equal 2016, t(@lucas, @created_at.year).to_i
238
252
  assert_equal 0, User.where(@created_at.year.eq("2012")).count
239
253
  #Month
240
- assert_equal 5, @camille.select((User.arel_table[:created_at].month).as("res")).first.res.to_i
241
- assert_equal 8,User.where(User.arel_table[:created_at].month.eq("05")).count
254
+ assert_equal 5, t(@camille, @created_at.month).to_i
255
+ assert_equal 8, User.where(@created_at.month.eq("05")).count
242
256
  #Week
243
- assert_equal 21,User.where(User.arel_table[:name].eq("Arthur")).select((User.arel_table[:created_at].week).as("res")).first.res.to_i
244
- assert_equal 8,User.where(User.arel_table[:created_at].month.eq("05")).count
257
+ assert_equal 21, t(@arthur, @created_at.week).to_i
258
+ assert_equal 8, User.where(@created_at.month.eq("05")).count
245
259
  #Day
246
- assert_equal 23,User.where(User.arel_table[:name].eq("Laure")).select((User.arel_table[:created_at].day).as("res")).first.res.to_i
247
- assert_equal 0,User.where(User.arel_table[:created_at].day.eq("05")).count
260
+ assert_equal 23, t(@laure, @created_at.day).to_i
261
+ assert_equal 0, User.where(@created_at.day.eq("05")).count
248
262
  end
249
263
 
250
-
251
264
  def test_is_null
252
265
  assert_equal "Test", User.where(@age.is_null).select(@name).first.name
253
266
  end
254
267
 
255
-
256
268
  def test_math_plus
257
269
  d = Date.new(1997, 6, 15)
258
270
  #Concat String
@@ -261,7 +273,8 @@ module ArelExtensions
261
273
  assert_equal "Sophie1997-06-15", t(@sophie, @name + d)
262
274
  assert_equal "Sophie15", t(@sophie, @name + @age)
263
275
  assert_equal "SophieSophie", t(@sophie, @name + @name)
264
- assert_equal "Sophie2016-05-23", t(@sophie, @name + @created_at)
276
+ #FIXME: should work as expected in Oracle
277
+ assert_equal "Sophie2016-05-23", t(@sophie, @name + @created_at) unless @env_db == 'oracle'
265
278
  #concat Integer
266
279
  assert_equal 1, User.where((@age + 10).eq(33)).count
267
280
  assert_equal 1, User.where((@age + "1").eq(6)).count
@@ -276,8 +289,8 @@ module ArelExtensions
276
289
  def test_math_moins
277
290
  d = Date.new(2016,05,20)
278
291
  #Datediff
279
- assert_equal 8, User.where((User.arel_table[:created_at] - User.arel_table[:created_at]).eq(0)).count
280
- assert_equal 3, User.where(User.arel_table[:name].eq("Laure")).select((User.arel_table[:created_at] - d).as("res")).first.res.abs.to_i
292
+ assert_equal 8, User.where((@created_at - User.arel_table[:created_at]).eq(0)).count
293
+ assert_equal 3, @laure.select((User.arel_table[:created_at] - d).as("res")).first.res.abs.to_i
281
294
  #Substraction
282
295
  assert_equal 0, User.where((@age - 10).eq(50)).count
283
296
  assert_equal 0, User.where((@age - "10").eq(50)).count
@@ -285,7 +298,7 @@ module ArelExtensions
285
298
 
286
299
  def test_wday
287
300
  d = Date.new(2016, 6, 26)
288
- assert_equal 1, t(@myung, @created_at.wday).to_i
301
+ assert_equal (@env_db == 'oracle' ? 2 : 1), t(@myung, @created_at.wday).to_i # monday
289
302
  assert_equal 0, User.select(d.wday).as("res").first.to_i
290
303
  end
291
304
 
@@ -7,16 +7,22 @@ module ArelExtensions
7
7
  class InsertManagerTest < Minitest::Test
8
8
  def setup_db
9
9
  ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
10
- ActiveRecord::Base.establish_connection(ENV['DB'].try(:to_sym) || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
10
+ if ENV['DB'] == 'oracle' && ((defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx") || (RUBY_PLATFORM == 'java')) # not supported
11
+ @env_db = (RUBY_PLATFORM == 'java' ? "jdbc-sqlite" : 'sqlite')
12
+ skip "Platform not supported"
13
+ else
14
+ @env_db = ENV['DB']
15
+ end
16
+ ActiveRecord::Base.establish_connection(@env_db.try(:to_sym) || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
11
17
  ActiveRecord::Base.default_timezone = :utc
12
18
  @cnx = ActiveRecord::Base.connection
13
19
  Arel::Table.engine = ActiveRecord::Base
14
- if File.exist?("init/#{ENV['DB']}.sql")
15
- sql = File.read("init/#{ENV['DB']}.sql")
20
+ if File.exist?("init/#{@env_db}.sql")
21
+ sql = File.read("init/#{@env_db}.sql")
16
22
  @cnx.execute(sql) unless sql.blank?
17
23
  end
18
- @cnx.drop_table(:users) rescue nil
19
- @cnx.create_table :users do |t|
24
+ @cnx.drop_table(:user_tests) rescue nil
25
+ @cnx.create_table :user_tests do |t|
20
26
  t.column :age, :integer
21
27
  t.column :name, :string
22
28
  t.column :comments, :text
@@ -28,7 +34,7 @@ module ArelExtensions
28
34
 
29
35
  def setup
30
36
  setup_db
31
- @table = Arel::Table.new(:users)
37
+ @table = Arel::Table.new(:user_tests)
32
38
  @cols = ['id', 'name', 'comments', 'created_at']
33
39
  @data = [
34
40
  [23, 'nom1', "sdfdsfdsfsdfsd fdsf dsf dsf sdf afdg fsdg sg sd gsdfg e 54435 344", '2016-01-01'],
@@ -37,7 +43,7 @@ module ArelExtensions
37
43
  end
38
44
 
39
45
  def teardown
40
- @cnx.drop_table(:users)
46
+ @cnx.drop_table(:user_tests)
41
47
  end
42
48
 
43
49
  # Math Functions