require 'spec_helper'
require 'json'

def fingerprint(qstr)
  q = PgQuery.parse(qstr)
  q.fingerprint
end

class FingerprintTestHash
  attr_reader :parts

  def initialize
    @parts = []
  end

  def update(part)
    @parts << part
  end
end

def fingerprint_parts(qstr)
  hash = FingerprintTestHash.new
  q = PgQuery.parse(qstr)
  q.send(:fingerprint_tree, hash)
  hash.parts
end

def fingerprint_defs
  @fingerprint_defs ||= JSON.parse File.read(File.join(__dir__, '../files/fingerprint.json'))
end

describe PgQuery, "#fingerprint" do
  fingerprint_defs.each do |testdef|
    it format("returns expected hash parts for '%s'", testdef['input']) do
      expect(fingerprint_parts(testdef['input'])).to eq(testdef['expectedParts'])
    end

    it format("returns expected hash value for '%s'", testdef['input']) do
      expect(fingerprint(testdef['input'])).to eq(testdef['expectedHash'])
    end
  end

  it "works for basic cases" do
    expect(fingerprint("SELECT 1")).to eq fingerprint("SELECT 2")
    expect(fingerprint("SELECT  1")).to eq fingerprint("SELECT 2")
    expect(fingerprint("SELECT A")).to eq fingerprint("SELECT a")
    expect(fingerprint("SELECT \"a\"")).to eq fingerprint("SELECT a")
    expect(fingerprint("  SELECT 1;")).to eq fingerprint("SELECT 2")
    expect(fingerprint("  ")).to eq fingerprint("")
    expect(fingerprint("--comment")).to eq fingerprint("")

    # Test uniqueness
    expect(fingerprint("SELECT a")).not_to eq fingerprint("SELECT b")
    expect(fingerprint("SELECT \"A\"")).not_to eq fingerprint("SELECT a")
    expect(fingerprint("SELECT * FROM a")).not_to eq fingerprint("SELECT * FROM b")
  end

  it "works for multi-statement queries" do
    expect(fingerprint("SET x=$1; SELECT A")).to eq fingerprint("SET x=$1; SELECT a")
    expect(fingerprint("SET x=$1; SELECT A")).not_to eq fingerprint("SELECT a")
  end

  it "ignores aliases" do
    expect(fingerprint("SELECT a AS b")).to eq fingerprint("SELECT a AS c")
    expect(fingerprint("SELECT a")).to eq fingerprint("SELECT a AS c")
    expect(fingerprint("SELECT * FROM a AS b")).to eq fingerprint("SELECT * FROM a AS c")
    expect(fingerprint("SELECT * FROM a")).to eq fingerprint("SELECT * FROM a AS c")
    expect(fingerprint("SELECT * FROM (SELECT * FROM x AS y) AS a")).to eq fingerprint("SELECT * FROM (SELECT * FROM x AS z) AS b")
    expect(fingerprint("SELECT a AS b UNION SELECT x AS y")).to eq fingerprint("SELECT a AS c UNION SELECT x AS z")
  end

  it "ignores param references" do
    expect(fingerprint("SELECT $1")).to eq fingerprint("SELECT $2")
  end

  it "ignores SELECT target list ordering" do
    expect(fingerprint("SELECT a, b FROM x")).to eq fingerprint("SELECT b, a FROM x")
    expect(fingerprint("SELECT $1, b FROM x")).to eq fingerprint("SELECT b, $1 FROM x")
    expect(fingerprint("SELECT $1, $2, b FROM x")).to eq fingerprint("SELECT $1, b, $2 FROM x")

    # Test uniqueness
    expect(fingerprint("SELECT a, c FROM x")).not_to eq fingerprint("SELECT b, a FROM x")
    expect(fingerprint("SELECT b FROM x")).not_to eq fingerprint("SELECT b, a FROM x")
  end

  it "ignores INSERT cols ordering" do
    expect(fingerprint("INSERT INTO test (a, b) VALUES ($1, $2)")).to eq fingerprint("INSERT INTO test (b, a) VALUES ($1, $2)")

    # Test uniqueness
    expect(fingerprint("INSERT INTO test (a, c) VALUES ($1, $2)")).not_to eq fingerprint("INSERT INTO test (b, a) VALUES ($1, $2)")
    expect(fingerprint("INSERT INTO test (b) VALUES ($1, $2)")).not_to eq fingerprint("INSERT INTO test (b, a) VALUES ($1, $2)")
  end

  it 'ignores IN list size (simple)' do
    q1 = 'SELECT * FROM x WHERE y IN ($1, $2, $3)'
    q2 = 'SELECT * FROM x WHERE y IN ($1)'
    expect(fingerprint(q1)).to eq fingerprint(q2)
  end

  it 'ignores IN list size (complex)' do
    q1 = 'SELECT * FROM x WHERE y IN ( $1::uuid, $2::uuid, $3::uuid )'
    q2 = 'SELECT * FROM x WHERE y IN ( $1::uuid )'
    expect(fingerprint(q1)).to eq fingerprint(q2)
  end
end
