# 2024-04-26
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# 
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix bestindexC

ifcapable !vtab {
  finish_test
  return
}

register_tcl_module db

proc vtab_command {lVal method args} {
  switch -- $method {
    xConnect {
      return "CREATE TABLE t1(a)"
    }

    xBestIndex {
      set hdl [lindex $args 0]
      set clist [$hdl constraints]
      set orderby [$hdl orderby]

      set idxstr [list]
      set res [list]

      set idx 0
      foreach c $clist {
        array set a $c
        if {$a(usable)==0} continue
        if {$a(op)=="limit" && ![info exists ::do_not_use_limit]} { 
          lappend idxstr limit
          lappend res omit $idx
        }
        if {$a(op)=="offset" && ![info exists ::do_not_use_offset]} { 
          lappend idxstr offset
          lappend res omit $idx
        }
        incr idx
      }

      return "cost 1000000 rows 1000000 idxnum 0 idxstr {$idxstr} $res"
    }

    xFilter {
      set idxstr [lindex $args 1]
      set LIMIT ""
      foreach a $idxstr b [lindex $args 2] {
        set x($a) $b
      }

      if {![info exists x(limit)]} { set x(limit) -1 }
      if {![info exists x(offset)]} { set x(offset) -1 }
      set LIMIT " LIMIT $x(limit) OFFSET $x(offset)"

      set idx 1
      foreach v $lVal {
        lappend lRow "($idx, '$v')"
        incr idx
      }

      return [list sql "
        SELECT * FROM ( VALUES [join $lRow ,]) $LIMIT
      "]
    }
  }

  return {}
}

do_execsql_test 1.0 {
  CREATE VIRTUAL TABLE x1 USING tcl(vtab_command "a b c d e f");
  CREATE VIRTUAL TABLE x2 USING tcl(vtab_command "A B C D E F a b");
} {}

do_execsql_test 1.1 {
  CREATE TEMP TABLE t_unionall AS 
    SELECT * FROM x1 UNION ALL SELECT * FROM x2;

  CREATE TEMP TABLE t_intersect AS 
    SELECT * FROM x1 INTERSECT SELECT * FROM x2;

  CREATE TEMP TABLE t_union AS 
    SELECT * FROM x1 UNION SELECT * FROM x2;

  CREATE TEMP TABLE t_except AS 
    SELECT * FROM x1 EXCEPT SELECT * FROM x2;
}

foreach {tn limit} {
  1 "LIMIT 8" 
  2 "LIMIT 4" 
  3 "LIMIT 4 OFFSET 2" 
  4 "LIMIT 8 OFFSET 4" 
} {

  foreach {op tbl} {
    "UNION ALL" t_unionall
    "UNION"     t_union
    "INTERSECT" t_intersect
    "EXCEPT"    t_except
  } {

    set expect [execsql "SELECT * FROM $tbl $limit"]
    do_execsql_test 1.2.$tbl.$tn "SELECT * FROM (
      SELECT * FROM x1 $op SELECT * FROM x2
    ) $limit" $expect

  }

}

#-------------------------------------------------------------------------
reset_db
register_tcl_module db

do_execsql_test 2.0 {
  CREATE VIRTUAL TABLE x1 USING tcl(vtab_command "a b c d e f");
  CREATE VIRTUAL TABLE x2 USING tcl(vtab_command "a b e f");
} {}

do_execsql_test 2.1 {
  SELECT * FROM x1 
    EXCEPT
  SELECT * FROM x2
  LIMIT 3
} {c d}

#-------------------------------------------------------------------------
reset_db
register_tcl_module db
do_execsql_test 3.0 {
  CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "1 2 3 4 5 6 7 8 9 10");
} {}

do_execsql_test 3.1 {
  SELECT * FROM y1 WHERE a = COALESCE('8', a) LIMIT 3
} {8}

do_execsql_test 3.2 {
  SELECT * FROM y1 WHERE a = '2' LIMIT 3
} {2}

load_static_extension db series
do_execsql_test 3.3 {
  SELECT * FROM generate_series(1, 5) WHERE value = (value & 14) LIMIT 3
} {2 4}

do_execsql_test 3.4 {
  SELECT value FROM generate_series(1,10) WHERE value>2 LIMIT 4 OFFSET 1;
} {4 5 6 7}

set ::do_not_use_limit 1
do_execsql_test 3.5 {
  SELECT * FROM y1 LIMIT 5 OFFSET 3
} {4 5 6 7 8}
unset ::do_not_use_limit
set ::do_not_use_offset 1
do_execsql_test 3.6 {
  SELECT * FROM y1 LIMIT 5 OFFSET 3
} {4 5 6 7 8}
unset ::do_not_use_offset

#-------------------------------------------------------------------------
reset_db
proc vtab_command {lVal method args} {
  switch -- $method {
    xConnect { error "not happy!" }
  }

  return {}
}

register_tcl_module db
do_catchsql_test 4.0 {
  CREATE VIRTUAL TABLE y1 USING tcl(vtab_command 1);
} {1 {not happy!}}
do_test 4.1 {
  sqlite3_errcode db
} SQLITE_ERROR

proc vtab_command {lVal method args} {
  switch -- $method {
    xConnect {
      return $lVal
    }
  }
  return {}
}

