/ Check-in [f5752517]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix a fairly obscure problem causing the planner to sometimes choose sub-optimal plans for a query with a single virtual table in the FROM clause, and at least one IN(...) constraint in the WHERE clause.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:f5752517f590b37bfc0267650f5800320e22a8ecaba34aa6893281ce8d268026
User & Date: dan 2019-03-29 13:17:50
Context
2019-03-29
15:21
Remove the unused P5 flag from the OP_Rewind opcode. check-in: c2edbf3b user: drh tags: trunk
13:17
Fix a fairly obscure problem causing the planner to sometimes choose sub-optimal plans for a query with a single virtual table in the FROM clause, and at least one IN(...) constraint in the WHERE clause. check-in: f5752517 user: dan tags: trunk
11:48
Fix typos in the comments of the sessions extension, one of which affects the generated documentation. No code changes. check-in: 040d5d51 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/where.c.

3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349

  /* First call xBestIndex() with all constraints usable. */
  WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName));
  WHERETRACE(0x40, ("  VirtualOne: all usable\n"));
  rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn);

  /* If the call to xBestIndex() with all terms enabled produced a plan
  ** that does not require any source tables (IOW: a plan with mBest==0),
  ** then there is no point in making any further calls to xBestIndex() 
  ** since they will all return the same result (if the xBestIndex()
  ** implementation is sane). */
  if( rc==SQLITE_OK && (mBest = (pNew->prereq & ~mPrereq))!=0 ){
    int seenZero = 0;             /* True if a plan with no prereqs seen */
    int seenZeroNoIN = 0;         /* Plan with no prereqs and no IN(...) seen */
    Bitmask mPrev = 0;
    Bitmask mBestNoIn = 0;

    /* If the plan produced by the earlier call uses an IN(...) term, call
    ** xBestIndex again, this time with IN(...) terms disabled. */







|
|
|
|
|







3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349

  /* First call xBestIndex() with all constraints usable. */
  WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName));
  WHERETRACE(0x40, ("  VirtualOne: all usable\n"));
  rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn);

  /* If the call to xBestIndex() with all terms enabled produced a plan
  ** that does not require any source tables (IOW: a plan with mBest==0)
  ** and does not use an IN(...) operator, then there is no point in making 
  ** any further calls to xBestIndex() since they will all return the same
  ** result (if the xBestIndex() implementation is sane). */
  if( rc==SQLITE_OK && ((mBest = (pNew->prereq & ~mPrereq))!=0 || bIn) ){
    int seenZero = 0;             /* True if a plan with no prereqs seen */
    int seenZeroNoIN = 0;         /* Plan with no prereqs and no IN(...) seen */
    Bitmask mPrev = 0;
    Bitmask mBestNoIn = 0;

    /* If the plan produced by the earlier call uses an IN(...) term, call
    ** xBestIndex again, this time with IN(...) terms disabled. */

Changes to test/bestindex1.test.

261
262
263
264
265
266
267
268









269
















































270
  WHERE a.FlagA=0 AND a.ColumnA IN ('ValueA', 'ValueB') 
} {
  1 0 ValueA 1 0 ValueA
  2 0 ValueA 2 0 ValueA
  3 0 ValueB 3 0 ValueB
  4 0 ValueB 4 0 ValueB
}



























































finish_test








>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
  WHERE a.FlagA=0 AND a.ColumnA IN ('ValueA', 'ValueB') 
} {
  1 0 ValueA 1 0 ValueA
  2 0 ValueA 2 0 ValueA
  3 0 ValueB 3 0 ValueB
  4 0 ValueB 4 0 ValueB
}

#-------------------------------------------------------------------------
# If there is an IN(..) condition in the WHERE clause of a query on a
# virtual table, the xBestIndex method is first invoked with the IN(...)
# represented by a "usable" SQLITE_INDEX_CONSTRAINT_EQ constraint. If
# the virtual table elects to use the IN(...) constraint, then the 
# xBestIndex method is invoked again, this time with the IN(...) marked
# as "not usable". Depending on the relative costs of the two plans as
# defined by the virtual table implementation, and the cardinality of the
# IN(...) operator, SQLite chooses the most efficient plan. 
#
# At one point the second invocation of xBestIndex() was only being made
# for join queries. The following tests check that this problem has been
# fixed.
#
proc vtab_command {method args} {
  switch -- $method {
    xConnect {
      return "CREATE TABLE t1(a, b, c, d)"
    }

    xBestIndex {
      set clist [lindex $args 0]
      lappend ::bestindex_calls $clist
      set ret "cost 1000000 idxnum 555"
      for {set i 0} {$i < [llength $clist]} {incr i} {
        array set C [lindex $clist $i]
        if {$C(usable)} { lappend ret use $i }
      }
      return $ret
    }
  }
  return {}
}

do_execsql_test 4.0 {
  CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
} {}

do_test 4.1 {
  set ::bestindex_calls [list]
  execsql {
    SELECT * FROM x1 WHERE a=? AND b BETWEEN ? AND ? AND c IN (1, 2, 3, 4);
  }
  set ::bestindex_calls
} [list \
    [list {op eq column 0 usable 1} \
          {op eq column 2 usable 1} \
          {op ge column 1 usable 1} \
          {op le column 1 usable 1} \
    ] \
    [list {op eq column 0 usable 1} \
          {op eq column 2 usable 0} \
          {op ge column 1 usable 1} \
          {op le column 1 usable 1}
    ]
]


finish_test