From bfc0f06e94730831bc092359446022c080588dd8 Mon Sep 17 00:00:00 2001
From: Steve Jorgensen <stevej@stevej.name>
Date: Sun, 26 Jun 2016 03:51:31 -0700
Subject: [PATCH] Use SQLite Ruby math extensions if present

---
 Gemfile                              |  1 +
 lib/geocoder/stores/active_record.rb | 22 +++++++++++++++++-----
 test/test_helper.rb                  |  5 +++++
 test/unit/near_test.rb               | 24 ++++++++++++++----------
 4 files changed, 37 insertions(+), 15 deletions(-)

diff --git a/Gemfile b/Gemfile
index 288a90ca..ad4ed6a9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -24,6 +24,7 @@ end
 
 group :test do
   gem 'sqlite3', :platform => [:ruby, :mswin, :mingw]
+  gem 'sqlite_ext', '~> 1.4.0', :platform => [:ruby, :mswin, :mingw]
   gem 'webmock'
 
   platforms :ruby do
diff --git a/lib/geocoder/stores/active_record.rb b/lib/geocoder/stores/active_record.rb
index 771d5f89..40a0108b 100644
--- a/lib/geocoder/stores/active_record.rb
+++ b/lib/geocoder/stores/active_record.rb
@@ -138,7 +138,7 @@ module Geocoder::Store
         ]
         bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
 
-        if using_sqlite?
+        if using_unextended_sqlite?
           conditions = bounding_box_conditions
         else
           min_radius = options.fetch(:min_radius, 0).to_f
@@ -160,7 +160,7 @@ module Geocoder::Store
       # capabilities (trig functions?).
       #
       def distance_sql(latitude, longitude, options = {})
-        method_prefix = using_sqlite? ? "approx" : "full"
+        method_prefix = using_unextended_sqlite? ? "approx" : "full"
         Geocoder::Sql.send(
           method_prefix + "_distance",
           latitude, longitude,
@@ -179,7 +179,7 @@ module Geocoder::Store
           options[:bearing] = Geocoder.config.distances
         end
         if options[:bearing]
-          method_prefix = using_sqlite? ? "approx" : "full"
+          method_prefix = using_unextended_sqlite? ? "approx" : "full"
           Geocoder::Sql.send(
             method_prefix + "_bearing",
             latitude, longitude,
@@ -225,8 +225,20 @@ module Geocoder::Store
         conditions
       end
 
+      def using_unextended_sqlite?
+        using_sqlite? && !using_sqlite_with_extensions?
+      end
+
       def using_sqlite?
-        connection.adapter_name.match(/sqlite/i)
+        !!connection.adapter_name.match(/sqlite/i)
+      end
+
+      def using_sqlite_with_extensions?
+        connection.adapter_name.match(/sqlite/i) &&
+          defined?(::SqliteExt) &&
+          %W(POWER SQRT PI SIN COS ASIN ATAN2).all?{ |fn_name|
+            connection.raw_connection.function_created?(fn_name)
+          }
       end
 
       def using_postgres?
@@ -244,7 +256,7 @@ module Geocoder::Store
       # Value which can be passed to where() to produce no results.
       #
       def false_condition
-        using_sqlite? ? 0 : "false"
+        using_unextended_sqlite? ? 0 : "false"
       end
 
       ##
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 89aa2bef..dda4330b 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -15,6 +15,11 @@ if configs.keys.include? ENV['DB']
   ActiveRecord::Base.configurations = configs
 
   db_name = ENV['DB']
+  if db_name == 'sqlite' && ENV['USE_SQLITE_EXT'] == '1' then
+    gem 'sqlite_ext'
+    require 'sqlite_ext'
+    SqliteExt.register_ruby_math
+  end
   ActiveRecord::Base.establish_connection(db_name)
   ActiveRecord::Base.default_timezone = :utc
 
diff --git a/test/unit/near_test.rb b/test/unit/near_test.rb
index 3cd9ae36..1e82c94c 100644
--- a/test/unit/near_test.rb
+++ b/test/unit/near_test.rb
@@ -3,23 +3,23 @@ require 'test_helper'
 
 class NearTest < GeocoderTestCase
 
-  def test_near_scope_options_without_sqlite_includes_bounding_box_condition
-    omit("Not applicable to SQLite") if ENV['DB'] == 'sqlite'
+  def test_near_scope_options_without_raw_sqlite_includes_bounding_box_condition
+    omit("Not applicable to unextended SQLite") if using_raw_sqlite?
 
     result = PlaceWithCustomResultsHandling.send(:near_scope_options, 1.0, 2.0, 5)
     table_name = PlaceWithCustomResultsHandling.table_name
     assert_match(/#{table_name}.latitude BETWEEN 0.9276\d* AND 1.0723\d* AND #{table_name}.longitude BETWEEN 1.9276\d* AND 2.0723\d* AND /, result[:conditions][0])
   end
 
-  def test_near_scope_options_without_sqlite_includes_radius_condition
-    omit("Not applicable to SQLite") if ENV['DB'] == 'sqlite'
+  def test_near_scope_options_without_raw_sqlite_includes_radius_condition
+    omit("Not applicable to unextended SQLite") if using_raw_sqlite?
 
     result = Place.send(:near_scope_options, 1.0, 2.0, 5)
     assert_match(/BETWEEN \? AND \?$/, result[:conditions][0])
   end
 
-  def test_near_scope_options_without_sqlite_includes_radius_default_min_radius
-    omit("Not applicable to SQLite") if ENV['DB'] == 'sqlite'
+  def test_near_scope_options_without_raw_sqlite_includes_radius_default_min_radius
+    omit("Not applicable to unextended SQLite") if using_raw_sqlite?
 
     result = Place.send(:near_scope_options, 1.0, 2.0, 5)
 
@@ -27,8 +27,8 @@ class NearTest < GeocoderTestCase
     assert_equal(5, result[:conditions][2])
   end
 
-  def test_near_scope_options_without_sqlite_includes_radius_custom_min_radius
-    omit("Not applicable to SQLite") if ENV['DB'] == 'sqlite'
+  def test_near_scope_options_without_raw_sqlite_includes_radius_custom_min_radius
+    omit("Not applicable to unextended SQLite") if using_raw_sqlite?
 
     result = Place.send(:near_scope_options, 1.0, 2.0, 5, :min_radius => 3)
 
@@ -36,8 +36,8 @@ class NearTest < GeocoderTestCase
     assert_equal(5, result[:conditions][2])
   end
 
-  def test_near_scope_options_without_sqlite_includes_radius_bogus_min_radius
-    omit("Not applicable to SQLite") if ENV['DB'] == 'sqlite'
+  def test_near_scope_options_without_raw_sqlite_includes_radius_bogus_min_radius
+    omit("Not applicable to unextended SQLite") if using_raw_sqlite?
     
     result = Place.send(:near_scope_options, 1.0, 2.0, 5, :min_radius => 'bogus')
 
@@ -94,4 +94,8 @@ class NearTest < GeocoderTestCase
   def assert_no_consecutive_comma(string)
     assert_no_match(/, *,/, string, "two consecutive commas")
   end
+
+  def using_raw_sqlite?
+    ENV['DB'] == 'sqlite' and ENV['USE_SQLITE_EXT'] != '1'
+  end
 end
-- 
GitLab