do_catchsql_test 4.2 {
  CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "PRAGMA page_size=1024");
} {1 {declare_vtab: syntax error}}
do_catchsql_test 4.3 {
  CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "CREATE TABLE x1(");
} {1 {declare_vtab: incomplete input}}
do_catchsql_test 4.4 {
  CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "CREATE TABLE x1(insert)");
} {1 {declare_vtab: near "insert": syntax error}}

#-------------------------------------------------------------------------
reset_db
register_tcl_module db

proc quote {str} {
  return "'[string map {' ''} $str]'"
}

proc vtab_command {lVal method args} {
  switch -- $method {
    xConnect {
      return "CREATE TABLE t1(a, b, c, d)"
    }

    xBestIndex {
      set hdl [lindex $args 0]
      set clist [$hdl constraints]

      set res [list]
      set idx 0
      set idxnum 0

      set cols(0) a
      set cols(1) b
      set cols(2) c

      set lCons [list]

      foreach c $clist {
        array set a $c
        if {$a(usable)==0} continue

        if {($a(op)=="eq" || $a(op)=="is") && [info exists cols($a(column))]} {
          lappend res omit $idx
          set coll [$hdl collation $idx]
          lappend lCons "$cols($a(column)) = %[llength $lCons]% COLLATE $coll"

          set idxnum [expr {$idx + (1 << $a(column))}]
          catch { unset cols($a(column)) }
        }

        incr idx
      }

      if {[llength [array names cols]]>0} {
        set missing [list]
        for {set i 0} {$i < 3} {incr i} {
          catch { lappend missing $cols($i) }
        }
        set msg "missing required constraints: [join $missing ,]"
        return [list constraint $msg]
      }

      set idxstr [join $lCons " AND "]
      return "cost 1000 rows 1000 idxnum $idxnum $res idxstr {$idxstr}"
    }

    xFilter {
      foreach {idxnum idxstr lArg} $args {}
      set i 0
      set where $idxstr
      foreach a $lArg {
        set where [string map [list %$i% [quote $a]] $where]
        incr i
      }
   #   puts $where
      return [list sql "SELECT rowid, * FROM $lVal WHERE $where"]
    }
  }

  return {}
}

do_execsql_test 5.1 {
  CREATE VIRTUAL TABLE x1 USING tcl(vtab_command t1);
  CREATE TABLE t1(a, b, c, d);
}

foreach {tn where ok} {
  0    "WHERE a=? AND b=? AND c=? AND c=?"              1
  1    "WHERE a=? AND b=? AND c=?"                      1
  2    "WHERE a=? AND b=? AND (c=? OR c=?)"             1
  3    "WHERE a=? AND b=? AND (c=? OR c=? OR c=?)"      1
  4    "WHERE a=? AND b=? AND (c IS ? OR c IS ?)"       1
  5    "WHERE a=? AND ((b=? AND c=?) OR (c=? AND b=?))" 1
  6    "WHERE a=? AND ((b=? AND c=?) OR (c=?))"         0
} {
  do_test 5.2.$tn {
    catch { execsql "SELECT * FROM x1 $::where" } msg
#    if {$tn==0 || $tn==2 || $tn==3} { puts "MSG: $msg" }
  } [expr !$ok]
}

do_execsql_test 5.3 {
  SELECT * FROM x1 WHERE (a, b, c) = (?, ?, ?);
}

do_execsql_test 5.4 {
  INSERT INTO t1(rowid, a, b, c, d) VALUES(1, 'x', 'y', 'z', 'one');
  INSERT INTO t1(rowid, a, b, c, d) VALUES(2, 'X', 'Y', 'Z', 'two');
  SELECT * FROM x1 WHERE (a, b, c) = ('X', 'Y', 'Z');
} {X Y Z two}
do_execsql_test 5.5 {
  SELECT * FROM x1 WHERE a='x' AND b='y' AND c='z';
} {x y z one}
do_execsql_test 5.6 {
  SELECT * FROM x1 
  WHERE a='x' COLLATE nocase AND b='y' COLLATE nocase AND c='z'COLLATE nocase;
} {x y z one X Y Z two}

do_execsql_test 5.7 {
  DELETE FROM t1;

  INSERT INTO t1(rowid, a, b, c, d) VALUES(0, 'x', 'y', 'z', 'zero');
  INSERT INTO t1(rowid, a, b, c, d) VALUES(1, 'x', 'y', 'Z', 'one');
  INSERT INTO t1(rowid, a, b, c, d) VALUES(2, 'x', 'Y', 'z', 'two');
  INSERT INTO t1(rowid, a, b, c, d) VALUES(3, 'x', 'Y', 'Z', 'three');
  INSERT INTO t1(rowid, a, b, c, d) VALUES(4, 'X', 'y', 'z', 'four');
  INSERT INTO t1(rowid, a, b, c, d) VALUES(5, 'X', 'y', 'Z', 'five');
  INSERT INTO t1(rowid, a, b, c, d) VALUES(6, 'X', 'Y', 'z', 'six');
  INSERT INTO t1(rowid, a, b, c, d) VALUES(7, 'X', 'Y', 'z', 'seven');
}

do_execsql_test 5.8 {
  SELECT d FROM x1 
  WHERE a='x' AND ((b='y' AND c='z') OR (b='Y' AND c='z' COLLATE nocase))
} {
  zero two three
}

do_execsql_test 5.9 {
  SELECT d FROM x1 
  WHERE a='x' COLLATE nocase 
  AND ((b='y' AND c='z') OR (b='Y' AND c='z' COLLATE nocase))
} {
  zero four two
  three six seven
}

finish_test
