# frozen_string_literal: true
require_relative "spec_helper"

Sequel.extension :pg_array, :pg_array_ops, :pg_hstore, :pg_hstore_ops

describe "Sequel::Postgres::ArrayOp" do
  before do
    @db = Sequel.connect('mock://postgres')
    @db.extend_datasets{def quote_identifiers?; false end}
    @a = Sequel.pg_array_op(:a)
  end

  it "should support the standard mathematical operators" do
    @db.literal(@a < @a).must_equal "(a < a)"
    @db.literal(@a <= @a).must_equal "(a <= a)"
    @db.literal(@a > @a).must_equal "(a > a)"
    @db.literal(@a >= @a).must_equal "(a >= a)"
  end

  it "#[] should support subscript access" do
    @db.literal(@a[1]).must_equal "(a)[1]"
    @db.literal(@a[1][2]).must_equal "(a)[1][2]"
  end

  it "#[] with a range should return an ArrayOp" do
    @db.literal(@a[1..2].any).must_equal "ANY((a)[1:2])"
  end

  it "#any should use the ANY method" do
    @db.literal(1=>@a.any).must_equal "(1 = ANY(a))"
  end

  it "#all should use the ALL method" do
    @db.literal(1=>@a.all).must_equal "(1 = ALL(a))"
  end

  it "#contains should use the @> operator" do
    @db.literal(@a.contains(:b)).must_equal "(a @> b)"
  end

  it "#contained_by should use the <@ operator" do
    @db.literal(@a.contained_by(:b)).must_equal "(a <@ b)"
  end

  it "#overlaps should use the && operator" do
    @db.literal(@a.overlaps(:b)).must_equal "(a && b)"
  end

  it "#push/concat should use the || operator in append mode" do
    @db.literal(@a.push(:b)).must_equal "(a || b)"
    @db.literal(@a.concat(:b)).must_equal "(a || b)"
  end

  it "#remove should remove the element from the array" do
    @db.literal(@a.remove(1)).must_equal "array_remove(a, 1)"
    @db.literal(@a.remove(1)[2]).must_equal "(array_remove(a, 1))[2]"
  end

  it "#remove should replace the element in the array with another" do
    @db.literal(@a.replace(1, 2)).must_equal "array_replace(a, 1, 2)"
    @db.literal(@a.replace(1, 2)[3]).must_equal "(array_replace(a, 1, 2))[3]"
  end

  it "#reverse should call the array_reverse function" do
    @db.literal(@a.reverse).must_equal "array_reverse(a)"
  end

  it "#sort should call the array_sort function" do
    @db.literal(@a.sort).must_equal "array_sort(a)"
  end

  it "#sort should respect the :desc option" do
    @db.literal(@a.sort(desc: true)).must_equal "array_sort(a, true)"
  end

  it "#sort should respect the nulls: :first option" do
    @db.literal(@a.sort(nulls: :first)).must_equal "array_sort(a, false, true)"
  end

  it "#sort should respect the desc: true, nulls: :last options" do
    @db.literal(@a.sort(desc: true, nulls: :last)).must_equal "array_sort(a, true, false)"
  end

  it "#sort should call the array_sort function" do
    @db.literal(@a.sort).must_equal "array_sort(a)"
  end

  it "#unshift should use the || operator in prepend mode" do
    @db.literal(@a.unshift(:b)).must_equal "(b || a)"
  end

  it "#cardinality should use the cardinality function" do
    @db.literal(@a.cardinality).must_equal "cardinality(a)"
  end

  it "#dims should use the array_dims function" do
    @db.literal(@a.dims).must_equal "array_dims(a)"
  end

  it "#length should use the array_length function" do
    @db.literal(@a.length).must_equal "array_length(a, 1)"
    @db.literal(@a.length(2)).must_equal "array_length(a, 2)"
  end

  it "#length should use the array_lower function" do
    @db.literal(@a.lower).must_equal "array_lower(a, 1)"
    @db.literal(@a.lower(2)).must_equal "array_lower(a, 2)"
  end

  it "#to_string/join should use the array_to_string function" do
    @db.literal(@a.to_string).must_equal "array_to_string(a, '')"
    @db.literal(@a.join).must_equal "array_to_string(a, '')"
    @db.literal(@a.join(':')).must_equal "array_to_string(a, ':')"
    @db.literal(@a.join(':', '*')).must_equal "array_to_string(a, ':', '*')"
  end

  it "#hstore should convert the item to an hstore using the hstore function" do
    @db.literal(@a.hstore).must_equal "hstore(a)"
    @db.literal(@a.hstore['a']).must_equal "(hstore(a) -> 'a')"
    @db.literal(@a.hstore(:b)).must_equal "hstore(a, b)"
    @db.literal(@a.hstore(:b)['a']).must_equal "(hstore(a, b) -> 'a')"
    @db.literal(@a.hstore(%w'1')).must_equal "hstore(a, ARRAY['1'])"
    @db.literal(@a.hstore(%w'1')['a']).must_equal "(hstore(a, ARRAY['1']) -> 'a')"
  end

  it "#unnest should use the unnest function" do
    @db.literal(@a.unnest).must_equal "unnest(a)"
    @db.literal(@a.unnest(:b, :c)).must_equal "unnest(a, b, c)"
    @db.literal(@a.unnest([1])).must_equal "unnest(a, ARRAY[1])"
  end

  it "#pg_array should return self" do
    @a.pg_array.must_be_same_as(@a)
  end

  it "Sequel.pg_array_op should return arg for ArrayOp" do
    Sequel.pg_array_op(@a).must_be_same_as(@a)
  end

  it "should be able to turn expressions into array ops using pg_array" do
    @db.literal(Sequel.qualify(:b, :a).pg_array.push(3)).must_equal "(b.a || 3)"
    @db.literal(Sequel.function(:a, :b).pg_array.push(3)).must_equal "(a(b) || 3)"
  end

  it "should be able to turn literal strings into array ops using pg_array" do
    @db.literal(Sequel.lit('a').pg_array.unnest).must_equal "unnest(a)"
  end

  it "should be able to turn symbols into array ops using Sequel.pg_array_op" do
    @db.literal(Sequel.pg_array_op(:a).unnest).must_equal "unnest(a)"
  end

  it "should be able to turn symbols into array ops using Sequel.pg_array" do
    @db.literal(Sequel.pg_array(:a).unnest).must_equal "unnest(a)"
  end

  it "should allow transforming PGArray instances into ArrayOp instances" do
    @db.literal(Sequel.pg_array([1,2]).op.push(3)).must_equal "(ARRAY[1,2] || 3)"
  end

  it "should wrap array arguments in PGArrays" do
    @db.literal(@a.contains([1, 2])).must_equal "(a @> ARRAY[1,2])"
    @db.literal(@a.contained_by([1, 2])).must_equal "(a <@ ARRAY[1,2])"
    @db.literal(@a.overlaps([1, 2])).must_equal "(a && ARRAY[1,2])"
    @db.literal(@a.push([1, 2])).must_equal "(a || ARRAY[1,2])"
    @db.literal(@a.concat([1, 2])).must_equal "(a || ARRAY[1,2])"
    @db.literal(@a.unshift([1, 2])).must_equal "(ARRAY[1,2] || a)"
  end
end
