require 'spec_helper'
require 'spatial_adapter/postgresql'
require 'db/postgis_raw'
require 'models/common'

describe "Modified PostgreSQLAdapter" do
  before :each do
    postgis_connection
    @connection = ActiveRecord::Base.connection
  end

  describe '#postgis_version' do
    it 'should report a version number if PostGIS is installed' do
      @connection.should_receive(:select_value).with('SELECT postgis_full_version()').and_return('POSTGIS="1.5.0" GEOS="3.2.0-CAPI-1.6.0" PROJ="Rel. 4.7.1, 23 September 2009" LIBXML="2.7.6" USE_STATS')
      @connection.postgis_version.should_not be_nil
    end

    it 'should report nil if PostGIS is not installed' do
      @connection.should_receive(:select_value).with('SELECT postgis_full_version()').and_raise(ActiveRecord::StatementInvalid)
      @connection.postgis_version.should be_nil
    end
  end

  describe '#postgis_major_version' do
    it 'should be the first component of the version number' do
      @connection.stub!(:postgis_version).and_return('1.5.0')
      @connection.postgis_major_version.should == 1
    end

    it 'should be nil if PostGIS is not installed' do
      @connection.stub!(:postgis_version).and_return(nil)
      @connection.postgis_major_version.should be_nil
    end
  end

  describe '#postgis_minor_version' do
    it 'should be the second component of the version number' do
      @connection.stub!(:postgis_version).and_return('1.5.0')
      @connection.postgis_minor_version.should == 5
    end

    it 'should be nil if PostGIS is not installed' do
      @connection.stub!(:postgis_version).and_return(nil)
      @connection.postgis_minor_version.should be_nil
    end
  end

  describe '#spatial?' do
    it 'should be true if PostGIS is installed' do
      @connection.should_receive(:select_value).with('SELECT postgis_full_version()').and_return('POSTGIS="1.5.0" GEOS="3.2.0-CAPI-1.6.0" PROJ="Rel. 4.7.1, 23 September 2009" LIBXML="2.7.6" USE_STATS')
      @connection.should be_spatial
    end

    it 'should be false if PostGIS is not installed' do
      @connection.should_receive(:select_value).with('SELECT postgis_full_version()').and_raise(ActiveRecord::StatementInvalid)
      @connection.should_not be_spatial
    end
  end

  describe '#supports_geographic?' do
    it "should be true for PostGIS version 1.5.0" do
      @connection.stub!(:postgis_version).and_return('1.5.0')
      @connection.supports_geographic?.should == true
    end

    it "should be true for PostGIS newer than 1.5.0" do
      @connection.stub!(:postgis_version).and_return('1.5.1')
      @connection.supports_geographic?.should == true
    end

    it "should be true for PostGIS older than 1.5.0" do
      @connection.stub!(:postgis_version).and_return('1.4.0')
      @connection.supports_geographic?.should == false
    end
  end

  describe "#columns" do
    describe "type" do
      it "should be a regular SpatialPostgreSQLColumn if column is a geometry data type" do
        column = PointModel.columns.select{|c| c.name == 'geom'}.first
        column.should be_a(ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn)
        column.geometry_type.should == :point
        column.should_not be_geographic
      end

      it "should be a geographic SpatialPostgreSQLColumn if column is a geography data type" do
        column = GeographyPointModel.columns.select{|c| c.name == 'geom'}.first
        column.should be_a(ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn)
        column.geometry_type.should == :point
        column.should be_geographic
      end

      it "should be PostgreSQLColumn if column is not a spatial data type" do
        PointModel.columns.select{|c| c.name == 'extra'}.first.should be_a(ActiveRecord::ConnectionAdapters::PostgreSQLColumn)
      end
    end

    describe "@geometry_type" do
      it "should be :point for geometry columns restricted to POINT types" do
        PointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :point
      end

      it "should be :line_string for geometry columns restricted to LINESTRING types" do
        LineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :line_string
      end

      it "should be :polygon for geometry columns restricted to POLYGON types" do
        PolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :polygon
      end

      it "should be :multi_point for geometry columns restricted to MULTIPOINT types" do
        MultiPointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_point
      end

      it "should be :multi_line_string for geometry columns restricted to MULTILINESTRING types" do
        MultiLineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_line_string
      end

      it "should be :multi_polygon for geometry columns restricted to MULTIPOLYGON types" do
        MultiPolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_polygon
      end

      it "should be :geometry_collection for geometry columns restricted to GEOMETRYCOLLECTION types" do
        GeometryCollectionModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry_collection
      end

      it "should be :geometry for geometry columns not restricted to a type" do
        GeometryModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry
      end

      it "should be :point for geography columns restricted to POINT types" do
        GeographyPointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :point
      end

      it "should be :line_string for geography columns restricted to LINESTRING types" do
        GeographyLineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :line_string
      end

      it "should be :polygon for geography columns restricted to POLYGON types" do
        GeographyPolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :polygon
      end

      it "should be :multi_point for geography columns restricted to MULTIPOINT types" do
        GeographyMultiPointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_point
      end

      it "should be :multi_line_string for geography columns restricted to MULTILINESTRING types" do
        GeographyMultiLineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_line_string
      end

      it "should be :multi_polygon for geography columns restricted to MULTIPOLYGON types" do
        GeographyMultiPolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_polygon
      end

      it "should be :geometry_collection for geography columns restricted to GEOMETRYCOLLECTION types" do
        GeographyGeometryCollectionModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry_collection
      end

      it "should be :geometry for geography columns not restricted to a type" do
        GeographyModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry
      end
    end
  end

  describe "#indexes" do
    before :each do
      @indexes = @connection.indexes('point_models')
    end

    it "should return an IndexDefinition for each index on the table" do
      @indexes.should have(2).items
      @indexes.each do |i|
        i.should be_a(ActiveRecord::ConnectionAdapters::IndexDefinition)
      end
    end

    it "should indicate the correct columns in the index" do
      @indexes.select{|i| i.name == 'index_point_models_on_geom'}.first.columns.should == ['geom']
      @indexes.select{|i| i.name == 'index_point_models_on_extra'}.first.columns.should == ['extra', 'more_extra']
    end

    it "should be marked as spatial if a GiST index on a geometry column" do
      @indexes.select{|i| i.name == 'index_point_models_on_geom'}.first.spatial.should == true
    end

    it "should be marked as spatial if a GiST index on a geography column" do
      @indexes = @connection.indexes('geography_point_models')
      @indexes.select{|i| i.name == 'index_geography_point_models_on_geom'}.first.spatial.should == true
    end

    it "should not be marked as spatial if not a GiST index" do
      @indexes.select{|i| i.name == 'index_point_models_on_extra'}.first.spatial.should == false
    end

    it "should not be marked as spatial if a GiST index on a non-geometry column" do
      @connection.execute(<<-SQL)
        create table non_spatial_models
        (
          id serial primary key,
          location point,
          extra varchar(255)
        );
        create index index_non_spatial_models_on_location on non_spatial_models using gist (box(location, location));
      SQL
      @indexes = @connection.indexes('non_spatial_models')
      @indexes.select{|i| i.name == 'index_non_spatial_models_on_location'}.first.spatial.should == false
      @connection.execute 'drop table non_spatial_models'
    end
  end

  describe "#add_index" do
    it "should create a spatial index given :spatial => true" do
      @connection.should_receive(:execute).with(/using gist/i)
      @connection.add_index('geometry_models', 'geom', :spatial => true)
    end

    it "should not create a spatial index unless specified" do
      @connection.should_not_receive(:execute).with(/using gist/i)
      @connection.add_index('geometry_models', 'extra')
    end
  end
end