files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  """Backend interface.
  
  To be used as a modeling and inference backend in a :class:`bayeslite.BayesDB`
  handle, a backends must first be registered with
  :func:`bayesdb_register_backend`.
  
  The CGPM backend is registered by default, but we can suppress
  that for illustration::
  
     import bayeslite
     from bayeslite.backends.cgpm_backend import CGPM_Backend
  
     bdb = bayeslite.bayesdb_open(pathname='foo.bdb', builtin_backends=False)
     backend = CGPM_Backend(cgpm_registry=dict(), multiprocess=False)
     bayeslite.bayesdb_register_backend(bdb, backend)
  
  Then you can model a table and query the probable implications of the data in
  the table::
  
     bdb.execute('create population p for t with schema(guess stattypes of (*))')
     bdb.execute('create generator p_cc for t using cgpm;')
     bdb.execute('initialize 10 models for t_cc')
     bdb.execute('analyze t_cc for 10 iterations')
     for x in bdb.execute('estimate pairwise dependence probability from t_cc'):
         print x
  """
  
  from bayeslite.util import cursor_value
  
  builtin_backends = []
  builtin_backend_names = set()
  
  def bayesdb_builtin_backend(backend):
      name = backend.name()
      assert name not in builtin_backend_names
      builtin_backends.append(backend)
      builtin_backend_names.add(name)
  
  def bayesdb_register_builtin_backends(bdb):
      """Register all builtin backends in `bdb`."""
      for backend in builtin_backends:
          bayesdb_register_backend(bdb, backend)
  
  def bayesdb_register_backend(bdb, backend):
      """Register `backend` in `bdb`, creating any necessary tables.
  
      `backend` must not already be registered in any BayesDB, nor any
      backend by the same name.
      """
      name = backend.name()
      if name in bdb.backends:
          raise ValueError('Backend already registered: %s' % (name,))
      with bdb.savepoint():
          backend.register(bdb)
          bdb.backends[name] = backend
  
  def bayesdb_deregister_backend(bdb, backend):
      """Deregister `backend`, which must have been registered in `bdb`."""
      name = backend.name()
      assert name in bdb.backends
      assert bdb.backends[name] == backend
      del bdb.backends[name]
  
  def bayesdb_backend_version(bdb, mm_name):
      cursor = bdb.sql_execute('''
          SELECT version FROM bayesdb_backend WHERE name = ?
      ''', (mm_name,))
      return cursor_value(cursor, nullok=True)
  
  class BayesDB_Backend(object):
      """BayesDB backend interface.
  
      Subclasses of :class:`BayesDB_Backend` implement the
      functionality needed by probabilistic BQL queries to sample from
      and inquire about the posterior distribution of a generative model
      conditioned on data in a table.  Instances of subclasses of
      `BayesDB_Backend` contain any in-memory state associated with
      the backend in the database.
      """
  
      def name(self):
          """Return the name of the backend as a str."""
          raise NotImplementedError
  
      def register(self, bdb):
          """Install any state needed for the backend in `bdb`.
  
          Called by :func:`bayeslite.bayesdb_register_backend`.
  
          Normally this will create SQL tables if necessary.
          """
          raise NotImplementedError
  
      def set_multiprocess(self, switch):
          """Switch between multiprocessing and single processing.
  
          The boolean variable `switch` toggles between single (`False`) and multi
          (`True`) processing, if the choice is available, and otherwise ignores
          the request.
          """
          raise NotImplementedError
  
      def create_generator(self, bdb, table, schema, **kwargs):
          """Create a generator for a table with the given schema.
  
          Called when executing ``CREATE GENERATOR``.
  
          Must parse `schema` to build the generator.
  
          The generator id and column numbers may be used to create
          backend-specific records in the database for the generator
          with foreign keys referring to the ``bayesdb_generator`` and
          ``bayesdb_variable`` tables.
  
          `schema` is a list of schema items corresponding to the
          comma-separated 'columns' from a BQL ``CREATE GENERATOR``
          command.  Each schema item is a list of strings or lists of
          schema items, corresponding to whitespace-separated tokens and
          parenthesized lists.  Note that within parenthesized lists,
          commas are not excluded.
          """
          raise NotImplementedError
  
      def drop_generator(self, bdb, generator_id):
          """Drop any backend-specific records for a generator.
  
          Called when executing ``DROP GENERATOR``.
          """
          raise NotImplementedError
  
      def rename_column(self, bdb, generator_id, oldname, newname):
          """Note that a table column has been renamed.
  
          Not currently used.  To be used in the future when executing::
  
              ALTER TABLE <table> RENAME COLUMN <oldname> TO <newname>
          """
          raise NotImplementedError
  
      def add_column(self, bdb, generator_id, colno):
          """Add `colno` from the population as a variable in the backend.
  
          Used by the MML::
  
              ALTER POPULATION <population> ADD VARIABLE <variable> <stattype>
          """
          raise NotImplementedError
  
      def initialize_models(self, bdb, generator_id, modelnos):
          """Initialize the specified model numbers for a generator."""
          raise NotImplementedError
  
      def drop_models(self, bdb, generator_id, modelnos=None):
          """Drop the specified model numbers of a generator.
  
          If none are specified, drop all models.
          """
          raise NotImplementedError
  
      def alter(self, bdb, generator_id, modelnos, commands):
          """Modify the generator according to the metamdoel-specific commands.
  
          Used by the MML::
  
              ALTER GENERATOR <generator> [MODELS [(<modelnos>)]]
                  commands...
          """
          raise NotImplementedError
  
      def analyze_models(self, bdb, generator_id, modelnos=None, iterations=1,
              max_seconds=None, ckpt_iterations=None, ckpt_seconds=None,
              program=None):
          """Analyze the specified model numbers of a generator.
  
          If none are specified, analyze all of them.
  
          :param int iterations: maximum number of iterations of analysis for
              each model
          :param int max_seconds: requested maximum number of seconds to analyze
          :param int ckpt_iterations: number of iterations before committing
              results of analysis to the database
          :param int ckpt_seconds: number of seconds before committing results of
              analysis to the database
          :param object program: None, or list of tokens of analysis program
          """
          raise NotImplementedError
  
      def column_dependence_probability(self, bdb, generator_id, modelnos, colno0,
              colno1):
          """Compute ``DEPENDENCE PROBABILITY OF <col0> WITH <col1>``."""
          raise NotImplementedError
  
      def column_mutual_information(self, bdb, generator_id, modelnos, colnos0,
              colnos1, constraints=None, numsamples=100):
          """Compute ``MUTUAL INFORMATION OF (<cols0>) WITH (<cols1>)``."""
          raise NotImplementedError
  
      def row_similarity(self, bdb, generator_id, modelnos, rowid, target_rowid,
              colnos):
          """Compute ``SIMILARITY TO <target_row>`` for given `rowid`."""
          raise NotImplementedError
  
      def predictive_relevance(self, bdb, generator_id, modelnos, rowid_target,
              rowid_query, hypotheticals, colno):
          """Compute predictive relevance, also known as relevance probability.
  
          `rowid_target` is an integer.
  
          `rowid_query` is a list of integers.
  
          `hypotheticals` is a list of hypothetical observations, where each item
              is itself a list of ``(colno, value)`` pairs.
          """
          raise NotImplementedError
  
  
      def predict(self, bdb, generator_id, modelnos, rowid, colno, threshold,
              numsamples=None):
          """Predict a value for a column, if confidence is high enough."""
          value, confidence = self.predict_confidence(
              bdb, generator_id, modelnos, rowid, colno, numsamples=numsamples)
          if confidence < threshold:
              return None
          return value
  
      def predict_confidence(self, bdb, generator_id, modelnos, rowid, colno,
              numsamples=None):
          """Predict a value for a column and return confidence."""
          raise NotImplementedError
  
      def simulate_joint(self, bdb, generator_id, modelnos, rowid, targets,
              constraints, num_samples=1, accuracy=None):
          """Simulate `targets` from a generator, subject to `constraints`.
  
          Returns a list of lists of values for the specified targets.
  
          `rowid` is an integer.
  
          `modelno` may be `None`, meaning "all models"
  
          `targets` is a list of ``(colno)``.
  
          `constraints` is a list of ``(colno, value)`` pairs.
  
          `num_samples` is the number of results to return.
  
          `accuracy` is a generic parameter (usually int) which specifies the
          desired accuracy, compute time, etc if the simulations are approximately
          distributed from the true target.
  
          The results are samples from the distribution on targets,
          independent conditioned on (the latent state of the backend
          and) the constraints.
          """
          raise NotImplementedError
  
      def logpdf_joint(self, bdb, generator_id, modelnos, rowid, targets,
              constraints):
          """Evalute the joint probability of `targets` subject to `constraints`.
  
          Returns the probability density of the targets (in log domain).
  
          `rowid` is an integer.
  
          `targets` is a list of ``(colno, value)`` pairs.
  
          `constraints` is a list of ``(colno, value)`` pairs.
  
          `modelno` is a model number or `None`, meaning all models.
          """
          raise NotImplementedError
  
files: src/backend.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  """Backend interface.
  
  To be used as a modeling and inference backend in a :class:`bayeslite.BayesDB`
  handle, a backends must first be registered with
  :func:`bayesdb_register_backend`.
  
  The CGPM backend is registered by default, but we can suppress
  that for illustration::
  
     import bayeslite
     from bayeslite.backends.cgpm_backend import CGPM_Backend
  
     bdb = bayeslite.bayesdb_open(pathname='foo.bdb', builtin_backends=False)
     backend = CGPM_Backend(cgpm_registry=dict(), multiprocess=False)
     bayeslite.bayesdb_register_backend(bdb, backend)
  
  Then you can model a table and query the probable implications of the data in
  the table::
  
     bdb.execute('create population p for t with schema(guess stattypes for (*))')
     bdb.execute('create generator p_cc for t using cgpm;')
     bdb.execute('initialize 10 models for t_cc')
     bdb.execute('analyze t_cc for 10 iterations')
     for x in bdb.execute('estimate pairwise dependence probability from t_cc'):
         print x
  """
  
  from bayeslite.util import cursor_value
  
  builtin_backends = []
  builtin_backend_names = set()
  
  def bayesdb_builtin_backend(backend):
      name = backend.name()
      assert name not in builtin_backend_names
      builtin_backends.append(backend)
      builtin_backend_names.add(name)
  
  def bayesdb_register_builtin_backends(bdb):
      """Register all builtin backends in `bdb`."""
      for backend in builtin_backends:
          bayesdb_register_backend(bdb, backend)
  
  def bayesdb_register_backend(bdb, backend):
      """Register `backend` in `bdb`, creating any necessary tables.
  
      `backend` must not already be registered in any BayesDB, nor any
      backend by the same name.
      """
      name = backend.name()
      if name in bdb.backends:
          raise ValueError('Backend already registered: %s' % (name,))
      with bdb.savepoint():
          backend.register(bdb)
          bdb.backends[name] = backend
  
  def bayesdb_deregister_backend(bdb, backend):
      """Deregister `backend`, which must have been registered in `bdb`."""
      name = backend.name()
      assert name in bdb.backends
      assert bdb.backends[name] == backend
      del bdb.backends[name]
  
  def bayesdb_backend_version(bdb, mm_name):
      cursor = bdb.sql_execute('''
          SELECT version FROM bayesdb_backend WHERE name = ?
      ''', (mm_name,))
      return cursor_value(cursor, nullok=True)
  
  class BayesDB_Backend(object):
      """BayesDB backend interface.
  
      Subclasses of :class:`BayesDB_Backend` implement the
      functionality needed by probabilistic BQL queries to sample from
      and inquire about the posterior distribution of a generative model
      conditioned on data in a table.  Instances of subclasses of
      `BayesDB_Backend` contain any in-memory state associated with
      the backend in the database.
      """
  
      def name(self):
          """Return the name of the backend as a str."""
          raise NotImplementedError
  
      def register(self, bdb):
          """Install any state needed for the backend in `bdb`.
  
          Called by :func:`bayeslite.bayesdb_register_backend`.
  
          Normally this will create SQL tables if necessary.
          """
          raise NotImplementedError
  
      def set_multiprocess(self, switch):
          """Switch between multiprocessing and single processing.
  
          The boolean variable `switch` toggles between single (`False`) and multi
          (`True`) processing, if the choice is available, and otherwise ignores
          the request.
          """
          raise NotImplementedError
  
      def create_generator(self, bdb, table, schema, **kwargs):
          """Create a generator for a table with the given schema.
  
          Called when executing ``CREATE GENERATOR``.
  
          Must parse `schema` to build the generator.
  
          The generator id and column numbers may be used to create
          backend-specific records in the database for the generator
          with foreign keys referring to the ``bayesdb_generator`` and
          ``bayesdb_variable`` tables.
  
          `schema` is a list of schema items corresponding to the
          comma-separated 'columns' from a BQL ``CREATE GENERATOR``
          command.  Each schema item is a list of strings or lists of
          schema items, corresponding to whitespace-separated tokens and
          parenthesized lists.  Note that within parenthesized lists,
          commas are not excluded.
          """
          raise NotImplementedError
  
      def drop_generator(self, bdb, generator_id):
          """Drop any backend-specific records for a generator.
  
          Called when executing ``DROP GENERATOR``.
          """
          raise NotImplementedError
  
      def rename_column(self, bdb, generator_id, oldname, newname):
          """Note that a table column has been renamed.
  
          Not currently used.  To be used in the future when executing::
  
              ALTER TABLE <table> RENAME COLUMN <oldname> TO <newname>
          """
          raise NotImplementedError
  
      def add_column(self, bdb, generator_id, colno):
          """Add `colno` from the population as a variable in the backend.
  
          Used by the MML::
  
              ALTER POPULATION <population> ADD VARIABLE <variable> <stattype>
          """
          raise NotImplementedError
  
      def initialize_models(self, bdb, generator_id, modelnos):
          """Initialize the specified model numbers for a generator."""
          raise NotImplementedError
  
      def drop_models(self, bdb, generator_id, modelnos=None):
          """Drop the specified model numbers of a generator.
  
          If none are specified, drop all models.
          """
          raise NotImplementedError
  
      def alter(self, bdb, generator_id, modelnos, commands):
          """Modify the generator according to the metamdoel-specific commands.
  
          Used by the MML::
  
              ALTER GENERATOR <generator> [MODELS [(<modelnos>)]]
                  commands...
          """
          raise NotImplementedError
  
      def analyze_models(self, bdb, generator_id, modelnos=None, iterations=1,
              max_seconds=None, ckpt_iterations=None, ckpt_seconds=None,
              program=None):
          """Analyze the specified model numbers of a generator.
  
          If none are specified, analyze all of them.
  
          :param int iterations: maximum number of iterations of analysis for
              each model
          :param int max_seconds: requested maximum number of seconds to analyze
          :param int ckpt_iterations: number of iterations before committing
              results of analysis to the database
          :param int ckpt_seconds: number of seconds before committing results of
              analysis to the database
          :param object program: None, or list of tokens of analysis program
          """
          raise NotImplementedError
  
      def column_dependence_probability(self, bdb, generator_id, modelnos, colno0,
              colno1):
          """Compute ``DEPENDENCE PROBABILITY OF <col0> WITH <col1>``."""
          raise NotImplementedError
  
      def column_mutual_information(self, bdb, generator_id, modelnos, colnos0,
              colnos1, constraints=None, numsamples=100):
          """Compute ``MUTUAL INFORMATION OF (<cols0>) WITH (<cols1>)``."""
          raise NotImplementedError
  
      def row_similarity(self, bdb, generator_id, modelnos, rowid, target_rowid,
              colnos):
          """Compute ``SIMILARITY TO <target_row>`` for given `rowid`."""
          raise NotImplementedError
  
      def predictive_relevance(self, bdb, generator_id, modelnos, rowid_target,
              rowid_query, hypotheticals, colno):
          """Compute predictive relevance, also known as relevance probability.
  
          `rowid_target` is an integer.
  
          `rowid_query` is a list of integers.
  
          `hypotheticals` is a list of hypothetical observations, where each item
              is itself a list of ``(colno, value)`` pairs.
          """
          raise NotImplementedError
  
  
      def predict(self, bdb, generator_id, modelnos, rowid, colno, threshold,
              numsamples=None):
          """Predict a value for a column, if confidence is high enough."""
          value, confidence = self.predict_confidence(
              bdb, generator_id, modelnos, rowid, colno, numsamples=numsamples)
          if confidence < threshold:
              return None
          return value
  
      def predict_confidence(self, bdb, generator_id, modelnos, rowid, colno,
              numsamples=None):
          """Predict a value for a column and return confidence."""
          raise NotImplementedError
  
      def simulate_joint(self, bdb, generator_id, modelnos, rowid, targets,
              constraints, num_samples=1, accuracy=None):
          """Simulate `targets` from a generator, subject to `constraints`.
  
          Returns a list of lists of values for the specified targets.
  
          `rowid` is an integer.
  
          `modelno` may be `None`, meaning "all models"
  
          `targets` is a list of ``(colno)``.
  
          `constraints` is a list of ``(colno, value)`` pairs.
  
          `num_samples` is the number of results to return.
  
          `accuracy` is a generic parameter (usually int) which specifies the
          desired accuracy, compute time, etc if the simulations are approximately
          distributed from the true target.
  
          The results are samples from the distribution on targets,
          independent conditioned on (the latent state of the backend
          and) the constraints.
          """
          raise NotImplementedError
  
      def logpdf_joint(self, bdb, generator_id, modelnos, rowid, targets,
              constraints):
          """Evalute the joint probability of `targets` subject to `constraints`.
  
          Returns the probability density of the targets (in log domain).
  
          `rowid` is an integer.
  
          `targets` is a list of ``(colno, value)`` pairs.
  
          `constraints` is a list of ``(colno, value)`` pairs.
  
          `modelno` is a model number or `None`, meaning all models.
          """
          raise NotImplementedError
  
files: src/backend.py
files: /*
   *  Copyright (c) 2010-2016, MIT Probabilistic Computing Project
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  
  bql(start)              ::= phrases(phrases).
  
  phrases(none)           ::= .
  phrases(some)           ::= phrases(phrases) phrase_opt(phrase) T_SEMI.
  phrase_opt(none)        ::= .
  phrase_opt(some)        ::= phrase(phrase).
  phrase(command)         ::= command(c).
  phrase(query)           ::= query(q).
  
  /*
   * Transactions
   */
  command(begin)          ::= K_BEGIN.
  command(rollback)       ::= K_ROLLBACK.
  command(commit)         ::= K_COMMIT.
  
  /* XXX Need database names.      */
  
  /*
   * SQL Data Definition Language subset
   */
  command(createtab_as)   ::= K_CREATE temp_opt(temp) K_TABLE
                                  ifnotexists(ifnotexists)
                                  table_name(name) K_AS query(query).
  command(createtab_csv)   ::= K_CREATE temp_opt(temp) K_TABLE
                                  ifnotexists(ifnotexists)
                                  table_name(name) K_FROM pathname(csv).
  command(droptab)        ::= K_DROP K_TABLE ifexists(ifexists) table_name(name).
  command(altertab)       ::= K_ALTER K_TABLE table_name(table)
                                  altertab_cmds(cmds).
  
  altertab_cmds(one)      ::= altertab_cmd(cmd).
  altertab_cmds(many)     ::= altertab_cmds(cmds) T_COMMA altertab_cmd(cmd).
  
  altertab_cmd(renametab) ::= K_RENAME K_TO table_name(name).
  altertab_cmd(renamecol) ::= K_RENAME k_column_opt column_name(old)
                                  K_TO column_name(new).
  
  k_column_opt            ::= .
  k_column_opt            ::= K_COLUMN.
  
  pathname(p)             ::= L_STRING(name).
  
  /*
   * BQL Model Definition Language
   */
  command(guess_schema)   ::= K_GUESS K_SCHEMA K_FOR table_name(table).
  
  command(create_pop)     ::= K_CREATE K_POPULATION ifnotexists(ifnotexists)
                                  population_name(name)
                                  K_FOR table_name(table)
                                  with_schema_opt
                                  T_LROUND|T_LCURLY pop_schema(schema)
                                          T_RROUND|T_RCURLY.
  command(drop_pop)       ::= K_DROP K_POPULATION ifexists(ifexists)
                                  population_name(name).
  
  with_schema_opt ::= .
  with_schema_opt ::= K_WITH K_SCHEMA.
  
  /*
   * XXX alter population xyz
   *      rename to pqr
   *      add variable ...
   *      remove variable ...
   *      set statistical type ...
   */
  
  command(alterpop)  ::= K_ALTER K_POPULATION
                                  population_name(population) alterpop_cmds(cmds).
  
  alterpop_cmds(one)      ::= alterpop_cmd(cmd).
  alterpop_cmds(many)     ::= alterpop_cmds(cmds) T_COMMA alterpop_cmd(cmd).
  
  alterpop_cmd(stattype)  ::= K_SET K_STATTYPES|K_STATTYPE
                                  K_FOR|K_OF pop_columns(cols)
                                  K_TO stattype(stattype).
  
  alterpop_cmd(addvar)    ::= K_ADD K_VARIABLE column_name(col) stattype_opt(st).
  
  pop_schema(one)         ::= pop_clause(cl).
  pop_schema(many)        ::= pop_schema(schema) T_SEMI pop_clause(cl).
  
  pop_clause(empty)       ::= .
  pop_clause(column)      ::= column_name(col) stattype(st).
  pop_clause(stattype)    ::= K_SET K_STATTYPES|K_STATTYPE
                                  K_OF pop_columns(cols)
                                  K_TO stattype(st).
  pop_clause(ignore)      ::= K_IGNORE pop_columns(cols).
  pop_clause(guess)       ::= K_GUESS stattypes_of_opt pop_columns_guess(cols).
  
  stattype_opt(none)      ::= .
  stattype_opt(one)       ::= stattype(st).
  
  stattype(st)            ::= L_NAME(name).
  
  pop_columns_guess(star) ::= T_LROUND T_STAR T_RROUND.
  pop_columns_guess(list) ::= pop_columns(cols).
  
  pop_columns(one)   ::= column_name(c).
  pop_columns(many)  ::= pop_columns(cols) T_COMMA column_name(c).
  
  stattypes_of_opt        ::= .
  stattypes_of_opt        ::= K_STATTYPE|K_STATTYPES K_OF.
  
  /* XXX Temporary generators?  */
  command(creategen)      ::= K_CREATE K_GENERATOR
                                  ifnotexists(ifnotexists0)
                                  generator_name(name)
                                  ifnotexists(ifnotexists1)
                                  K_FOR population_name(pop)
                                  backend_name_opt(backend)
                                  generator_schema_opt(schema).
  command(dropgen)        ::= K_DROP K_GENERATOR ifexists(ifexists)
                                  generator_name(name).
  command(altergen)       ::= K_ALTER K_GENERATOR
                                  generator_name(generator)
                                  anmodelset_matched_opt(models)
                                  altergen_cmds(cmds).
  
  altergen_cmds(one)      ::= altergen_cmd(cmd).
  altergen_cmds(many)     ::= altergen_cmds(cmds) T_COMMA altergen_cmd(cmd).
  
  altergen_cmd(renamegen) ::= K_RENAME K_TO generator_name(name).
  altergen_cmd(generic)   ::= generator_schemum(s).
  
  backend_name_opt(none)          ::= .
  backend_name_opt(one)           ::= K_USING backend_name(backend).
  
  generator_schema_opt(none)      ::= .
  generator_schema_opt(some)      ::= T_LROUND|T_LCURLY generator_schema(s)
                                          T_RROUND|T_RCURLY.
  
  generator_schema(one)   ::= generator_schemum(s).
  generator_schema(many)  ::= generator_schema(ss) T_COMMA generator_schemum(s).
  
  generator_schemum(empty)        ::= .
  generator_schemum(nonempty)     ::= generator_schemum(s) gs_token(t).
  gs_token(comp)                  ::= T_LROUND generator_schemum(s) T_RROUND.
  gs_token(prim)                  ::= ANY(t).
  
  /*
   * BQL Model Analysis Language
   */
  /* XXX No way to initialize individual models after DROP.  */
  command(init_models)    ::= K_INITIALIZE L_INTEGER(n) model_token
                                  ifnotexists(ifnotexists)
                                  K_FOR generator_name(generator).
  command(analyze_models) ::= K_ANALYZE generator_name(generator)
                                  anmodelset_opt(models) anlimit(anlimit)
                                  anckpt_opt(anckpt)
                                  analysis_program_opt(program).
  command(drop_models)    ::= K_DROP model_token modelset_opt(models)
                                  K_FROM generator_name(generator).
  
  temp_opt(none)          ::= .
  temp_opt(some)          ::= K_TEMP|K_TEMPORARY.
  ifexists(none)          ::= .
  ifexists(some)          ::= K_IF K_EXISTS.
  ifnotexists(none)       ::= .
  ifnotexists(some)       ::= K_IF K_NOT K_EXISTS.
  
  anmodelset_opt(none)    ::= .
  anmodelset_opt(some)    ::= model_token modelset(m).
  
  anmodelset_matched_opt(none)    ::= .
  anmodelset_matched_opt(some)    ::= model_token T_LROUND modelset(m) T_RROUND.
  
  /* XXX Temporary generators?  */
  
  modelset_opt(none)      ::= .
  modelset_opt(some)      ::= modelset(m).
  
  modelset(one)           ::= modelrange(r).
  modelset(many)          ::= modelset(m) T_COMMA modelrange(r).
  
  modelrange(single)      ::= L_INTEGER(modelno).
  modelrange(multi)       ::= L_INTEGER(minno) T_MINUS L_INTEGER(maxno).
  
  anlimit(one)      ::= K_FOR anduration(duration).
  anlimit(two)      ::= K_FOR anduration(duration0) K_OR anduration(duration1).
  
  anckpt_opt(none)        ::= .
  anckpt_opt(some)        ::= K_CHECKPOINT anduration(duration).
  
  anduration(iterations)  ::= L_INTEGER(n) K_ITERATION|K_ITERATIONS.
  anduration(minutes)     ::= L_INTEGER(n) K_MINUTE|K_MINUTES.
  anduration(seconds)     ::= L_INTEGER(n) K_SECOND|K_SECONDS.
  
  analysis_program_opt(none)      ::= .
  analysis_program_opt(some)      ::= T_LROUND analysis_program(p) T_RROUND.
  analysis_program(empty)         ::= .
  analysis_program(nonempty)      ::= analysis_program(p) analysis_token(t).
  analysis_token(compound)        ::= T_LROUND analysis_program(p) T_RROUND.
  analysis_token(primitive)       ::= ANY(t).
  
  /*
   * Commands based on ad-hoc capabilities.
   */
  
  command(regress)        ::= K_REGRESS column_name(target)
                                  K_GIVEN T_LROUND select_columns(givens) T_RROUND
                                  nsamples_opt(nsamp)
                                  K_BY|K_WITHIN population_name(pop)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos).
  
  /*
   * Queries
   */
  query(select)           ::= select(q).
  query(estimate)         ::= estimate(q).
  query(estby)            ::= estby(q).
  query(estcol)           ::= estcol(q).          /* Legacy syntax.  */
  query(estpairrow)       ::= estpairrow(q).      /* Legacy syntax.  */
  query(estpaircol)       ::= estpaircol(q).      /* Legacy syntax.  */
  query(infer)            ::= infer(q).
  query(simulate)         ::= simulate(q).
  /*
  query(estimate_pairwise_row)
                          ::= estimate_pairwise_row(q).
  query(create_column_list)
                          ::= create_column_list(q).
  */
  
  /* XXX Support WITH ... SELECT ... (i.e., common table expressions).  */
  select(s)               ::= K_SELECT select_quant(quant) select_columns(cols)
                                  from_sel_opt(tabs)
                                  where(cond)
                                  group_by(grouping)
                                  order_by(ord)
                                  limit_opt(lim).
  
  estimate(e)             ::= K_ESTIMATE select_quant(quant) select_columns(cols)
                                  from_est(tabs)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  where(cond)
                                  group_by(grouping)
                                  order_by(ord)
                                  limit_opt(lim).
  
  estcol(e)               ::= K_ESTIMATE K_COLUMNS error T_SEMI.
  estpairrow(e)           ::= K_ESTIMATE K_PAIRWISE K_ROW error T_SEMI.
  estpaircol(e)           ::= K_ESTIMATE K_PAIRWISE error T_SEMI.
  
  estby(e)                ::= K_ESTIMATE select_quant(quant) select_columns(cols)
                                  K_BY|K_WITHIN population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos).
  
  infer(auto)             ::= K_INFER infer_auto_columns(cols)
                                  withconf_opt(conf)
                                  nsamples_opt(nsamp)
                                  K_FROM population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  where(cond) group_by(grouping) order_by(ord)
                                  limit_opt(lim).
  infer(explicit)         ::= K_INFER K_EXPLICIT infer_exp_columns(cols)
                                  K_FROM population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  where(cond) group_by(grouping) order_by(ord)
                                  limit_opt(lim).
  
  infer_auto_columns(one)         ::= infer_auto_column(c).
  infer_auto_columns(many)        ::= infer_auto_columns(cs) T_COMMA
                                          infer_auto_column(c).
  
  infer_auto_column(all)          ::= T_STAR.
  infer_auto_column(one)          ::= column_name(col) as(name).
  
  withconf_opt(none)      ::= .
  withconf_opt(some)      ::= withconf(conf).
  
  withconf(conf)          ::= K_WITH K_CONFIDENCE primary(conf).
  
  infer_exp_columns(one)  ::= infer_exp_column(c).
  infer_exp_columns(many) ::= infer_exp_columns(cs) T_COMMA
                                  infer_exp_column(c).
  
  infer_exp_column(sel)   ::= select_column(c).
  infer_exp_column(pred)  ::= K_PREDICT column_name(col) as(name)
                                  conf_opt(confname)
                                  nsamples_opt(nsamp).
  
  conf_opt(none)  ::= .
  conf_opt(some)  ::= K_CONFIDENCE column_name(confname).
  
  simulate(s)             ::= K_SIMULATE select_columns(cols)
                                  K_FROM population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  given_opt(constraints)
                                  limit(lim)
                                  accuracy_opt(acc).
  simulate(nolimit)       ::= K_SIMULATE select_columns(cols)
                                  K_FROM population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  given_opt(constraints).
  
  given_opt(none)         ::= .
  given_opt(some)         ::= K_GIVEN constraints(constraints).
  constraints(one)        ::= constraint(c).
  constraints(many)       ::= constraints(cs) T_COMMA constraint(c).
  constraint(c)           ::= column_name(col) T_EQ expression(value).
  constraints_opt(none)   ::= .
  constraints_opt(some)   ::= constraints(cs).
  constraints_list(one)   ::= T_LROUND constraints(cs) T_RROUND.
  constraints_list(some)  ::= constraints_list(css) T_COMMA
                                  T_LROUND constraints(cs) T_RROUND.
  
  simulate(models)        ::= K_SIMULATE select_columns(cols)
                                  K_FROM K_MODELS K_OF
                                          population_name(population)
                                  modeledby_opt(generator).
  
  select_quant(distinct)  ::= K_DISTINCT.
  select_quant(all)       ::= K_ALL.
  select_quant(default)   ::= .
  
  select_columns(one)     ::= select_column(c).
  select_columns(many)    ::= select_columns(cs) T_COMMA select_column(c).
  
  select_column(star)     ::= T_STAR.
  select_column(qstar)    ::= table_name(table) T_DOT T_STAR.
  select_column(qsub)     ::= table_name(table) T_DOT T_LROUND query(q) T_RROUND.
  select_column(exp)      ::= expression(e) as(name).
  
  as(none)                ::= .
  as(some)                ::= K_AS L_NAME(name).
  
  from_sel_opt(empty)     ::= .
  from_sel_opt(nonempty)  ::= K_FROM select_tables(tables).
  
  from_est(row)           ::= K_FROM population_name(name).
  from_est(pairrow)       ::= K_FROM K_PAIRWISE population_name(name).
  from_est(col)           ::= K_FROM K_COLUMNS|K_VARIABLES
                                  K_OF population_name(name).
  from_est(paircol)       ::= K_FROM K_PAIRWISE K_COLUMNS|K_VARIABLES K_OF
                                  population_name(name) for(subcols).
  
  modeledby_opt(none)    ::= .
  modeledby_opt(some)    ::= K_MODELED|K_MODELLED K_BY generator_name(gen).
  
  /*
   * XXX This mechanism is completely wrong.  The set of models should
   * be treated as just another table on which to do relational algebra.
   */
  usingmodel_opt(none)    ::= .
  usingmodel_opt(some)    ::= K_USING model_token modelset(modelnos).
  
  /* XXX Allow all kinds of joins.  */
  select_tables(one)      ::= select_table(t).
  select_tables(many)     ::= select_tables(ts) T_COMMA select_table(t).
  
  select_table(named)     ::= table_name(table) as(name).
  select_table(subquery)  ::= T_LROUND query(q) T_RROUND as(name).
  
  for(none)               ::= .
  for(one)                ::= K_FOR column_lists(collist).
  
  where(unconditional)    ::= .
  where(conditional)      ::= K_WHERE expression(condition).
  
  /* XXX Allow database-qualified names.  */
  column_name(cn)         ::= L_NAME(name).
  generator_name(unqualified) ::= L_NAME(name).
  backend_name(bn)        ::= L_NAME(name).
  population_name(pn)     ::= L_NAME(name).
  table_name(unqualified) ::= L_NAME(name).
  
  model_token             ::= K_MODEL.
  model_token             ::= K_MODELS.
  
  group_by(none)          ::= .
  group_by(some)          ::= K_GROUP K_BY expressions(keys).
  group_by(having)        ::= K_GROUP K_BY expressions(keys)
                                  K_HAVING expression(cond).
  
  order_by(none)          ::= .
  order_by(some)          ::= K_ORDER K_BY order_keys(keys).
  order_keys(one)         ::= order_key(key).
  order_keys(many)        ::= order_keys(keys) T_COMMA order_key(key).
  order_key(k)            ::= expression(e) order_sense(s).
  order_sense(none)       ::= .
  order_sense(asc)        ::= K_ASC.
  order_sense(desc)       ::= K_DESC.
  
  accuracy_opt(none)      ::= .
  accuracy_opt(some)      ::= K_ACCURACY L_INTEGER(acc).
  
  limit_opt(none)         ::= .
  limit_opt(some)         ::= limit(lim).
  
  limit(n)                ::= K_LIMIT expression(limit).
  limit(offset)           ::= K_LIMIT expression(limit)
                                  K_OFFSET expression(offset).
  limit(comma)            ::= K_LIMIT expression(offset)
                                  T_COMMA expression(limit).
  
  expressions_opt(none)   ::= .
  expressions_opt(some)   ::= expressions(es).
  
  expressions(one)        ::= expression(e).
  expressions(many)       ::= expressions(es) T_COMMA expression(e).
  
  expression(top)         ::= boolean_or(e).
  
  boolean_or(or)          ::= boolean_or(l) K_OR boolean_and(r).
  boolean_or(and)         ::= boolean_and(a).
  
  boolean_and(and)        ::= boolean_and(l) K_AND boolean_not(r).
  boolean_and(not)        ::= boolean_not(n).
  
  boolean_not(not)        ::= K_NOT boolean_not(n).
  boolean_not(equality)   ::= equality(c).
  
  equality(is)            ::= equality(l) K_IS ordering(r).
  equality(isnot)         ::= equality(l) K_IS K_NOT ordering(r).
  equality(like)          ::= equality(l) K_LIKE ordering(r).
  equality(notlike)       ::= equality(l) K_NOT K_LIKE ordering(r).
  equality(like_esc)      ::= equality(l) K_LIKE ordering(r)
                                  K_ESCAPE ordering(e).
  equality(notlike_esc)   ::= equality(l) K_NOT K_LIKE ordering(r)
                                  K_ESCAPE ordering(e).
  equality(glob)          ::= equality(l) K_GLOB ordering(r).
  equality(notglob)       ::= equality(l) K_NOT K_GLOB ordering(r).
  equality(glob_esc)      ::= equality(l) K_GLOB ordering(r)
                                  K_ESCAPE ordering(e).
  equality(notglob_esc)   ::= equality(l) K_NOT K_GLOB ordering(r)
                                  K_ESCAPE ordering(e).
  equality(regexp)        ::= equality(l) K_REGEXP ordering(r).
  equality(notregexp)     ::= equality(l) K_NOT K_REGEXP ordering(r).
  equality(regexp_esc)    ::= equality(l) K_REGEXP ordering(r)
                                  K_ESCAPE ordering(e).
  equality(notregexp_esc) ::= equality(l) K_NOT K_REGEXP ordering(r)
                                  K_ESCAPE ordering(e).
  equality(match)         ::= equality(l) K_MATCH ordering(r).
  equality(notmatch)      ::= equality(l) K_NOT K_MATCH ordering(r).
  equality(match_esc)     ::= equality(l) K_MATCH ordering(r)
                                  K_ESCAPE ordering(e).
  equality(notmatch_esc)  ::= equality(l) K_NOT K_MATCH ordering(r)
                                  K_ESCAPE ordering(e).
  equality(between)       ::= equality(m) K_BETWEEN ordering(l)
                                  K_AND ordering(r).
  equality(notbetween)    ::= equality(m) K_NOT K_BETWEEN ordering(l)
                                  K_AND ordering(r).
  equality(in_query)      ::= equality(e) K_IN T_LROUND query(q) T_RROUND.
  equality(notin_query)   ::= equality(e) K_NOT K_IN T_LROUND query(q) T_RROUND.
  equality(in_exp)        ::= equality(e) K_IN T_LROUND expressions_opt(es)
                                  T_RROUND.
  equality(notin_exp)     ::= equality(e) K_NOT K_IN T_LROUND expressions_opt(es)
                                  T_RROUND.
  equality(isnull)        ::= equality(e) K_ISNULL.
  equality(notnull)       ::= equality(e) K_NOTNULL.
  equality(neq)           ::= equality(l) T_NEQ ordering(r).
  equality(eq)            ::= equality(l) T_EQ ordering(r).
  equality(ordering)      ::= ordering(o).
  
  ordering(lt)            ::= ordering(l) T_LT bitwise(r).
  ordering(leq)           ::= ordering(l) T_LEQ bitwise(r).
  ordering(geq)           ::= ordering(l) T_GEQ bitwise(r).
  ordering(gt)            ::= ordering(l) T_GT bitwise(r).
  ordering(bitwise)       ::= bitwise(b).
  
  bitwise(and)            ::= bitwise(l) T_BITAND additive(r).
  bitwise(ior)            ::= bitwise(l) T_BITIOR additive(r).
  bitwise(lshift)         ::= bitwise(l) T_LSHIFT additive(r).
  bitwise(rshift)         ::= bitwise(l) T_RSHIFT additive(r).
  bitwise(additive)       ::= additive(a).
  
  additive(add)           ::= additive(l) T_PLUS multiplicative(r).
  additive(sub)           ::= additive(l) T_MINUS multiplicative(r).
  additive(mult)          ::= multiplicative(m).
  
  multiplicative(mul)     ::= multiplicative(l) T_STAR concatenative(r).
  multiplicative(div)     ::= multiplicative(l) T_SLASH concatenative(r).
  multiplicative(rem)     ::= multiplicative(l) T_PERCENT concatenative(r).
  multiplicative(conc)    ::= concatenative(c).
  
  concatenative(concat)   ::= concatenative(l) T_CONCAT collating(r).
  concatenative(collate)  ::= collating(c).
  
  collating(collate)      ::= collating(e) K_COLLATE L_NAME|L_STRING(c).
  collating(unary)        ::= unary(u).
  
  unary(bitwise_not)      ::= T_BITNOT unary(u).
  unary(minus)            ::= T_MINUS unary(u).
  unary(plus)             ::= T_PLUS unary(u).
  unary(bql)              ::= bqlfn(b).
  
  /*
   * The BQL functions come in five flavours:
   *
   * (1) Functions of two columns: DEPENDENCE PROBABILITY.
   * (2) Functions of one column: DEPENDENCE PROBABILITY WITH C.
   * (3) Functions of two rows: SIMILARITY IN THE CONTEXT OF C.
   * (4) Functions of one row: SIMILARITY TO 5 IN THE CONTEXT OF C.
   * (5) Constants: DEPENDENCE PROBABILITY OF C WITH D.
   *
   * Although constants can appear in any context (subject to the
   * constraint that a table is implied by context -- really, the table
   * should be named in the expression), any context for an expression
   * makes sense with only one flavour of functions.  For example:
   *
   * (1) ESTIMATE DEPENDENCE PROBABILITY PAIRWISE COLUMNS OF FROM T;
   * (2) ESTIMATE * FROM COLUMNS OF T ORDER BY DEPENDENCE PROBABILITY WITH C;
   * (3) SELECT SIMILARITY TO 5 IN THE CONTEXT OF C FROM T;
   *
   * It makes no sense to say
   *
   *      ESTIMATE * FROM COLUMNS OF T WHERE DEPENDENCE PROBABILITY > 5,
   *
   * because WHERE filters a single set of columns, so there's no second
   * argument for DEPENDENCE PROBABILITY.  Similarly, it makes no sense
   * to say
   *
   *      ESTIMATE * FROM COLUMNS OF T
   *              WHERE SIMILARITY TO 5 IN THE CONTEXT OF C > 5,
   *
   * because SIMILARITY TO 5 IN THE CONTEXT OF C is a function of a row,
   * not a function of a column.
   *
   * We could invent four different expression nonterminals alike in
   * every way except for which of these options are allowed, but it
   * would require duplicating all the other rules for expressions, so
   * instead we'll do a simple-minded type-check after parsing to detect
   * such mistakes as the above.
   *
   * It is tempting to split the `bqlfn' nonterminal into `bql2colfn',
   * `bql1colfn', `bqlrowfn', `bqlconstfn', but we used to have an
   * ambiguity here.
   *
   * XXX It would be nice if
   *
   *      SELECT PROBABILITY DENSITY OF X = 1 - PROBABILITY DENSITY OF Y = 0
   *          FROM T;
   *
   * worked to mean
   *
   *      SELECT PROBABILITY DENSITY OF X = (1 - PROBABILITY DENSITY OF Y = 0)
   *          FROM T;
   *
   * so that you could also write, e.g.,
   *
   *      SELECT PROBABILITY DENSITY OF X = A + B FROM T;
   *
   * with A + B meaning the right-hand side of the equation.
   *
   * However, changing primary(e) to expression(e) on the right-hand
   * side of the bqlfn(prob) rule makes the grammar ambiguous, and the
   * surgery necessary to resolve the ambiguity is too much trouble.  So
   * instead we'll reject unparenthesized PROBABILITY DENSITY OF X = V
   * with other operators altogether and require explicit parentheses
   * until someone wants to do that surgery.
   *
   * XXX Oops -- some restructing of the grammar caused us to cease
   * rejecting unparenthesized PROBABILITY DENSITY OF X = V with other
   * operators.
   */
  bqlfn(predprob_row)     ::= K_PREDICTIVE K_PROBABILITY K_OF column_list(target).
  
  bqlfn(jpredprob_row)    ::= K_PREDICTIVE K_PROBABILITY K_OF
                                  T_LROUND column_lists(targets) T_RROUND.
  
  bqlfn(condpredprob_row) ::= K_PREDICTIVE K_PROBABILITY K_OF column_list(target)
                                  K_GIVEN T_LROUND column_lists(constraints)
                                  T_RROUND.
  
  bqlfn(condjpredprob_row) ::= K_PREDICTIVE K_PROBABILITY K_OF
                                  T_LROUND column_lists(targets) T_RROUND
                                  K_GIVEN T_LROUND column_lists(constraints)
                                  T_RROUND.
  
  bqlfn(prob_const)       ::= K_PROBABILITY K_DENSITY K_OF column_name(col)
                                  T_EQ unary(e).
  bqlfn(jprob_const)      ::= K_PROBABILITY K_DENSITY K_OF
                                  T_LROUND constraints_opt(targets) T_RROUND.
  bqlfn(condprob_const)   ::= K_PROBABILITY K_DENSITY K_OF column_name(col)
                                  T_EQ primary(e)
                                  K_GIVEN T_LROUND constraints_opt(constraints)
                                          T_RROUND.
  bqlfn(condjprob_const)  ::= K_PROBABILITY K_DENSITY K_OF
                                  T_LROUND constraints_opt(targets) T_RROUND
                                  K_GIVEN T_LROUND constraints_opt(constraints)
                                          T_RROUND.
  /* XXX Givens for PROBABILITY DENSITY OF VALUE function of columns?      */
  bqlfn(prob_1col)        ::= K_PROBABILITY K_DENSITY K_OF K_VALUE unary(e).
  bqlfn(condprob_1col)    ::= K_PROBABILITY K_DENSITY K_OF K_VALUE primary(e)
                                  K_GIVEN T_LROUND constraints_opt(constraints)
                                          T_RROUND.
  bqlfn(sim_const)         ::= K_SIMILARITY
                                  K_OF T_LROUND expression(cond0) T_RROUND
                                  K_TO T_LROUND expression(cond1) T_RROUND
                                  wrt(col).
  bqlfn(sim_1row)         ::= K_SIMILARITY K_TO
                                  T_LROUND expression(cond) T_RROUND
                                  wrt(col).
  bqlfn(sim_2row)         ::= K_SIMILARITY wrt(col).
  
  bqlfn(predrel_existing)          ::= K_PREDICTIVE K_RELEVANCE
                                          predrel_of_opt(cond0)
                                          K_TO existing_rows(cond1)
                                          wrt(col).
  
  bqlfn(predrel_hypothetical)      ::= K_PREDICTIVE K_RELEVANCE
                                          predrel_of_opt(cond0)
                                          K_TO hypothetical_rows(constraints)
                                          wrt(col).
  
  bqlfn(predrel_both)              ::= K_PREDICTIVE K_RELEVANCE
                                          predrel_of_opt(cond0)
                                          K_TO existing_rows(cond1)
                                          K_AND hypothetical_rows(constraints)
                                          wrt(col).
  
  bqlfn(depprob)          ::= K_DEPENDENCE K_PROBABILITY ofwith(cols).
  
  bqlfn(mutinf)           ::= K_MUTUAL K_INFORMATION ofwithmulti(cols)
                                  mi_given_opt(constraints) nsamples_opt(nsamp).
  bqlfn(prob_est)         ::= K_PROBABILITY K_OF T_LROUND expression(e) T_RROUND.
  
  predrel_of_opt(none)    ::= .
  predrel_of_opt(one)     ::= K_OF T_LROUND expression(cond0) T_RROUND.
  
  existing_rows(one)      ::= K_EXISTING K_ROWS
                                  T_LROUND expression(cond) T_RROUND.
  
  hypothetical_rows(one)  ::= K_HYPOTHETICAL K_ROWS K_WITH K_VALUES
                                  T_LROUND constraints_list(cs) T_RROUND.
  
  ofwithmulti(bql_2col)   ::= .
  ofwithmulti(bql_1col)   ::= K_WITH mi_columns(cols).
  ofwithmulti(bql_const)  ::= K_OF mi_columns(cols0) K_WITH mi_columns(cols1).
  
  mi_columns(one)         ::= column_name(col).
  mi_columns(many)        ::= T_LROUND mi_column_list(cols) T_RROUND.
  
  mi_column_list(one)     ::= column_name(col).
  mi_column_list(many)    ::= mi_column_list(cols) T_COMMA column_name(col).
  
  mi_given_opt(none)      ::= .
  mi_given_opt(some)      ::= K_GIVEN T_LROUND mi_constraints(constraints) T_RROUND.
  
  mi_constraints(one)     ::= mi_constraint(c).
  mi_constraints(many)    ::= mi_constraints(cs) T_COMMA mi_constraint(c).
  
  mi_constraint(equality) ::= column_name(col) T_EQ expression(value).
  mi_constraint(marginal) ::= column_name(col).
  
  bqlfn(correl)           ::= K_CORRELATION ofwith(cols).
  bqlfn(correl_pval)      ::= K_CORRELATION K_PVALUE ofwith(cols).
  bqlfn(predict)          ::= K_PREDICT column_name(col) withconf(conf)
                                  nsamples_opt(nsamp).
  bqlfn(primary)          ::= primary(p).
  
  /*
   * Parenthesizing the column lists is not what we did before, but is
   * necessary to avoid ambiguity at the comma: is it another select
   * column, or is it another wrt column?
   */
  wrt(one)                ::= K_IN K_THE K_CONTEXT K_OF column_list(col).
  
  ofwith(bql_2col)        ::= .
  ofwith(bql_1col)        ::= K_WITH column_name(col).
  ofwith(bql_const)       ::= K_OF column_name(col1) K_WITH column_name(col2).
  
  nsamples_opt(none)      ::= .
  nsamples_opt(some)      ::= K_USING primary(nsamples) K_SAMPLES.
  
  column_lists(one)       ::= column_list(collist).
  column_lists(many)      ::= column_lists(collists)
                                  T_COMMA|K_AND column_list(collist).
  column_list(all)        ::= T_STAR.
  column_list(column)     ::= column_name(col).
  /*
   * XXX Should really allow any SELECT on a table of columns.  But
   * until we have that notion, are there any other kinds of subqueries
   * that make sense here?
   */
  column_list(subquery)   ::= T_LROUND query(q) T_RROUND.
  
  primary(literal)        ::= literal(v).
  primary(numpar)         ::= L_NUMPAR(n).
  primary(nampar)         ::= L_NAMPAR(n).
  primary(apply)          ::= L_NAME(fn) T_LROUND expressions_opt(es) T_RROUND.
  primary(apply_distinct) ::= L_NAME(fn) T_LROUND K_DISTINCT expressions_opt(es)
                                  T_RROUND.
  primary(apply_star)     ::= L_NAME(fn) T_LROUND T_STAR T_RROUND.
  primary(paren)          ::= T_LROUND expression(e) T_RROUND.
  primary(subquery)       ::= T_LROUND query(q) T_RROUND.
  primary(cast)           ::= K_CAST T_LROUND expression(e)
                                  K_AS type(t) T_RROUND.
  primary(exists)         ::= K_EXISTS T_LROUND query(q) T_RROUND.
  primary(column)         ::= column_name(col).
  primary(tabcol)         ::= table_name(tab) T_DOT column_name(col).
  primary(case)           ::= K_CASE case_key_opt(k) case_whens_opt(ws)
                                  case_else_opt(e) K_END.
  /*
   * XXX To do:
   *
   * - RAISE (IGNORE|ROLLBACK|ABORT|FAIL, "message")
   */
  
  case_key_opt(none)      ::= .
  case_key_opt(some)      ::= expression(k).
  
  case_whens_opt(none)    ::= .
  case_whens_opt(some)    ::= case_whens_opt(ws) K_WHEN expression(w)
                                  K_THEN expression(t).
  
  case_else_opt(none)     ::= .
  case_else_opt(some)     ::= K_ELSE expression(e).
  
  literal(null)           ::= K_NULL.
  literal(integer)        ::= L_INTEGER(i).
  literal(float)          ::= L_FLOAT(f).
  literal(string)         ::= L_STRING(s).
  
  type(name)              ::= typename(n).
  type(onearg)            ::= typename(n) T_LROUND typearg(a) T_RROUND.
  type(twoarg)            ::= typename(n) T_LROUND typearg(a) T_COMMA
                                  typearg(b) T_RROUND.
  typename(one)           ::= L_NAME(n).
  typename(many)          ::= typename(tn) L_NAME(n).
  typearg(unsigned)       ::= L_INTEGER(i).
  typearg(positive)       ::= T_PLUS L_INTEGER(i).
  typearg(negative)       ::= T_MINUS L_INTEGER(i).
  
  /*
   * XXX For some reason, putting CASE and WHEN here break the parser.
   * Needs further analysis.
   *
   *      grep -o 'K_[A-Z_]*' | awk '{ printf("\t%s\n", $1) }' | sort -u
   */
  %fallback L_NAME
          K_ACCURACY
          K_ADD
          K_ALL
          K_ALTER
          K_ANALYZE
          K_AND
          K_AS
          K_ASC
          K_BEGIN
          K_BETWEEN
          K_BTABLE
          K_BY
          /* K_CASE */
          K_CAST
          K_CHECKPOINT
          K_COLLATE
          K_COLUMN
          K_COLUMNS
          K_COMMIT
          K_CONF
          K_CONFIDENCE
          K_CONTEXT
          K_CORRELATION
          K_CREATE
          K_DEFAULT
          K_DENSITY
          K_DEPENDENCE
          K_DESC
          K_DISTINCT
          K_DROP
          K_ELSE
          K_END
          K_ESCAPE
          K_ESTIMATE
          K_EXISTS
          K_EXISTING
          K_EXPLICIT
          K_FOR
          K_FROM
          K_GENERATOR
          K_GIVEN
          K_GLOB
          K_GROUP
          K_GUESS
          K_HAVING
          K_HYPOTHETICAL
          K_IF
          K_IGNORE
          K_IN
          K_INFER
          K_INFORMATION
          K_INITIALIZE
          K_IS
          K_ISNULL
          K_ITERATION
          K_ITERATIONS
          K_LATENT
          K_LIKE
          K_LIMIT
          K_MATCH
          K_MINUTE
          K_MINUTES
          K_MODEL
          K_MODELED
          K_MODELLED
          K_MODELS
          K_MUTUAL
          K_NOT
          K_NOTNULL
          K_NULL
          K_OF
          K_OFFSET
          K_OR
          K_ORDER
          K_PAIRWISE
          K_POPULATION
          K_PREDICT
          K_PREDICTIVE
          K_PROBABILITY
          K_PVALUE
          K_REGEXP
          K_REGRESS
          K_RELEVANCE
          K_RENAME
          K_ROLLBACK
          K_ROW
          K_ROWS
          K_SAMPLES
          K_SCHEMA
          K_SECOND
          K_SECONDS
          K_SELECT
          K_SET
          K_SIMILARITY
          K_SIMULATE
          K_STATTYPE
          K_STATTYPES
          K_TABLE
          K_TEMP
          K_TEMPORARY
          K_THE
          K_THEN
          K_TO
          K_UNSET
          K_USING
          K_VALUE
          K_VALUES
          K_VARIABLE
          K_VARIABLES
          /* K_WHEN */
          K_WHERE
          K_WITH
          K_WITHIN
          .
  
  /*
   * Wildcard token, matches anything else.
   */
  %wildcard ANY.
  
files: src/grammar.y
files: /*
   *  Copyright (c) 2010-2016, MIT Probabilistic Computing Project
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  
  bql(start)              ::= phrases(phrases).
  
  phrases(none)           ::= .
  phrases(some)           ::= phrases(phrases) phrase_opt(phrase) T_SEMI.
  phrase_opt(none)        ::= .
  phrase_opt(some)        ::= phrase(phrase).
  phrase(command)         ::= command(c).
  phrase(query)           ::= query(q).
  
  /*
   * Transactions
   */
  command(begin)          ::= K_BEGIN.
  command(rollback)       ::= K_ROLLBACK.
  command(commit)         ::= K_COMMIT.
  
  /* XXX Need database names.      */
  
  /*
   * SQL Data Definition Language subset
   */
  command(createtab_as)   ::= K_CREATE temp_opt(temp) K_TABLE
                                  ifnotexists(ifnotexists)
                                  table_name(name) K_AS query(query).
  command(createtab_csv)   ::= K_CREATE temp_opt(temp) K_TABLE
                                  ifnotexists(ifnotexists)
                                  table_name(name) K_FROM pathname(csv).
  command(droptab)        ::= K_DROP K_TABLE ifexists(ifexists) table_name(name).
  command(altertab)       ::= K_ALTER K_TABLE table_name(table)
                                  altertab_cmds(cmds).
  
  altertab_cmds(one)      ::= altertab_cmd(cmd).
  altertab_cmds(many)     ::= altertab_cmds(cmds) T_COMMA altertab_cmd(cmd).
  
  altertab_cmd(renametab) ::= K_RENAME K_TO table_name(name).
  altertab_cmd(renamecol) ::= K_RENAME k_column_opt column_name(old)
                                  K_TO column_name(new).
  
  k_column_opt            ::= .
  k_column_opt            ::= K_COLUMN.
  
  pathname(p)             ::= L_STRING(name).
  
  /*
   * BQL Model Definition Language
   */
  command(guess_schema)   ::= K_GUESS K_SCHEMA K_FOR table_name(table).
  
  command(create_pop)     ::= K_CREATE K_POPULATION ifnotexists(ifnotexists)
                                  population_name(name)
                                  K_FOR table_name(table)
                                  with_schema_opt
                                  T_LROUND|T_LCURLY pop_schema(schema)
                                          T_RROUND|T_RCURLY.
  command(drop_pop)       ::= K_DROP K_POPULATION ifexists(ifexists)
                                  population_name(name).
  
  with_schema_opt ::= .
  with_schema_opt ::= K_WITH K_SCHEMA.
  
  /*
   * XXX alter population xyz
   *      rename to pqr
   *      add variable ...
   *      remove variable ...
   *      set statistical type ...
   */
  
  command(alterpop)  ::= K_ALTER K_POPULATION
                                  population_name(population) alterpop_cmds(cmds).
  
  alterpop_cmds(one)      ::= alterpop_cmd(cmd).
  alterpop_cmds(many)     ::= alterpop_cmds(cmds) T_COMMA alterpop_cmd(cmd).
  
  alterpop_cmd(stattype)  ::= K_SET K_STATTYPES|K_STATTYPE
                                  K_FOR|K_OF pop_columns(cols)
                                  K_TO stattype(stattype).
  
  alterpop_cmd(addvar)    ::= K_ADD K_VARIABLE column_name(col) stattype_opt(st).
  
  pop_schema(one)         ::= pop_clause(cl).
  pop_schema(many)        ::= pop_schema(schema) T_SEMI pop_clause(cl).
  
  pop_clause(empty)       ::= .
  pop_clause(column)      ::= column_name(col) stattype(st).
  pop_clause(model)       ::= K_MODEL pop_columns(cols) K_AS stattype(st).
  pop_clause(ignore)      ::= K_IGNORE pop_columns(cols).
  pop_clause(guess)       ::= K_GUESS stattypes_for_opt pop_columns_guess(cols).
  
  stattype_opt(none)      ::= .
  stattype_opt(one)       ::= stattype(st).
  
  stattype(st)            ::= L_NAME(name).
  
  pop_columns_guess(star) ::= T_LROUND T_STAR T_RROUND.
  pop_columns_guess(list) ::= pop_columns(cols).
  
  pop_columns(one)   ::= column_name(c).
  pop_columns(many)  ::= pop_columns(cols) T_COMMA column_name(c).
  
  stattypes_for_opt       ::= .
  stattypes_for_opt       ::= K_STATTYPES K_FOR.
  
  
  /* XXX Temporary generators?  */
  command(creategen)      ::= K_CREATE K_GENERATOR
                                  ifnotexists(ifnotexists0)
                                  generator_name(name)
                                  ifnotexists(ifnotexists1)
                                  K_FOR population_name(pop)
                                  backend_name_opt(backend)
                                  generator_schema_opt(schema).
  command(dropgen)        ::= K_DROP K_GENERATOR ifexists(ifexists)
                                  generator_name(name).
  command(altergen)       ::= K_ALTER K_GENERATOR
                                  generator_name(generator)
                                  anmodelset_matched_opt(models)
                                  altergen_cmds(cmds).
  
  altergen_cmds(one)      ::= altergen_cmd(cmd).
  altergen_cmds(many)     ::= altergen_cmds(cmds) T_COMMA altergen_cmd(cmd).
  
  altergen_cmd(renamegen) ::= K_RENAME K_TO generator_name(name).
  altergen_cmd(generic)   ::= generator_schemum(s).
  
  backend_name_opt(none)          ::= .
  backend_name_opt(one)           ::= K_USING backend_name(backend).
  
  generator_schema_opt(none)      ::= .
  generator_schema_opt(some)      ::= T_LROUND|T_LCURLY generator_schema(s)
                                          T_RROUND|T_RCURLY.
  
  generator_schema(one)   ::= generator_schemum(s).
  generator_schema(many)  ::= generator_schema(ss) T_COMMA generator_schemum(s).
  
  generator_schemum(empty)        ::= .
  generator_schemum(nonempty)     ::= generator_schemum(s) gs_token(t).
  gs_token(comp)                  ::= T_LROUND generator_schemum(s) T_RROUND.
  gs_token(prim)                  ::= ANY(t).
  
  /*
   * BQL Model Analysis Language
   */
  /* XXX No way to initialize individual models after DROP.  */
  command(init_models)    ::= K_INITIALIZE L_INTEGER(n) model_token
                                  ifnotexists(ifnotexists)
                                  K_FOR generator_name(generator).
  command(analyze_models) ::= K_ANALYZE generator_name(generator)
                                  anmodelset_opt(models) anlimit(anlimit)
                                  anckpt_opt(anckpt)
                                  analysis_program_opt(program).
  command(drop_models)    ::= K_DROP model_token modelset_opt(models)
                                  K_FROM generator_name(generator).
  
  temp_opt(none)          ::= .
  temp_opt(some)          ::= K_TEMP|K_TEMPORARY.
  ifexists(none)          ::= .
  ifexists(some)          ::= K_IF K_EXISTS.
  ifnotexists(none)       ::= .
  ifnotexists(some)       ::= K_IF K_NOT K_EXISTS.
  
  anmodelset_opt(none)    ::= .
  anmodelset_opt(some)    ::= model_token modelset(m).
  
  anmodelset_matched_opt(none)    ::= .
  anmodelset_matched_opt(some)    ::= model_token T_LROUND modelset(m) T_RROUND.
  
  /* XXX Temporary generators?  */
  
  modelset_opt(none)      ::= .
  modelset_opt(some)      ::= modelset(m).
  
  modelset(one)           ::= modelrange(r).
  modelset(many)          ::= modelset(m) T_COMMA modelrange(r).
  
  modelrange(single)      ::= L_INTEGER(modelno).
  modelrange(multi)       ::= L_INTEGER(minno) T_MINUS L_INTEGER(maxno).
  
  anlimit(one)      ::= K_FOR anduration(duration).
  anlimit(two)      ::= K_FOR anduration(duration0) K_OR anduration(duration1).
  
  anckpt_opt(none)        ::= .
  anckpt_opt(some)        ::= K_CHECKPOINT anduration(duration).
  
  anduration(iterations)  ::= L_INTEGER(n) K_ITERATION|K_ITERATIONS.
  anduration(minutes)     ::= L_INTEGER(n) K_MINUTE|K_MINUTES.
  anduration(seconds)     ::= L_INTEGER(n) K_SECOND|K_SECONDS.
  
  analysis_program_opt(none)      ::= .
  analysis_program_opt(some)      ::= T_LROUND analysis_program(p) T_RROUND.
  analysis_program(empty)         ::= .
  analysis_program(nonempty)      ::= analysis_program(p) analysis_token(t).
  analysis_token(compound)        ::= T_LROUND analysis_program(p) T_RROUND.
  analysis_token(primitive)       ::= ANY(t).
  
  /*
   * Commands based on ad-hoc capabilities.
   */
  
  command(regress)        ::= K_REGRESS column_name(target)
                                  K_GIVEN T_LROUND select_columns(givens) T_RROUND
                                  nsamples_opt(nsamp)
                                  K_BY|K_WITHIN population_name(pop)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos).
  
  /*
   * Queries
   */
  query(select)           ::= select(q).
  query(estimate)         ::= estimate(q).
  query(estby)            ::= estby(q).
  query(estcol)           ::= estcol(q).          /* Legacy syntax.  */
  query(estpairrow)       ::= estpairrow(q).      /* Legacy syntax.  */
  query(estpaircol)       ::= estpaircol(q).      /* Legacy syntax.  */
  query(infer)            ::= infer(q).
  query(simulate)         ::= simulate(q).
  /*
  query(estimate_pairwise_row)
                          ::= estimate_pairwise_row(q).
  query(create_column_list)
                          ::= create_column_list(q).
  */
  
  /* XXX Support WITH ... SELECT ... (i.e., common table expressions).  */
  select(s)               ::= K_SELECT select_quant(quant) select_columns(cols)
                                  from_sel_opt(tabs)
                                  where(cond)
                                  group_by(grouping)
                                  order_by(ord)
                                  limit_opt(lim).
  
  estimate(e)             ::= K_ESTIMATE select_quant(quant) select_columns(cols)
                                  from_est(tabs)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  where(cond)
                                  group_by(grouping)
                                  order_by(ord)
                                  limit_opt(lim).
  
  estcol(e)               ::= K_ESTIMATE K_COLUMNS error T_SEMI.
  estpairrow(e)           ::= K_ESTIMATE K_PAIRWISE K_ROW error T_SEMI.
  estpaircol(e)           ::= K_ESTIMATE K_PAIRWISE error T_SEMI.
  
  estby(e)                ::= K_ESTIMATE select_quant(quant) select_columns(cols)
                                  K_BY|K_WITHIN population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos).
  
  infer(auto)             ::= K_INFER infer_auto_columns(cols)
                                  withconf_opt(conf)
                                  nsamples_opt(nsamp)
                                  K_FROM population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  where(cond) group_by(grouping) order_by(ord)
                                  limit_opt(lim).
  infer(explicit)         ::= K_INFER K_EXPLICIT infer_exp_columns(cols)
                                  K_FROM population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  where(cond) group_by(grouping) order_by(ord)
                                  limit_opt(lim).
  
  infer_auto_columns(one)         ::= infer_auto_column(c).
  infer_auto_columns(many)        ::= infer_auto_columns(cs) T_COMMA
                                          infer_auto_column(c).
  
  infer_auto_column(all)          ::= T_STAR.
  infer_auto_column(one)          ::= column_name(col) as(name).
  
  withconf_opt(none)      ::= .
  withconf_opt(some)      ::= withconf(conf).
  
  withconf(conf)          ::= K_WITH K_CONFIDENCE primary(conf).
  
  infer_exp_columns(one)  ::= infer_exp_column(c).
  infer_exp_columns(many) ::= infer_exp_columns(cs) T_COMMA
                                  infer_exp_column(c).
  
  infer_exp_column(sel)   ::= select_column(c).
  infer_exp_column(pred)  ::= K_PREDICT column_name(col) as(name)
                                  conf_opt(confname)
                                  nsamples_opt(nsamp).
  
  conf_opt(none)  ::= .
  conf_opt(some)  ::= K_CONFIDENCE column_name(confname).
  
  simulate(s)             ::= K_SIMULATE select_columns(cols)
                                  K_FROM population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  given_opt(constraints)
                                  limit(lim)
                                  accuracy_opt(acc).
  simulate(nolimit)       ::= K_SIMULATE select_columns(cols)
                                  K_FROM population_name(population)
                                  modeledby_opt(generator)
                                  usingmodel_opt(modelnos)
                                  given_opt(constraints).
  
  given_opt(none)         ::= .
  given_opt(some)         ::= K_GIVEN constraints(constraints).
  constraints(one)        ::= constraint(c).
  constraints(many)       ::= constraints(cs) T_COMMA constraint(c).
  constraint(c)           ::= column_name(col) T_EQ expression(value).
  constraints_opt(none)   ::= .
  constraints_opt(some)   ::= constraints(cs).
  constraints_list(one)   ::= T_LROUND constraints(cs) T_RROUND.
  constraints_list(some)  ::= constraints_list(css) T_COMMA
                                  T_LROUND constraints(cs) T_RROUND.
  
  simulate(models)        ::= K_SIMULATE select_columns(cols)
                                  K_FROM K_MODELS K_OF
                                          population_name(population)
                                  modeledby_opt(generator).
  
  select_quant(distinct)  ::= K_DISTINCT.
  select_quant(all)       ::= K_ALL.
  select_quant(default)   ::= .
  
  select_columns(one)     ::= select_column(c).
  select_columns(many)    ::= select_columns(cs) T_COMMA select_column(c).
  
  select_column(star)     ::= T_STAR.
  select_column(qstar)    ::= table_name(table) T_DOT T_STAR.
  select_column(qsub)     ::= table_name(table) T_DOT T_LROUND query(q) T_RROUND.
  select_column(exp)      ::= expression(e) as(name).
  
  as(none)                ::= .
  as(some)                ::= K_AS L_NAME(name).
  
  from_sel_opt(empty)     ::= .
  from_sel_opt(nonempty)  ::= K_FROM select_tables(tables).
  
  from_est(row)           ::= K_FROM population_name(name).
  from_est(pairrow)       ::= K_FROM K_PAIRWISE population_name(name).
  from_est(col)           ::= K_FROM K_COLUMNS|K_VARIABLES
                                  K_OF population_name(name).
  from_est(paircol)       ::= K_FROM K_PAIRWISE K_COLUMNS|K_VARIABLES K_OF
                                  population_name(name) for(subcols).
  
  modeledby_opt(none)    ::= .
  modeledby_opt(some)    ::= K_MODELED|K_MODELLED K_BY generator_name(gen).
  
  /*
   * XXX This mechanism is completely wrong.  The set of models should
   * be treated as just another table on which to do relational algebra.
   */
  usingmodel_opt(none)    ::= .
  usingmodel_opt(some)    ::= K_USING model_token modelset(modelnos).
  
  /* XXX Allow all kinds of joins.  */
  select_tables(one)      ::= select_table(t).
  select_tables(many)     ::= select_tables(ts) T_COMMA select_table(t).
  
  select_table(named)     ::= table_name(table) as(name).
  select_table(subquery)  ::= T_LROUND query(q) T_RROUND as(name).
  
  for(none)               ::= .
  for(one)                ::= K_FOR column_lists(collist).
  
  where(unconditional)    ::= .
  where(conditional)      ::= K_WHERE expression(condition).
  
  /* XXX Allow database-qualified names.  */
  column_name(cn)         ::= L_NAME(name).
  generator_name(unqualified) ::= L_NAME(name).
  backend_name(bn)        ::= L_NAME(name).
  population_name(pn)     ::= L_NAME(name).
  table_name(unqualified) ::= L_NAME(name).
  
  model_token             ::= K_MODEL.
  model_token             ::= K_MODELS.
  
  group_by(none)          ::= .
  group_by(some)          ::= K_GROUP K_BY expressions(keys).
  group_by(having)        ::= K_GROUP K_BY expressions(keys)
                                  K_HAVING expression(cond).
  
  order_by(none)          ::= .
  order_by(some)          ::= K_ORDER K_BY order_keys(keys).
  order_keys(one)         ::= order_key(key).
  order_keys(many)        ::= order_keys(keys) T_COMMA order_key(key).
  order_key(k)            ::= expression(e) order_sense(s).
  order_sense(none)       ::= .
  order_sense(asc)        ::= K_ASC.
  order_sense(desc)       ::= K_DESC.
  
  accuracy_opt(none)      ::= .
  accuracy_opt(some)      ::= K_ACCURACY L_INTEGER(acc).
  
  limit_opt(none)         ::= .
  limit_opt(some)         ::= limit(lim).
  
  limit(n)                ::= K_LIMIT expression(limit).
  limit(offset)           ::= K_LIMIT expression(limit)
                                  K_OFFSET expression(offset).
  limit(comma)            ::= K_LIMIT expression(offset)
                                  T_COMMA expression(limit).
  
  expressions_opt(none)   ::= .
  expressions_opt(some)   ::= expressions(es).
  
  expressions(one)        ::= expression(e).
  expressions(many)       ::= expressions(es) T_COMMA expression(e).
  
  expression(top)         ::= boolean_or(e).
  
  boolean_or(or)          ::= boolean_or(l) K_OR boolean_and(r).
  boolean_or(and)         ::= boolean_and(a).
  
  boolean_and(and)        ::= boolean_and(l) K_AND boolean_not(r).
  boolean_and(not)        ::= boolean_not(n).
  
  boolean_not(not)        ::= K_NOT boolean_not(n).
  boolean_not(equality)   ::= equality(c).
  
  equality(is)            ::= equality(l) K_IS ordering(r).
  equality(isnot)         ::= equality(l) K_IS K_NOT ordering(r).
  equality(like)          ::= equality(l) K_LIKE ordering(r).
  equality(notlike)       ::= equality(l) K_NOT K_LIKE ordering(r).
  equality(like_esc)      ::= equality(l) K_LIKE ordering(r)
                                  K_ESCAPE ordering(e).
  equality(notlike_esc)   ::= equality(l) K_NOT K_LIKE ordering(r)
                                  K_ESCAPE ordering(e).
  equality(glob)          ::= equality(l) K_GLOB ordering(r).
  equality(notglob)       ::= equality(l) K_NOT K_GLOB ordering(r).
  equality(glob_esc)      ::= equality(l) K_GLOB ordering(r)
                                  K_ESCAPE ordering(e).
  equality(notglob_esc)   ::= equality(l) K_NOT K_GLOB ordering(r)
                                  K_ESCAPE ordering(e).
  equality(regexp)        ::= equality(l) K_REGEXP ordering(r).
  equality(notregexp)     ::= equality(l) K_NOT K_REGEXP ordering(r).
  equality(regexp_esc)    ::= equality(l) K_REGEXP ordering(r)
                                  K_ESCAPE ordering(e).
  equality(notregexp_esc) ::= equality(l) K_NOT K_REGEXP ordering(r)
                                  K_ESCAPE ordering(e).
  equality(match)         ::= equality(l) K_MATCH ordering(r).
  equality(notmatch)      ::= equality(l) K_NOT K_MATCH ordering(r).
  equality(match_esc)     ::= equality(l) K_MATCH ordering(r)
                                  K_ESCAPE ordering(e).
  equality(notmatch_esc)  ::= equality(l) K_NOT K_MATCH ordering(r)
                                  K_ESCAPE ordering(e).
  equality(between)       ::= equality(m) K_BETWEEN ordering(l)
                                  K_AND ordering(r).
  equality(notbetween)    ::= equality(m) K_NOT K_BETWEEN ordering(l)
                                  K_AND ordering(r).
  equality(in_query)      ::= equality(e) K_IN T_LROUND query(q) T_RROUND.
  equality(notin_query)   ::= equality(e) K_NOT K_IN T_LROUND query(q) T_RROUND.
  equality(in_exp)        ::= equality(e) K_IN T_LROUND expressions_opt(es)
                                  T_RROUND.
  equality(notin_exp)     ::= equality(e) K_NOT K_IN T_LROUND expressions_opt(es)
                                  T_RROUND.
  equality(isnull)        ::= equality(e) K_ISNULL.
  equality(notnull)       ::= equality(e) K_NOTNULL.
  equality(neq)           ::= equality(l) T_NEQ ordering(r).
  equality(eq)            ::= equality(l) T_EQ ordering(r).
  equality(ordering)      ::= ordering(o).
  
  ordering(lt)            ::= ordering(l) T_LT bitwise(r).
  ordering(leq)           ::= ordering(l) T_LEQ bitwise(r).
  ordering(geq)           ::= ordering(l) T_GEQ bitwise(r).
  ordering(gt)            ::= ordering(l) T_GT bitwise(r).
  ordering(bitwise)       ::= bitwise(b).
  
  bitwise(and)            ::= bitwise(l) T_BITAND additive(r).
  bitwise(ior)            ::= bitwise(l) T_BITIOR additive(r).
  bitwise(lshift)         ::= bitwise(l) T_LSHIFT additive(r).
  bitwise(rshift)         ::= bitwise(l) T_RSHIFT additive(r).
  bitwise(additive)       ::= additive(a).
  
  additive(add)           ::= additive(l) T_PLUS multiplicative(r).
  additive(sub)           ::= additive(l) T_MINUS multiplicative(r).
  additive(mult)          ::= multiplicative(m).
  
  multiplicative(mul)     ::= multiplicative(l) T_STAR concatenative(r).
  multiplicative(div)     ::= multiplicative(l) T_SLASH concatenative(r).
  multiplicative(rem)     ::= multiplicative(l) T_PERCENT concatenative(r).
  multiplicative(conc)    ::= concatenative(c).
  
  concatenative(concat)   ::= concatenative(l) T_CONCAT collating(r).
  concatenative(collate)  ::= collating(c).
  
  collating(collate)      ::= collating(e) K_COLLATE L_NAME|L_STRING(c).
  collating(unary)        ::= unary(u).
  
  unary(bitwise_not)      ::= T_BITNOT unary(u).
  unary(minus)            ::= T_MINUS unary(u).
  unary(plus)             ::= T_PLUS unary(u).
  unary(bql)              ::= bqlfn(b).
  
  /*
   * The BQL functions come in five flavours:
   *
   * (1) Functions of two columns: DEPENDENCE PROBABILITY.
   * (2) Functions of one column: DEPENDENCE PROBABILITY WITH C.
   * (3) Functions of two rows: SIMILARITY IN THE CONTEXT OF C.
   * (4) Functions of one row: SIMILARITY TO 5 IN THE CONTEXT OF C.
   * (5) Constants: DEPENDENCE PROBABILITY OF C WITH D.
   *
   * Although constants can appear in any context (subject to the
   * constraint that a table is implied by context -- really, the table
   * should be named in the expression), any context for an expression
   * makes sense with only one flavour of functions.  For example:
   *
   * (1) ESTIMATE DEPENDENCE PROBABILITY PAIRWISE COLUMNS OF FROM T;
   * (2) ESTIMATE * FROM COLUMNS OF T ORDER BY DEPENDENCE PROBABILITY WITH C;
   * (3) SELECT SIMILARITY TO 5 IN THE CONTEXT OF C FROM T;
   *
   * It makes no sense to say
   *
   *      ESTIMATE * FROM COLUMNS OF T WHERE DEPENDENCE PROBABILITY > 5,
   *
   * because WHERE filters a single set of columns, so there's no second
   * argument for DEPENDENCE PROBABILITY.  Similarly, it makes no sense
   * to say
   *
   *      ESTIMATE * FROM COLUMNS OF T
   *              WHERE SIMILARITY TO 5 IN THE CONTEXT OF C > 5,
   *
   * because SIMILARITY TO 5 IN THE CONTEXT OF C is a function of a row,
   * not a function of a column.
   *
   * We could invent four different expression nonterminals alike in
   * every way except for which of these options are allowed, but it
   * would require duplicating all the other rules for expressions, so
   * instead we'll do a simple-minded type-check after parsing to detect
   * such mistakes as the above.
   *
   * It is tempting to split the `bqlfn' nonterminal into `bql2colfn',
   * `bql1colfn', `bqlrowfn', `bqlconstfn', but we used to have an
   * ambiguity here.
   *
   * XXX It would be nice if
   *
   *      SELECT PROBABILITY DENSITY OF X = 1 - PROBABILITY DENSITY OF Y = 0
   *          FROM T;
   *
   * worked to mean
   *
   *      SELECT PROBABILITY DENSITY OF X = (1 - PROBABILITY DENSITY OF Y = 0)
   *          FROM T;
   *
   * so that you could also write, e.g.,
   *
   *      SELECT PROBABILITY DENSITY OF X = A + B FROM T;
   *
   * with A + B meaning the right-hand side of the equation.
   *
   * However, changing primary(e) to expression(e) on the right-hand
   * side of the bqlfn(prob) rule makes the grammar ambiguous, and the
   * surgery necessary to resolve the ambiguity is too much trouble.  So
   * instead we'll reject unparenthesized PROBABILITY DENSITY OF X = V
   * with other operators altogether and require explicit parentheses
   * until someone wants to do that surgery.
   *
   * XXX Oops -- some restructing of the grammar caused us to cease
   * rejecting unparenthesized PROBABILITY DENSITY OF X = V with other
   * operators.
   */
  bqlfn(predprob_row)     ::= K_PREDICTIVE K_PROBABILITY K_OF column_list(target).
  
  bqlfn(jpredprob_row)    ::= K_PREDICTIVE K_PROBABILITY K_OF
                                  T_LROUND column_lists(targets) T_RROUND.
  
  bqlfn(condpredprob_row) ::= K_PREDICTIVE K_PROBABILITY K_OF column_list(target)
                                  K_GIVEN T_LROUND column_lists(constraints)
                                  T_RROUND.
  
  bqlfn(condjpredprob_row) ::= K_PREDICTIVE K_PROBABILITY K_OF
                                  T_LROUND column_lists(targets) T_RROUND
                                  K_GIVEN T_LROUND column_lists(constraints)
                                  T_RROUND.
  
  bqlfn(prob_const)       ::= K_PROBABILITY K_DENSITY K_OF column_name(col)
                                  T_EQ unary(e).
  bqlfn(jprob_const)      ::= K_PROBABILITY K_DENSITY K_OF
                                  T_LROUND constraints_opt(targets) T_RROUND.
  bqlfn(condprob_const)   ::= K_PROBABILITY K_DENSITY K_OF column_name(col)
                                  T_EQ primary(e)
                                  K_GIVEN T_LROUND constraints_opt(constraints)
                                          T_RROUND.
  bqlfn(condjprob_const)  ::= K_PROBABILITY K_DENSITY K_OF
                                  T_LROUND constraints_opt(targets) T_RROUND
                                  K_GIVEN T_LROUND constraints_opt(constraints)
                                          T_RROUND.
  /* XXX Givens for PROBABILITY DENSITY OF VALUE function of columns?      */
  bqlfn(prob_1col)        ::= K_PROBABILITY K_DENSITY K_OF K_VALUE unary(e).
  bqlfn(condprob_1col)    ::= K_PROBABILITY K_DENSITY K_OF K_VALUE primary(e)
                                  K_GIVEN T_LROUND constraints_opt(constraints)
                                          T_RROUND.
  bqlfn(sim_const)         ::= K_SIMILARITY
                                  K_OF T_LROUND expression(cond0) T_RROUND
                                  K_TO T_LROUND expression(cond1) T_RROUND
                                  wrt(col).
  bqlfn(sim_1row)         ::= K_SIMILARITY K_TO
                                  T_LROUND expression(cond) T_RROUND
                                  wrt(col).
  bqlfn(sim_2row)         ::= K_SIMILARITY wrt(col).
  
  bqlfn(predrel_existing)          ::= K_PREDICTIVE K_RELEVANCE
                                          predrel_of_opt(cond0)
                                          K_TO existing_rows(cond1)
                                          wrt(col).
  
  bqlfn(predrel_hypothetical)      ::= K_PREDICTIVE K_RELEVANCE
                                          predrel_of_opt(cond0)
                                          K_TO hypothetical_rows(constraints)
                                          wrt(col).
  
  bqlfn(predrel_both)              ::= K_PREDICTIVE K_RELEVANCE
                                          predrel_of_opt(cond0)
                                          K_TO existing_rows(cond1)
                                          K_AND hypothetical_rows(constraints)
                                          wrt(col).
  
  bqlfn(depprob)          ::= K_DEPENDENCE K_PROBABILITY ofwith(cols).
  
  bqlfn(mutinf)           ::= K_MUTUAL K_INFORMATION ofwithmulti(cols)
                                  mi_given_opt(constraints) nsamples_opt(nsamp).
  bqlfn(prob_est)         ::= K_PROBABILITY K_OF T_LROUND expression(e) T_RROUND.
  
  predrel_of_opt(none)    ::= .
  predrel_of_opt(one)     ::= K_OF T_LROUND expression(cond0) T_RROUND.
  
  existing_rows(one)      ::= K_EXISTING K_ROWS
                                  T_LROUND expression(cond) T_RROUND.
  
  hypothetical_rows(one)  ::= K_HYPOTHETICAL K_ROWS K_WITH K_VALUES
                                  T_LROUND constraints_list(cs) T_RROUND.
  
  ofwithmulti(bql_2col)   ::= .
  ofwithmulti(bql_1col)   ::= K_WITH mi_columns(cols).
  ofwithmulti(bql_const)  ::= K_OF mi_columns(cols0) K_WITH mi_columns(cols1).
  
  mi_columns(one)         ::= column_name(col).
  mi_columns(many)        ::= T_LROUND mi_column_list(cols) T_RROUND.
  
  mi_column_list(one)     ::= column_name(col).
  mi_column_list(many)    ::= mi_column_list(cols) T_COMMA column_name(col).
  
  mi_given_opt(none)      ::= .
  mi_given_opt(some)      ::= K_GIVEN T_LROUND mi_constraints(constraints) T_RROUND.
  
  mi_constraints(one)     ::= mi_constraint(c).
  mi_constraints(many)    ::= mi_constraints(cs) T_COMMA mi_constraint(c).
  
  mi_constraint(equality) ::= column_name(col) T_EQ expression(value).
  mi_constraint(marginal) ::= column_name(col).
  
  bqlfn(correl)           ::= K_CORRELATION ofwith(cols).
  bqlfn(correl_pval)      ::= K_CORRELATION K_PVALUE ofwith(cols).
  bqlfn(predict)          ::= K_PREDICT column_name(col) withconf(conf)
                                  nsamples_opt(nsamp).
  bqlfn(primary)          ::= primary(p).
  
  /*
   * Parenthesizing the column lists is not what we did before, but is
   * necessary to avoid ambiguity at the comma: is it another select
   * column, or is it another wrt column?
   */
  wrt(one)                ::= K_IN K_THE K_CONTEXT K_OF column_list(col).
  
  ofwith(bql_2col)        ::= .
  ofwith(bql_1col)        ::= K_WITH column_name(col).
  ofwith(bql_const)       ::= K_OF column_name(col1) K_WITH column_name(col2).
  
  nsamples_opt(none)      ::= .
  nsamples_opt(some)      ::= K_USING primary(nsamples) K_SAMPLES.
  
  column_lists(one)       ::= column_list(collist).
  column_lists(many)      ::= column_lists(collists)
                                  T_COMMA|K_AND column_list(collist).
  column_list(all)        ::= T_STAR.
  column_list(column)     ::= column_name(col).
  /*
   * XXX Should really allow any SELECT on a table of columns.  But
   * until we have that notion, are there any other kinds of subqueries
   * that make sense here?
   */
  column_list(subquery)   ::= T_LROUND query(q) T_RROUND.
  
  primary(literal)        ::= literal(v).
  primary(numpar)         ::= L_NUMPAR(n).
  primary(nampar)         ::= L_NAMPAR(n).
  primary(apply)          ::= L_NAME(fn) T_LROUND expressions_opt(es) T_RROUND.
  primary(apply_distinct) ::= L_NAME(fn) T_LROUND K_DISTINCT expressions_opt(es)
                                  T_RROUND.
  primary(apply_star)     ::= L_NAME(fn) T_LROUND T_STAR T_RROUND.
  primary(paren)          ::= T_LROUND expression(e) T_RROUND.
  primary(subquery)       ::= T_LROUND query(q) T_RROUND.
  primary(cast)           ::= K_CAST T_LROUND expression(e)
                                  K_AS type(t) T_RROUND.
  primary(exists)         ::= K_EXISTS T_LROUND query(q) T_RROUND.
  primary(column)         ::= column_name(col).
  primary(tabcol)         ::= table_name(tab) T_DOT column_name(col).
  primary(case)           ::= K_CASE case_key_opt(k) case_whens_opt(ws)
                                  case_else_opt(e) K_END.
  /*
   * XXX To do:
   *
   * - RAISE (IGNORE|ROLLBACK|ABORT|FAIL, "message")
   */
  
  case_key_opt(none)      ::= .
  case_key_opt(some)      ::= expression(k).
  
  case_whens_opt(none)    ::= .
  case_whens_opt(some)    ::= case_whens_opt(ws) K_WHEN expression(w)
                                  K_THEN expression(t).
  
  case_else_opt(none)     ::= .
  case_else_opt(some)     ::= K_ELSE expression(e).
  
  literal(null)           ::= K_NULL.
  literal(integer)        ::= L_INTEGER(i).
  literal(float)          ::= L_FLOAT(f).
  literal(string)         ::= L_STRING(s).
  
  type(name)              ::= typename(n).
  type(onearg)            ::= typename(n) T_LROUND typearg(a) T_RROUND.
  type(twoarg)            ::= typename(n) T_LROUND typearg(a) T_COMMA
                                  typearg(b) T_RROUND.
  typename(one)           ::= L_NAME(n).
  typename(many)          ::= typename(tn) L_NAME(n).
  typearg(unsigned)       ::= L_INTEGER(i).
  typearg(positive)       ::= T_PLUS L_INTEGER(i).
  typearg(negative)       ::= T_MINUS L_INTEGER(i).
  
  /*
   * XXX For some reason, putting CASE and WHEN here break the parser.
   * Needs further analysis.
   *
   *      grep -o 'K_[A-Z_]*' | awk '{ printf("\t%s\n", $1) }' | sort -u
   */
  %fallback L_NAME
          K_ACCURACY
          K_ADD
          K_ALL
          K_ALTER
          K_ANALYZE
          K_AND
          K_AS
          K_ASC
          K_BEGIN
          K_BETWEEN
          K_BTABLE
          K_BY
          /* K_CASE */
          K_CAST
          K_CHECKPOINT
          K_COLLATE
          K_COLUMN
          K_COLUMNS
          K_COMMIT
          K_CONF
          K_CONFIDENCE
          K_CONTEXT
          K_CORRELATION
          K_CREATE
          K_DEFAULT
          K_DENSITY
          K_DEPENDENCE
          K_DESC
          K_DISTINCT
          K_DROP
          K_ELSE
          K_END
          K_ESCAPE
          K_ESTIMATE
          K_EXISTS
          K_EXISTING
          K_EXPLICIT
          K_FOR
          K_FROM
          K_GENERATOR
          K_GIVEN
          K_GLOB
          K_GROUP
          K_GUESS
          K_HAVING
          K_HYPOTHETICAL
          K_IF
          K_IGNORE
          K_IN
          K_INFER
          K_INFORMATION
          K_INITIALIZE
          K_IS
          K_ISNULL
          K_ITERATION
          K_ITERATIONS
          K_LATENT
          K_LIKE
          K_LIMIT
          K_MATCH
          K_MINUTE
          K_MINUTES
          K_MODEL
          K_MODELED
          K_MODELLED
          K_MODELS
          K_MUTUAL
          K_NOT
          K_NOTNULL
          K_NULL
          K_OF
          K_OFFSET
          K_OR
          K_ORDER
          K_PAIRWISE
          K_POPULATION
          K_PREDICT
          K_PREDICTIVE
          K_PROBABILITY
          K_PVALUE
          K_REGEXP
          K_REGRESS
          K_RELEVANCE
          K_RENAME
          K_ROLLBACK
          K_ROW
          K_ROWS
          K_SAMPLES
          K_SCHEMA
          K_SECOND
          K_SECONDS
          K_SELECT
          K_SET
          K_SIMILARITY
          K_SIMULATE
          K_STATTYPE
          K_STATTYPES
          K_TABLE
          K_TEMP
          K_TEMPORARY
          K_THE
          K_THEN
          K_TO
          K_UNSET
          K_USING
          K_VALUE
          K_VALUES
          K_VARIABLE
          K_VARIABLES
          /* K_WHEN */
          K_WHERE
          K_WITH
          K_WITHIN
          .
  
  /*
   * Wildcard token, matches anything else.
   */
  %wildcard ANY.
  
files: src/grammar.y
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  """BQL parser front end."""
  
  import StringIO
  
  import bayeslite.ast as ast
  import bayeslite.grammar as grammar
  import bayeslite.scan as scan
  from bayeslite.exception import BQLParseError
  
  def parse_bql_phrases(scanner):
      semantics = BQLSemantics()
      parser = grammar.Parser(semantics)
      while not semantics.failed:
          token = scanner.read()
          semantics.context.append(token)
          if token[0] == -1:      # error
              semantics.syntax_error(token)
          else:
              if token[0] == 0:   # EOF
                  # Implicit ; at EOF.
                  parser.feed((grammar.T_SEMI, ''))
              parser.feed(token)
          if semantics.phrase is not None:
              phrase = semantics.phrase
              semantics.phrase = None
              if 0 < len(semantics.errors):
                  # Keep parsing in order to detect more errors, but
                  # don't yield any broken phrases in case the caller
                  # will try to process them before we finish parsing
                  # the whole thing.
                  continue
              if 0 < scanner.n_numpar:
                  n_numpar = scanner.n_numpar
                  nampar_map = scanner.nampar_map
                  yield ast.Parametrized(phrase, n_numpar, nampar_map)
              else:
                  yield phrase
          if token[0] == 0:       # EOF
              break
      if 0 < len(semantics.errors):
          raise BQLParseError(semantics.errors)
      if semantics.failed:
          raise BQLParseError(['parse failed mysteriously!'])
  
  def parse_bql_string_pos(string):
      """Yield ``(phrase, pos)`` for each BQL phrase in `string`.
  
      `phrase` is the parsed AST.  `pos` is zero-based index of the code
      point at which `phrase` starts.
      """
      scanner = scan.BQLScanner(StringIO.StringIO(string), '(string)')
      phrases = parse_bql_phrases(scanner)
      # XXX Don't dig out internals of scanner: fix plex to have a
      # public API for finding the current position.
      return ((phrase, scanner.cur_pos) for phrase in phrases)
  
  def parse_bql_string_pos_1(string):
      """Return ``(phrase, pos)`` for the first BQL phrase in `string`.
  
      May not report parse errors afterward.
      """
      for phrase, pos in parse_bql_string_pos(string):
          return (phrase, pos)
      return None
  
  def parse_bql_string(string):
      """Yield each parsed BQL phrase AST in `string`."""
      return (phrase for phrase, _pos in parse_bql_string_pos(string))
  
  def bql_string_complete_p(string):
      """True if `string` has at least one complete BQL phrase or error.
  
      False if empty or if the last BQL phrase is incomplete.
      """
      scanner = scan.BQLScanner(StringIO.StringIO(string), '(string)')
      semantics = BQLSemantics()
      parser = grammar.Parser(semantics)
      nonsemi = False
      while not semantics.failed:
          token = scanner.read()
          if token[0] == -1:      # error
              # Say it's complete so the caller will try to parse it and
              # choke on the error.
              return True
          elif token[0] == 0:
              # EOF.  Hope we have a complete phrase.
              break
          elif token[0] != grammar.T_SEMI:
              # Got a non-semicolon token.  Clear any previous phrase,
              # if we had one.
              nonsemi = True
              semantics.phrase = None
          parser.feed(token)
      if 0 < len(semantics.errors):
          return True
      if semantics.failed:
          return True
      return (not nonsemi) or (semantics.phrase is not None)
  
  class BQLSemantics(object):
      def __init__(self):
          self.phrase = None
          self.context = []
          self.errors = []
          self.failed = False
  
      def accept(self):
          pass
      def parse_failed(self):
          self.failed = True
      def syntax_error(self, (number, text)):
          # XXX Adapt lemonade to help us identify what the allowed
          # subsequent tokens are.
          #
          # XXX Record source position.
          if number == -1:        # error
              self.errors.append('Skipping bad token: %s' % (text,))
          else:
              self.errors.append('Syntax error near [%s] after [%s]' % (
                  text, ' '.join([str(tok) for (pos, tok) in self.context[:-1]])))
  
      def p_bql_start(self, phrases):
          pass
      def p_phrases_none(self):
          pass
      def p_phrases_some(self, phrases, phrase):
          pass
  
      def p_phrase_opt_none(self):
          pass
      def p_phrase_opt_some(self, phrase):
          assert self.phrase is None
          self.phrase = phrase
  
      def p_phrase_command(self, c):              return c
      def p_phrase_query(self, q):                return q
  
      # Transactions
      def p_command_begin(self):                  return ast.Begin()
      def p_command_rollback(self):               return ast.Rollback()
      def p_command_commit(self):                 return ast.Commit()
  
      # SQL Data Definition Language subset
      def p_command_createtab_as(self, temp, ifnotexists, name, query):
          return ast.CreateTabAs(temp, ifnotexists, name, query)
      def p_command_createtab_csv(self, temp, ifnotexists, name, csv):
          return ast.CreateTabCsv(temp, ifnotexists, name, csv)
      def p_command_droptab(self, ifexists, name):
          return ast.DropTab(ifexists, name)
      def p_command_altertab(self, table, cmds):
          return ast.AlterTab(table, cmds)
  
      def p_pathname_p(self, name):               return name
  
      def p_altertab_cmds_one(self, cmd):         return [cmd]
      def p_altertab_cmds_many(self, cmds, cmd):  cmds.append(cmd); return cmds
      def p_altertab_cmd_renametab(self, name):
          return ast.AlterTabRenameTab(name)
      def p_altertab_cmd_renamecol(self, old, new):
          return ast.AlterTabRenameCol(old, new)
  
      # BQL Model Definition Language
      def p_command_guess_schema(self, table):
          return ast.GuessSchema(table)
  
      def p_command_create_pop(self, ifnotexists, name, table, schema):
          return ast.CreatePop(ifnotexists, name, table, schema)
      def p_command_drop_pop(self, ifexists, name):
          return ast.DropPop(ifexists, name)
      def p_command_alterpop(self, population, cmds):
          return ast.AlterPop(population, cmds)
  
      def p_alterpop_cmds_one(self, cmd):         return [cmd]
      def p_alterpop_cmds_many(self, cmds, cmd):  cmds.append(cmd); return cmds
      def p_alterpop_cmd_stattype(self, cols, stattype):
          return ast.AlterPopStatType(cols, stattype)
      def p_alterpop_cmd_addvar(self, col, st):
          return ast.AlterPopAddVar(col, st)
  
      def p_pop_schema_one(self, cl):             return [cl]
      def p_pop_schema_many(self, schema, cl):
          if cl:
              schema.append(cl);
          return schema
  
      def p_pop_clause_empty(self):            return None
      def p_pop_clause_column(self, col, st):  return ast.PopModelVars([col], st)
      def p_pop_clause_stattype(self, cols, st): return ast.PopModelVars(cols, st)
      def p_pop_clause_ignore(self, cols):     return ast.PopIgnoreVars(cols)
      def p_pop_clause_guess(self, cols):      return ast.PopGuessVars(cols)
  
      def p_model_opt_none(self):              return None
      def p_model_opt_one(self):               return None
      def p_as_opt_none(self):                 return None
      def p_as_opt_one(self):                  return None
  
      def p_stattype_opt_none(self):           return None
      def p_stattype_opt_one(self, st):        return st
      def p_stattype_st(self, name):           return name
  
      def p_pop_columns_one(self, c):          return [c]
      def p_pop_columns_many(self, cols, c):   cols.append(c); return cols
  
      def p_pop_columns_guess_star(self):       return '*'
      def p_pop_columns_guess_list(self, cols): return cols
  
      def p_command_creategen(self, ifnotexists0, name, ifnotexists1, pop,
              backend, schema):
          ifnotexists = ifnotexists0 or ifnotexists1
          return ast.CreateGen(name, ifnotexists, pop, backend, schema)
      def p_command_dropgen(self, ifexists, name):
          return ast.DropGen(ifexists, name)
      def p_command_altergen(self, generator, models, cmds):
          return ast.AlterGen(generator, models, cmds)
  
      def p_altergen_cmds_one(self, cmd):         return [cmd]
      def p_altergen_cmds_many(self, cmds, cmd):  cmds.append(cmd); return cmds
      def p_altergen_cmd_renamegen(self, name):
          return ast.AlterGenRenameGen(name)
      def p_altergen_cmd_generic(self, s):
          return ast.AlterGenGeneric(s)
  
      def p_backend_name_opt_none(self):            return None
      def p_backend_name_opt_one(self, backend):    return backend
  
      def p_generator_schema_opt_none(self):      return [[]]
      def p_generator_schema_opt_some(self, s):   return s
      def p_generator_schema_one(self, s):        return [s]
      def p_generator_schema_many(self, ss, s):   ss.append(s); return ss
      def p_generator_schemum_empty(self):        return []
      def p_generator_schemum_nonempty(self, s, t): s.append(t); return s
      def p_gs_token_prim(self, t):               return t
      def p_gs_token_comp(self, s):               return s
  
      def p_stattype_s(self, name):
          return name
  
      # BQL Model Analysis Language
      def p_command_init_models(self, n, ifnotexists, generator):
          return ast.InitModels(ifnotexists, generator, n)
      def p_command_analyze_models(
              self, generator, models, anlimit, anckpt, program):
          iters = [lim[1] for lim in anlimit if lim and lim[0] == 'iterations']
          secs = [lim[1] for lim in anlimit if lim and lim[0] == 'seconds']
          iterations = min(iters) if iters else None
          seconds = min(secs) if secs else None
          ckpt_iterations = None
          ckpt_seconds = None
          if anckpt is not None:
              ckpt_iterations = anckpt[1] if anckpt[0] == 'iterations' else None
              ckpt_seconds = anckpt[1] if anckpt[0] == 'seconds' else None
          return ast.AnalyzeModels(generator, models, iterations, seconds,
              ckpt_iterations, ckpt_seconds, program)
      def p_command_drop_models(self, models, generator):
          return ast.DropModels(generator, models)
  
      def p_temp_opt_none(self):                  return False
      def p_temp_opt_some(self):                  return True
      def p_ifexists_none(self):                  return False
      def p_ifexists_some(self):                  return True
      def p_ifnotexists_none(self):               return False
      def p_ifnotexists_some(self):               return True
  
      def p_anmodelset_opt_none(self):            return None
      def p_anmodelset_opt_some(self, m):         return sorted(m)
      def p_anmodelset_matched_opt_none(self):    return None
      def p_anmodelset_matched_opt_some(self, m): return sorted(m)
      def p_modelset_opt_none(self):              return None
      def p_modelset_opt_some(self, m):           return sorted(m)
      def p_modelset_one(self, r):                return r
      def p_modelset_many(self, m, r):            m += r; return m
      def p_modelrange_single(self, modelno):     return [modelno]
      def p_modelrange_multi(self, minno, maxno): return range(minno, maxno + 1)
  
      def p_anlimit_one(self, duration):             return (duration, None)
      def p_anlimit_two(self, duration0, duration1): return (duration0, duration1)
      def p_anckpt_opt_none(self):                return None
      def p_anckpt_opt_some(self, duration):      return duration
  
      def p_anduration_iterations(self, n):       return ('iterations', n)
      def p_anduration_minutes(self, n):          return ('seconds', 60*n)
      def p_anduration_seconds(self, n):          return ('seconds', n)
  
      def p_analysis_program_opt_none(self):      return None
      def p_analysis_program_opt_some(self, p):   return p
      def p_analysis_program_empty(self):         return []
      def p_analysis_program_nonempty(self, p, t): p += t; return p
      def p_analysis_token_compound(self, p):     return ['('] + p + [')']
      def p_analysis_token_primitive(self, t):    return [t]
  
      def p_command_regress(self, target, givens, nsamp, pop, generator,
              modelnos):
          return ast.Regress(target, givens, nsamp, pop, generator, modelnos)
  
      def p_simulate_s(self, cols, population, generator, modelnos, constraints,
              lim, acc):
          for c in cols:
              if isinstance(c, ast.SelColSub):
                  continue
              if isinstance(c, ast.SelColExp) and \
                 isinstance(c.expression, ast.ExpCol):
                  continue
              self.errors.append('simulate only accepts population variables.')
              return None
          return ast.Simulate(
              cols, population, generator, modelnos, constraints, lim.limit, acc)
      def p_simulate_nolimit(self, cols, population, generator, modelnos,
              constraints):
          # XXX Report source location.
          self.errors.append('simulate missing limit')
          for c in cols:
              if isinstance(c, ast.SelColSub):
                  continue
              if isinstance(c, ast.SelColExp) and \
                 isinstance(c.expression, ast.ExpCol):
                  continue
              self.errors.append('simulate only accepts population variables.')
              return None
          return ast.Simulate(
              cols, population, generator, modelnos, constraints, 0, None)
      def p_simulate_models(self, cols, population, generator):
          return ast.SimulateModelsExp(cols, population, generator)
  
      def p_given_opt_none(self):                 return []
      def p_given_opt_some(self, constraints):    return constraints
      def p_constraints_one(self, c):             return [c]
      def p_constraints_many(self, cs, c):        cs.append(c); return cs
      def p_constraint_c(self, col, value):       return (col, value)
      def p_constraints_opt_none(self):           return []
      def p_constraints_opt_some(self, cs):       return cs
      def p_constraints_list_one(self, cs):       return [cs]
      def p_constraints_list_some(self, css, cs): css.append(cs); return css
  
      def p_query_select(self, q):                return q
      def p_query_estimate(self, q):              return q
      def p_query_estcol(self, q):                return q
      def p_query_estpairrow(self, q):            return q
      def p_query_estpaircol(self, q):            return q
      def p_query_estby(self, q):                 return q
      def p_query_infer(self, q):                 return q
      def p_query_simulate(self, q):              return q
      def p_query_estimate_pairwise_row(self, q): return q
      def p_query_create_column_list(self, q):    return q
  
      def p_select_s(self, quant, cols, tabs, cond, grouping, ord, lim):
          return ast.Select(quant, cols, tabs, cond, grouping, ord, lim)
  
      def p_estimate_e(self, quant, cols, tabs, generator, modelnos, cond,
              grouping, ord, lim):
          constructor = tabs
          return constructor(quant, cols, generator, modelnos, cond, grouping,
              ord, lim)
  
      def p_estcol_e(self):
          self.errors.append("deprecated `ESTIMATE COLUMNS'"
              ": use `ESTIMATE ... FROM COLUMNS OF'")
      def p_estpairrow_e(self):
          self.errors.append("deprecated `ESTIMATE PAIRWISE ROW'"
              ": use `ESTIMATE ... FROM PAIRWISE'")
      def p_estpaircol_e(self):
          self.errors.append("deprecated `ESTIMATE PAIRWISE'"
              ": use `ESTIMATE ... FROM PAIRWISE COLUMNS OF'")
  
      def p_estby_e(self, quant, cols, population, generator, modelnos):
          return ast.EstBy(quant, cols, population, generator, modelnos)
  
      def p_infer_auto(self, cols, conf, nsamp, population, generator, modelnos,
              cond, grouping, ord, lim):
          return ast.InferAuto(cols, conf, nsamp, population, generator, modelnos,
              cond, grouping, ord, lim)
      def p_infer_explicit(self, cols, population, generator, modelnos, cond,
              grouping, ord, lim):
          return ast.InferExplicit(cols, population, generator, modelnos, cond,
              grouping, ord, lim)
  
      def p_infer_auto_columns_one(self, c):      return [c]
      def p_infer_auto_columns_many(self, cs, c): cs.append(c); return cs
  
      def p_infer_auto_column_all(self):
          return ast.InfColAll()
      def p_infer_auto_column_one(self, col, name):
          return ast.InfColOne(col, name)
  
      def p_withconf_opt_none(self):
          return ast.ExpLit(ast.LitInt(0))
      def p_withconf_opt_some(self, conf):        return conf
      def p_withconf_conf(self, conf):            return conf
  
      def p_infer_exp_columns_one(self, c):       return [c]
      def p_infer_exp_columns_many(self, cs, c):  cs.append(c); return cs
      def p_infer_exp_column_sel(self, c):        return c
      def p_infer_exp_column_pred(self, col, name, confname, nsamp):
          return ast.PredCol(col, name, confname, nsamp)
  
      def p_conf_opt_none(self):                  return None
      def p_conf_opt_some(self, confname):        return confname
  
      def p_select_quant_distinct(self):          return ast.SELQUANT_DISTINCT
      def p_select_quant_all(self):               return ast.SELQUANT_ALL
      def p_select_quant_default(self):           return ast.SELQUANT_ALL
  
      def p_select_columns_one(self, c):          return [c]
      def p_select_columns_many(self, cs, c):     cs.append(c); return cs
  
      def p_select_column_star(self):             return ast.SelColAll(None)
      def p_select_column_qstar(self, table):     return ast.SelColAll(table)
      def p_select_column_qsub(self, table, q):   return ast.SelColSub(table, q)
      def p_select_column_exp(self, e, name):     return ast.SelColExp(e, name)
  
      def p_as_none(self):                        return None
      def p_as_some(self, name):                  return name
  
      def p_from_sel_opt_empty(self):             return None
      def p_from_sel_opt_nonempty(self, tables):  return tables
  
      def p_from_est_row(self, name):
          def c(quant, cols, generator, modelnos, cond, grouping, ord, lim):
              return ast.Estimate(quant, cols, name, generator, modelnos, cond,
                  grouping, ord, lim)
          return c
      def p_from_est_pairrow(self, name):
          def c(quant, cols, generator, modelnos, cond, grouping, ord, lim):
              return ast.EstPairRow(cols, name, generator, modelnos, cond, ord,
                  lim)
          return c
      def p_from_est_col(self, name):
          def c(quant, cols, generator, modelnos, cond, grouping, ord, lim):
              return ast.EstCols(cols, name, generator, modelnos, cond, ord, lim)
          return c
      def p_from_est_paircol(self, name, subcols):
          def c(quant, cols, generator, modelnos, cond, grouping, ord, lim):
              return ast.EstPairCols(cols, name, subcols, generator, modelnos,
                  cond, ord, lim)
          return c
  
      def p_modeledby_opt_none(self):             return None
      def p_modeledby_opt_some(self, gen):        return gen
  
      def p_usingmodel_opt_none(self):            return None
      def p_usingmodel_opt_some(self, modelnos):  return modelnos
  
      def p_select_tables_one(self, t):           return [t]
      def p_select_tables_many(self, ts, t):      ts.append(t); return ts
      def p_select_table_named(self, table, name): return ast.SelTab(table, name)
      def p_select_table_subquery(self, q, name):  return ast.SelTab(q, name)
  
      def p_for_none(self):                       return None
      def p_for_one(self, collist):               return collist
  
      def p_where_unconditional(self):            return None
      def p_where_conditional(self, condition):   return condition
  
      def p_column_name_cn(self, name):           return name
      def p_generator_name_unqualified(self, name): return name
      def p_backend_name_bn(self, name):          return name
      def p_population_name_pn(self, name):       return name
      def p_table_name_unqualified(self, name):   return name
  
      def p_group_by_none(self):                  return None
      def p_group_by_some(self, keys):            return ast.Grouping(keys, None)
      def p_group_by_having(self, keys, cond):    return ast.Grouping(keys, cond)
  
      def p_order_by_none(self):                  return None
      def p_order_by_some(self, keys):            return keys
      def p_order_keys_one(self, key):            return [key]
      def p_order_keys_many(self, keys, key):     keys.append(key); return keys
      def p_order_key_k(self, e, s):              return ast.Ord(e, s)
      def p_order_sense_none(self):               return ast.ORD_ASC
      def p_order_sense_asc(self):                return ast.ORD_ASC
      def p_order_sense_desc(self):               return ast.ORD_DESC
  
      def p_limit_opt_none(self):                 return None
      def p_limit_opt_some(self, lim):            return lim
  
      def p_accuracy_opt_none(self):              return None
      def p_accuracy_opt_some(self, acc):         return acc
  
      def p_limit_n(self, limit):                 return ast.Lim(limit, None)
      def p_limit_offset(self, limit, offset):    return ast.Lim(limit, offset)
      def p_limit_comma(self, offset, limit):     return ast.Lim(limit, offset)
  
      def p_expressions_opt_none(self):           return []
      def p_expressions_opt_some(self, es):       return es
  
      def p_expressions_one(self, e):             return [e]
      def p_expressions_many(self, es, e):        es.append(e); return es
  
      def p_expression_top(self, e):      return e
      def p_bqlfn0_exp(self, e):          return e
      def p_expression1_top(self, e):     return e
  
      def p_boolean_or_or(self, l, r):    return ast.op(ast.OP_BOOLOR, l, r)
      def p_boolean_or_and(self, a):      return a
      def p_boolean_and_and(self, l, r):  return ast.op(ast.OP_BOOLAND, l, r)
      def p_boolean_and_not(self, n):     return n
      def p_boolean_not_not(self, n):     return ast.op(ast.OP_BOOLNOT, n)
      def p_boolean_not_equality(self, c):
                                          return c
      def p_equality_is(self, l, r):      return ast.op(ast.OP_IS, l, r)
      def p_equality_isnot(self, l, r):   return ast.op(ast.OP_ISNOT, l, r)
      def p_equality_like(self, l, r):    return ast.op(ast.OP_LIKE, l, r)
      def p_equality_notlike(self, l, r): return ast.op(ast.OP_NOTLIKE, l, r)
      def p_equality_like_esc(self, l, r, e):
                                          return ast.op(ast.OP_LIKE_ESC, l, r, e)
      def p_equality_notlike_esc(self, l, r, e):
                                          return ast.op(ast.OP_NOTLIKE_ESC,
                                              l, r, e)
      def p_equality_glob(self, l, r):    return ast.op(ast.OP_GLOB, l, r)
      def p_equality_notglob(self, l, r): return ast.op(ast.OP_NOTGLOB, l, r)
      def p_equality_glob_esc(self, l, r, e):
                                          return ast.op(ast.OP_GLOB_ESC, l, r, e)
      def p_equality_notglob_esc(self, l, r, e):
                                          return ast.op(ast.OP_NOTGLOB_ESC,
                                              l, r, e)
      def p_equality_regexp(self, l, r):  return ast.op(ast.OP_REGEXP, l, r)
      def p_equality_notregexp(self, l, r):
                                          return ast.op(ast.OP_NOTREGEXP, l, r)
      def p_equality_regexp_esc(self, l, r, e):
                                          return ast.op(ast.OP_REGEXP_ESC,
                                              l, r, e)
      def p_equality_notregexp_esc(self, l, r, e):
                                          return ast.op(ast.OP_NOTREGEXP_ESC,
                                              l, r, e)
      def p_equality_match(self, l, r):   return ast.op(ast.OP_MATCH, l, r)
      def p_equality_notmatch(self, l, r):
                                          return ast.op(ast.OP_NOTMATCH, l, r)
      def p_equality_match_esc(self, l, r, e):
                                          return ast.op(ast.OP_MATCH_ESC, l, r, e)
      def p_equality_notmatch_esc(self, l, r, e):
                                          return ast.op(ast.OP_NOTMATCH_ESC,
                                              l, r, e)
      def p_equality_between(self, m, l, r):
                                          return ast.op(ast.OP_BETWEEN, m, l, r)
      def p_equality_notbetween(self, m, l, r):
                                          return ast.op(ast.OP_NOTBETWEEN, m,l,r)
      def p_equality_in_query(self, e, q): return ast.ExpInQuery(e, True, q)
      def p_equality_notin_query(self, e, q): return ast.ExpInQuery(e, False, q)
      def p_equality_in_exp(self, e, es): return ast.ExpInExp(e, True, es)
      def p_equality_notin_exp(self, e, es): return ast.ExpInExp(e, False, es)
      def p_equality_isnull(self, e):     return ast.op(ast.OP_ISNULL, e)
      def p_equality_notnull(self, e):    return ast.op(ast.OP_NOTNULL, e)
      def p_equality_neq(self, l, r):     return ast.op(ast.OP_NEQ, l, r)
      def p_equality_eq(self, l, r):      return ast.op(ast.OP_EQ, l, r)
      def p_equality_ordering(self, o):   return o
      def p_ordering_lt(self, l, r):      return ast.op(ast.OP_LT, l, r)
      def p_ordering_leq(self, l, r):     return ast.op(ast.OP_LEQ, l, r)
      def p_ordering_geq(self, l, r):     return ast.op(ast.OP_GEQ, l, r)
      def p_ordering_gt(self, l, r):      return ast.op(ast.OP_GT, l, r)
      def p_ordering_bitwise(self, b):    return b
      def p_bitwise_and(self, l, r):      return ast.op(ast.OP_BITAND, l, r)
      def p_bitwise_ior(self, l, r):      return ast.op(ast.OP_BITIOR, l, r)
      def p_bitwise_lshift(self, l, r):   return ast.op(ast.OP_LSHIFT, l, r)
      def p_bitwise_rshift(self, l, r):   return ast.op(ast.OP_RSHIFT, l, r)
      def p_bitwise_additive(self, a):    return a
      def p_additive_add(self, l, r):     return ast.op(ast.OP_ADD, l, r)
      def p_additive_sub(self, l, r):     return ast.op(ast.OP_SUB, l, r)
      def p_additive_mult(self, m):       return m
      def p_multiplicative_mul(self, l, r):
                                          return ast.op(ast.OP_MUL, l, r)
      def p_multiplicative_div(self, l, r):
                                          return ast.op(ast.OP_DIV, l, r)
      def p_multiplicative_rem(self, l, r):
                                          return ast.op(ast.OP_REM, l, r)
      def p_multiplicative_conc(self, c): return c
      def p_concatenative_concat(self, l, r):
                                          return ast.op(ast.OP_CONCAT, l, r)
      def p_concatenative_collate(self, c):
                                          return c
      def p_collating_collate(self, e, c):
                                          return ast.ExpCollate(e, c)
      def p_collating_unary(self, u):     return u
      def p_unary_bitwise_not(self, u):   return ast.op(ast.OP_BITNOT, u)
      def p_unary_minus(self, u):         return ast.op(ast.OP_NEGATE, u)
      def p_unary_plus(self, u):          return ast.op(ast.OP_PLUSID, u)
      def p_unary_bql(self, b):           return b
  
      def p_bqlfn_predprob_row(self, target):
          return ast.ExpBQLPredProb([target], [])
      def p_bqlfn_jpredprob_row(self, targets):
          return ast.ExpBQLPredProb(targets, [])
      def p_bqlfn_condpredprob_row(self, target, constraints):
          return ast.ExpBQLPredProb([target], constraints)
      def p_bqlfn_condjpredprob_row(self, targets, constraints):
          return ast.ExpBQLPredProb(targets, constraints)
      def p_bqlfn_prob_const(self, col, e):       return ast.ExpBQLProbDensity(
                                                      [(col, e)], [])
      def p_bqlfn_jprob_const(self, targets):     return ast.ExpBQLProbDensity(
                                                      targets, [])
      def p_bqlfn_condprob_const(self, col, e, constraints):
                                                  return ast.ExpBQLProbDensity(
                                                      [(col, e)], constraints)
      def p_bqlfn_condjprob_const(self, targets, constraints):
                                                  return ast.ExpBQLProbDensity(
                                                      targets, constraints)
      def p_bqlfn_prob_1col(self, e):             return ast.ExpBQLProbDensityFn(
                                                      e, [])
      def p_bqlfn_condprob_1col(self, e, constraints):
                                                  return ast.ExpBQLProbDensityFn(
                                                      e, constraints)
      def p_bqlfn_sim_const(self, cond0, cond1, col):
          return ast.ExpBQLSim(cond0, cond1, col)
      def p_bqlfn_sim_1row(self, cond, col):
          return ast.ExpBQLSim(None, cond, col)
      def p_bqlfn_sim_2row(self, col):
          return ast.ExpBQLSim(None, None, col)
  
      def p_bqlfn_predrel_existing(self, cond0, cond1, col):
          return ast.ExpBQLPredRel(cond0, cond1, None, col)
      def p_bqlfn_predrel_hypothetical(self, cond0, constraints, col):
          return ast.ExpBQLPredRel(cond0, None, constraints, col)
      def p_bqlfn_predrel_both(self, cond0, cond1, constraints, col):
          return ast.ExpBQLPredRel(cond0, cond1, constraints, col)
  
      def p_bqlfn_depprob(self, cols):            return ast.ExpBQLDepProb(*cols)
  
      def p_bqlfn_mutinf(self, cols, constraints, nsamp):
          return ast.ExpBQLMutInf(cols[0], cols[1], constraints, nsamp)
      def p_bqlfn_prob_est(self, e):              return ast.ExpBQLProbEst(e)
  
      def p_predrel_of_opt_none(self):            return None
      def p_predrel_of_opt_one(self, cond0):      return cond0
  
      def p_existing_rows_one(self, cond):        return cond
      def p_hypothetical_rows_one(self, cs):      return cs
  
      def p_ofwithmulti_bql_2col(self):                   return (None, None)
      def p_ofwithmulti_bql_1col(self, cols):             return (cols, None)
      def p_ofwithmulti_bql_const(self, cols0, cols1):    return (cols0, cols1)
  
      def p_mi_columns_one(self, col):                return [col]
      def p_mi_columns_many(self, cols):              return cols
  
      def p_mi_column_list_one(self, col):            return [col]
      def p_mi_column_list_many(self, cols, col):
          cols.append(col)
          return cols
  
      def p_mi_given_opt_none(self):                  return None
      def p_mi_given_opt_some(self, constraints):     return constraints
  
      def p_mi_constraint_equality(self, col, value):
          return (col, value)
      def p_mi_constraint_marginal(self, col):
          return (col, ast.ExpLit(ast.LitNull(0)))
      def p_mi_constraints_one(self, c):
          return [c]
      def p_mi_constraints_many(self, cs, c):
          cs.append(c); return cs
  
      def p_bqlfn_correl(self, cols):             return ast.ExpBQLCorrel(*cols)
      def p_bqlfn_correl_pval(self, cols):
          return ast.ExpBQLCorrelPval(*cols)
      def p_bqlfn_predict(self, col, conf, nsamp):
          return ast.ExpBQLPredict(col, conf, nsamp)
      def p_bqlfn_primary(self, p):               return p
  
      def p_wrt_one(self, col):                   return [col]
  
      def p_ofwith_bql_2col(self):                return (None, None)
      def p_ofwith_bql_1col(self, col):           return (col, None)
      def p_ofwith_bql_const(self, col1, col2):   return (col1, col2)
  
      def p_nsamples_opt_none(self):              return None
      def p_nsamples_opt_some(self, nsamples):    return nsamples
  
      def p_column_lists_one(self, collist):
          return [collist]
      def p_column_lists_many(self, collists, collist):
          collists.append(collist)
          return collists
  
      def p_column_list_all(self):                return ast.ColListAll()
      def p_column_list_column(self, col):        return ast.ColListLit([col])
      def p_column_list_subquery(self, q):        return ast.ColListSub(q)
  
      def p_primary_literal(self, v):             return ast.ExpLit(v)
      def p_primary_numpar(self, n):              return ast.ExpNumpar(n)
      def p_primary_nampar(self, n):              return ast.ExpNampar(n[0],n[1])
      def p_primary_apply(self, fn, es):          return ast.ExpApp(False,fn,es)
      def p_primary_apply_distinct(self, fn, es): return ast.ExpApp(True, fn, es)
      def p_primary_apply_star(self, fn):         return ast.ExpAppStar(fn)
      def p_primary_paren(self, e):               return e
      def p_primary_subquery(self, q):            return ast.ExpSub(q)
      def p_primary_cast(self, e, t):             return ast.ExpCast(e, t)
      def p_primary_exists(self, q):              return ast.ExpExists(q)
      def p_primary_column(self, col):            return ast.ExpCol(None, col)
      def p_primary_tabcol(self, tab, col):       return ast.ExpCol(tab, col)
      def p_primary_case(self, k, ws, e):         return ast.ExpCase(k, ws, e)
  
      def p_case_key_opt_none(self):              return None
      def p_case_key_opt_some(self, k):           return k
      def p_case_whens_opt_none(self):            return []
      def p_case_whens_opt_some(self, ws, w, t):  ws.append((w, t)); return ws
      def p_case_else_opt_none(self):             return None
      def p_case_else_opt_some(self, e):          return e
  
      def p_literal_null(self):                   return ast.LitNull(None)
      def p_literal_integer(self, i):             return ast.LitInt(i)
      def p_literal_float(self, f):               return ast.LitFloat(f)
      def p_literal_string(self, s):              return ast.LitString(s)
  
      def p_type_name(self, n):                   return ast.Type(n, [])
      def p_type_onearg(self, n, a):              return ast.Type(n, [a])
      def p_type_twoarg(self, n, a, b):           return ast.Type(n, [a, b])
      def p_typename_one(self, n):                return [n]
      def p_typename_many(self, tn, n):           tn.append(n); return tn
      def p_typearg_unsigned(self, i):            return i
      def p_typearg_positive(self, i):            return i
      def p_typearg_negative(self, i):            return -i
  
files: src/parse.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  """BQL parser front end."""
  
  import StringIO
  
  import bayeslite.ast as ast
  import bayeslite.grammar as grammar
  import bayeslite.scan as scan
  from bayeslite.exception import BQLParseError
  
  def parse_bql_phrases(scanner):
      semantics = BQLSemantics()
      parser = grammar.Parser(semantics)
      while not semantics.failed:
          token = scanner.read()
          semantics.context.append(token)
          if token[0] == -1:      # error
              semantics.syntax_error(token)
          else:
              if token[0] == 0:   # EOF
                  # Implicit ; at EOF.
                  parser.feed((grammar.T_SEMI, ''))
              parser.feed(token)
          if semantics.phrase is not None:
              phrase = semantics.phrase
              semantics.phrase = None
              if 0 < len(semantics.errors):
                  # Keep parsing in order to detect more errors, but
                  # don't yield any broken phrases in case the caller
                  # will try to process them before we finish parsing
                  # the whole thing.
                  continue
              if 0 < scanner.n_numpar:
                  n_numpar = scanner.n_numpar
                  nampar_map = scanner.nampar_map
                  yield ast.Parametrized(phrase, n_numpar, nampar_map)
              else:
                  yield phrase
          if token[0] == 0:       # EOF
              break
      if 0 < len(semantics.errors):
          raise BQLParseError(semantics.errors)
      if semantics.failed:
          raise BQLParseError(['parse failed mysteriously!'])
  
  def parse_bql_string_pos(string):
      """Yield ``(phrase, pos)`` for each BQL phrase in `string`.
  
      `phrase` is the parsed AST.  `pos` is zero-based index of the code
      point at which `phrase` starts.
      """
      scanner = scan.BQLScanner(StringIO.StringIO(string), '(string)')
      phrases = parse_bql_phrases(scanner)
      # XXX Don't dig out internals of scanner: fix plex to have a
      # public API for finding the current position.
      return ((phrase, scanner.cur_pos) for phrase in phrases)
  
  def parse_bql_string_pos_1(string):
      """Return ``(phrase, pos)`` for the first BQL phrase in `string`.
  
      May not report parse errors afterward.
      """
      for phrase, pos in parse_bql_string_pos(string):
          return (phrase, pos)
      return None
  
  def parse_bql_string(string):
      """Yield each parsed BQL phrase AST in `string`."""
      return (phrase for phrase, _pos in parse_bql_string_pos(string))
  
  def bql_string_complete_p(string):
      """True if `string` has at least one complete BQL phrase or error.
  
      False if empty or if the last BQL phrase is incomplete.
      """
      scanner = scan.BQLScanner(StringIO.StringIO(string), '(string)')
      semantics = BQLSemantics()
      parser = grammar.Parser(semantics)
      nonsemi = False
      while not semantics.failed:
          token = scanner.read()
          if token[0] == -1:      # error
              # Say it's complete so the caller will try to parse it and
              # choke on the error.
              return True
          elif token[0] == 0:
              # EOF.  Hope we have a complete phrase.
              break
          elif token[0] != grammar.T_SEMI:
              # Got a non-semicolon token.  Clear any previous phrase,
              # if we had one.
              nonsemi = True
              semantics.phrase = None
          parser.feed(token)
      if 0 < len(semantics.errors):
          return True
      if semantics.failed:
          return True
      return (not nonsemi) or (semantics.phrase is not None)
  
  class BQLSemantics(object):
      def __init__(self):
          self.phrase = None
          self.context = []
          self.errors = []
          self.failed = False
  
      def accept(self):
          pass
      def parse_failed(self):
          self.failed = True
      def syntax_error(self, (number, text)):
          # XXX Adapt lemonade to help us identify what the allowed
          # subsequent tokens are.
          #
          # XXX Record source position.
          if number == -1:        # error
              self.errors.append('Skipping bad token: %s' % (text,))
          else:
              self.errors.append('Syntax error near [%s] after [%s]' % (
                  text, ' '.join([str(tok) for (pos, tok) in self.context[:-1]])))
  
      def p_bql_start(self, phrases):
          pass
      def p_phrases_none(self):
          pass
      def p_phrases_some(self, phrases, phrase):
          pass
  
      def p_phrase_opt_none(self):
          pass
      def p_phrase_opt_some(self, phrase):
          assert self.phrase is None
          self.phrase = phrase
  
      def p_phrase_command(self, c):              return c
      def p_phrase_query(self, q):                return q
  
      # Transactions
      def p_command_begin(self):                  return ast.Begin()
      def p_command_rollback(self):               return ast.Rollback()
      def p_command_commit(self):                 return ast.Commit()
  
      # SQL Data Definition Language subset
      def p_command_createtab_as(self, temp, ifnotexists, name, query):
          return ast.CreateTabAs(temp, ifnotexists, name, query)
      def p_command_createtab_csv(self, temp, ifnotexists, name, csv):
          return ast.CreateTabCsv(temp, ifnotexists, name, csv)
      def p_command_droptab(self, ifexists, name):
          return ast.DropTab(ifexists, name)
      def p_command_altertab(self, table, cmds):
          return ast.AlterTab(table, cmds)
  
      def p_pathname_p(self, name):               return name
  
      def p_altertab_cmds_one(self, cmd):         return [cmd]
      def p_altertab_cmds_many(self, cmds, cmd):  cmds.append(cmd); return cmds
      def p_altertab_cmd_renametab(self, name):
          return ast.AlterTabRenameTab(name)
      def p_altertab_cmd_renamecol(self, old, new):
          return ast.AlterTabRenameCol(old, new)
  
      # BQL Model Definition Language
      def p_command_guess_schema(self, table):
          return ast.GuessSchema(table)
  
      def p_command_create_pop(self, ifnotexists, name, table, schema):
          return ast.CreatePop(ifnotexists, name, table, schema)
      def p_command_drop_pop(self, ifexists, name):
          return ast.DropPop(ifexists, name)
      def p_command_alterpop(self, population, cmds):
          return ast.AlterPop(population, cmds)
  
      def p_alterpop_cmds_one(self, cmd):         return [cmd]
      def p_alterpop_cmds_many(self, cmds, cmd):  cmds.append(cmd); return cmds
      def p_alterpop_cmd_stattype(self, cols, stattype):
          return ast.AlterPopStatType(cols, stattype)
      def p_alterpop_cmd_addvar(self, col, st):
          return ast.AlterPopAddVar(col, st)
  
      def p_pop_schema_one(self, cl):             return [cl]
      def p_pop_schema_many(self, schema, cl):
          if cl:
              schema.append(cl);
          return schema
  
      def p_pop_clause_empty(self):            return None
      def p_pop_clause_column(self, col, st):  return ast.PopModelVars([col], st)
      def p_pop_clause_model(self, cols, st):  return ast.PopModelVars(cols, st)
      def p_pop_clause_ignore(self, cols):     return ast.PopIgnoreVars(cols)
      def p_pop_clause_guess(self, cols):      return ast.PopGuessVars(cols)
  
      def p_model_opt_none(self):              return None
      def p_model_opt_one(self):               return None
      def p_as_opt_none(self):                 return None
      def p_as_opt_one(self):                  return None
  
      def p_stattype_opt_none(self):           return None
      def p_stattype_opt_one(self, st):        return st
      def p_stattype_st(self, name):           return name
  
      def p_pop_columns_one(self, c):          return [c]
      def p_pop_columns_many(self, cols, c):   cols.append(c); return cols
  
      def p_pop_columns_guess_star(self):       return '*'
      def p_pop_columns_guess_list(self, cols): return cols
  
      def p_command_creategen(self, ifnotexists0, name, ifnotexists1, pop,
              backend, schema):
          ifnotexists = ifnotexists0 or ifnotexists1
          return ast.CreateGen(name, ifnotexists, pop, backend, schema)
      def p_command_dropgen(self, ifexists, name):
          return ast.DropGen(ifexists, name)
      def p_command_altergen(self, generator, models, cmds):
          return ast.AlterGen(generator, models, cmds)
  
      def p_altergen_cmds_one(self, cmd):         return [cmd]
      def p_altergen_cmds_many(self, cmds, cmd):  cmds.append(cmd); return cmds
      def p_altergen_cmd_renamegen(self, name):
          return ast.AlterGenRenameGen(name)
      def p_altergen_cmd_generic(self, s):
          return ast.AlterGenGeneric(s)
  
      def p_backend_name_opt_none(self):            return None
      def p_backend_name_opt_one(self, backend):    return backend
  
      def p_generator_schema_opt_none(self):      return [[]]
      def p_generator_schema_opt_some(self, s):   return s
      def p_generator_schema_one(self, s):        return [s]
      def p_generator_schema_many(self, ss, s):   ss.append(s); return ss
      def p_generator_schemum_empty(self):        return []
      def p_generator_schemum_nonempty(self, s, t): s.append(t); return s
      def p_gs_token_prim(self, t):               return t
      def p_gs_token_comp(self, s):               return s
  
      def p_stattype_s(self, name):
          return name
  
      # BQL Model Analysis Language
      def p_command_init_models(self, n, ifnotexists, generator):
          return ast.InitModels(ifnotexists, generator, n)
      def p_command_analyze_models(
              self, generator, models, anlimit, anckpt, program):
          iters = [lim[1] for lim in anlimit if lim and lim[0] == 'iterations']
          secs = [lim[1] for lim in anlimit if lim and lim[0] == 'seconds']
          iterations = min(iters) if iters else None
          seconds = min(secs) if secs else None
          ckpt_iterations = None
          ckpt_seconds = None
          if anckpt is not None:
              ckpt_iterations = anckpt[1] if anckpt[0] == 'iterations' else None
              ckpt_seconds = anckpt[1] if anckpt[0] == 'seconds' else None
          return ast.AnalyzeModels(generator, models, iterations, seconds,
              ckpt_iterations, ckpt_seconds, program)
      def p_command_drop_models(self, models, generator):
          return ast.DropModels(generator, models)
  
      def p_temp_opt_none(self):                  return False
      def p_temp_opt_some(self):                  return True
      def p_ifexists_none(self):                  return False
      def p_ifexists_some(self):                  return True
      def p_ifnotexists_none(self):               return False
      def p_ifnotexists_some(self):               return True
  
      def p_anmodelset_opt_none(self):            return None
      def p_anmodelset_opt_some(self, m):         return sorted(m)
      def p_anmodelset_matched_opt_none(self):    return None
      def p_anmodelset_matched_opt_some(self, m): return sorted(m)
      def p_modelset_opt_none(self):              return None
      def p_modelset_opt_some(self, m):           return sorted(m)
      def p_modelset_one(self, r):                return r
      def p_modelset_many(self, m, r):            m += r; return m
      def p_modelrange_single(self, modelno):     return [modelno]
      def p_modelrange_multi(self, minno, maxno): return range(minno, maxno + 1)
  
      def p_anlimit_one(self, duration):             return (duration, None)
      def p_anlimit_two(self, duration0, duration1): return (duration0, duration1)
      def p_anckpt_opt_none(self):                return None
      def p_anckpt_opt_some(self, duration):      return duration
  
      def p_anduration_iterations(self, n):       return ('iterations', n)
      def p_anduration_minutes(self, n):          return ('seconds', 60*n)
      def p_anduration_seconds(self, n):          return ('seconds', n)
  
      def p_analysis_program_opt_none(self):      return None
      def p_analysis_program_opt_some(self, p):   return p
      def p_analysis_program_empty(self):         return []
      def p_analysis_program_nonempty(self, p, t): p += t; return p
      def p_analysis_token_compound(self, p):     return ['('] + p + [')']
      def p_analysis_token_primitive(self, t):    return [t]
  
      def p_command_regress(self, target, givens, nsamp, pop, generator,
              modelnos):
          return ast.Regress(target, givens, nsamp, pop, generator, modelnos)
  
      def p_simulate_s(self, cols, population, generator, modelnos, constraints,
              lim, acc):
          for c in cols:
              if isinstance(c, ast.SelColSub):
                  continue
              if isinstance(c, ast.SelColExp) and \
                 isinstance(c.expression, ast.ExpCol):
                  continue
              self.errors.append('simulate only accepts population variables.')
              return None
          return ast.Simulate(
              cols, population, generator, modelnos, constraints, lim.limit, acc)
      def p_simulate_nolimit(self, cols, population, generator, modelnos,
              constraints):
          # XXX Report source location.
          self.errors.append('simulate missing limit')
          for c in cols:
              if isinstance(c, ast.SelColSub):
                  continue
              if isinstance(c, ast.SelColExp) and \
                 isinstance(c.expression, ast.ExpCol):
                  continue
              self.errors.append('simulate only accepts population variables.')
              return None
          return ast.Simulate(
              cols, population, generator, modelnos, constraints, 0, None)
      def p_simulate_models(self, cols, population, generator):
          return ast.SimulateModelsExp(cols, population, generator)
  
      def p_given_opt_none(self):                 return []
      def p_given_opt_some(self, constraints):    return constraints
      def p_constraints_one(self, c):             return [c]
      def p_constraints_many(self, cs, c):        cs.append(c); return cs
      def p_constraint_c(self, col, value):       return (col, value)
      def p_constraints_opt_none(self):           return []
      def p_constraints_opt_some(self, cs):       return cs
      def p_constraints_list_one(self, cs):       return [cs]
      def p_constraints_list_some(self, css, cs): css.append(cs); return css
  
      def p_query_select(self, q):                return q
      def p_query_estimate(self, q):              return q
      def p_query_estcol(self, q):                return q
      def p_query_estpairrow(self, q):            return q
      def p_query_estpaircol(self, q):            return q
      def p_query_estby(self, q):                 return q
      def p_query_infer(self, q):                 return q
      def p_query_simulate(self, q):              return q
      def p_query_estimate_pairwise_row(self, q): return q
      def p_query_create_column_list(self, q):    return q
  
      def p_select_s(self, quant, cols, tabs, cond, grouping, ord, lim):
          return ast.Select(quant, cols, tabs, cond, grouping, ord, lim)
  
      def p_estimate_e(self, quant, cols, tabs, generator, modelnos, cond,
              grouping, ord, lim):
          constructor = tabs
          return constructor(quant, cols, generator, modelnos, cond, grouping,
              ord, lim)
  
      def p_estcol_e(self):
          self.errors.append("deprecated `ESTIMATE COLUMNS'"
              ": use `ESTIMATE ... FROM COLUMNS OF'")
      def p_estpairrow_e(self):
          self.errors.append("deprecated `ESTIMATE PAIRWISE ROW'"
              ": use `ESTIMATE ... FROM PAIRWISE'")
      def p_estpaircol_e(self):
          self.errors.append("deprecated `ESTIMATE PAIRWISE'"
              ": use `ESTIMATE ... FROM PAIRWISE COLUMNS OF'")
  
      def p_estby_e(self, quant, cols, population, generator, modelnos):
          return ast.EstBy(quant, cols, population, generator, modelnos)
  
      def p_infer_auto(self, cols, conf, nsamp, population, generator, modelnos,
              cond, grouping, ord, lim):
          return ast.InferAuto(cols, conf, nsamp, population, generator, modelnos,
              cond, grouping, ord, lim)
      def p_infer_explicit(self, cols, population, generator, modelnos, cond,
              grouping, ord, lim):
          return ast.InferExplicit(cols, population, generator, modelnos, cond,
              grouping, ord, lim)
  
      def p_infer_auto_columns_one(self, c):      return [c]
      def p_infer_auto_columns_many(self, cs, c): cs.append(c); return cs
  
      def p_infer_auto_column_all(self):
          return ast.InfColAll()
      def p_infer_auto_column_one(self, col, name):
          return ast.InfColOne(col, name)
  
      def p_withconf_opt_none(self):
          return ast.ExpLit(ast.LitInt(0))
      def p_withconf_opt_some(self, conf):        return conf
      def p_withconf_conf(self, conf):            return conf
  
      def p_infer_exp_columns_one(self, c):       return [c]
      def p_infer_exp_columns_many(self, cs, c):  cs.append(c); return cs
      def p_infer_exp_column_sel(self, c):        return c
      def p_infer_exp_column_pred(self, col, name, confname, nsamp):
          return ast.PredCol(col, name, confname, nsamp)
  
      def p_conf_opt_none(self):                  return None
      def p_conf_opt_some(self, confname):        return confname
  
      def p_select_quant_distinct(self):          return ast.SELQUANT_DISTINCT
      def p_select_quant_all(self):               return ast.SELQUANT_ALL
      def p_select_quant_default(self):           return ast.SELQUANT_ALL
  
      def p_select_columns_one(self, c):          return [c]
      def p_select_columns_many(self, cs, c):     cs.append(c); return cs
  
      def p_select_column_star(self):             return ast.SelColAll(None)
      def p_select_column_qstar(self, table):     return ast.SelColAll(table)
      def p_select_column_qsub(self, table, q):   return ast.SelColSub(table, q)
      def p_select_column_exp(self, e, name):     return ast.SelColExp(e, name)
  
      def p_as_none(self):                        return None
      def p_as_some(self, name):                  return name
  
      def p_from_sel_opt_empty(self):             return None
      def p_from_sel_opt_nonempty(self, tables):  return tables
  
      def p_from_est_row(self, name):
          def c(quant, cols, generator, modelnos, cond, grouping, ord, lim):
              return ast.Estimate(quant, cols, name, generator, modelnos, cond,
                  grouping, ord, lim)
          return c
      def p_from_est_pairrow(self, name):
          def c(quant, cols, generator, modelnos, cond, grouping, ord, lim):
              return ast.EstPairRow(cols, name, generator, modelnos, cond, ord,
                  lim)
          return c
      def p_from_est_col(self, name):
          def c(quant, cols, generator, modelnos, cond, grouping, ord, lim):
              return ast.EstCols(cols, name, generator, modelnos, cond, ord, lim)
          return c
      def p_from_est_paircol(self, name, subcols):
          def c(quant, cols, generator, modelnos, cond, grouping, ord, lim):
              return ast.EstPairCols(cols, name, subcols, generator, modelnos,
                  cond, ord, lim)
          return c
  
      def p_modeledby_opt_none(self):             return None
      def p_modeledby_opt_some(self, gen):        return gen
  
      def p_usingmodel_opt_none(self):            return None
      def p_usingmodel_opt_some(self, modelnos):  return modelnos
  
      def p_select_tables_one(self, t):           return [t]
      def p_select_tables_many(self, ts, t):      ts.append(t); return ts
      def p_select_table_named(self, table, name): return ast.SelTab(table, name)
      def p_select_table_subquery(self, q, name):  return ast.SelTab(q, name)
  
      def p_for_none(self):                       return None
      def p_for_one(self, collist):               return collist
  
      def p_where_unconditional(self):            return None
      def p_where_conditional(self, condition):   return condition
  
      def p_column_name_cn(self, name):           return name
      def p_generator_name_unqualified(self, name): return name
      def p_backend_name_bn(self, name):          return name
      def p_population_name_pn(self, name):       return name
      def p_table_name_unqualified(self, name):   return name
  
      def p_group_by_none(self):                  return None
      def p_group_by_some(self, keys):            return ast.Grouping(keys, None)
      def p_group_by_having(self, keys, cond):    return ast.Grouping(keys, cond)
  
      def p_order_by_none(self):                  return None
      def p_order_by_some(self, keys):            return keys
      def p_order_keys_one(self, key):            return [key]
      def p_order_keys_many(self, keys, key):     keys.append(key); return keys
      def p_order_key_k(self, e, s):              return ast.Ord(e, s)
      def p_order_sense_none(self):               return ast.ORD_ASC
      def p_order_sense_asc(self):                return ast.ORD_ASC
      def p_order_sense_desc(self):               return ast.ORD_DESC
  
      def p_limit_opt_none(self):                 return None
      def p_limit_opt_some(self, lim):            return lim
  
      def p_accuracy_opt_none(self):              return None
      def p_accuracy_opt_some(self, acc):         return acc
  
      def p_limit_n(self, limit):                 return ast.Lim(limit, None)
      def p_limit_offset(self, limit, offset):    return ast.Lim(limit, offset)
      def p_limit_comma(self, offset, limit):     return ast.Lim(limit, offset)
  
      def p_expressions_opt_none(self):           return []
      def p_expressions_opt_some(self, es):       return es
  
      def p_expressions_one(self, e):             return [e]
      def p_expressions_many(self, es, e):        es.append(e); return es
  
      def p_expression_top(self, e):      return e
      def p_bqlfn0_exp(self, e):          return e
      def p_expression1_top(self, e):     return e
  
      def p_boolean_or_or(self, l, r):    return ast.op(ast.OP_BOOLOR, l, r)
      def p_boolean_or_and(self, a):      return a
      def p_boolean_and_and(self, l, r):  return ast.op(ast.OP_BOOLAND, l, r)
      def p_boolean_and_not(self, n):     return n
      def p_boolean_not_not(self, n):     return ast.op(ast.OP_BOOLNOT, n)
      def p_boolean_not_equality(self, c):
                                          return c
      def p_equality_is(self, l, r):      return ast.op(ast.OP_IS, l, r)
      def p_equality_isnot(self, l, r):   return ast.op(ast.OP_ISNOT, l, r)
      def p_equality_like(self, l, r):    return ast.op(ast.OP_LIKE, l, r)
      def p_equality_notlike(self, l, r): return ast.op(ast.OP_NOTLIKE, l, r)
      def p_equality_like_esc(self, l, r, e):
                                          return ast.op(ast.OP_LIKE_ESC, l, r, e)
      def p_equality_notlike_esc(self, l, r, e):
                                          return ast.op(ast.OP_NOTLIKE_ESC,
                                              l, r, e)
      def p_equality_glob(self, l, r):    return ast.op(ast.OP_GLOB, l, r)
      def p_equality_notglob(self, l, r): return ast.op(ast.OP_NOTGLOB, l, r)
      def p_equality_glob_esc(self, l, r, e):
                                          return ast.op(ast.OP_GLOB_ESC, l, r, e)
      def p_equality_notglob_esc(self, l, r, e):
                                          return ast.op(ast.OP_NOTGLOB_ESC,
                                              l, r, e)
      def p_equality_regexp(self, l, r):  return ast.op(ast.OP_REGEXP, l, r)
      def p_equality_notregexp(self, l, r):
                                          return ast.op(ast.OP_NOTREGEXP, l, r)
      def p_equality_regexp_esc(self, l, r, e):
                                          return ast.op(ast.OP_REGEXP_ESC,
                                              l, r, e)
      def p_equality_notregexp_esc(self, l, r, e):
                                          return ast.op(ast.OP_NOTREGEXP_ESC,
                                              l, r, e)
      def p_equality_match(self, l, r):   return ast.op(ast.OP_MATCH, l, r)
      def p_equality_notmatch(self, l, r):
                                          return ast.op(ast.OP_NOTMATCH, l, r)
      def p_equality_match_esc(self, l, r, e):
                                          return ast.op(ast.OP_MATCH_ESC, l, r, e)
      def p_equality_notmatch_esc(self, l, r, e):
                                          return ast.op(ast.OP_NOTMATCH_ESC,
                                              l, r, e)
      def p_equality_between(self, m, l, r):
                                          return ast.op(ast.OP_BETWEEN, m, l, r)
      def p_equality_notbetween(self, m, l, r):
                                          return ast.op(ast.OP_NOTBETWEEN, m,l,r)
      def p_equality_in_query(self, e, q): return ast.ExpInQuery(e, True, q)
      def p_equality_notin_query(self, e, q): return ast.ExpInQuery(e, False, q)
      def p_equality_in_exp(self, e, es): return ast.ExpInExp(e, True, es)
      def p_equality_notin_exp(self, e, es): return ast.ExpInExp(e, False, es)
      def p_equality_isnull(self, e):     return ast.op(ast.OP_ISNULL, e)
      def p_equality_notnull(self, e):    return ast.op(ast.OP_NOTNULL, e)
      def p_equality_neq(self, l, r):     return ast.op(ast.OP_NEQ, l, r)
      def p_equality_eq(self, l, r):      return ast.op(ast.OP_EQ, l, r)
      def p_equality_ordering(self, o):   return o
      def p_ordering_lt(self, l, r):      return ast.op(ast.OP_LT, l, r)
      def p_ordering_leq(self, l, r):     return ast.op(ast.OP_LEQ, l, r)
      def p_ordering_geq(self, l, r):     return ast.op(ast.OP_GEQ, l, r)
      def p_ordering_gt(self, l, r):      return ast.op(ast.OP_GT, l, r)
      def p_ordering_bitwise(self, b):    return b
      def p_bitwise_and(self, l, r):      return ast.op(ast.OP_BITAND, l, r)
      def p_bitwise_ior(self, l, r):      return ast.op(ast.OP_BITIOR, l, r)
      def p_bitwise_lshift(self, l, r):   return ast.op(ast.OP_LSHIFT, l, r)
      def p_bitwise_rshift(self, l, r):   return ast.op(ast.OP_RSHIFT, l, r)
      def p_bitwise_additive(self, a):    return a
      def p_additive_add(self, l, r):     return ast.op(ast.OP_ADD, l, r)
      def p_additive_sub(self, l, r):     return ast.op(ast.OP_SUB, l, r)
      def p_additive_mult(self, m):       return m
      def p_multiplicative_mul(self, l, r):
                                          return ast.op(ast.OP_MUL, l, r)
      def p_multiplicative_div(self, l, r):
                                          return ast.op(ast.OP_DIV, l, r)
      def p_multiplicative_rem(self, l, r):
                                          return ast.op(ast.OP_REM, l, r)
      def p_multiplicative_conc(self, c): return c
      def p_concatenative_concat(self, l, r):
                                          return ast.op(ast.OP_CONCAT, l, r)
      def p_concatenative_collate(self, c):
                                          return c
      def p_collating_collate(self, e, c):
                                          return ast.ExpCollate(e, c)
      def p_collating_unary(self, u):     return u
      def p_unary_bitwise_not(self, u):   return ast.op(ast.OP_BITNOT, u)
      def p_unary_minus(self, u):         return ast.op(ast.OP_NEGATE, u)
      def p_unary_plus(self, u):          return ast.op(ast.OP_PLUSID, u)
      def p_unary_bql(self, b):           return b
  
      def p_bqlfn_predprob_row(self, target):
          return ast.ExpBQLPredProb([target], [])
      def p_bqlfn_jpredprob_row(self, targets):
          return ast.ExpBQLPredProb(targets, [])
      def p_bqlfn_condpredprob_row(self, target, constraints):
          return ast.ExpBQLPredProb([target], constraints)
      def p_bqlfn_condjpredprob_row(self, targets, constraints):
          return ast.ExpBQLPredProb(targets, constraints)
      def p_bqlfn_prob_const(self, col, e):       return ast.ExpBQLProbDensity(
                                                      [(col, e)], [])
      def p_bqlfn_jprob_const(self, targets):     return ast.ExpBQLProbDensity(
                                                      targets, [])
      def p_bqlfn_condprob_const(self, col, e, constraints):
                                                  return ast.ExpBQLProbDensity(
                                                      [(col, e)], constraints)
      def p_bqlfn_condjprob_const(self, targets, constraints):
                                                  return ast.ExpBQLProbDensity(
                                                      targets, constraints)
      def p_bqlfn_prob_1col(self, e):             return ast.ExpBQLProbDensityFn(
                                                      e, [])
      def p_bqlfn_condprob_1col(self, e, constraints):
                                                  return ast.ExpBQLProbDensityFn(
                                                      e, constraints)
      def p_bqlfn_sim_const(self, cond0, cond1, col):
          return ast.ExpBQLSim(cond0, cond1, col)
      def p_bqlfn_sim_1row(self, cond, col):
          return ast.ExpBQLSim(None, cond, col)
      def p_bqlfn_sim_2row(self, col):
          return ast.ExpBQLSim(None, None, col)
  
      def p_bqlfn_predrel_existing(self, cond0, cond1, col):
          return ast.ExpBQLPredRel(cond0, cond1, None, col)
      def p_bqlfn_predrel_hypothetical(self, cond0, constraints, col):
          return ast.ExpBQLPredRel(cond0, None, constraints, col)
      def p_bqlfn_predrel_both(self, cond0, cond1, constraints, col):
          return ast.ExpBQLPredRel(cond0, cond1, constraints, col)
  
      def p_bqlfn_depprob(self, cols):            return ast.ExpBQLDepProb(*cols)
  
      def p_bqlfn_mutinf(self, cols, constraints, nsamp):
          return ast.ExpBQLMutInf(cols[0], cols[1], constraints, nsamp)
      def p_bqlfn_prob_est(self, e):              return ast.ExpBQLProbEst(e)
  
      def p_predrel_of_opt_none(self):            return None
      def p_predrel_of_opt_one(self, cond0):      return cond0
  
      def p_existing_rows_one(self, cond):        return cond
      def p_hypothetical_rows_one(self, cs):      return cs
  
      def p_ofwithmulti_bql_2col(self):                   return (None, None)
      def p_ofwithmulti_bql_1col(self, cols):             return (cols, None)
      def p_ofwithmulti_bql_const(self, cols0, cols1):    return (cols0, cols1)
  
      def p_mi_columns_one(self, col):                return [col]
      def p_mi_columns_many(self, cols):              return cols
  
      def p_mi_column_list_one(self, col):            return [col]
      def p_mi_column_list_many(self, cols, col):
          cols.append(col)
          return cols
  
      def p_mi_given_opt_none(self):                  return None
      def p_mi_given_opt_some(self, constraints):     return constraints
  
      def p_mi_constraint_equality(self, col, value):
          return (col, value)
      def p_mi_constraint_marginal(self, col):
          return (col, ast.ExpLit(ast.LitNull(0)))
      def p_mi_constraints_one(self, c):
          return [c]
      def p_mi_constraints_many(self, cs, c):
          cs.append(c); return cs
  
      def p_bqlfn_correl(self, cols):             return ast.ExpBQLCorrel(*cols)
      def p_bqlfn_correl_pval(self, cols):
          return ast.ExpBQLCorrelPval(*cols)
      def p_bqlfn_predict(self, col, conf, nsamp):
          return ast.ExpBQLPredict(col, conf, nsamp)
      def p_bqlfn_primary(self, p):               return p
  
      def p_wrt_one(self, col):                   return [col]
  
      def p_ofwith_bql_2col(self):                return (None, None)
      def p_ofwith_bql_1col(self, col):           return (col, None)
      def p_ofwith_bql_const(self, col1, col2):   return (col1, col2)
  
      def p_nsamples_opt_none(self):              return None
      def p_nsamples_opt_some(self, nsamples):    return nsamples
  
      def p_column_lists_one(self, collist):
          return [collist]
      def p_column_lists_many(self, collists, collist):
          collists.append(collist)
          return collists
  
      def p_column_list_all(self):                return ast.ColListAll()
      def p_column_list_column(self, col):        return ast.ColListLit([col])
      def p_column_list_subquery(self, q):        return ast.ColListSub(q)
  
      def p_primary_literal(self, v):             return ast.ExpLit(v)
      def p_primary_numpar(self, n):              return ast.ExpNumpar(n)
      def p_primary_nampar(self, n):              return ast.ExpNampar(n[0],n[1])
      def p_primary_apply(self, fn, es):          return ast.ExpApp(False,fn,es)
      def p_primary_apply_distinct(self, fn, es): return ast.ExpApp(True, fn, es)
      def p_primary_apply_star(self, fn):         return ast.ExpAppStar(fn)
      def p_primary_paren(self, e):               return e
      def p_primary_subquery(self, q):            return ast.ExpSub(q)
      def p_primary_cast(self, e, t):             return ast.ExpCast(e, t)
      def p_primary_exists(self, q):              return ast.ExpExists(q)
      def p_primary_column(self, col):            return ast.ExpCol(None, col)
      def p_primary_tabcol(self, tab, col):       return ast.ExpCol(tab, col)
      def p_primary_case(self, k, ws, e):         return ast.ExpCase(k, ws, e)
  
      def p_case_key_opt_none(self):              return None
      def p_case_key_opt_some(self, k):           return k
      def p_case_whens_opt_none(self):            return []
      def p_case_whens_opt_some(self, ws, w, t):  ws.append((w, t)); return ws
      def p_case_else_opt_none(self):             return None
      def p_case_else_opt_some(self, e):          return e
  
      def p_literal_null(self):                   return ast.LitNull(None)
      def p_literal_integer(self, i):             return ast.LitInt(i)
      def p_literal_float(self, f):               return ast.LitFloat(f)
      def p_literal_string(self, s):              return ast.LitString(s)
  
      def p_type_name(self, n):                   return ast.Type(n, [])
      def p_type_onearg(self, n, a):              return ast.Type(n, [a])
      def p_type_twoarg(self, n, a, b):           return ast.Type(n, [a, b])
      def p_typename_one(self, n):                return [n]
      def p_typename_many(self, tn, n):           tn.append(n); return tn
      def p_typearg_unsigned(self, i):            return i
      def p_typearg_positive(self, i):            return i
      def p_typearg_negative(self, i):            return -i
  
files: src/parse.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import StringIO
  import apsw
  import pytest
  import struct
  
  import bayeslite
  import bayeslite.ast as ast
  import bayeslite.compiler as compiler
  import bayeslite.core as core
  import bayeslite.guess as guess
  import bayeslite.backends.troll_rng as troll
  import bayeslite.parse as parse
  
  from bayeslite.exception import BQLError
  from bayeslite.math_util import relerr
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  from bayeslite.util import cursor_value
  
  import test_core
  import test_csv
  
  from stochastic import stochastic
  
  def bql2sql(string, setup=None):
      with bayeslite.bayesdb_open(':memory:') as bdb:
          test_core.t1_schema(bdb)
          test_core.t1_data(bdb)
          bdb.execute('''
              create population p1 for t1 (
                  id ignore;
                  label categorical;
                  age numerical;
                  weight numerical
              )
          ''')
          if setup is not None:
              setup(bdb)
          phrases = parse.parse_bql_string(string)
          out = compiler.Output(0, {}, ())
          for phrase in phrases:
              assert ast.is_query(phrase)
              compiler.compile_query(bdb, phrase, out)
              out.write(';')
          return out.getvalue()
  
  # XXX Kludgey mess.  Please reorganize.
  def bql2sqlparam(string):
      with bayeslite.bayesdb_open(':memory:') as bdb:
          test_core.t1_schema(bdb)
          test_core.t1_data(bdb)
          bdb.execute('''
              create population p1 for t1 (
                  id ignore;
                  label categorical;
                  age numerical;
                  weight numerical
              )
          ''')
          phrases = parse.parse_bql_string(string)
          out0 = StringIO.StringIO()
          for phrase in phrases:
              out = None
              if isinstance(phrase, ast.Parametrized):
                  bindings = (None,) * phrase.n_numpar
                  out = compiler.Output(phrase.n_numpar, phrase.nampar_map,
                      bindings)
                  phrase = phrase.phrase
              else:
                  out = StringIO.StringIO()
              assert ast.is_query(phrase)
              compiler.compile_query(bdb, phrase, out)
              # XXX Do something about the parameters.
              out0.write(out.getvalue())
              out0.write(';')
          return out0.getvalue()
  
  def bql_execute(bdb, string, bindings=()):
      return map(tuple, bdb.execute(string, bindings))
  
  def empty(cursor):
      assert cursor is not None
      assert cursor.description is not None
      assert len(cursor.description) == 0
      with pytest.raises(StopIteration):
          cursor.next()
  
  def test_trivial_population():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          # XXX if (not) exists
          bdb.execute('''
              create population p for t (
                  guess stattypes of (*);
                  age numerical
              )
          ''')
          bdb.execute('drop population p')
  
  def test_population_invalid_numerical():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          with pytest.raises(BQLError):
              bdb.execute('''
                  create population p for t (
                      guess stattypes of (*);
                      gender numerical
                  )
              ''')
  
  def test_population_invalid_numerical_alterpop_addvar():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          bdb.execute('''
              create population p for t (
                  guess stattypes of (*);
                  ignore gender
              )
          ''')
          with pytest.raises(BQLError):
              bdb.execute('alter population p add variable gender numerical')
          bdb.execute('drop population p')
  
  def test_population_invalid_numerical_alterpop_stattype():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          bdb.execute('''
                  create population p for t (
                      guess stattypes of (*);
                      gender nominal
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  alter population p set stattypes for gender to numerical
              ''')
          bdb.execute('drop population p')
  
  def test_similarity_identity():
      with test_core.t1() as (bdb, population_id, _generator_id):
          bdb.execute('initialize 6 models for p1_cc;')
          rowids = bdb.sql_execute('select rowid from t1')
          for rowid in rowids:
              c = bdb.execute('''
                  estimate similarity of (rowid=?) to (rowid=?)
                  in the context of age by p1
              ''', (rowid[0], rowid[0])).fetchall()
              assert len(c) == 1
              assert c[0][0] == 1
  
  def test_predictive_relevance():
      assert bql2sql('''
          estimate predictive relevance
              of (label = 'Uganda')
              to existing rows (rowid < 4)
              and hypothetical rows with values (
                  ("age" = 82, "weight" = 14),
                  ("age" = 74, label = 'Europe', "weight" = 7)
              )
              in the context of "weight"
          by p1
      ''') == \
          'SELECT bql_row_predictive_relevance(1, NULL, NULL, ' \
              '(SELECT _rowid_ FROM "t1" WHERE ("label" = \'Uganda\')), '\
              '\'[1, 2, 3]\', 3, '\
              '2, 82, 3, 14, NULL, 2, 74, 1, \'Europe\', 3, 7, NULL);'
      assert bql2sql('''
          estimate predictive relevance
              of (label = 'mumble')
              to existing rows (label = 'frotz' or age <= 4)
              in the context of "label"
          by p1
      ''') == \
          'SELECT bql_row_predictive_relevance(1, NULL, NULL, ' \
              '(SELECT _rowid_ FROM "t1" WHERE ("label" = \'mumble\')), '\
              '\'[5, 8]\', 1);'
      assert bql2sql('''
          estimate label,
              predictive relevance
              to hypothetical rows with values (
                  ("age" = 82, "weight" = 14),
                  ("age" = 74, label = 'hunf', "weight" = 7)
              )
              in the context of "age",
              _rowid_ + 1
          from p1
      ''') == \
          'SELECT "label", bql_row_predictive_relevance(1, NULL, NULL, _rowid_, '\
          '\'[]\', 2, 2, 82, 3, 14, NULL, 2, 74, 1, \'hunf\', 3, 7, NULL), '\
          '("_rowid_" + 1) FROM "t1";'
      # No matching rows should still compile.
      assert bql2sql('''
          estimate label,
              predictive relevance to existing rows (rowid < 0)
              in the context of "age"
          from p1
      ''') == \
          'SELECT "label", bql_row_predictive_relevance(1, NULL, NULL, _rowid_, '\
          '\'[]\', 2) FROM "t1";'
      # When using `BY`, require OF to be specified.
      with pytest.raises(BQLError):
          bql2sql('''
              estimate predictive relevance
                  to hypothetical rows with values (
                      ("age" = 82, "weight" = 14),
                      ("age" = 74, label = 'Europe', "weight" = 7)
                  )
                  in the context of "age"
              by p1
          ''')
      # When using `FROM`, require OF to be unspecified.
      with pytest.raises(BQLError):
          bql2sql('''
              estimate predictive relevance
                  of (name = 'mansour')
                  to hypothetical rows with values (
                      ("age" = 82, "weight" = 14)
                  )
                  in the context of "age"
              from p1
          ''')
      assert bql2sql('''
          estimate label from p1
          where
              (predictive relevance to existing rows (label = 'quux' and age < 5)
              in the context of "weight") > 1
          order by
              predictive relevance
                  to hypothetical rows with values ((label='zot'))
                  in the context of "age"
      ''') == \
          'SELECT "label" FROM "t1" WHERE '\
          '(bql_row_predictive_relevance(1, NULL, NULL, '\
              '_rowid_, \'[5]\', 3) > 1) '\
          'ORDER BY bql_row_predictive_relevance(1, NULL, NULL, '\
              '_rowid_, \'[]\', 2, 1, \'zot\', NULL);'
  
  
  @stochastic(max_runs=2, min_passes=1)
  def test_conditional_probability(seed):
      with test_core.t1(seed=seed) as (bdb, _population_id, _generator_id):
          bdb.execute('drop generator p1_cc')
          bdb.execute('drop population p1')
          bdb.execute('''
              create population p1 for t1 (
                  ignore id, label;
                  set stattype of age to numerical;
                  set stattype of weight to numerical
              )
          ''')
          bdb.execute('''
              create generator p1_cond_prob_cc for p1;
          ''')
          bdb.execute('initialize 1 model for p1_cond_prob_cc')
          bdb.execute('alter generator p1_cond_prob_cc '
              'ensure variables * dependent')
          bdb.execute('analyze p1_cond_prob_cc for 1 iteration')
          q0 = 'estimate probability density of age = 8 by p1'
          q1 = 'estimate probability density of age = 8 given () by p1'
          age_is_8 = bdb.execute(q0).fetchvalue()
          assert age_is_8 == bdb.execute(q1).fetchvalue()
          q2 = 'estimate probability density of age = 8 given (weight = 16)' \
              ' by p1'
          age_is_8_given_weight_is_16 = bdb.execute(q2).fetchvalue()
          assert age_is_8 < age_is_8_given_weight_is_16
  
          probs = bdb.execute(
              'estimate probability density of value 8 given (weight = 16)'
              ' from columns of p1 where v.name != \'weight\'').fetchall()
          assert [(age_is_8_given_weight_is_16,)] == probs
  
  @stochastic(max_runs=2, min_passes=1)
  def test_joint_probability(seed):
      with test_core.t1(seed=seed) as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 10 models for p1_cc')
          bdb.execute('analyze p1_cc for 10 iterations')
          q0 = 'estimate probability density of age = 8 by p1'
          q1 = 'estimate probability density of (age = 8) by p1'
          assert bdb.execute(q0).fetchvalue() == bdb.execute(q1).fetchvalue()
          q1 = 'estimate probability density of (age = 8) given () by p1'
          assert bdb.execute(q0).fetchvalue() == bdb.execute(q1).fetchvalue()
          q2 = 'estimate probability density of age = 8 given (weight = 16)' \
              ' by p1'
          assert bdb.execute(q0).fetchvalue() < bdb.execute(q2).fetchvalue()
          q0 = 'estimate probability density of age = 8 by p1'
          q1 = 'estimate probability density of (age = 8, weight = 16) by p1'
          assert bdb.execute(q1).fetchvalue() < bdb.execute(q0).fetchvalue()
          q2 = 'estimate probability density of (age = 8, weight = 16)' \
              " given (label = 'mumble') by p1"
          assert bdb.execute(q1).fetchvalue() < bdb.execute(q2).fetchvalue()
  
  def test_badbql():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          with pytest.raises(ValueError):
              bdb.execute('')
          with pytest.raises(ValueError):
              bdb.execute(';')
          with pytest.raises(ValueError):
              bdb.execute('select 0; select 1')
  
  def test_select_trivial():
      assert bql2sql('select null;') == 'SELECT NULL;'
      assert bql2sql("select 'x';") == "SELECT 'x';"
      assert bql2sql("select 'x''y';") == "SELECT 'x''y';"
      assert bql2sql('select "x";') == 'SELECT "x";'
      assert bql2sql('select "x""y";') == 'SELECT "x""y";'
      assert bql2sql('select 0;') == 'SELECT 0;'
      assert bql2sql('select 0.;') == 'SELECT 0.0;'
      assert bql2sql('select .0;') == 'SELECT 0.0;'
      assert bql2sql('select 0.0;') == 'SELECT 0.0;'
      assert bql2sql('select 1e0;') == 'SELECT 1.0;'
      assert bql2sql('select 1e+1;') == 'SELECT 10.0;'
      assert bql2sql('select 1e-1;') == 'SELECT 0.1;'
      assert bql2sql('select -1e+1;') == 'SELECT (- 10.0);'
      assert bql2sql('select +1e-1;') == 'SELECT (+ 0.1);'
      assert bql2sql('select SQRT(1-EXP(-2*value)) FROM bm_mi;') == \
          'SELECT "SQRT"((1 - "EXP"(((- 2) * "value")))) FROM "bm_mi";'
      assert bql2sql('select .1e0;') == 'SELECT 0.1;'
      assert bql2sql('select 1.e10;') == 'SELECT 10000000000.0;'
      assert bql2sql('select all 0;') == 'SELECT 0;'
      assert bql2sql('select distinct 0;') == 'SELECT DISTINCT 0;'
      assert bql2sql('select 0 as z;') == 'SELECT 0 AS "z";'
      assert bql2sql('select * from t;') == 'SELECT * FROM "t";'
      assert bql2sql('select t.* from t;') == 'SELECT "t".* FROM "t";'
      assert bql2sql('select c from t;') == 'SELECT "c" FROM "t";'
      assert bql2sql('select c as d from t;') == 'SELECT "c" AS "d" FROM "t";'
      assert bql2sql('select t.c as d from t;') == \
          'SELECT "t"."c" AS "d" FROM "t";'
      assert bql2sql('select t.c as d, p as q, x from t;') == \
          'SELECT "t"."c" AS "d", "p" AS "q", "x" FROM "t";'
      assert bql2sql('select * from t, u;') == 'SELECT * FROM "t", "u";'
      assert bql2sql('select * from t as u;') == 'SELECT * FROM "t" AS "u";'
      assert bql2sql('select * from (select 0);') == 'SELECT * FROM (SELECT 0);'
      assert bql2sql('select t.c from (select d as c from u) as t;') == \
          'SELECT "t"."c" FROM (SELECT "d" AS "c" FROM "u") AS "t";'
      assert bql2sql('select * where x;') == 'SELECT * WHERE "x";'
      assert bql2sql('select * from t where x;') == \
          'SELECT * FROM "t" WHERE "x";'
      assert bql2sql('select * group by x;') == 'SELECT * GROUP BY "x";'
      assert bql2sql('select * from t where x group by y;') == \
          'SELECT * FROM "t" WHERE "x" GROUP BY "y";'
      assert bql2sql('select * from t where x group by y, z;') == \
          'SELECT * FROM "t" WHERE "x" GROUP BY "y", "z";'
      assert bql2sql('select * from t where x group by y having sum(z) < 1') == \
          'SELECT * FROM "t" WHERE "x" GROUP BY "y" HAVING ("sum"("z") < 1);'
      assert bql2sql('select * order by x;') == 'SELECT * ORDER BY "x";'
      assert bql2sql('select * order by x asc;') == 'SELECT * ORDER BY "x";'
      assert bql2sql('select * order by x desc;') == \
          'SELECT * ORDER BY "x" DESC;'
      assert bql2sql('select * order by x, y;') == 'SELECT * ORDER BY "x", "y";'
      assert bql2sql('select * order by x desc, y;') == \
          'SELECT * ORDER BY "x" DESC, "y";'
      assert bql2sql('select * order by x, y asc;') == \
          'SELECT * ORDER BY "x", "y";'
      assert bql2sql('select * limit 32;') == 'SELECT * LIMIT 32;'
      assert bql2sql('select * limit 32 offset 16;') == \
          'SELECT * LIMIT 32 OFFSET 16;'
      assert bql2sql('select * limit 16, 32;') == 'SELECT * LIMIT 32 OFFSET 16;'
      assert bql2sql('select (select0);') == 'SELECT "select0";'
      assert bql2sql('select (select 0);') == 'SELECT (SELECT 0);'
      assert bql2sql('select f(f(), f(x), y);') == \
          'SELECT "f"("f"(), "f"("x"), "y");'
      assert bql2sql('select a and b or c or not d is e is not f like j;') == \
          'SELECT ((("a" AND "b") OR "c") OR' \
          + ' (NOT ((("d" IS "e") IS NOT "f") LIKE "j")));'
      assert bql2sql('select a like b not like c like d escape e;') == \
          'SELECT ((("a" LIKE "b") NOT LIKE "c") LIKE "d" ESCAPE "e");'
      assert bql2sql('select a like b escape c glob d not glob e;') == \
          'SELECT ((("a" LIKE "b" ESCAPE "c") GLOB "d") NOT GLOB "e");'
      assert bql2sql('select a not glob b glob c escape d;') == \
          'SELECT (("a" NOT GLOB "b") GLOB "c" ESCAPE "d");'
      assert bql2sql('select a glob b escape c regexp e not regexp f;') == \
          'SELECT ((("a" GLOB "b" ESCAPE "c") REGEXP "e") NOT REGEXP "f");'
      assert bql2sql('select a not regexp b regexp c escape d;') == \
          'SELECT (("a" NOT REGEXP "b") REGEXP "c" ESCAPE "d");'
      assert bql2sql('select a regexp b escape c not regexp d escape e;') == \
          'SELECT (("a" REGEXP "b" ESCAPE "c") NOT REGEXP "d" ESCAPE "e");'
      assert bql2sql('select a not regexp b escape c match e not match f;') == \
          'SELECT ((("a" NOT REGEXP "b" ESCAPE "c") MATCH "e") NOT MATCH "f");'
      assert bql2sql('select a not match b match c escape d;') == \
          'SELECT (("a" NOT MATCH "b") MATCH "c" ESCAPE "d");'
      assert bql2sql('select a match b escape c not match d escape e;') == \
          'SELECT (("a" MATCH "b" ESCAPE "c") NOT MATCH "d" ESCAPE "e");'
      assert bql2sql('select a not match b escape c between d and e;') == \
          'SELECT (("a" NOT MATCH "b" ESCAPE "c") BETWEEN "d" AND "e");'
      assert bql2sql('select a between b and c and d;') == \
          'SELECT (("a" BETWEEN "b" AND "c") AND "d");'
      assert bql2sql('select a like b like c escape d between e and f;') == \
          'SELECT ((("a" LIKE "b") LIKE "c" ESCAPE "d") BETWEEN "e" AND "f");'
      assert bql2sql('select a between b and c not between d and e;') == \
          'SELECT (("a" BETWEEN "b" AND "c") NOT BETWEEN "d" AND "e");'
      assert bql2sql('select a not between b and c in (select f);') == \
          'SELECT (("a" NOT BETWEEN "b" AND "c") IN (SELECT "f"));'
      assert bql2sql('select a in (select b) and c not in (select d);') == \
          'SELECT (("a" IN (SELECT "b")) AND ("c" NOT IN (SELECT "d")));'
      assert bql2sql("select a in (1 + 2, '3') and b not in (select c);") == \
          'SELECT (("a" IN ((1 + 2), \'3\')) AND ("b" NOT IN (SELECT "c")));'
      assert bql2sql('select a in (select b) isnull notnull!=c<>d<e<=f>g;') == \
          'SELECT ((((("a" IN (SELECT "b")) ISNULL) NOTNULL) != "c") !=' \
          + ' ((("d" < "e") <= "f") > "g"));'
      assert bql2sql('select a>b>=c<<d>>e&f|g+h-i*j/k;') == \
          'SELECT (("a" > "b") >= (((("c" << "d") >> "e") & "f") |' \
          + ' (("g" + "h") - (("i" * "j") / "k"))));'
      assert bql2sql('select a/b%c||~~d collate e collate\'f\'||1;') == \
          'SELECT (("a" / "b") % (("c" || (((~ (~ "d")) COLLATE "e")' \
          + ' COLLATE "f")) || 1));'
      assert bql2sql('select cast(f(x) as binary blob);') == \
          'SELECT CAST("f"("x") AS "binary" "blob");'
      assert bql2sql('select cast(42 as varint(73));') == \
          'SELECT CAST(42 AS "varint"(73));'
      assert bql2sql('select cast(f(x, y, z) as varchar(12 ,34));') == \
          'SELECT CAST("f"("x", "y", "z") AS "varchar"(12, 34));'
      assert bql2sql('select exists (select a) and not exists (select b);') == \
          'SELECT (EXISTS (SELECT "a") AND (NOT EXISTS (SELECT "b")));'
      assert bql2sql('select case when a - b then c else d end from t;') == \
          'SELECT CASE WHEN ("a" - "b") THEN "c" ELSE "d" END FROM "t";'
      assert bql2sql('select case f(a) when b + c then d else e end from t;') \
          == \
          'SELECT CASE "f"("a") WHEN ("b" + "c") THEN "d" ELSE "e" END FROM "t";'
  
  def test_estimate_bql():
      # PREDICTIVE PROBABILITY
      assert bql2sql('estimate predictive probability of weight from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[3]\', \'[]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of (age, weight) '
              'from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[2, 3]\', \'[]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of (age, weight) given '
              '(label) from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[2, 3]\', \'[1]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of (*) from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[1, 2, 3]\', \'[]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of (*) given (age, weight) '
              'from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[1]\', \'[2, 3]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of age given (*) '
              'from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[2]\', \'[1, 3]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate label, predictive probability of weight'
              ' from p1;') \
          == \
          'SELECT "label", ' \
              'bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[3]\', \'[]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of weight, label'
              ' from p1;') \
          == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[3]\', \'[]\'),' \
              ' "label"' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of weight + 1'
              ' from p1;') == \
          'SELECT (bql_row_column_predictive_probability(1, NULL, NULL, '\
                  '_rowid_, \'[3]\', \'[]\') + 1)' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of weight given (*) + 1'
              ' from p1;') == \
          'SELECT (bql_row_column_predictive_probability(1, NULL, NULL, '\
                  '_rowid_, \'[3]\', \'[1, 2]\') + 1)' \
              ' FROM "t1";'
      # PREDICTIVE PROBABILITY parse and compilation errors.
      with pytest.raises(parse.BQLParseError):
          # Need a table.
          bql2sql('estimate predictive probability of weight;')
      with pytest.raises(parse.BQLParseError):
          # Need at most one generator.
          bql2sql('estimate predictive probability of weight'
              ' from p1, p1;')
      with pytest.raises(parse.BQLParseError):
          # Need a generator name, not a subquery.
          bql2sql('estimate predictive probability of weight'
              ' from (select 0);')
      with pytest.raises(parse.BQLParseError):
          # Need a column.
          bql2sql('estimate predictive probability from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Using (*) in both targets and constraints.
          bql2sql('estimate predictive probability of (*) given (*) from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Using (weight, *) in targets.
          bql2sql('estimate predictive probability of (weight, *) given (age) '
              'from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Using (age, *) in constraints.
          bql2sql('estimate predictive probability of weight given (*, age) '
              'from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Using duplicate column age.
          bql2sql('estimate predictive probability of age given (weight, age) '
              'from p1;')
      # PROBABILITY DENISTY.
      assert bql2sql('estimate probability density of weight = 20 from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, 20) FROM "t1";'
      assert bql2sql('estimate probability density of weight = 20'
              ' given (age = 8)'
              ' from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, 20, NULL, 2, 8) FROM "t1";'
      assert bql2sql('estimate probability density of (weight = 20, age = 8)'
              ' from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, 20, 2, 8) FROM "t1";'
      assert bql2sql('estimate probability density of (weight = 20, age = 8)'
              " given (label = 'mumble') from p1;") == \
          "SELECT bql_pdf_joint(1, NULL, NULL, 3, 20, 2, 8, NULL, 1, 'mumble')" \
              ' FROM "t1";'
      assert bql2sql('estimate probability density of weight = (c + 1)'
              ' from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, ("c" + 1)) FROM "t1";'
      assert bql2sql('estimate probability density of weight = f(c)'
              ' from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, "f"("c")) FROM "t1";'
      assert bql2sql('estimate similarity to (rowid = 5) '
              'in the context of weight from p1;') == \
          'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)), 3) FROM "t1";'
      assert bql2sql(
              'estimate similarity of (rowid = 12) to (rowid = 5) '
              'in the context of weight from p1;') == \
          'SELECT bql_row_similarity(1, NULL, NULL,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 12)),' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)), 3) FROM "t1";'
      assert bql2sql('estimate similarity to (rowid = 5) in the context of age'
              ' from p1') == \
          'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)), 2) FROM "t1";'
      assert bql2sql(
          'estimate similarity of (rowid = 5) to (height = 7 and age < 10)'
              ' in the context of weight from p1;') == \
          'SELECT bql_row_similarity(1, NULL, NULL,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)),' \
          ' (SELECT _rowid_ FROM "t1" WHERE (("height" = 7) AND ("age" < 10))),' \
          ' 3) FROM "t1";'
      with pytest.raises(bayeslite.BQLError):
          # Cannot use all variables for similarity.
          bql2sql(
              'estimate similarity to (rowid = 5) in the context of * from p1;')
      assert bql2sql('estimate similarity to (rowid = 5)'
              ' in the context of age from p1;') == \
          'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)), 2) FROM "t1";'
      assert bql2sql('estimate dependence probability of age with weight'
              ' from p1;') == \
          'SELECT bql_column_dependence_probability(1, NULL, NULL, 2, 3) '\
          'FROM "t1";'
      with pytest.raises(bayeslite.BQLError):
          # Need both rows fixed.
          bql2sql('estimate similarity to (rowid=2) in the context of r by p1')
      with pytest.raises(bayeslite.BQLError):
          # Need both rows fixed.
          bql2sql('estimate similarity in the context of r within p1')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate dependence probability with age from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate dependence probability from p1;')
      assert bql2sql('estimate mutual information of age with weight' +
          ' from p1;') == \
          'SELECT bql_column_mutual_information('\
              '1, NULL, NULL, \'[2]\', \'[3]\', NULL)'\
          ' FROM "t1";'
      assert bql2sql('estimate mutual information of age with weight' +
          ' using 42 samples from p1;') == \
          'SELECT bql_column_mutual_information('\
              '1, NULL, NULL, \'[2]\', \'[3]\', 42)'\
          ' FROM "t1";'
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate mutual information with age from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate mutual information from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate mutual information with age using 42 samples'
              ' from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate mutual information using 42 samples from p1;')
      # XXX Should be SELECT, not ESTIMATE, here?
      assert bql2sql('estimate correlation of age with weight from p1;') == \
          'SELECT bql_column_correlation(1, NULL, NULL, 2, 3) FROM "t1";'
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate correlation with age from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate correlation from p1;')
      with pytest.raises(BQLError):
          # Variable must exist.
          bql2sql('estimate correlation with agee from variables of p1')
  
  def test_predict_outside_infer():
      with pytest.raises(bayeslite.BQLError):
          # No PREDICT outside INFER.
          bql2sql('estimate predict age with confidence 0.9 from p1;')
  
  def test_infer_explicit_predict_confidence():
      assert bql2sql('infer explicit predict age with confidence 0.9'
              ' from p1;') == \
          'SELECT bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, NULL) FROM "t1";'
  
  def test_infer_explicit_predict_confidence_nsamples():
      assert bql2sql('infer explicit'
              ' predict age with confidence 0.9 using 42 samples'
              ' from p1;') == \
          'SELECT bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, 42) FROM "t1";'
  
  def test_infer_explicit_verbatim_and_predict_confidence():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age confidence age_conf from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age",' \
              ' bql_json_get(c2, \'confidence\') AS "age_conf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, NULL)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_noconfidence():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, NULL)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_confidence_nsamples():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age confidence age_conf using 42 samples from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age",' \
              ' bql_json_get(c2, \'confidence\') AS "age_conf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, 42)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_noconfidence_nsamples():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age using 42 samples from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, 42)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_confidence_as():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age as age_inf confidence age_conf from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age_inf",' \
              ' bql_json_get(c2, \'confidence\') AS "age_conf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, NULL)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_noconfidence_as():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age as age_inf from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age_inf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, NULL)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_confidence_as_nsamples():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age as age_inf confidence age_conf using 87 samples'
              ' from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age_inf",' \
              ' bql_json_get(c2, \'confidence\') AS "age_conf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, 87)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_noconfidence_as_nsamples():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age as age_inf using 87 samples'
              ' from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age_inf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, 87)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_auto():
      assert bql2sql('infer rowid, age, weight from p1') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0, NULL))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_nsamples():
      assert bql2sql('infer rowid, age, weight using (1+2) samples from p1') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0, (1 + 2)))' \
              ' AS "age",' \
          ' "IFNULL"("weight",'\
                  ' bql_predict(1, NULL, NULL, _rowid_, 3, 0, (1 + 2)))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_with_confidence():
      assert bql2sql('infer rowid, age, weight with confidence 0.9 from p1') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight",'\
                  ' bql_predict(1, NULL, NULL, _rowid_, 3, 0.9, NULL))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_with_confidence_nsamples():
      assert bql2sql('infer rowid, age, weight with confidence 0.9'
              ' using sqrt(2) samples'
              ' from p1') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9,' \
                  ' "sqrt"(2)))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9,' \
                  ' "sqrt"(2)))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_with_confidence_where():
      assert bql2sql('infer rowid, age, weight with confidence 0.9 from p1'
              ' where label = \'foo\'') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9,'\
                  ' NULL))' \
              ' AS "weight"' \
          ' FROM "t1"' \
          ' WHERE ("label" = \'foo\');'
  
  def test_infer_auto_with_confidence_nsamples_where():
      assert bql2sql('infer rowid, age, weight with confidence 0.9'
              ' using 42 samples'
              ' from p1'
              ' where label = \'foo\'') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, 42))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9, 42))' \
              ' AS "weight"' \
          ' FROM "t1"' \
          ' WHERE ("label" = \'foo\');'
  
  def test_infer_auto_with_confidence_nsamples_where_predict():
      assert bql2sql('infer rowid, age, weight with confidence 0.9 from p1'
              ' where ifnull(label, predict label with confidence 0.7)'
                  ' = \'foo\'') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9,' \
                  ' NULL))' \
              ' AS "weight"' \
          ' FROM "t1"' \
          ' WHERE ("ifnull"("label",' \
                  ' bql_predict(1, NULL, NULL, _rowid_, 1, 0.7, NULL))' \
              ' = \'foo\');'
  
  def test_infer_auto_with_confidence_nsamples_where_predict_nsamples():
      assert bql2sql('infer rowid, age, weight with confidence 0.9'
              ' using 42 samples'
              ' from p1'
              ' where ifnull(label, predict label with confidence 0.7'
                     ' using 73 samples)'
                  ' = \'foo\'') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, 42))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9, 42))' \
              ' AS "weight"' \
          ' FROM "t1"' \
          ' WHERE ("ifnull"("label",' \
                  ' bql_predict(1, NULL, NULL, _rowid_, 1, 0.7, 73))' \
              ' = \'foo\');'
  
  def test_infer_auto_star():
      assert bql2sql('infer rowid, * from p1') == \
          'SELECT "rowid" AS "rowid", "id" AS "id",' \
          ' "IFNULL"("label", bql_predict(1, NULL, NULL, _rowid_, 1, 0, NULL))' \
              ' AS "label",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0, NULL))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_star_nsamples():
      assert bql2sql('infer rowid, * using 1 samples from p1') == \
          'SELECT "rowid" AS "rowid", "id" AS "id",' \
          ' "IFNULL"("label", bql_predict(1, NULL, NULL, _rowid_, 1, 0, 1))' \
              ' AS "label",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0, 1))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0, 1))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_estimate_columns_trivial():
      prefix0 = 'SELECT v.name AS name'
      prefix1 = ' FROM bayesdb_variable AS v' \
          ' WHERE v.population_id = 1' \
          ' AND v.generator_id IS NULL'
      prefix = prefix0 + prefix1
      assert bql2sql('estimate * from columns of p1;') == \
          prefix + ';'
      assert bql2sql('estimate * from columns of p1 where' +
              ' (probability density of value 42) > 0.5') == \
          prefix + \
          ' AND (bql_column_value_probability(1, NULL, NULL, v.colno, 42) > 0.5);'
      assert bql2sql('estimate * from columns of p1'
              ' where (probability density of value 8)'
              ' > (probability density of age = 16)') == \
          prefix + \
          ' AND (bql_column_value_probability(1, NULL, NULL, v.colno, 8) >' \
          ' bql_pdf_joint(1, NULL, NULL, 2, 16));'
      assert bql2sql('estimate *, probability density of value 8 given (age = 8)'
              ' from columns of p1;') == \
          prefix0 + \
          ', bql_column_value_probability(1, NULL, NULL, v.colno, 8, 2, 8)' + \
          prefix1 + ';'
      with pytest.raises(bayeslite.BQLError):
          bql2sql('estimate probability density of value 8 given (agee = 8)'
              ' from columns of p1')
      with pytest.raises(bayeslite.BQLError):
          # PREDICTIVE PROBABILITY makes no sense without row.
          bql2sql('estimate * from columns of p1 where' +
              ' predictive probability of x > 0;')
      with pytest.raises(bayeslite.BQLError):
          # SIMILARITY makes no sense without row.
          bql2sql('estimate * from columns of p1 where' +
              ' similarity to (rowid = x) in the context of c > 0;')
      assert bql2sql('estimate * from columns of p1 where' +
              ' dependence probability with age > 0.5;') == \
          prefix + \
          ' AND (bql_column_dependence_probability(1, NULL, NULL, 2, v.colno)' \
              ' > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 where' +
              ' dependence probability of age with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1'
              ' where dependence probability > 0.5;')
      assert bql2sql('estimate * from columns of p1 order by' +
              ' mutual information with age;') == \
          prefix + \
          ' ORDER BY bql_column_mutual_information(1, NULL, NULL, \'[2]\','\
          ' \'[\' || v.colno || \']\', NULL);'
      assert bql2sql('estimate * from columns of p1 order by' +
              ' mutual information with (age, label) using 42 samples;') == \
          prefix + \
          ' ORDER BY bql_column_mutual_information(1, NULL, NULL, \'[2, 1]\','\
          ' \'[\' || v.colno || \']\', 42);'
      assert bql2sql('estimate * from columns of p1 order by' +
              ' mutual information with (age, label)'
              ' given (weight=12) using 42 samples;') == \
          prefix + \
          ' ORDER BY bql_column_mutual_information(1, NULL, NULL, \'[2, 1]\','\
          ' \'[\' || v.colno || \']\', 42, 3, 12);'
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 order by' +
              ' mutual information of age with weight;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1'
              ' where mutual information > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 order by' +
              ' mutual information of age with weight using 42 samples;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 where' +
              ' mutual information using 42 samples > 0.5;')
      assert bql2sql('estimate * from columns of p1 order by' +
              ' correlation with age desc;') == \
          prefix + ' ORDER BY bql_column_correlation(1, NULL, NULL, 2, v.colno)' \
              ' DESC;'
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 order by' +
              ' correlation of age with weight;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 where correlation > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Makes no sense.
          bql2sql('estimate * from columns of p1'
              ' where predict age with confidence 0.9 > 30;')
      assert bql2sql('estimate'
              ' *, dependence probability with weight as depprob,'
              ' mutual information with weight as mutinf'
              ' from columns of p1'
              ' where depprob > 0.5 order by mutinf desc') == \
          prefix0 + \
          ', bql_column_dependence_probability(1, NULL, NULL, 3, v.colno)' \
              ' AS "depprob"' \
          ', bql_column_mutual_information(1, NULL, NULL, \'[3]\',' \
          ' \'[\' || v.colno || \']\', NULL) AS "mutinf"' \
          + prefix1 + \
          ' AND ("depprob" > 0.5)' \
          ' ORDER BY "mutinf" DESC;'
      assert bql2sql('estimate'
              ' *, dependence probability with weight as depprob,'
              ' mutual information with (age, weight) as mutinf'
              ' from columns of p1'
              ' where depprob > 0.5 order by mutinf desc') == \
          prefix0 + \
          ', bql_column_dependence_probability(1, NULL, NULL, 3, v.colno)' \
              ' AS "depprob"' \
          ', bql_column_mutual_information(1, NULL, NULL, \'[2, 3]\',' \
          ' \'[\' || v.colno || \']\', NULL) AS "mutinf"' \
          + prefix1 + \
          ' AND ("depprob" > 0.5)' \
          ' ORDER BY "mutinf" DESC;'
      # XXX This mixes up target and reference variables, which is OK,
      # because MI is symmetric, but...oops.
      assert bql2sql('estimate * from variables of p1'
              ' where probability of (mutual information with age < 0.1)'
              ' > 0.8') == \
          prefix + \
          ' AND ((SELECT "AVG"("x") FROM (SELECT ("v0" < 0.1) AS "x"' \
              ' FROM (SELECT mi AS "v0" FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[' || v.colno || ']'))) > 0.8);"
      assert bql2sql('estimate * from variables of p1'
              ' order by probability of (mutual information with age < 0.1)') ==\
          prefix + \
          ' ORDER BY (SELECT "AVG"("x") FROM (SELECT ("v0" < 0.1) AS "x"' \
              ' FROM (SELECT mi AS "v0" FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[' || v.colno || ']')));"
  
  def test_estimate_pairwise_trivial():
      prefix = 'SELECT 1 AS population_id, v0.name AS name0, v1.name AS name1, '
      infix = ' AS value'
      infix0 = ' FROM bayesdb_population AS p,'
      infix0 += ' bayesdb_variable AS v0,'
      infix0 += ' bayesdb_variable AS v1'
      infix0 += ' WHERE p.id = 1'
      infix0 += ' AND v0.population_id = p.id AND v1.population_id = p.id'
      infix0 += ' AND v0.generator_id IS NULL'
      infix0 += ' AND v1.generator_id IS NULL'
      infix += infix0
      assert bql2sql('estimate dependence probability'
              ' from pairwise columns of p1;') == \
          prefix + \
          'bql_column_dependence_probability(1, NULL, NULL, v0.colno,'\
              ' v1.colno)' + \
          infix + ';'
      assert bql2sql('estimate mutual information'
              ' from pairwise columns of p1 where'
              ' (probability density of age = 0) > 0.5;') == \
          prefix + \
          'bql_column_mutual_information(1, NULL, NULL, '\
              '\'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', NULL)' + \
          infix + \
          ' AND (bql_pdf_joint(1, NULL, NULL, 2, 0) > 0.5);'
      assert bql2sql('estimate mutual information given (label=\'go\', weight)'
              ' from pairwise columns of p1 where'
              ' (probability density of age = 0) > 0.5;') == \
          prefix + \
          'bql_column_mutual_information(1, NULL, NULL,'\
          ' \'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', NULL,'\
          ' 1, \'go\', 3, NULL)' + \
          infix + \
          ' AND (bql_pdf_joint(1, NULL, NULL, 2, 0) > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # PROBABILITY DENSITY OF VALUE is 1-column.
          bql2sql('estimate correlation from pairwise columns of p1 where' +
              ' (probability density of value 0) > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # PREDICTIVE PROBABILITY OF is a row function.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1' +
              ' where predictive probability of x > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where dependence probability of age with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information from pairwise columns of p1'
              ' where dependence probability with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information using 42 samples'
              ' from pairwise columns of p1'
              ' where dependence probability with weight > 0.5;')
      assert bql2sql('estimate correlation from pairwise columns of p1'
              ' where dependence probability > 0.5;') == \
          prefix + 'bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno)' + \
          infix + ' AND' \
          ' (bql_column_dependence_probability(1, NULL, NULL, v0.colno,' \
                  ' v1.colno)' \
              ' > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where mutual information of age with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where mutual information of age with weight using 42 samples'
                  ' > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information from pairwise columns of p1'
              ' where mutual information with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information using 42 samples'
              ' from pairwise columns of p1'
              ' where mutual information with weight using 42 samples > 0.5;')
      assert bql2sql('estimate correlation from pairwise columns of p1' +
              ' where mutual information > 0.5;') == \
          prefix + 'bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno)' + \
          infix + ' AND' + \
          ' (bql_column_mutual_information(1, NULL, NULL,'\
          ' \'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', NULL) > 0.5);'
      assert bql2sql('estimate correlation from pairwise columns of p1' +
              ' where mutual information using 42 samples > 0.5;') == \
          prefix + 'bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno)' + \
          infix + ' AND' + \
          ' (bql_column_mutual_information(1, NULL, NULL,'\
          ' \'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', 42) > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where correlation of age with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information from pairwise columns of p1'
              ' where correlation with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information using 42 samples'
              ' from pairwise columns of p1'
              ' where correlation with weight > 0.5;')
      assert bql2sql('estimate correlation from pairwise columns of p1'
              ' where correlation > 0.5;') == \
          prefix + 'bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno)' + \
          infix + ' AND' + \
          ' (bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno) > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # Makes no sense.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where predict age with confidence 0.9 > 30;')
      assert bql2sql('estimate dependence probability as depprob,'
              ' mutual information as mutinf'
              ' from pairwise columns of p1'
              ' where depprob > 0.5 order by mutinf desc') == \
          prefix + \
          'bql_column_dependence_probability(1, NULL, NULL, v0.colno, v1.colno)' \
          ' AS "depprob",' \
          ' bql_column_mutual_information(1, NULL, NULL,'\
          ' \'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', NULL)'\
          ' AS "mutinf"' \
          + infix0 + \
          ' AND ("depprob" > 0.5)' \
          ' ORDER BY "mutinf" DESC;'
  
  def test_estimate_pairwise_row():
      prefix = 'SELECT r0._rowid_ AS rowid0, r1._rowid_ AS rowid1'
      infix = ' AS value FROM "t1" AS r0, "t1" AS r1'
      assert bql2sql('estimate similarity in the context of age' +
              ' from pairwise p1;') == \
          prefix + ', bql_row_similarity(1, NULL, NULL,'\
              ' r0._rowid_, r1._rowid_, 2)' + \
          infix + ';'
      with pytest.raises(bayeslite.BQLError):
          # PREDICT is a 1-row function.
          bql2sql('estimate predict age with confidence 0.9 from pairwise t1;')
  
  def test_estimate_pairwise_selected_columns():
      assert bql2sql('estimate dependence probability'
              ' from pairwise columns of p1 for label, age') == \
          'SELECT 1 AS population_id, v0.name AS name0, v1.name AS name1,' \
          ' bql_column_dependence_probability(1, NULL, NULL,' \
                  ' v0.colno, v1.colno)' \
              ' AS value' \
          ' FROM bayesdb_population AS p,' \
          ' bayesdb_variable AS v0,' \
          ' bayesdb_variable AS v1' \
          ' WHERE p.id = 1' \
          ' AND v0.population_id = p.id AND v1.population_id = p.id' \
          ' AND v0.generator_id IS NULL AND v1.generator_id IS NULL' \
          ' AND v0.colno IN (1, 2) AND v1.colno IN (1, 2);'
      assert bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' for (ESTIMATE * FROM COLUMNS OF p1'
                  ' ORDER BY name DESC LIMIT 2)') == \
          'SELECT 1 AS population_id, v0.name AS name0, v1.name AS name1,' \
          ' bql_column_dependence_probability(1, NULL, NULL, v0.colno,' \
                  ' v1.colno)' \
              ' AS value' \
          ' FROM bayesdb_population AS p,' \
          ' bayesdb_variable AS v0,' \
          ' bayesdb_variable AS v1' \
          ' WHERE p.id = 1' \
          ' AND v0.population_id = p.id AND v1.population_id = p.id' \
          ' AND v0.generator_id IS NULL AND v1.generator_id IS NULL' \
          ' AND v0.colno IN (3, 1) AND v1.colno IN (3, 1);'
  
  def test_select_columns_subquery():
      assert bql2sql('select id, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from t1') == \
          'SELECT "id", "t1"."age", "t1"."label" FROM "t1";'
  
  @pytest.mark.xfail(strict=True, reason='no simulate vars from models of')
  def test_simulate_models_columns_subquery():
      assert bql2sql('simulate weight, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from models of p1') == \
          'SELECT * FROM "bayesdb_temp_0";'
      assert bql2sql('simulate 0, weight, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from models of p1') == \
          'SELECT 0, "v0" AS "weight", "v1" AS "age", "v2" AS "label" FROM' \
          ' (SELECT * FROM "bayesdb_temp_0");'
      assert bql2sql('simulate weight + 1, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from models of p1') == \
          'SELECT ("v0" + 1), "v1" AS "age", "v2" AS "label" FROM' \
          ' (SELECT * FROM "bayesdb_temp_0");'
      assert bql2sql('simulate weight + 1 AS wp1,'
              ' t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from models of p1') == \
          'SELECT ("v0" + 1) AS "wp1", "v1" AS "age", "v2" AS "label" FROM' \
          ' (SELECT * FROM "bayesdb_temp_0");'
  
  def test_simulate_columns_subquery():
      # XXX This test is a little unsatisfactory -- we do not get to see
      # what the variables in the result are named...
      assert bql2sql('simulate weight, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from p1 limit 10') == \
          'SELECT * FROM "bayesdb_temp_0";'
      with pytest.raises(parse.BQLParseError):
          # Compound columns not yet implemented for SIMULATE.
          bql2sql('simulate weight + 1, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from p1 limit 10')
  
  def test_simulate_models():
      # Base case.
      assert bql2sql('simulate mutual information of age with weight'
              ' from models of p1') == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]';"
      # Multiple target variables.
      assert bql2sql('simulate mutual information of (label, age) with weight'
              ' from models of p1') == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[1, 2]'" \
                  " AND reference_vars = '[3]';"
      # Multiple reference variables.
      assert bql2sql('simulate mutual information of age with (label, weight)'
              ' from models of p1') == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[1, 3]';"
      # Specified number of samples.
      assert bql2sql('simulate mutual information of age with weight'
              ' using 42 samples from models of p1') == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]'" \
                  ' AND nsamples = 42;'
      # Conditional.
      assert bql2sql('simulate mutual information of age with weight'
              " given (label = 'foo') from models of p1") == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]'" \
                  " AND conditions = '{\"1\": \"foo\"}';"
      # Modeled by a specific generator.
      assert bql2sql('simulate mutual information of age with weight'
                  ' from models of p1 modeled by g1',
              lambda bdb: bdb.execute('create generator g1 for p1')) == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  ' AND generator_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]';"
      # Two mutual informations.
      assert bql2sql('simulate mutual information of age with weight AS "mi(aw)",'
              ' mutual information of label with weight AS "mi(lw)"'
              ' from models of p1') == \
          'SELECT t0."mi(aw)" AS "mi(aw)", t1."mi(lw)" AS "mi(lw)"' \
              ' FROM (SELECT _rowid_, mi AS "mi(aw)" FROM bql_mutinf' \
                      ' WHERE population_id = 1' \
                          " AND target_vars = '[2]'" \
                          " AND reference_vars = '[3]') AS t0," \
                  ' (SELECT _rowid_, mi AS "mi(lw)" FROM bql_mutinf' \
                      ' WHERE population_id = 1' \
                          " AND target_vars = '[1]'" \
                          " AND reference_vars = '[3]') AS t1" \
              ' WHERE t0._rowid_ = t1._rowid_;'
  
  def test_probability_of_mutinf():
      assert bql2sql('estimate probability of'
              ' (mutual information of age with weight < 0.1) > 0.5'
              ' within p1') == \
          'SELECT ((SELECT "AVG"("x") FROM (SELECT ("v0" < 0.1) AS "x"' \
          ' FROM (SELECT mi AS "v0" FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]'))) > 0.5);"
  
  def test_modeledby_usingmodels_trival():
      def setup(bdb):
          bdb.execute('create generator m1 for p1 using cgpm;')
      assert bql2sql('estimate predictive probability of weight + 1'
              ' from p1 modeled by m1 using models 1-3, 5;', setup=setup) == \
          'SELECT (bql_row_column_predictive_probability(1, 1, \'[1, 2, 3, 5]\','\
                  ' _rowid_, \'[3]\', \'[]\') + 1)' \
              ' FROM "t1";'
      assert bql2sql(
          'infer rowid, age, weight from p1 modeled by m1 using model 7',
              setup=setup) == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, 1, \'[7]\', _rowid_, 2, 0, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, 1, \'[7]\', _rowid_, 3, 0, NULL))' \
              ' AS "weight"' \
          ' FROM "t1";'
      assert bql2sql('infer explicit predict age with confidence 0.9'
              ' from p1 using models 0, 3-5;',
              setup=setup) == \
          'SELECT bql_predict(1, NULL, \'[0, 3, 4, 5]\', _rowid_, 2, 0.9, NULL)'\
          ' FROM "t1";'
      assert bql2sql('''
          estimate predictive relevance
              of (label = 'Uganda')
              to existing rows (rowid < 4)
              and hypothetical rows with values (
                  ("age" = 82, "weight" = 14),
                  ("age" = 74, label = 'Europe', "weight" = 7)
              )
              in the context of "weight"
          by p1 modeled by m1 using models 8, 10-12
      ''', setup=setup) == \
          'SELECT bql_row_predictive_relevance(1, 1, \'[8, 10, 11, 12]\', ' \
              '(SELECT _rowid_ FROM "t1" WHERE ("label" = \'Uganda\')), '\
              '\'[1, 2, 3]\', 3, '\
              '2, 82, 3, 14, NULL, 2, 74, 1, \'Europe\', 3, 7, NULL);'
      assert bql2sql('''
          estimate dependence probability
          from pairwise columns of p1
          for label, age
          modeled by m1
          using models 1, 4, 12
      ''', setup=setup) == \
          'SELECT 1 AS population_id, v0.name AS name0, v1.name AS name1,' \
          ' bql_column_dependence_probability(1, 1, \'[1, 4, 12]\',' \
                  ' v0.colno, v1.colno)' \
              ' AS value' \
          ' FROM bayesdb_population AS p,' \
          ' bayesdb_variable AS v0,' \
          ' bayesdb_variable AS v1' \
          ' WHERE p.id = 1' \
          ' AND v0.population_id = p.id AND v1.population_id = p.id' \
          ' AND (v0.generator_id IS NULL OR v0.generator_id = 1)' \
          ' AND (v1.generator_id IS NULL OR v1.generator_id = 1)' \
          ' AND v0.colno IN (1, 2) AND v1.colno IN (1, 2);'
      assert bql2sql('''
          estimate mutual information of age with weight
          from p1 modeled by m1 using model 1;
      ''', setup=setup) == \
          'SELECT bql_column_mutual_information('\
              '1, 1, \'[1]\', \'[2]\', \'[3]\', NULL)'\
          ' FROM "t1";'
  
  def test_simulate_columns_all():
      with pytest.raises(parse.BQLParseError):
          bql2sql('simulate * from p1 limit 1')
  
  def test_trivial_commands():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          # XXX Query parameters!
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          with open(fname, 'rU') as f:
              with pytest.raises(ValueError):
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True,
                  ifnotexists=True)
          guess.bayesdb_guess_population(bdb, 'p', 't')
          with pytest.raises(ValueError):
              guess.bayesdb_guess_population(bdb, 'p', 't')
          guess.bayesdb_guess_population(bdb, 'p', 't', ifnotexists=True)
          bdb.execute('create generator p_cc for p;')
          bdb.execute('initialize 2 models for p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('initialize 2 models for p_cc')
          bdb.execute('drop models from p_cc')
          bdb.execute('drop models from p_cc')
          bdb.execute('initialize 2 models for p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('initialize 2 models for p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop models 0-2 from p_cc')
          bdb.execute('drop models 0-1 from p_cc')
          with bdb.savepoint():
              bdb.execute('initialize 2 models for p_cc')
              bdb.execute('drop models 0-1 from p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop models 0-1 from p_cc')
          bdb.execute('initialize 2 models for p_cc')
          bdb.execute('initialize 1 model if not exists for p_cc')
          bdb.execute('initialize 2 models if not exists for p_cc')
          population_id = core.bayesdb_get_population(bdb, 'p')
          generator_id = core.bayesdb_get_generator(bdb, population_id, 'p_cc')
          assert core.bayesdb_generator_table(bdb, generator_id) == 't'
          bdb.execute('alter table t rename to t')
          assert core.bayesdb_generator_table(bdb, generator_id) == 't'
          bdb.execute('alter table t rename to T')
          assert core.bayesdb_generator_table(bdb, generator_id) == 'T'
          bdb.execute('estimate count(*) from p').fetchall()
          bdb.execute('alter table t rename to t')
          assert core.bayesdb_generator_table(bdb, generator_id) == 't'
          bdb.execute('alter generator p_cc rename to p0_cc')
          assert core.bayesdb_generator_name(bdb, generator_id) == 'p0_cc'
          bdb.execute('alter generator p0_cc rename to zot, rename to P0_CC')
          assert core.bayesdb_generator_name(bdb, generator_id) == 'P0_CC'
          bdb.execute('alter generator P0_cc rename to P0_cc')
          assert core.bayesdb_generator_name(bdb, generator_id) == 'P0_cc'
          bdb.execute('alter generator p0_CC rename to p0_cc')
          assert core.bayesdb_generator_name(bdb, generator_id) == 'p0_cc'
          bdb.execute('estimate count(*) from p').fetchall()
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate count(*) from p_cc')
          bdb.execute('alter generator p0_cc rename to P0_cc')
          bdb.execute('analyze p0_cc for 1 iteration')
          colno = core.bayesdb_variable_number(bdb, population_id, generator_id,
              'gender')
          with pytest.raises(parse.BQLParseError):
              # Rename the table's columns, not the generator's columns.
              bdb.execute('alter generator p0_cc rename gender to sex')
          with pytest.raises(NotImplementedError): # XXX
              bdb.execute('alter table t rename to t0, rename gender to sex')
              assert core.bayesdb_variable_number(
                      bdb, population_id, generator_id, 'sex') \
                  == colno
              bdb.execute('analyze p0_cc model 0 for 1 iteration')
              bdb.execute('alter generator p0_cc rename to p_cc')
              assert core.bayesdb_variable_number(
                      bdb, population_id, generator_id, 'sex') \
                  == colno
              bdb.execute('select sex from t0').fetchall()
              with pytest.raises(AssertionError): # XXX
                  bdb.execute('select gender from t0')
                  assert False, 'Need to fix quoting of unknown columns!'
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('estimate predict sex with confidence 0.9'
                      ' from p').fetchall()
              bdb.execute('infer explicit predict sex with confidence 0.9'
                  ' from p').fetchall()
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('estimate predict gender with confidence 0.9'
                      ' from p')
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('infer explicit predict gender with confidence 0.9'
                      ' from p')
              bdb.execute('alter table t0 rename sex to gender')
              assert core.bayesdb_variable_number(
                      bdb, population_id, generator_id, 'gender') \
                  == colno
          bdb.execute('alter generator p0_cc rename to p_cc')     # XXX
          bdb.execute('alter table t rename to T0')               # XXX
          bdb.sql_execute('create table t0_temp(x)')
          bdb.execute('alter table T0 rename to t0')
          assert bdb.execute('select count(*) from t0_temp').fetchvalue() == 0
          assert bdb.execute('select count(*) from t0').fetchvalue() > 0
          with pytest.raises(bayeslite.BQLError):
              # Cannot specify models with rename.
              bdb.execute('alter generator p_cc models (1) rename to p_cc_fail')
          bdb.execute('drop table T0_TEMP')
          bdb.execute('analyze p_cc model 0 for 1 iteration')
          bdb.execute('analyze p_cc model 1 for 1 iteration')
          bdb.execute('analyze p_cc models 0-1 for 1 iteration')
          bdb.execute('analyze p_cc models 0,1 for 1 iteration')
          bdb.execute('analyze p_cc for 1 iteration')
          bdb.execute('select * from t0').fetchall()
          bdb.execute('select * from T0').fetchall()
          bdb.execute('estimate * from p').fetchall()
          bdb.execute('estimate * from P').fetchall()
          # SIMIARITY IN THE CONTEXT OF requires exactly 1 variable.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate similarity in the context of * '
                  'from pairwise p').fetchall()
          bdb.execute('estimate similarity in the context of age '
              'from pairwise p').fetchall()
          bdb.execute('estimate similarity to (rowid=1) in the context of rank '
              'from p').fetchall()
          bdb.execute('select value from'
              ' (estimate correlation from pairwise columns of p)').fetchall()
          bdb.execute('infer explicit predict age with confidence 0.9'
              ' from p').fetchall()
          bdb.execute('infer explicit predict AGE with confidence 0.9'
              ' from P').fetchall()
          bdb.execute('infer explicit predict aGe with confidence 0.9'
              ' from P').fetchall()
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate predict agee with confidence 0.9 from p')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('infer explicit predict agee with confidence 0.9'
                  ' from p')
          guess.bayesdb_guess_population(bdb, 'pe', 't0',
              overrides=[
                  ('age', 'numerical'),
                  ('rank', 'numerical'),
              ])
          bdb.execute('create generator pe_cc for pe;')
          with pytest.raises(bayeslite.BQLError):
              # No models to analyze.
              bdb.execute('analyze pe_cc for 1 iteration')
          bdb.execute('initialize 1 model if not exists for pe_cc')
          bdb.execute('analyze pe_cc for 1 iteration')
          bdb.execute('estimate correlation'
              ' from pairwise columns of pe').fetchall()
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('initialize 4 models if not exists for t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('analyze t0 for 1 iteration')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate * from t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate * from columns of t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate correlation from pairwise columns of t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate similarity in the context of age '
                  'from pairwise t')
          bdb.execute('initialize 6 models if not exists for p_cc')
          bdb.execute('analyze p_cc for 1 iteration')
  
  def test_trivial_deadline():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 second')
  
  def test_parametrized():
      assert bql2sqlparam('select * from t where id = ?') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where id = :foo') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where id = $foo') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where id = @foo') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where id = ?123') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where a = $foo and b = ?1;') == \
          'SELECT * FROM "t" WHERE (("a" = ?1) AND ("b" = ?1));'
      assert bql2sqlparam('select * from t' +
              ' where a = ?123 and b = :foo and c = ?124') == \
          'SELECT * FROM "t" WHERE' + \
          ' ((("a" = ?1) AND ("b" = ?2)) AND ("c" = ?2));'
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          assert bql_execute(bdb, 'select count(*) from t') == [(7,)]
          assert bql_execute(bdb, 'select count(distinct division) from t') == \
              [(6,)]
          assert bql_execute(bdb, 'select * from t where height > ?', (70,)) == \
              [
                  (41, 'M', 65600, 72, 'marketing', 4),
                  (30, 'M', 70000, 73, 'sales', 4),
                  (30, 'F', 81000, 73, 'engineering', 3),
              ]
          assert bql_execute(bdb, 'select * from t where height > ?123',
                  (0,)*122 + (70,)) == \
              [
                  (41, 'M', 65600, 72, 'marketing', 4),
                  (30, 'M', 70000, 73, 'sales', 4),
                  (30, 'F', 81000, 73, 'engineering', 3),
              ]
          assert bql_execute(bdb, 'select age from t where division = :division',
                  {':division': 'sales'}) == \
              [(34,), (30,)]
          assert bql_execute(bdb, 'select division from t' +
                      ' where age < @age and rank > ?;',
                  (40, 4)) == \
              [('accounting',)]
          assert bql_execute(bdb, 'select division from t' +
                      ' where age < @age and rank > :rank;',
                  {':RANK': 4, '@aGe': 40}) == \
              [('accounting',)]
          with pytest.raises(ValueError):
              bdb.execute('select * from t where age < ? and rank > :r',
                  {':r': 4})
          def traced_execute(query, *args):
              bql = []
              def trace(string, _bindings):
                  bql.append(' '.join(string.split()))
              bdb.trace(trace)
              with bdb.savepoint():
                  bdb.execute(query, *args)
              bdb.untrace(trace)
              return bql
          def sqltraced_execute(query, *args):
              sql = []
              def trace(string, _bindings):
                  sql.append(' '.join(string.split()))
              bdb.sql_trace(trace)
              with bdb.savepoint():
                  bdb.execute(query, *args)
              bdb.sql_untrace(trace)
              return sql
          guess.bayesdb_guess_population(bdb, 'p', 't')
          bdb.execute('create generator p_cc for p;')
          bdb.execute('initialize 1 model for p_cc;')
          assert traced_execute('estimate similarity to (rowid = 1)'
                  ' in the context of (estimate * from columns of p limit 1)'
                  ' from p;') == [
              'estimate similarity to (rowid = 1)' \
                  ' in the context of (estimate * from columns of p limit 1)' \
                  ' from p;',
          ]
          assert sqltraced_execute('estimate similarity to (rowid = 1)'
                  ' in the context of (estimate * from columns of p limit 1)'
                  ' from p;') == [
              'SELECT COUNT(*) FROM bayesdb_population WHERE name = ?',
              'SELECT id FROM bayesdb_population WHERE name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'SELECT COUNT(*) FROM bayesdb_population WHERE name = ?',
              'SELECT id FROM bayesdb_population WHERE name = ?',
              'SELECT v.name AS name FROM bayesdb_variable AS v'
                  ' WHERE v.population_id = 1'
                      ' AND v.generator_id IS NULL'
                  ' LIMIT 1',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT tabname FROM bayesdb_population'
                  ' WHERE id = ?',
              'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,'
                  ' (SELECT _rowid_ FROM "t" WHERE ("rowid" = 1)), 0) FROM "t"',
              'SELECT id FROM bayesdb_generator WHERE population_id = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual '
                  'WHERE generator_id = ? AND table_rowid = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator '
                  'WHERE generator_id = ?'
              ]
  
          assert sqltraced_execute('estimate similarity to (rowid = 1)'
                  ' in the context of (estimate * from columns of p limit ?)'
                  ' from p;',
                  (1,)) == [
              'SELECT COUNT(*) FROM bayesdb_population'
                  ' WHERE name = ?',
              'SELECT id FROM bayesdb_population'
                  ' WHERE name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'SELECT COUNT(*) FROM bayesdb_population'
                  ' WHERE name = ?',
              'SELECT id FROM bayesdb_population'
                  ' WHERE name = ?',
              # ESTIMATE * FROM COLUMNS OF:
              'SELECT v.name AS name'
                  ' FROM bayesdb_variable AS v'
                  ' WHERE v.population_id = 1'
                      ' AND v.generator_id IS NULL'
                  ' LIMIT ?1',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              # ESTIMATE SIMILARITY TO (rowid=1):
              'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,'
                  ' (SELECT _rowid_ FROM "t" WHERE ("rowid" = 1)), 0) FROM "t"',
              'SELECT id FROM bayesdb_generator WHERE population_id = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?'
          ]
          assert sqltraced_execute(
                  'create temp table if not exists sim as '
                  'simulate age, RANK, division '
                  'from p given gender = \'F\' limit 4') == [
              'PRAGMA table_info("sim")',
              'PRAGMA table_info("bayesdb_temp_0")',
              'SELECT COUNT(*) FROM bayesdb_population WHERE name = ?',
              'SELECT id FROM bayesdb_population WHERE name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'PRAGMA table_info("t")',
              'SELECT CAST(4 AS INTEGER), \'F\'',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'SELECT MAX(_rowid_) FROM "t"',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT id FROM bayesdb_generator'
                  ' WHERE population_id = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT tabname FROM bayesdb_generator WHERE id = ?',
              'SELECT 1 FROM "t" WHERE oid = ?',
              'SELECT 1 FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ? LIMIT 1',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT code FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND value = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'CREATE TEMP TABLE "bayesdb_temp_0"'
                  ' ("age" NUMERIC,"RANK" NUMERIC,"division" NUMERIC)',
              'INSERT INTO "bayesdb_temp_0" ("age","RANK","division")'
                  ' VALUES (?,?,?)',
              'INSERT INTO "bayesdb_temp_0" ("age","RANK","division")'
                  ' VALUES (?,?,?)',
              'INSERT INTO "bayesdb_temp_0" ("age","RANK","division")'
                  ' VALUES (?,?,?)',
              'INSERT INTO "bayesdb_temp_0" ("age","RANK","division")'
                  ' VALUES (?,?,?)',
              'CREATE TEMP TABLE IF NOT EXISTS "sim" AS'
                  ' SELECT * FROM "bayesdb_temp_0"',
              'DROP TABLE "bayesdb_temp_0"'
          ]
          assert sqltraced_execute(
                  'select * from (simulate age from p '
                  'given gender = \'F\' limit 4)') == [
              'PRAGMA table_info("bayesdb_temp_1")',
              'SELECT COUNT(*) FROM bayesdb_population WHERE name = ?',
              'SELECT id FROM bayesdb_population WHERE name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'PRAGMA table_info("t")',
              'SELECT CAST(4 AS INTEGER), \'F\'',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'SELECT MAX(_rowid_) FROM "t"',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT id FROM bayesdb_generator WHERE population_id = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT tabname FROM bayesdb_generator WHERE id = ?',
              'SELECT 1 FROM "t" WHERE oid = ?',
              'SELECT 1 FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ? LIMIT 1',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT code FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND value = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'CREATE TEMP TABLE "bayesdb_temp_1" ("age" NUMERIC)',
              'INSERT INTO "bayesdb_temp_1" ("age") VALUES (?)',
              'INSERT INTO "bayesdb_temp_1" ("age") VALUES (?)',
              'INSERT INTO "bayesdb_temp_1" ("age") VALUES (?)',
              'INSERT INTO "bayesdb_temp_1" ("age") VALUES (?)',
              'SELECT * FROM (SELECT * FROM "bayesdb_temp_1")',
              'DROP TABLE "bayesdb_temp_1"',
          ]
          bdb.execute('''
              create population q for t (
                  age NUMERICAL;
                  gender CATEGORICAL;   -- Not binary!
                  salary NUMERICAL;
                  height NUMERICAL;
                  division CATEGORICAL;
                  rank CATEGORICAL
              )
          ''')
          bdb.execute('create generator q_cc for q;')
          bdb.execute('initialize 1 model for q_cc;')
          assert sqltraced_execute('analyze q_cc for 1 iteration;') == [
              'SELECT COUNT(*) FROM bayesdb_generator WHERE name = ?',
              'SELECT id FROM bayesdb_generator WHERE name = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT engine_json, engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?',
              'UPDATE bayesdb_cgpm_generator'
                  ' SET engine_json = :engine_json, engine_stamp = :engine_stamp'
                  ' WHERE generator_id = :generator_id']
  
  def test_create_table_ifnotexists_as_simulate():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
              # If not exists table tests
              guess.bayesdb_guess_population(bdb, 'p', 't',
                  overrides=[('age', 'numerical')])
              bdb.execute('create generator p_cc for p;')
              bdb.execute('initialize 1 model for p_cc')
              bdb.execute('analyze p_cc for 1 iteration')
              bdb.execute('''
                  create table if not exists u as
                      simulate age from p limit 10
              ''')
              bdb.execute("drop table u")
              bdb.execute('''
                  create table if not exists w as simulate age from p
                      given division='sales' limit 10
              ''')
              bdb.execute("drop table w")
              bdb.execute("create table u as simulate age from p limit 10")
              x = bdb.execute("select count (*) from u").fetchvalue()
              bdb.execute('''
                  create table if not exists u as simulate age from p limit 10
              ''')
              bdb.execute('''
                  create table if not exists u as simulate age from p
                      given division='sales' limit 10
              ''')
              assert x == bdb.execute("select count (*) from u").fetchvalue()
  
  def test_createtab():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with pytest.raises(apsw.SQLError):
              bdb.execute('drop table t')
          bdb.execute('drop table if exists t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop population p')
          bdb.execute('drop population if exists p')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop generator p_cc')
          bdb.execute('drop generator if exists p_cc')
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          with bdb.savepoint():
              # Savepoint because we don't actually want the new data to
              # be inserted.
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True, ifnotexists=True)
          guess.bayesdb_guess_population(bdb, 'p', 't',
              overrides=[('age', 'numerical')])
          bdb.execute('create generator p_cc for p;')
          with pytest.raises(bayeslite.BQLError):
              # Redefining population.
              bdb.execute('create population p for t (age numerical)')
          with pytest.raises(bayeslite.BQLError):
              # Redefining generator.
              bdb.execute('create generator p_cc for p;')
          # Make sure ignore columns work.
          #
          # XXX Also check key columns.
          guess.bayesdb_guess_population(bdb, 'p0', 't',
              overrides=[('age', 'ignore')])
          bdb.execute('drop population p0')
          population_id = core.bayesdb_get_population(bdb, 'p')
          colno = core.bayesdb_variable_number(bdb, population_id, None, 'age')
          assert core.bayesdb_variable_stattype(
              bdb, population_id, None, colno) == 'numerical'
          bdb.execute('initialize 1 model for p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop table t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop population p')
          bdb.execute('drop generator p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop generator p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop table t')
          bdb.execute('drop generator if exists p_cc')
          bdb.execute('drop population p')
          bdb.execute('drop population if exists p')
          bdb.execute('drop table t')
          bdb.execute('drop table if exists t')
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          guess.bayesdb_guess_population(bdb, 'p', 't')
          bdb.execute("create table u as select * from t where gender = 'F'")
          assert bql_execute(bdb, 'select * from u') == [
              (23, 'F', 81000, 67, 'data science', 3),
              (36, 'F', 96000, 70, 'management', 2),
              (30, 'F', 81000, 73, 'engineering', 3),
          ]
          with pytest.raises(bayeslite.BQLError):
              bdb.execute("create table u as select * from t where gender = 'F'")
          bdb.execute('drop table u')
          with pytest.raises(apsw.SQLError):
              bql_execute(bdb, 'select * from u')
          bdb.execute("create temp table u as"
              " select * from t where gender = 'F'")
          assert bql_execute(bdb, 'select * from u') == [
              (23, 'F', 81000, 67, 'data science', 3),
              (36, 'F', 96000, 70, 'management', 2),
              (30, 'F', 81000, 73, 'engineering', 3),
          ]
          # XXX Test to make sure TEMP is passed through, and the table
          # doesn't persist on disk.
  
  def test_alterpop_addvar():
      with bayeslite.bayesdb_open() as bdb:
          bayeslite.bayesdb_read_csv(
              bdb, 't', StringIO.StringIO(test_csv.csv_data),
              header=True, create=True)
          bdb.execute('''
              create population p for t with schema(
                  age         numerical;
                  gender      nominal;
                  salary      numerical;
                  height      ignore;
                  division    ignore;
                  rank        ignore;
              )
          ''')
          population_id = core.bayesdb_get_population(bdb, 'p')
          bdb.execute('create generator m for p;')
          # Fail when variable does not exist in base table.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('alter population p add variable quux;')
          # Fail when variable already in population.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('alter population p add variable age numerical;')
          # Fail when given invalid statistical type.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('alter population p add variable heigh numr;')
          # Alter pop with stattype.
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'height')
          bdb.execute('alter population p add variable height numerical;')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'height')
          # Alter pop multiple without stattype.
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'rank')
          assert not core.bayesdb_has_variable(
              bdb, population_id, None, 'division')
          bdb.execute('''
              alter population p
                  add variable rank,
                  add variable division;
          ''')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'rank')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'division')
          # Add a new column weight to the base table.
          bdb.sql_execute('alter table t add column weight real;')
          # Fail when no values in new column.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('alter population p add variable weight numerical;')
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'weight')
          # Update a single value and update the population.
          bdb.sql_execute('update t set weight = 1 where oid = 1;')
          bdb.execute('alter population p add variable weight numerical;')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'weight')
  
  def test_txn():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          # Make sure rollback and commit fail outside a transaction.
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('ROLLBACK')
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('COMMIT')
  
          # Open a transaction which we'll roll back.
          bdb.execute('BEGIN')
          try:
              # Make sure transactions don't nest.  (Use savepoints.)
              with pytest.raises(bayeslite.BayesDBTxnError):
                  bdb.execute('BEGIN')
          finally:
              bdb.execute('ROLLBACK')
  
          # Make sure rollback and commit still fail outside a transaction.
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('ROLLBACK')
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('COMMIT')
  
          # Open a transaction which we'll commit.
          bdb.execute('BEGIN')
          try:
              with pytest.raises(bayeslite.BayesDBTxnError):
                  bdb.execute('BEGIN')
          finally:
              bdb.execute('COMMIT')
  
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('ROLLBACK')
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('COMMIT')
  
          # Make sure ROLLBACK undoes the effects of the transaction.
          bdb.execute('BEGIN')
          try:
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
              bdb.execute('SELECT * FROM t').fetchall()
              guess.bayesdb_guess_population(bdb, 'p', 't')
              bdb.execute('ESTIMATE * FROM p').fetchall()
          finally:
              bdb.execute('ROLLBACK')
          with pytest.raises(apsw.SQLError):
              bdb.execute('SELECT * FROM t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('ESTIMATE * FROM p')
  
          # Make sure CREATE and DROP both work in the transaction.
          bdb.execute('BEGIN')
          try:
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
              bdb.execute('SELECT * FROM t').fetchall()
              guess.bayesdb_guess_population(bdb, 'p', 't')
              bdb.execute('ESTIMATE * FROM p').fetchall()
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('DROP TABLE t')
              bdb.execute('DROP POPULATION p')
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('ESTIMATE * FROM p')
              bdb.execute('DROP TABLE t')
              with pytest.raises(apsw.SQLError):
                  bdb.execute('SELECT * FROM t')
          finally:
              bdb.execute('ROLLBACK')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('ESTIMATE * FROM p')
          with pytest.raises(apsw.SQLError):
              bdb.execute('SELECT * FROM t')
  
          # Make sure CREATE and DROP work even if we commit.
          bdb.execute('BEGIN')
          try:
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
              bdb.execute('SELECT * FROM t').fetchall()
              guess.bayesdb_guess_population(bdb, 'p', 't')
              bdb.execute('ESTIMATE * FROM p').fetchall()
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('DROP TABLE t')
              bdb.execute('DROP POPULATION p')
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('ESTIMATE * FROM p')
              bdb.execute('DROP TABLE t')
              with pytest.raises(apsw.SQLError):
                  bdb.execute('SELECT * FROM t')
          finally:
              bdb.execute('COMMIT')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('ESTIMATE * FROM p')
          with pytest.raises(apsw.SQLError):
              bdb.execute('SELECT * FROM t')
  
          # Make sure CREATE persists if we commit.
          bdb.execute('BEGIN')
          try:
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
              bdb.execute('SELECT * FROM t').fetchall()
              guess.bayesdb_guess_population(bdb, 'p', 't')
              bdb.execute('ESTIMATE * FROM p').fetchall()
          finally:
              bdb.execute('COMMIT')
          bdb.execute('SELECT * FROM t').fetchall()
          bdb.execute('ESTIMATE * FROM p').fetchall()
  
          # Make sure bdb.transaction works, rolls back on exception,
          # and handles nesting correctly in the context of savepoints.
          try:
              with bdb.transaction():
                  bdb.sql_execute('create table quagga(x)')
                  raise StopIteration
          except StopIteration:
              pass
          with pytest.raises(apsw.SQLError):
              bdb.execute('select * from quagga')
          with bdb.transaction():
              with bdb.savepoint():
                  with bdb.savepoint():
                      pass
          with bdb.savepoint():
              with pytest.raises(bayeslite.BayesDBTxnError):
                  with bdb.transaction():
                      pass
  
          # XXX To do: Make sure other effects (e.g., analysis) get
          # rolled back by ROLLBACK.
  
  def test_predprob_null():
      backend = CGPM_Backend({}, multiprocess=False)
      with test_core.bayesdb(backend=backend) as bdb:
          bdb.sql_execute('''
              create table foo (
                  id integer primary key not null,
                  x numeric,
                  y numeric,
                  z numeric
              )
          ''')
          bdb.sql_execute("insert into foo values (1, 1, 'strange', 3)")
          bdb.sql_execute("insert into foo values (2, 1.2, 'strange', 1)")
          bdb.sql_execute("insert into foo values (3, 0.8, 'strange', 3)")
          bdb.sql_execute("insert into foo values (4, NULL, 'strange', 9)")
          bdb.sql_execute("insert into foo values (5, 73, 'up', 11)")
          bdb.sql_execute("insert into foo values (6, 80, 'up', -1)")
          bdb.sql_execute("insert into foo values (7, 60, 'up', NULL)")
          bdb.sql_execute("insert into foo values (8, 67, NULL, NULL)")
          bdb.sql_execute("insert into foo values (9, 3.1415926, 'down', 1)")
          bdb.sql_execute("insert into foo values (10, 1.4142135, 'down', 0)")
          bdb.sql_execute("insert into foo values (11, 2.7182818, 'down', -1)")
          bdb.sql_execute("insert into foo values (12, NULL, 'down', 10)")
          bdb.execute('''
              create population pfoo for foo (
                  id ignore;
                  x numerical;
                  y categorical;
                  z numerical;
              )
          ''')
          bdb.execute('create generator pfoo_cc for pfoo using cgpm;')
          bdb.execute('initialize 1 model for pfoo_cc')
          bdb.execute('analyze pfoo_cc for 1 iteration')
          # Null value => null predictive probability.
          assert bdb.execute('estimate predictive probability of x'
                  ' from pfoo where id = 4;').fetchall() == \
              [(None,)]
          # Nonnull value => nonnull predictive probability.
          x = bdb.execute('estimate predictive probability of x'
              ' from pfoo where id = 5').fetchall()
          assert len(x) == 1
          assert len(x[0]) == 1
          assert isinstance(x[0][0], (int, float))
          # All null values => null predictive probability.
          assert bdb.execute('estimate predictive probability of (y, z)'
                  ' from pfoo where id = 8;').fetchall() == \
              [(None,)]
          # Some nonnull values => nonnull predictive probability.
          x = bdb.execute('estimate predictive probability of (x, z)'
                  ' from pfoo where id = 8;').fetchall()
          assert len(x) == 1
          assert len(x[0]) == 1
          assert isinstance(x[0][0], (int, float))
          # All NULL constraints => same result regardless of given clause.
          c0 = bdb.execute('estimate predictive probability of x'
                  ' from pfoo where id = 8;')
          v0 = cursor_value(c0)
          assert v0 is not None
          c1 = bdb.execute('estimate predictive probability of x given (y, z)'
                  ' from pfoo where id = 8;')
          v1 = cursor_value(c1)
          assert relerr(v0, v1) < 0.0001
  
  def test_guess_all():
      with test_core.bayesdb() as bdb:
          bdb.sql_execute('create table foo (x numeric, y numeric, z numeric)')
          bdb.sql_execute('insert into foo values (1, 2, 3)')
          bdb.sql_execute('insert into foo values (4, 5, 6)')
          # XXX GUESS(*)
          guess.bayesdb_guess_population(bdb, 'pfoo', 'foo')
  
  def test_misc_errors():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('create table t1 as SELECT 1 FROM t1'
              # t1 already exists as a table.
                  ' limit 1')
          with pytest.raises(bayeslite.BQLError):
              # t1 already exists as a table.
              bdb.execute('create table t1 as simulate weight from p1'
                  ' limit 1')
          with pytest.raises(bayeslite.BQLError):
              # t1x does not exist as a population.
              bdb.execute('create table t1_sim as simulate weight from t1x'
                  ' limit 1')
          with pytest.raises(bayeslite.BQLError):
              # p1 does not have a variable waught.
              bdb.execute('create table t1_sim as simulate waught from p1'
                  ' limit 1')
          with pytest.raises(bayeslite.BQLError):
              # p1 does not have a variable agee.
              bdb.execute('create table t1_sim as simulate weight from p1'
                  ' given agee = 42 limit 1')
          with bdb.savepoint():
              bdb.sql_execute('create table t2(x)')
              with pytest.raises(bayeslite.BQLError):
                  # t1 already exists as a table.
                  bdb.execute('alter table t2 rename to t1')
          with pytest.raises(NotImplementedError):
              # Renaming columns is not yet implemented.
              bdb.execute('alter table t1 rename weight to mass')
          with pytest.raises(bayeslite.BQLError):
              # xcat does not exist as a backend.
              bdb.execute('create generator p1_xc for p1 using xcat()')
          with pytest.raises(bayeslite.BQLError):
              # p1 already exists as a population.
              bdb.execute('create generator p1_cc for p1;')
          with pytest.raises(bayeslite.BQLError):
              # multinomial is not a known statistical type.
              bdb.execute('''
                  create population q1 for t1(
                      ignore id, label, weight;
                      weight multinomial
                  )
              ''')
          with pytest.raises(bayeslite.BQLError):
              # p1_xc does not exist as a generator.
              bdb.execute('alter generator p1_xc rename to p1_xcat')
          with bdb.savepoint():
              bdb.execute('create generator p1_xc for p1;')
              with pytest.raises(bayeslite.BQLError):
                  # p1_xc already exists as a generator.
                  bdb.execute('alter generator p1_cc rename to p1_xc')
          with pytest.raises(bayeslite.BQLParseError):
              # WAIT is not allowed.
              bdb.execute('analyze p1_cc for 1 iteration wait')
          with bdb.savepoint():
              bdb.execute('initialize 1 model for p1_cc')
              bdb.execute('analyze p1_cc for 1 iteration')
              bdb.execute('initialize 1 model for p1_xc')
              bdb.execute('analyze p1_xc for 1 iteration')
              with pytest.raises(apsw.SQLError):
                  bdb.execute('select'
                      ' nonexistent((simulate age from p1 limit 1));')
          with pytest.raises(ValueError):
              bdb.execute('select :x', {'y': 42})
          with pytest.raises(ValueError):
              bdb.execute('select :x', {'x': 53, 'y': 42})
          with pytest.raises(ValueError):
              bdb.execute('select ?, ?', (1,))
          with pytest.raises(ValueError):
              bdb.execute('select ?', (1, 2))
          with pytest.raises(TypeError):
              bdb.execute('select ?', 42)
          with pytest.raises(NotImplementedError):
              bdb.execute('infer explicit predict age confidence ac, *'
                  ' from p1')
          with pytest.raises(NotImplementedError):
              bdb.execute('infer explicit predict age confidence ac,'
                  ' t1.(select age from t1 limit 1) from p1')
          with pytest.raises(bayeslite.BQLError):
              try:
                  bdb.execute('estimate similarity to (rowid=1)'
                      ' in the context of agee from p1')
              except bayeslite.BQLError as e:
                  assert 'No such columns in population:' in str(e)
                  raise
  
  def test_nested_simulate():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration')
          bdb.execute('select (simulate age from p1 limit 1),'
              ' (simulate weight from p1 limit 1)').fetchall()
          assert bdb.temp_table_name() == 'bayesdb_temp_2'
          assert not core.bayesdb_has_table(bdb, 'bayesdb_temp_0')
          assert not core.bayesdb_has_table(bdb, 'bayesdb_temp_1')
          bdb.execute('simulate weight from p1'
              ' given age = (simulate age from p1 limit 1)'
              ' limit 1').fetchall()
          # Make sure unwinding doesn't raise an exception.  Calling
          # __del__ directly, rather than via del(), has two effects:
          #
          # (a) It actually raises any exceptions in the method, unlike
          # del(), which suppresses them.
          #
          # (b) It may cause a subsequent __del__ to fail and raise an
          # exception, so that a subsequent del(), including an implicit
          # one at the end of a scope, may print a message to stderr.
          #
          # Effect (a) is what we are actually trying to test.  Effect
          # (b) is a harmless consequence as far as pytest is concerned,
          # as long as the test otherwise passes.
          bdb.execute('simulate weight from p1'
              ' given age = (simulate age from p1 limit 1)'
              ' limit 1').__del__()
  
  def test_checkpoint__ci_slow():
      with test_core.t1() as (bdb, population_id, generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 10 iterations checkpoint 1 iteration')
          # No checkpoint by seconds.
          with pytest.raises(NotImplementedError):
              bdb.execute('analyze p1_cc for 5 seconds checkpoint 1 second')
          bdb.execute('drop models from p1_cc')
          bdb.execute('initialize 1 model for p1_cc')
          # No checkpoint by seconds.
          with pytest.raises(NotImplementedError):
              bdb.execute('analyze p1_cc for 5 iterations checkpoint 1 second')
          sql = '''
              select iterations from bayesdb_generator_model
                  where generator_id = ?
          '''
          with pytest.raises(AssertionError):
              # XXX CGPM_Backend does not update bayesdb_generator_model.
              assert bdb.execute(sql, (generator_id,)).fetchvalue() == 5
          bdb.execute('drop models from p1_cc')
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration checkpoint 2 iterations')
          sql = '''
              select iterations from bayesdb_generator_model
                  where generator_id = ?
          '''
          with pytest.raises(AssertionError):
              # XXX CGPM_Backend does not update bayesdb_generator_model.
              assert bdb.execute(sql, (generator_id,)).fetchvalue() == 1
  
  def test_infer_confidence__ci_slow():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration')
          bdb.execute('infer explicit rowid, rowid as another_rowid, 4,'
              ' age, predict age as age_inf confidence age_conf'
              ' from p1').fetchall()
  
  def test_infer_as_estimate():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration')
          bdb.execute('infer explicit predictive probability of age'
              ' from p1').fetchall()
  
  def test_infer_error():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('infer explicit predict age confidence age_conf'
              ' from p1').fetchall()
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('infer explicit predict agee confidence age_conf'
                  ' from p1').fetchall()
  
  def test_estimate_by():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate predictive probability of age'
                  ' by p1')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate similarity to (rowid=1) '
                  'in the context of age by p1')
          def check(x, bindings=None):
              assert len(bdb.execute(x, bindings=bindings).fetchall()) == 1
          check('estimate probability density of age = 42 by p1')
          check('estimate dependence probability of age with weight by p1')
          check('estimate mutual information of age with weight by p1')
          check('estimate correlation of age with weight by p1')
          check('estimate correlation pvalue of age with weight by p1')
          rowid = bdb.execute('select min(rowid) from t1').fetchall()[0][0]
          check('''
              estimate similarity of (rowid=?) to (rowid=?)
              in the context of weight by p1
          ''', (rowid, rowid,))
  
  def test_empty_cursor():
      with bayeslite.bayesdb_open() as bdb:
          assert bdb.execute('SELECT 0').connection == bdb
          empty(bdb.execute('BEGIN'))
          empty(bdb.execute('COMMIT'))
          empty(bdb.sql_execute('CREATE TABLE t(x, y, z)'))
          empty(bdb.sql_execute('INSERT INTO t VALUES(1,2,3)'))
          empty(bdb.sql_execute('INSERT INTO t VALUES(4,5,6)'))
          empty(bdb.sql_execute('INSERT INTO t VALUES(7,8,9)'))
          empty(bdb.execute('CREATE POPULATION p FOR t '
              '(IGNORE z,y; x CATEGORICAL)'))
          empty(bdb.execute('CREATE GENERATOR p_cc FOR p;'))
          empty(bdb.execute('INITIALIZE 1 MODEL FOR p_cc'))
          empty(bdb.execute('DROP GENERATOR p_cc'))
          empty(bdb.execute('DROP POPULATION p'))
          empty(bdb.execute('DROP TABLE t'))
  
  def test_create_generator_ifnotexists():
      # XXX Test other backends too, because they have a role in ensuring that
      # this works. Their create_generator will still be called.
      #
      # [TRC 20160627: The above comment appears to be no longer true --
      # if it was ever true.]
      for using_clause in ('cgpm()',):
          with bayeslite.bayesdb_open() as bdb:
              bdb.sql_execute('CREATE TABLE t(x, y, z)')
              bdb.sql_execute('INSERT INTO t VALUES(1,2,3)')
              bdb.execute('''
                  CREATE POPULATION p FOR t (
                      x NUMERICAL;
                      y NUMERICAL;
                      z CATEGORICAL
                  )
              ''')
              for _i in (0, 1):
                  bdb.execute('CREATE GENERATOR p_cc IF NOT EXISTS FOR p USING '
                              + using_clause)
              try:
                  bdb.execute('CREATE GENERATOR p_cc FOR p USING ' + using_clause)
                  assert False  # Should have said it exists.
              except bayeslite.BQLError:
                  pass
  
  def test_bql_rand():
      with bayeslite.bayesdb_open() as bdb:
          bdb.sql_execute('CREATE TABLE frobotz(x)')
          for _ in range(10):
              bdb.sql_execute('INSERT INTO frobotz VALUES(2)')
          cursor = bdb.execute('SELECT bql_rand() FROM frobotz LIMIT 10;')
          rands = cursor.fetchall()
          # These are "the" random numbers (internal PRNG is seeded to 0)
          ans = [(0.28348770982811367,), (0.4789774612650598,), (0.07824908989551316,),
                 (0.6091223239372148,), (0.03906608409906187,), (0.3690599096081546,),
                 (0.8223420512129717,), (0.7777771914916722,), (0.061856771629497986,),
                 (0.6492586781908201,)]
          assert rands == ans
  
  def test_bql_rand2():
      seed = struct.pack('<QQQQ', 0, 0, 0, 3)
      with bayeslite.bayesdb_open(seed=seed) as bdb:
          bdb.sql_execute('CREATE TABLE frobotz(x)')
          for _ in range(10):
              bdb.sql_execute('INSERT INTO frobotz VALUES(2)')
          cursor = bdb.execute('SELECT bql_rand() FROM frobotz LIMIT 10;')
          rands = cursor.fetchall()
          ans = [(0.8351877951287725,), (0.9735099617243271,), (0.026142315910925418,),
                 (0.09380653289687524,), (0.1097050387582088,), (0.33154896906379605,),
                 (0.4579314980719317,), (0.09072802203491703,), (0.5276180968829105,),
                 (0.9993280772797679,)]
          assert rands == ans
  
  class MockTracerOneQuery(bayeslite.IBayesDBTracer):
      def __init__(self, q, qid):
          self.q = q
          self.qid = qid
          self.start_calls = 0
          self.ready_calls = 0
          self.error_calls = 0
          self.finished_calls = 0
          self.abandoned_calls = 0
      def start(self, qid, query, bindings):
          assert qid == self.qid
          assert query == self.q
          assert bindings == ()
          self.start_calls += 1
      def ready(self, qid, _cursor):
          assert qid == self.qid
          self.ready_calls += 1
      def error(self, qid, _e):
          assert qid == self.qid
          self.error_calls += 1
      def finished(self, qid):
          assert qid == self.qid
          self.finished_calls += 1
      def abandoned(self, qid):
          assert qid == self.qid
          self.abandoned_calls += 1
  
  def test_tracing_smoke():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          q = 'SELECT * FROM t1'
          tracer = MockTracerOneQuery(q, 1)
          bdb.trace(tracer)
          cursor = bdb.execute(q)
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
          cursor.fetchall()
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 1
          assert tracer.abandoned_calls == 0
          del cursor
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 1
          assert tracer.abandoned_calls == 1
          bdb.untrace(tracer)
          # XXX Make sure the whole cursor API works.
          q = 'SELECT 42'
          tracer = MockTracerOneQuery(q, 2)
          bdb.trace(tracer)
          cursor = bdb.execute(q)
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
          assert cursor.fetchvalue() == 42
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 1
          assert tracer.abandoned_calls == 0
          del cursor
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 1
          assert tracer.abandoned_calls == 1
  
  def test_tracing_error_smoke():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          q = 'SELECT * FROM wrong'
          tracer = MockTracerOneQuery(q, 1)
          bdb.trace(tracer)
          with pytest.raises(apsw.SQLError):
              bdb.execute(q)
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 0
          assert tracer.error_calls == 1
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
  
  class Boom(Exception):
      pass
  class ErroneousBackend(troll.TrollBackend):
      def __init__(self):
          self.call_ct = 0
      def name(self):
          return 'erroneous'
      def logpdf_joint(self, *_args, **_kwargs):
          if self.call_ct > 10: # Wait to avoid raising during sqlite's prefetch
              raise Boom()
          self.call_ct += 1
          return 0
  
  def test_tracing_execution_error_smoke():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bayeslite.bayesdb_register_backend(bdb, ErroneousBackend())
          bdb.execute('DROP GENERATOR p1_cc')
          bdb.execute('CREATE GENERATOR p1_err FOR p1 USING erroneous()')
          q = 'ESTIMATE PREDICTIVE PROBABILITY OF age FROM p1'
          tracer = MockTracerOneQuery(q, 1)
          bdb.trace(tracer)
          cursor = bdb.execute(q)
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
          with pytest.raises(Boom):
              cursor.fetchall()
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 1
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
  
  def test_pdf_var():
      with test_core.t1() as (bdb, population_id, _generator_id):
          bdb.execute('initialize 6 models for p1_cc;')
          c = bdb.execute(
              'estimate probability density of label = label from p1')
          c.fetchall()
          assert bql2sql(
                  'estimate probability density of label = label from p1') == \
              'SELECT bql_pdf_joint(1, NULL, NULL, 1, "label") FROM "t1";'
  
files: tests/test_bql.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import StringIO
  import apsw
  import pytest
  import struct
  
  import bayeslite
  import bayeslite.ast as ast
  import bayeslite.compiler as compiler
  import bayeslite.core as core
  import bayeslite.guess as guess
  import bayeslite.backends.troll_rng as troll
  import bayeslite.parse as parse
  
  from bayeslite.exception import BQLError
  from bayeslite.math_util import relerr
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  from bayeslite.util import cursor_value
  
  import test_core
  import test_csv
  
  from stochastic import stochastic
  
  def bql2sql(string, setup=None):
      with bayeslite.bayesdb_open(':memory:') as bdb:
          test_core.t1_schema(bdb)
          test_core.t1_data(bdb)
          bdb.execute('''
              create population p1 for t1 (
                  id ignore;
                  label categorical;
                  age numerical;
                  weight numerical
              )
          ''')
          if setup is not None:
              setup(bdb)
          phrases = parse.parse_bql_string(string)
          out = compiler.Output(0, {}, ())
          for phrase in phrases:
              assert ast.is_query(phrase)
              compiler.compile_query(bdb, phrase, out)
              out.write(';')
          return out.getvalue()
  
  # XXX Kludgey mess.  Please reorganize.
  def bql2sqlparam(string):
      with bayeslite.bayesdb_open(':memory:') as bdb:
          test_core.t1_schema(bdb)
          test_core.t1_data(bdb)
          bdb.execute('''
              create population p1 for t1 (
                  id ignore;
                  label categorical;
                  age numerical;
                  weight numerical
              )
          ''')
          phrases = parse.parse_bql_string(string)
          out0 = StringIO.StringIO()
          for phrase in phrases:
              out = None
              if isinstance(phrase, ast.Parametrized):
                  bindings = (None,) * phrase.n_numpar
                  out = compiler.Output(phrase.n_numpar, phrase.nampar_map,
                      bindings)
                  phrase = phrase.phrase
              else:
                  out = StringIO.StringIO()
              assert ast.is_query(phrase)
              compiler.compile_query(bdb, phrase, out)
              # XXX Do something about the parameters.
              out0.write(out.getvalue())
              out0.write(';')
          return out0.getvalue()
  
  def bql_execute(bdb, string, bindings=()):
      return map(tuple, bdb.execute(string, bindings))
  
  def empty(cursor):
      assert cursor is not None
      assert cursor.description is not None
      assert len(cursor.description) == 0
      with pytest.raises(StopIteration):
          cursor.next()
  
  def test_trivial_population():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          # XXX if (not) exists
          bdb.execute('''
              create population p for t (
                  guess stattypes for (*);
                  age numerical
              )
          ''')
          bdb.execute('drop population p')
  
  def test_population_invalid_numerical():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          with pytest.raises(BQLError):
              bdb.execute('''
                  create population p for t (
                      guess stattypes for (*);
                      gender numerical
                  )
              ''')
  
  def test_population_invalid_numerical_alterpop_addvar():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          bdb.execute('''
              create population p for t (
                  guess stattypes for (*);
                  ignore gender
              )
          ''')
          with pytest.raises(BQLError):
              bdb.execute('alter population p add variable gender numerical')
          bdb.execute('drop population p')
  
  def test_population_invalid_numerical_alterpop_stattype():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          bdb.execute('''
                  create population p for t (
                      guess stattypes for (*);
                      gender nominal
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  alter population p set stattypes for gender to numerical
              ''')
          bdb.execute('drop population p')
  
  def test_similarity_identity():
      with test_core.t1() as (bdb, population_id, _generator_id):
          bdb.execute('initialize 6 models for p1_cc;')
          rowids = bdb.sql_execute('select rowid from t1')
          for rowid in rowids:
              c = bdb.execute('''
                  estimate similarity of (rowid=?) to (rowid=?)
                  in the context of age by p1
              ''', (rowid[0], rowid[0])).fetchall()
              assert len(c) == 1
              assert c[0][0] == 1
  
  def test_predictive_relevance():
      assert bql2sql('''
          estimate predictive relevance
              of (label = 'Uganda')
              to existing rows (rowid < 4)
              and hypothetical rows with values (
                  ("age" = 82, "weight" = 14),
                  ("age" = 74, label = 'Europe', "weight" = 7)
              )
              in the context of "weight"
          by p1
      ''') == \
          'SELECT bql_row_predictive_relevance(1, NULL, NULL, ' \
              '(SELECT _rowid_ FROM "t1" WHERE ("label" = \'Uganda\')), '\
              '\'[1, 2, 3]\', 3, '\
              '2, 82, 3, 14, NULL, 2, 74, 1, \'Europe\', 3, 7, NULL);'
      assert bql2sql('''
          estimate predictive relevance
              of (label = 'mumble')
              to existing rows (label = 'frotz' or age <= 4)
              in the context of "label"
          by p1
      ''') == \
          'SELECT bql_row_predictive_relevance(1, NULL, NULL, ' \
              '(SELECT _rowid_ FROM "t1" WHERE ("label" = \'mumble\')), '\
              '\'[5, 8]\', 1);'
      assert bql2sql('''
          estimate label,
              predictive relevance
              to hypothetical rows with values (
                  ("age" = 82, "weight" = 14),
                  ("age" = 74, label = 'hunf', "weight" = 7)
              )
              in the context of "age",
              _rowid_ + 1
          from p1
      ''') == \
          'SELECT "label", bql_row_predictive_relevance(1, NULL, NULL, _rowid_, '\
          '\'[]\', 2, 2, 82, 3, 14, NULL, 2, 74, 1, \'hunf\', 3, 7, NULL), '\
          '("_rowid_" + 1) FROM "t1";'
      # No matching rows should still compile.
      assert bql2sql('''
          estimate label,
              predictive relevance to existing rows (rowid < 0)
              in the context of "age"
          from p1
      ''') == \
          'SELECT "label", bql_row_predictive_relevance(1, NULL, NULL, _rowid_, '\
          '\'[]\', 2) FROM "t1";'
      # When using `BY`, require OF to be specified.
      with pytest.raises(BQLError):
          bql2sql('''
              estimate predictive relevance
                  to hypothetical rows with values (
                      ("age" = 82, "weight" = 14),
                      ("age" = 74, label = 'Europe', "weight" = 7)
                  )
                  in the context of "age"
              by p1
          ''')
      # When using `FROM`, require OF to be unspecified.
      with pytest.raises(BQLError):
          bql2sql('''
              estimate predictive relevance
                  of (name = 'mansour')
                  to hypothetical rows with values (
                      ("age" = 82, "weight" = 14)
                  )
                  in the context of "age"
              from p1
          ''')
      assert bql2sql('''
          estimate label from p1
          where
              (predictive relevance to existing rows (label = 'quux' and age < 5)
              in the context of "weight") > 1
          order by
              predictive relevance
                  to hypothetical rows with values ((label='zot'))
                  in the context of "age"
      ''') == \
          'SELECT "label" FROM "t1" WHERE '\
          '(bql_row_predictive_relevance(1, NULL, NULL, '\
              '_rowid_, \'[5]\', 3) > 1) '\
          'ORDER BY bql_row_predictive_relevance(1, NULL, NULL, '\
              '_rowid_, \'[]\', 2, 1, \'zot\', NULL);'
  
  
  @stochastic(max_runs=2, min_passes=1)
  def test_conditional_probability(seed):
      with test_core.t1(seed=seed) as (bdb, _population_id, _generator_id):
          bdb.execute('drop generator p1_cc')
          bdb.execute('drop population p1')
          bdb.execute('''
              create population p1 for t1 (
                  ignore id, label;
                  model age as numerical;
                  model weight as numerical
              )
          ''')
          bdb.execute('''
              create generator p1_cond_prob_cc for p1;
          ''')
          bdb.execute('initialize 1 model for p1_cond_prob_cc')
          bdb.execute('alter generator p1_cond_prob_cc '
              'ensure variables * dependent')
          bdb.execute('analyze p1_cond_prob_cc for 1 iteration')
          q0 = 'estimate probability density of age = 8 by p1'
          q1 = 'estimate probability density of age = 8 given () by p1'
          age_is_8 = bdb.execute(q0).fetchvalue()
          assert age_is_8 == bdb.execute(q1).fetchvalue()
          q2 = 'estimate probability density of age = 8 given (weight = 16)' \
              ' by p1'
          age_is_8_given_weight_is_16 = bdb.execute(q2).fetchvalue()
          assert age_is_8 < age_is_8_given_weight_is_16
  
          probs = bdb.execute(
              'estimate probability density of value 8 given (weight = 16)'
              ' from columns of p1 where v.name != \'weight\'').fetchall()
          assert [(age_is_8_given_weight_is_16,)] == probs
  
  @stochastic(max_runs=2, min_passes=1)
  def test_joint_probability(seed):
      with test_core.t1(seed=seed) as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 10 models for p1_cc')
          bdb.execute('analyze p1_cc for 10 iterations')
          q0 = 'estimate probability density of age = 8 by p1'
          q1 = 'estimate probability density of (age = 8) by p1'
          assert bdb.execute(q0).fetchvalue() == bdb.execute(q1).fetchvalue()
          q1 = 'estimate probability density of (age = 8) given () by p1'
          assert bdb.execute(q0).fetchvalue() == bdb.execute(q1).fetchvalue()
          q2 = 'estimate probability density of age = 8 given (weight = 16)' \
              ' by p1'
          assert bdb.execute(q0).fetchvalue() < bdb.execute(q2).fetchvalue()
          q0 = 'estimate probability density of age = 8 by p1'
          q1 = 'estimate probability density of (age = 8, weight = 16) by p1'
          assert bdb.execute(q1).fetchvalue() < bdb.execute(q0).fetchvalue()
          q2 = 'estimate probability density of (age = 8, weight = 16)' \
              " given (label = 'mumble') by p1"
          assert bdb.execute(q1).fetchvalue() < bdb.execute(q2).fetchvalue()
  
  def test_badbql():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          with pytest.raises(ValueError):
              bdb.execute('')
          with pytest.raises(ValueError):
              bdb.execute(';')
          with pytest.raises(ValueError):
              bdb.execute('select 0; select 1')
  
  def test_select_trivial():
      assert bql2sql('select null;') == 'SELECT NULL;'
      assert bql2sql("select 'x';") == "SELECT 'x';"
      assert bql2sql("select 'x''y';") == "SELECT 'x''y';"
      assert bql2sql('select "x";') == 'SELECT "x";'
      assert bql2sql('select "x""y";') == 'SELECT "x""y";'
      assert bql2sql('select 0;') == 'SELECT 0;'
      assert bql2sql('select 0.;') == 'SELECT 0.0;'
      assert bql2sql('select .0;') == 'SELECT 0.0;'
      assert bql2sql('select 0.0;') == 'SELECT 0.0;'
      assert bql2sql('select 1e0;') == 'SELECT 1.0;'
      assert bql2sql('select 1e+1;') == 'SELECT 10.0;'
      assert bql2sql('select 1e-1;') == 'SELECT 0.1;'
      assert bql2sql('select -1e+1;') == 'SELECT (- 10.0);'
      assert bql2sql('select +1e-1;') == 'SELECT (+ 0.1);'
      assert bql2sql('select SQRT(1-EXP(-2*value)) FROM bm_mi;') == \
          'SELECT "SQRT"((1 - "EXP"(((- 2) * "value")))) FROM "bm_mi";'
      assert bql2sql('select .1e0;') == 'SELECT 0.1;'
      assert bql2sql('select 1.e10;') == 'SELECT 10000000000.0;'
      assert bql2sql('select all 0;') == 'SELECT 0;'
      assert bql2sql('select distinct 0;') == 'SELECT DISTINCT 0;'
      assert bql2sql('select 0 as z;') == 'SELECT 0 AS "z";'
      assert bql2sql('select * from t;') == 'SELECT * FROM "t";'
      assert bql2sql('select t.* from t;') == 'SELECT "t".* FROM "t";'
      assert bql2sql('select c from t;') == 'SELECT "c" FROM "t";'
      assert bql2sql('select c as d from t;') == 'SELECT "c" AS "d" FROM "t";'
      assert bql2sql('select t.c as d from t;') == \
          'SELECT "t"."c" AS "d" FROM "t";'
      assert bql2sql('select t.c as d, p as q, x from t;') == \
          'SELECT "t"."c" AS "d", "p" AS "q", "x" FROM "t";'
      assert bql2sql('select * from t, u;') == 'SELECT * FROM "t", "u";'
      assert bql2sql('select * from t as u;') == 'SELECT * FROM "t" AS "u";'
      assert bql2sql('select * from (select 0);') == 'SELECT * FROM (SELECT 0);'
      assert bql2sql('select t.c from (select d as c from u) as t;') == \
          'SELECT "t"."c" FROM (SELECT "d" AS "c" FROM "u") AS "t";'
      assert bql2sql('select * where x;') == 'SELECT * WHERE "x";'
      assert bql2sql('select * from t where x;') == \
          'SELECT * FROM "t" WHERE "x";'
      assert bql2sql('select * group by x;') == 'SELECT * GROUP BY "x";'
      assert bql2sql('select * from t where x group by y;') == \
          'SELECT * FROM "t" WHERE "x" GROUP BY "y";'
      assert bql2sql('select * from t where x group by y, z;') == \
          'SELECT * FROM "t" WHERE "x" GROUP BY "y", "z";'
      assert bql2sql('select * from t where x group by y having sum(z) < 1') == \
          'SELECT * FROM "t" WHERE "x" GROUP BY "y" HAVING ("sum"("z") < 1);'
      assert bql2sql('select * order by x;') == 'SELECT * ORDER BY "x";'
      assert bql2sql('select * order by x asc;') == 'SELECT * ORDER BY "x";'
      assert bql2sql('select * order by x desc;') == \
          'SELECT * ORDER BY "x" DESC;'
      assert bql2sql('select * order by x, y;') == 'SELECT * ORDER BY "x", "y";'
      assert bql2sql('select * order by x desc, y;') == \
          'SELECT * ORDER BY "x" DESC, "y";'
      assert bql2sql('select * order by x, y asc;') == \
          'SELECT * ORDER BY "x", "y";'
      assert bql2sql('select * limit 32;') == 'SELECT * LIMIT 32;'
      assert bql2sql('select * limit 32 offset 16;') == \
          'SELECT * LIMIT 32 OFFSET 16;'
      assert bql2sql('select * limit 16, 32;') == 'SELECT * LIMIT 32 OFFSET 16;'
      assert bql2sql('select (select0);') == 'SELECT "select0";'
      assert bql2sql('select (select 0);') == 'SELECT (SELECT 0);'
      assert bql2sql('select f(f(), f(x), y);') == \
          'SELECT "f"("f"(), "f"("x"), "y");'
      assert bql2sql('select a and b or c or not d is e is not f like j;') == \
          'SELECT ((("a" AND "b") OR "c") OR' \
          + ' (NOT ((("d" IS "e") IS NOT "f") LIKE "j")));'
      assert bql2sql('select a like b not like c like d escape e;') == \
          'SELECT ((("a" LIKE "b") NOT LIKE "c") LIKE "d" ESCAPE "e");'
      assert bql2sql('select a like b escape c glob d not glob e;') == \
          'SELECT ((("a" LIKE "b" ESCAPE "c") GLOB "d") NOT GLOB "e");'
      assert bql2sql('select a not glob b glob c escape d;') == \
          'SELECT (("a" NOT GLOB "b") GLOB "c" ESCAPE "d");'
      assert bql2sql('select a glob b escape c regexp e not regexp f;') == \
          'SELECT ((("a" GLOB "b" ESCAPE "c") REGEXP "e") NOT REGEXP "f");'
      assert bql2sql('select a not regexp b regexp c escape d;') == \
          'SELECT (("a" NOT REGEXP "b") REGEXP "c" ESCAPE "d");'
      assert bql2sql('select a regexp b escape c not regexp d escape e;') == \
          'SELECT (("a" REGEXP "b" ESCAPE "c") NOT REGEXP "d" ESCAPE "e");'
      assert bql2sql('select a not regexp b escape c match e not match f;') == \
          'SELECT ((("a" NOT REGEXP "b" ESCAPE "c") MATCH "e") NOT MATCH "f");'
      assert bql2sql('select a not match b match c escape d;') == \
          'SELECT (("a" NOT MATCH "b") MATCH "c" ESCAPE "d");'
      assert bql2sql('select a match b escape c not match d escape e;') == \
          'SELECT (("a" MATCH "b" ESCAPE "c") NOT MATCH "d" ESCAPE "e");'
      assert bql2sql('select a not match b escape c between d and e;') == \
          'SELECT (("a" NOT MATCH "b" ESCAPE "c") BETWEEN "d" AND "e");'
      assert bql2sql('select a between b and c and d;') == \
          'SELECT (("a" BETWEEN "b" AND "c") AND "d");'
      assert bql2sql('select a like b like c escape d between e and f;') == \
          'SELECT ((("a" LIKE "b") LIKE "c" ESCAPE "d") BETWEEN "e" AND "f");'
      assert bql2sql('select a between b and c not between d and e;') == \
          'SELECT (("a" BETWEEN "b" AND "c") NOT BETWEEN "d" AND "e");'
      assert bql2sql('select a not between b and c in (select f);') == \
          'SELECT (("a" NOT BETWEEN "b" AND "c") IN (SELECT "f"));'
      assert bql2sql('select a in (select b) and c not in (select d);') == \
          'SELECT (("a" IN (SELECT "b")) AND ("c" NOT IN (SELECT "d")));'
      assert bql2sql("select a in (1 + 2, '3') and b not in (select c);") == \
          'SELECT (("a" IN ((1 + 2), \'3\')) AND ("b" NOT IN (SELECT "c")));'
      assert bql2sql('select a in (select b) isnull notnull!=c<>d<e<=f>g;') == \
          'SELECT ((((("a" IN (SELECT "b")) ISNULL) NOTNULL) != "c") !=' \
          + ' ((("d" < "e") <= "f") > "g"));'
      assert bql2sql('select a>b>=c<<d>>e&f|g+h-i*j/k;') == \
          'SELECT (("a" > "b") >= (((("c" << "d") >> "e") & "f") |' \
          + ' (("g" + "h") - (("i" * "j") / "k"))));'
      assert bql2sql('select a/b%c||~~d collate e collate\'f\'||1;') == \
          'SELECT (("a" / "b") % (("c" || (((~ (~ "d")) COLLATE "e")' \
          + ' COLLATE "f")) || 1));'
      assert bql2sql('select cast(f(x) as binary blob);') == \
          'SELECT CAST("f"("x") AS "binary" "blob");'
      assert bql2sql('select cast(42 as varint(73));') == \
          'SELECT CAST(42 AS "varint"(73));'
      assert bql2sql('select cast(f(x, y, z) as varchar(12 ,34));') == \
          'SELECT CAST("f"("x", "y", "z") AS "varchar"(12, 34));'
      assert bql2sql('select exists (select a) and not exists (select b);') == \
          'SELECT (EXISTS (SELECT "a") AND (NOT EXISTS (SELECT "b")));'
      assert bql2sql('select case when a - b then c else d end from t;') == \
          'SELECT CASE WHEN ("a" - "b") THEN "c" ELSE "d" END FROM "t";'
      assert bql2sql('select case f(a) when b + c then d else e end from t;') \
          == \
          'SELECT CASE "f"("a") WHEN ("b" + "c") THEN "d" ELSE "e" END FROM "t";'
  
  def test_estimate_bql():
      # PREDICTIVE PROBABILITY
      assert bql2sql('estimate predictive probability of weight from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[3]\', \'[]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of (age, weight) '
              'from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[2, 3]\', \'[]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of (age, weight) given '
              '(label) from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[2, 3]\', \'[1]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of (*) from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[1, 2, 3]\', \'[]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of (*) given (age, weight) '
              'from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[1]\', \'[2, 3]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of age given (*) '
              'from p1;') == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[2]\', \'[1, 3]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate label, predictive probability of weight'
              ' from p1;') \
          == \
          'SELECT "label", ' \
              'bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[3]\', \'[]\')' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of weight, label'
              ' from p1;') \
          == \
          'SELECT bql_row_column_predictive_probability(1, NULL, NULL, _rowid_, '\
                  '\'[3]\', \'[]\'),' \
              ' "label"' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of weight + 1'
              ' from p1;') == \
          'SELECT (bql_row_column_predictive_probability(1, NULL, NULL, '\
                  '_rowid_, \'[3]\', \'[]\') + 1)' \
              ' FROM "t1";'
      assert bql2sql('estimate predictive probability of weight given (*) + 1'
              ' from p1;') == \
          'SELECT (bql_row_column_predictive_probability(1, NULL, NULL, '\
                  '_rowid_, \'[3]\', \'[1, 2]\') + 1)' \
              ' FROM "t1";'
      # PREDICTIVE PROBABILITY parse and compilation errors.
      with pytest.raises(parse.BQLParseError):
          # Need a table.
          bql2sql('estimate predictive probability of weight;')
      with pytest.raises(parse.BQLParseError):
          # Need at most one generator.
          bql2sql('estimate predictive probability of weight'
              ' from p1, p1;')
      with pytest.raises(parse.BQLParseError):
          # Need a generator name, not a subquery.
          bql2sql('estimate predictive probability of weight'
              ' from (select 0);')
      with pytest.raises(parse.BQLParseError):
          # Need a column.
          bql2sql('estimate predictive probability from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Using (*) in both targets and constraints.
          bql2sql('estimate predictive probability of (*) given (*) from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Using (weight, *) in targets.
          bql2sql('estimate predictive probability of (weight, *) given (age) '
              'from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Using (age, *) in constraints.
          bql2sql('estimate predictive probability of weight given (*, age) '
              'from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Using duplicate column age.
          bql2sql('estimate predictive probability of age given (weight, age) '
              'from p1;')
      # PROBABILITY DENISTY.
      assert bql2sql('estimate probability density of weight = 20 from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, 20) FROM "t1";'
      assert bql2sql('estimate probability density of weight = 20'
              ' given (age = 8)'
              ' from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, 20, NULL, 2, 8) FROM "t1";'
      assert bql2sql('estimate probability density of (weight = 20, age = 8)'
              ' from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, 20, 2, 8) FROM "t1";'
      assert bql2sql('estimate probability density of (weight = 20, age = 8)'
              " given (label = 'mumble') from p1;") == \
          "SELECT bql_pdf_joint(1, NULL, NULL, 3, 20, 2, 8, NULL, 1, 'mumble')" \
              ' FROM "t1";'
      assert bql2sql('estimate probability density of weight = (c + 1)'
              ' from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, ("c" + 1)) FROM "t1";'
      assert bql2sql('estimate probability density of weight = f(c)'
              ' from p1;') == \
          'SELECT bql_pdf_joint(1, NULL, NULL, 3, "f"("c")) FROM "t1";'
      assert bql2sql('estimate similarity to (rowid = 5) '
              'in the context of weight from p1;') == \
          'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)), 3) FROM "t1";'
      assert bql2sql(
              'estimate similarity of (rowid = 12) to (rowid = 5) '
              'in the context of weight from p1;') == \
          'SELECT bql_row_similarity(1, NULL, NULL,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 12)),' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)), 3) FROM "t1";'
      assert bql2sql('estimate similarity to (rowid = 5) in the context of age'
              ' from p1') == \
          'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)), 2) FROM "t1";'
      assert bql2sql(
          'estimate similarity of (rowid = 5) to (height = 7 and age < 10)'
              ' in the context of weight from p1;') == \
          'SELECT bql_row_similarity(1, NULL, NULL,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)),' \
          ' (SELECT _rowid_ FROM "t1" WHERE (("height" = 7) AND ("age" < 10))),' \
          ' 3) FROM "t1";'
      with pytest.raises(bayeslite.BQLError):
          # Cannot use all variables for similarity.
          bql2sql(
              'estimate similarity to (rowid = 5) in the context of * from p1;')
      assert bql2sql('estimate similarity to (rowid = 5)'
              ' in the context of age from p1;') == \
          'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,' \
          ' (SELECT _rowid_ FROM "t1" WHERE ("rowid" = 5)), 2) FROM "t1";'
      assert bql2sql('estimate dependence probability of age with weight'
              ' from p1;') == \
          'SELECT bql_column_dependence_probability(1, NULL, NULL, 2, 3) '\
          'FROM "t1";'
      with pytest.raises(bayeslite.BQLError):
          # Need both rows fixed.
          bql2sql('estimate similarity to (rowid=2) in the context of r by p1')
      with pytest.raises(bayeslite.BQLError):
          # Need both rows fixed.
          bql2sql('estimate similarity in the context of r within p1')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate dependence probability with age from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate dependence probability from p1;')
      assert bql2sql('estimate mutual information of age with weight' +
          ' from p1;') == \
          'SELECT bql_column_mutual_information('\
              '1, NULL, NULL, \'[2]\', \'[3]\', NULL)'\
          ' FROM "t1";'
      assert bql2sql('estimate mutual information of age with weight' +
          ' using 42 samples from p1;') == \
          'SELECT bql_column_mutual_information('\
              '1, NULL, NULL, \'[2]\', \'[3]\', 42)'\
          ' FROM "t1";'
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate mutual information with age from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate mutual information from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate mutual information with age using 42 samples'
              ' from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate mutual information using 42 samples from p1;')
      # XXX Should be SELECT, not ESTIMATE, here?
      assert bql2sql('estimate correlation of age with weight from p1;') == \
          'SELECT bql_column_correlation(1, NULL, NULL, 2, 3) FROM "t1";'
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate correlation with age from p1;')
      with pytest.raises(bayeslite.BQLError):
          # Need both columns fixed.
          bql2sql('estimate correlation from p1;')
      with pytest.raises(BQLError):
          # Variable must exist.
          bql2sql('estimate correlation with agee from variables of p1')
  
  def test_predict_outside_infer():
      with pytest.raises(bayeslite.BQLError):
          # No PREDICT outside INFER.
          bql2sql('estimate predict age with confidence 0.9 from p1;')
  
  def test_infer_explicit_predict_confidence():
      assert bql2sql('infer explicit predict age with confidence 0.9'
              ' from p1;') == \
          'SELECT bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, NULL) FROM "t1";'
  
  def test_infer_explicit_predict_confidence_nsamples():
      assert bql2sql('infer explicit'
              ' predict age with confidence 0.9 using 42 samples'
              ' from p1;') == \
          'SELECT bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, 42) FROM "t1";'
  
  def test_infer_explicit_verbatim_and_predict_confidence():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age confidence age_conf from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age",' \
              ' bql_json_get(c2, \'confidence\') AS "age_conf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, NULL)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_noconfidence():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, NULL)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_confidence_nsamples():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age confidence age_conf using 42 samples from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age",' \
              ' bql_json_get(c2, \'confidence\') AS "age_conf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, 42)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_noconfidence_nsamples():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age using 42 samples from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, 42)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_confidence_as():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age as age_inf confidence age_conf from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age_inf",' \
              ' bql_json_get(c2, \'confidence\') AS "age_conf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, NULL)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_noconfidence_as():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age as age_inf from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age_inf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, NULL)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_confidence_as_nsamples():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age as age_inf confidence age_conf using 87 samples'
              ' from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age_inf",' \
              ' bql_json_get(c2, \'confidence\') AS "age_conf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, 87)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_explicit_verbatim_and_predict_noconfidence_as_nsamples():
      assert bql2sql('infer explicit rowid, age,'
              ' predict age as age_inf using 87 samples'
              ' from p1') == \
          'SELECT c0 AS "rowid", c1 AS "age",' \
              ' bql_json_get(c2, \'value\') AS "age_inf"' \
              ' FROM (SELECT "rowid" AS c0, "age" AS c1,' \
                  ' bql_predict_confidence(1, NULL, NULL, _rowid_, 2, 87)' \
                  ' AS c2 FROM "t1");'
  
  def test_infer_auto():
      assert bql2sql('infer rowid, age, weight from p1') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0, NULL))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_nsamples():
      assert bql2sql('infer rowid, age, weight using (1+2) samples from p1') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0, (1 + 2)))' \
              ' AS "age",' \
          ' "IFNULL"("weight",'\
                  ' bql_predict(1, NULL, NULL, _rowid_, 3, 0, (1 + 2)))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_with_confidence():
      assert bql2sql('infer rowid, age, weight with confidence 0.9 from p1') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight",'\
                  ' bql_predict(1, NULL, NULL, _rowid_, 3, 0.9, NULL))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_with_confidence_nsamples():
      assert bql2sql('infer rowid, age, weight with confidence 0.9'
              ' using sqrt(2) samples'
              ' from p1') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9,' \
                  ' "sqrt"(2)))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9,' \
                  ' "sqrt"(2)))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_with_confidence_where():
      assert bql2sql('infer rowid, age, weight with confidence 0.9 from p1'
              ' where label = \'foo\'') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9,'\
                  ' NULL))' \
              ' AS "weight"' \
          ' FROM "t1"' \
          ' WHERE ("label" = \'foo\');'
  
  def test_infer_auto_with_confidence_nsamples_where():
      assert bql2sql('infer rowid, age, weight with confidence 0.9'
              ' using 42 samples'
              ' from p1'
              ' where label = \'foo\'') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, 42))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9, 42))' \
              ' AS "weight"' \
          ' FROM "t1"' \
          ' WHERE ("label" = \'foo\');'
  
  def test_infer_auto_with_confidence_nsamples_where_predict():
      assert bql2sql('infer rowid, age, weight with confidence 0.9 from p1'
              ' where ifnull(label, predict label with confidence 0.7)'
                  ' = \'foo\'') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9,' \
                  ' NULL))' \
              ' AS "weight"' \
          ' FROM "t1"' \
          ' WHERE ("ifnull"("label",' \
                  ' bql_predict(1, NULL, NULL, _rowid_, 1, 0.7, NULL))' \
              ' = \'foo\');'
  
  def test_infer_auto_with_confidence_nsamples_where_predict_nsamples():
      assert bql2sql('infer rowid, age, weight with confidence 0.9'
              ' using 42 samples'
              ' from p1'
              ' where ifnull(label, predict label with confidence 0.7'
                     ' using 73 samples)'
                  ' = \'foo\'') \
          == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0.9, 42))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0.9, 42))' \
              ' AS "weight"' \
          ' FROM "t1"' \
          ' WHERE ("ifnull"("label",' \
                  ' bql_predict(1, NULL, NULL, _rowid_, 1, 0.7, 73))' \
              ' = \'foo\');'
  
  def test_infer_auto_star():
      assert bql2sql('infer rowid, * from p1') == \
          'SELECT "rowid" AS "rowid", "id" AS "id",' \
          ' "IFNULL"("label", bql_predict(1, NULL, NULL, _rowid_, 1, 0, NULL))' \
              ' AS "label",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0, NULL))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_infer_auto_star_nsamples():
      assert bql2sql('infer rowid, * using 1 samples from p1') == \
          'SELECT "rowid" AS "rowid", "id" AS "id",' \
          ' "IFNULL"("label", bql_predict(1, NULL, NULL, _rowid_, 1, 0, 1))' \
              ' AS "label",' \
          ' "IFNULL"("age", bql_predict(1, NULL, NULL, _rowid_, 2, 0, 1))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, NULL, NULL, _rowid_, 3, 0, 1))' \
              ' AS "weight"' \
          ' FROM "t1";'
  
  def test_estimate_columns_trivial():
      prefix0 = 'SELECT v.name AS name'
      prefix1 = ' FROM bayesdb_variable AS v' \
          ' WHERE v.population_id = 1' \
          ' AND v.generator_id IS NULL'
      prefix = prefix0 + prefix1
      assert bql2sql('estimate * from columns of p1;') == \
          prefix + ';'
      assert bql2sql('estimate * from columns of p1 where' +
              ' (probability density of value 42) > 0.5') == \
          prefix + \
          ' AND (bql_column_value_probability(1, NULL, NULL, v.colno, 42) > 0.5);'
      assert bql2sql('estimate * from columns of p1'
              ' where (probability density of value 8)'
              ' > (probability density of age = 16)') == \
          prefix + \
          ' AND (bql_column_value_probability(1, NULL, NULL, v.colno, 8) >' \
          ' bql_pdf_joint(1, NULL, NULL, 2, 16));'
      assert bql2sql('estimate *, probability density of value 8 given (age = 8)'
              ' from columns of p1;') == \
          prefix0 + \
          ', bql_column_value_probability(1, NULL, NULL, v.colno, 8, 2, 8)' + \
          prefix1 + ';'
      with pytest.raises(bayeslite.BQLError):
          bql2sql('estimate probability density of value 8 given (agee = 8)'
              ' from columns of p1')
      with pytest.raises(bayeslite.BQLError):
          # PREDICTIVE PROBABILITY makes no sense without row.
          bql2sql('estimate * from columns of p1 where' +
              ' predictive probability of x > 0;')
      with pytest.raises(bayeslite.BQLError):
          # SIMILARITY makes no sense without row.
          bql2sql('estimate * from columns of p1 where' +
              ' similarity to (rowid = x) in the context of c > 0;')
      assert bql2sql('estimate * from columns of p1 where' +
              ' dependence probability with age > 0.5;') == \
          prefix + \
          ' AND (bql_column_dependence_probability(1, NULL, NULL, 2, v.colno)' \
              ' > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 where' +
              ' dependence probability of age with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1'
              ' where dependence probability > 0.5;')
      assert bql2sql('estimate * from columns of p1 order by' +
              ' mutual information with age;') == \
          prefix + \
          ' ORDER BY bql_column_mutual_information(1, NULL, NULL, \'[2]\','\
          ' \'[\' || v.colno || \']\', NULL);'
      assert bql2sql('estimate * from columns of p1 order by' +
              ' mutual information with (age, label) using 42 samples;') == \
          prefix + \
          ' ORDER BY bql_column_mutual_information(1, NULL, NULL, \'[2, 1]\','\
          ' \'[\' || v.colno || \']\', 42);'
      assert bql2sql('estimate * from columns of p1 order by' +
              ' mutual information with (age, label)'
              ' given (weight=12) using 42 samples;') == \
          prefix + \
          ' ORDER BY bql_column_mutual_information(1, NULL, NULL, \'[2, 1]\','\
          ' \'[\' || v.colno || \']\', 42, 3, 12);'
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 order by' +
              ' mutual information of age with weight;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1'
              ' where mutual information > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 order by' +
              ' mutual information of age with weight using 42 samples;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 where' +
              ' mutual information using 42 samples > 0.5;')
      assert bql2sql('estimate * from columns of p1 order by' +
              ' correlation with age desc;') == \
          prefix + ' ORDER BY bql_column_correlation(1, NULL, NULL, 2, v.colno)' \
              ' DESC;'
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 order by' +
              ' correlation of age with weight;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit exactly one column.
          bql2sql('estimate * from columns of p1 where correlation > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Makes no sense.
          bql2sql('estimate * from columns of p1'
              ' where predict age with confidence 0.9 > 30;')
      assert bql2sql('estimate'
              ' *, dependence probability with weight as depprob,'
              ' mutual information with weight as mutinf'
              ' from columns of p1'
              ' where depprob > 0.5 order by mutinf desc') == \
          prefix0 + \
          ', bql_column_dependence_probability(1, NULL, NULL, 3, v.colno)' \
              ' AS "depprob"' \
          ', bql_column_mutual_information(1, NULL, NULL, \'[3]\',' \
          ' \'[\' || v.colno || \']\', NULL) AS "mutinf"' \
          + prefix1 + \
          ' AND ("depprob" > 0.5)' \
          ' ORDER BY "mutinf" DESC;'
      assert bql2sql('estimate'
              ' *, dependence probability with weight as depprob,'
              ' mutual information with (age, weight) as mutinf'
              ' from columns of p1'
              ' where depprob > 0.5 order by mutinf desc') == \
          prefix0 + \
          ', bql_column_dependence_probability(1, NULL, NULL, 3, v.colno)' \
              ' AS "depprob"' \
          ', bql_column_mutual_information(1, NULL, NULL, \'[2, 3]\',' \
          ' \'[\' || v.colno || \']\', NULL) AS "mutinf"' \
          + prefix1 + \
          ' AND ("depprob" > 0.5)' \
          ' ORDER BY "mutinf" DESC;'
      # XXX This mixes up target and reference variables, which is OK,
      # because MI is symmetric, but...oops.
      assert bql2sql('estimate * from variables of p1'
              ' where probability of (mutual information with age < 0.1)'
              ' > 0.8') == \
          prefix + \
          ' AND ((SELECT "AVG"("x") FROM (SELECT ("v0" < 0.1) AS "x"' \
              ' FROM (SELECT mi AS "v0" FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[' || v.colno || ']'))) > 0.8);"
      assert bql2sql('estimate * from variables of p1'
              ' order by probability of (mutual information with age < 0.1)') ==\
          prefix + \
          ' ORDER BY (SELECT "AVG"("x") FROM (SELECT ("v0" < 0.1) AS "x"' \
              ' FROM (SELECT mi AS "v0" FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[' || v.colno || ']')));"
  
  def test_estimate_pairwise_trivial():
      prefix = 'SELECT 1 AS population_id, v0.name AS name0, v1.name AS name1, '
      infix = ' AS value'
      infix0 = ' FROM bayesdb_population AS p,'
      infix0 += ' bayesdb_variable AS v0,'
      infix0 += ' bayesdb_variable AS v1'
      infix0 += ' WHERE p.id = 1'
      infix0 += ' AND v0.population_id = p.id AND v1.population_id = p.id'
      infix0 += ' AND v0.generator_id IS NULL'
      infix0 += ' AND v1.generator_id IS NULL'
      infix += infix0
      assert bql2sql('estimate dependence probability'
              ' from pairwise columns of p1;') == \
          prefix + \
          'bql_column_dependence_probability(1, NULL, NULL, v0.colno,'\
              ' v1.colno)' + \
          infix + ';'
      assert bql2sql('estimate mutual information'
              ' from pairwise columns of p1 where'
              ' (probability density of age = 0) > 0.5;') == \
          prefix + \
          'bql_column_mutual_information(1, NULL, NULL, '\
              '\'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', NULL)' + \
          infix + \
          ' AND (bql_pdf_joint(1, NULL, NULL, 2, 0) > 0.5);'
      assert bql2sql('estimate mutual information given (label=\'go\', weight)'
              ' from pairwise columns of p1 where'
              ' (probability density of age = 0) > 0.5;') == \
          prefix + \
          'bql_column_mutual_information(1, NULL, NULL,'\
          ' \'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', NULL,'\
          ' 1, \'go\', 3, NULL)' + \
          infix + \
          ' AND (bql_pdf_joint(1, NULL, NULL, 2, 0) > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # PROBABILITY DENSITY OF VALUE is 1-column.
          bql2sql('estimate correlation from pairwise columns of p1 where' +
              ' (probability density of value 0) > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # PREDICTIVE PROBABILITY OF is a row function.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1' +
              ' where predictive probability of x > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where dependence probability of age with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information from pairwise columns of p1'
              ' where dependence probability with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information using 42 samples'
              ' from pairwise columns of p1'
              ' where dependence probability with weight > 0.5;')
      assert bql2sql('estimate correlation from pairwise columns of p1'
              ' where dependence probability > 0.5;') == \
          prefix + 'bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno)' + \
          infix + ' AND' \
          ' (bql_column_dependence_probability(1, NULL, NULL, v0.colno,' \
                  ' v1.colno)' \
              ' > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where mutual information of age with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where mutual information of age with weight using 42 samples'
                  ' > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information from pairwise columns of p1'
              ' where mutual information with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information using 42 samples'
              ' from pairwise columns of p1'
              ' where mutual information with weight using 42 samples > 0.5;')
      assert bql2sql('estimate correlation from pairwise columns of p1' +
              ' where mutual information > 0.5;') == \
          prefix + 'bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno)' + \
          infix + ' AND' + \
          ' (bql_column_mutual_information(1, NULL, NULL,'\
          ' \'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', NULL) > 0.5);'
      assert bql2sql('estimate correlation from pairwise columns of p1' +
              ' where mutual information using 42 samples > 0.5;') == \
          prefix + 'bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno)' + \
          infix + ' AND' + \
          ' (bql_column_mutual_information(1, NULL, NULL,'\
          ' \'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', 42) > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where correlation of age with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information from pairwise columns of p1'
              ' where correlation with weight > 0.5;')
      with pytest.raises(bayeslite.BQLError):
          # Must omit both columns.
          bql2sql('estimate mutual information using 42 samples'
              ' from pairwise columns of p1'
              ' where correlation with weight > 0.5;')
      assert bql2sql('estimate correlation from pairwise columns of p1'
              ' where correlation > 0.5;') == \
          prefix + 'bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno)' + \
          infix + ' AND' + \
          ' (bql_column_correlation(1, NULL, NULL, v0.colno, v1.colno) > 0.5);'
      with pytest.raises(bayeslite.BQLError):
          # Makes no sense.
          bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' where predict age with confidence 0.9 > 30;')
      assert bql2sql('estimate dependence probability as depprob,'
              ' mutual information as mutinf'
              ' from pairwise columns of p1'
              ' where depprob > 0.5 order by mutinf desc') == \
          prefix + \
          'bql_column_dependence_probability(1, NULL, NULL, v0.colno, v1.colno)' \
          ' AS "depprob",' \
          ' bql_column_mutual_information(1, NULL, NULL,'\
          ' \'[\' || v0.colno || \']\', \'[\' || v1.colno || \']\', NULL)'\
          ' AS "mutinf"' \
          + infix0 + \
          ' AND ("depprob" > 0.5)' \
          ' ORDER BY "mutinf" DESC;'
  
  def test_estimate_pairwise_row():
      prefix = 'SELECT r0._rowid_ AS rowid0, r1._rowid_ AS rowid1'
      infix = ' AS value FROM "t1" AS r0, "t1" AS r1'
      assert bql2sql('estimate similarity in the context of age' +
              ' from pairwise p1;') == \
          prefix + ', bql_row_similarity(1, NULL, NULL,'\
              ' r0._rowid_, r1._rowid_, 2)' + \
          infix + ';'
      with pytest.raises(bayeslite.BQLError):
          # PREDICT is a 1-row function.
          bql2sql('estimate predict age with confidence 0.9 from pairwise t1;')
  
  def test_estimate_pairwise_selected_columns():
      assert bql2sql('estimate dependence probability'
              ' from pairwise columns of p1 for label, age') == \
          'SELECT 1 AS population_id, v0.name AS name0, v1.name AS name1,' \
          ' bql_column_dependence_probability(1, NULL, NULL,' \
                  ' v0.colno, v1.colno)' \
              ' AS value' \
          ' FROM bayesdb_population AS p,' \
          ' bayesdb_variable AS v0,' \
          ' bayesdb_variable AS v1' \
          ' WHERE p.id = 1' \
          ' AND v0.population_id = p.id AND v1.population_id = p.id' \
          ' AND v0.generator_id IS NULL AND v1.generator_id IS NULL' \
          ' AND v0.colno IN (1, 2) AND v1.colno IN (1, 2);'
      assert bql2sql('estimate dependence probability'
              ' from pairwise columns of p1'
              ' for (ESTIMATE * FROM COLUMNS OF p1'
                  ' ORDER BY name DESC LIMIT 2)') == \
          'SELECT 1 AS population_id, v0.name AS name0, v1.name AS name1,' \
          ' bql_column_dependence_probability(1, NULL, NULL, v0.colno,' \
                  ' v1.colno)' \
              ' AS value' \
          ' FROM bayesdb_population AS p,' \
          ' bayesdb_variable AS v0,' \
          ' bayesdb_variable AS v1' \
          ' WHERE p.id = 1' \
          ' AND v0.population_id = p.id AND v1.population_id = p.id' \
          ' AND v0.generator_id IS NULL AND v1.generator_id IS NULL' \
          ' AND v0.colno IN (3, 1) AND v1.colno IN (3, 1);'
  
  def test_select_columns_subquery():
      assert bql2sql('select id, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from t1') == \
          'SELECT "id", "t1"."age", "t1"."label" FROM "t1";'
  
  @pytest.mark.xfail(strict=True, reason='no simulate vars from models of')
  def test_simulate_models_columns_subquery():
      assert bql2sql('simulate weight, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from models of p1') == \
          'SELECT * FROM "bayesdb_temp_0";'
      assert bql2sql('simulate 0, weight, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from models of p1') == \
          'SELECT 0, "v0" AS "weight", "v1" AS "age", "v2" AS "label" FROM' \
          ' (SELECT * FROM "bayesdb_temp_0");'
      assert bql2sql('simulate weight + 1, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from models of p1') == \
          'SELECT ("v0" + 1), "v1" AS "age", "v2" AS "label" FROM' \
          ' (SELECT * FROM "bayesdb_temp_0");'
      assert bql2sql('simulate weight + 1 AS wp1,'
              ' t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from models of p1') == \
          'SELECT ("v0" + 1) AS "wp1", "v1" AS "age", "v2" AS "label" FROM' \
          ' (SELECT * FROM "bayesdb_temp_0");'
  
  def test_simulate_columns_subquery():
      # XXX This test is a little unsatisfactory -- we do not get to see
      # what the variables in the result are named...
      assert bql2sql('simulate weight, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from p1 limit 10') == \
          'SELECT * FROM "bayesdb_temp_0";'
      with pytest.raises(parse.BQLParseError):
          # Compound columns not yet implemented for SIMULATE.
          bql2sql('simulate weight + 1, t1.(estimate * from columns of p1'
              ' order by name asc limit 2) from p1 limit 10')
  
  def test_simulate_models():
      # Base case.
      assert bql2sql('simulate mutual information of age with weight'
              ' from models of p1') == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]';"
      # Multiple target variables.
      assert bql2sql('simulate mutual information of (label, age) with weight'
              ' from models of p1') == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[1, 2]'" \
                  " AND reference_vars = '[3]';"
      # Multiple reference variables.
      assert bql2sql('simulate mutual information of age with (label, weight)'
              ' from models of p1') == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[1, 3]';"
      # Specified number of samples.
      assert bql2sql('simulate mutual information of age with weight'
              ' using 42 samples from models of p1') == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]'" \
                  ' AND nsamples = 42;'
      # Conditional.
      assert bql2sql('simulate mutual information of age with weight'
              " given (label = 'foo') from models of p1") == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]'" \
                  " AND conditions = '{\"1\": \"foo\"}';"
      # Modeled by a specific generator.
      assert bql2sql('simulate mutual information of age with weight'
                  ' from models of p1 modeled by g1',
              lambda bdb: bdb.execute('create generator g1 for p1')) == \
          'SELECT mi FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  ' AND generator_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]';"
      # Two mutual informations.
      assert bql2sql('simulate mutual information of age with weight AS "mi(aw)",'
              ' mutual information of label with weight AS "mi(lw)"'
              ' from models of p1') == \
          'SELECT t0."mi(aw)" AS "mi(aw)", t1."mi(lw)" AS "mi(lw)"' \
              ' FROM (SELECT _rowid_, mi AS "mi(aw)" FROM bql_mutinf' \
                      ' WHERE population_id = 1' \
                          " AND target_vars = '[2]'" \
                          " AND reference_vars = '[3]') AS t0," \
                  ' (SELECT _rowid_, mi AS "mi(lw)" FROM bql_mutinf' \
                      ' WHERE population_id = 1' \
                          " AND target_vars = '[1]'" \
                          " AND reference_vars = '[3]') AS t1" \
              ' WHERE t0._rowid_ = t1._rowid_;'
  
  def test_probability_of_mutinf():
      assert bql2sql('estimate probability of'
              ' (mutual information of age with weight < 0.1) > 0.5'
              ' within p1') == \
          'SELECT ((SELECT "AVG"("x") FROM (SELECT ("v0" < 0.1) AS "x"' \
          ' FROM (SELECT mi AS "v0" FROM bql_mutinf' \
              ' WHERE population_id = 1' \
                  " AND target_vars = '[2]'" \
                  " AND reference_vars = '[3]'))) > 0.5);"
  
  def test_modeledby_usingmodels_trival():
      def setup(bdb):
          bdb.execute('create generator m1 for p1 using cgpm;')
      assert bql2sql('estimate predictive probability of weight + 1'
              ' from p1 modeled by m1 using models 1-3, 5;', setup=setup) == \
          'SELECT (bql_row_column_predictive_probability(1, 1, \'[1, 2, 3, 5]\','\
                  ' _rowid_, \'[3]\', \'[]\') + 1)' \
              ' FROM "t1";'
      assert bql2sql(
          'infer rowid, age, weight from p1 modeled by m1 using model 7',
              setup=setup) == \
          'SELECT "rowid" AS "rowid",' \
          ' "IFNULL"("age", bql_predict(1, 1, \'[7]\', _rowid_, 2, 0, NULL))' \
              ' AS "age",' \
          ' "IFNULL"("weight", bql_predict(1, 1, \'[7]\', _rowid_, 3, 0, NULL))' \
              ' AS "weight"' \
          ' FROM "t1";'
      assert bql2sql('infer explicit predict age with confidence 0.9'
              ' from p1 using models 0, 3-5;',
              setup=setup) == \
          'SELECT bql_predict(1, NULL, \'[0, 3, 4, 5]\', _rowid_, 2, 0.9, NULL)'\
          ' FROM "t1";'
      assert bql2sql('''
          estimate predictive relevance
              of (label = 'Uganda')
              to existing rows (rowid < 4)
              and hypothetical rows with values (
                  ("age" = 82, "weight" = 14),
                  ("age" = 74, label = 'Europe', "weight" = 7)
              )
              in the context of "weight"
          by p1 modeled by m1 using models 8, 10-12
      ''', setup=setup) == \
          'SELECT bql_row_predictive_relevance(1, 1, \'[8, 10, 11, 12]\', ' \
              '(SELECT _rowid_ FROM "t1" WHERE ("label" = \'Uganda\')), '\
              '\'[1, 2, 3]\', 3, '\
              '2, 82, 3, 14, NULL, 2, 74, 1, \'Europe\', 3, 7, NULL);'
      assert bql2sql('''
          estimate dependence probability
          from pairwise columns of p1
          for label, age
          modeled by m1
          using models 1, 4, 12
      ''', setup=setup) == \
          'SELECT 1 AS population_id, v0.name AS name0, v1.name AS name1,' \
          ' bql_column_dependence_probability(1, 1, \'[1, 4, 12]\',' \
                  ' v0.colno, v1.colno)' \
              ' AS value' \
          ' FROM bayesdb_population AS p,' \
          ' bayesdb_variable AS v0,' \
          ' bayesdb_variable AS v1' \
          ' WHERE p.id = 1' \
          ' AND v0.population_id = p.id AND v1.population_id = p.id' \
          ' AND (v0.generator_id IS NULL OR v0.generator_id = 1)' \
          ' AND (v1.generator_id IS NULL OR v1.generator_id = 1)' \
          ' AND v0.colno IN (1, 2) AND v1.colno IN (1, 2);'
      assert bql2sql('''
          estimate mutual information of age with weight
          from p1 modeled by m1 using model 1;
      ''', setup=setup) == \
          'SELECT bql_column_mutual_information('\
              '1, 1, \'[1]\', \'[2]\', \'[3]\', NULL)'\
          ' FROM "t1";'
  
  def test_simulate_columns_all():
      with pytest.raises(parse.BQLParseError):
          bql2sql('simulate * from p1 limit 1')
  
  def test_trivial_commands():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          # XXX Query parameters!
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          with open(fname, 'rU') as f:
              with pytest.raises(ValueError):
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True,
                  ifnotexists=True)
          guess.bayesdb_guess_population(bdb, 'p', 't')
          with pytest.raises(ValueError):
              guess.bayesdb_guess_population(bdb, 'p', 't')
          guess.bayesdb_guess_population(bdb, 'p', 't', ifnotexists=True)
          bdb.execute('create generator p_cc for p;')
          bdb.execute('initialize 2 models for p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('initialize 2 models for p_cc')
          bdb.execute('drop models from p_cc')
          bdb.execute('drop models from p_cc')
          bdb.execute('initialize 2 models for p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('initialize 2 models for p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop models 0-2 from p_cc')
          bdb.execute('drop models 0-1 from p_cc')
          with bdb.savepoint():
              bdb.execute('initialize 2 models for p_cc')
              bdb.execute('drop models 0-1 from p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop models 0-1 from p_cc')
          bdb.execute('initialize 2 models for p_cc')
          bdb.execute('initialize 1 model if not exists for p_cc')
          bdb.execute('initialize 2 models if not exists for p_cc')
          population_id = core.bayesdb_get_population(bdb, 'p')
          generator_id = core.bayesdb_get_generator(bdb, population_id, 'p_cc')
          assert core.bayesdb_generator_table(bdb, generator_id) == 't'
          bdb.execute('alter table t rename to t')
          assert core.bayesdb_generator_table(bdb, generator_id) == 't'
          bdb.execute('alter table t rename to T')
          assert core.bayesdb_generator_table(bdb, generator_id) == 'T'
          bdb.execute('estimate count(*) from p').fetchall()
          bdb.execute('alter table t rename to t')
          assert core.bayesdb_generator_table(bdb, generator_id) == 't'
          bdb.execute('alter generator p_cc rename to p0_cc')
          assert core.bayesdb_generator_name(bdb, generator_id) == 'p0_cc'
          bdb.execute('alter generator p0_cc rename to zot, rename to P0_CC')
          assert core.bayesdb_generator_name(bdb, generator_id) == 'P0_CC'
          bdb.execute('alter generator P0_cc rename to P0_cc')
          assert core.bayesdb_generator_name(bdb, generator_id) == 'P0_cc'
          bdb.execute('alter generator p0_CC rename to p0_cc')
          assert core.bayesdb_generator_name(bdb, generator_id) == 'p0_cc'
          bdb.execute('estimate count(*) from p').fetchall()
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate count(*) from p_cc')
          bdb.execute('alter generator p0_cc rename to P0_cc')
          bdb.execute('analyze p0_cc for 1 iteration')
          colno = core.bayesdb_variable_number(bdb, population_id, generator_id,
              'gender')
          with pytest.raises(parse.BQLParseError):
              # Rename the table's columns, not the generator's columns.
              bdb.execute('alter generator p0_cc rename gender to sex')
          with pytest.raises(NotImplementedError): # XXX
              bdb.execute('alter table t rename to t0, rename gender to sex')
              assert core.bayesdb_variable_number(
                      bdb, population_id, generator_id, 'sex') \
                  == colno
              bdb.execute('analyze p0_cc model 0 for 1 iteration')
              bdb.execute('alter generator p0_cc rename to p_cc')
              assert core.bayesdb_variable_number(
                      bdb, population_id, generator_id, 'sex') \
                  == colno
              bdb.execute('select sex from t0').fetchall()
              with pytest.raises(AssertionError): # XXX
                  bdb.execute('select gender from t0')
                  assert False, 'Need to fix quoting of unknown columns!'
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('estimate predict sex with confidence 0.9'
                      ' from p').fetchall()
              bdb.execute('infer explicit predict sex with confidence 0.9'
                  ' from p').fetchall()
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('estimate predict gender with confidence 0.9'
                      ' from p')
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('infer explicit predict gender with confidence 0.9'
                      ' from p')
              bdb.execute('alter table t0 rename sex to gender')
              assert core.bayesdb_variable_number(
                      bdb, population_id, generator_id, 'gender') \
                  == colno
          bdb.execute('alter generator p0_cc rename to p_cc')     # XXX
          bdb.execute('alter table t rename to T0')               # XXX
          bdb.sql_execute('create table t0_temp(x)')
          bdb.execute('alter table T0 rename to t0')
          assert bdb.execute('select count(*) from t0_temp').fetchvalue() == 0
          assert bdb.execute('select count(*) from t0').fetchvalue() > 0
          with pytest.raises(bayeslite.BQLError):
              # Cannot specify models with rename.
              bdb.execute('alter generator p_cc models (1) rename to p_cc_fail')
          bdb.execute('drop table T0_TEMP')
          bdb.execute('analyze p_cc model 0 for 1 iteration')
          bdb.execute('analyze p_cc model 1 for 1 iteration')
          bdb.execute('analyze p_cc models 0-1 for 1 iteration')
          bdb.execute('analyze p_cc models 0,1 for 1 iteration')
          bdb.execute('analyze p_cc for 1 iteration')
          bdb.execute('select * from t0').fetchall()
          bdb.execute('select * from T0').fetchall()
          bdb.execute('estimate * from p').fetchall()
          bdb.execute('estimate * from P').fetchall()
          # SIMIARITY IN THE CONTEXT OF requires exactly 1 variable.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate similarity in the context of * '
                  'from pairwise p').fetchall()
          bdb.execute('estimate similarity in the context of age '
              'from pairwise p').fetchall()
          bdb.execute('estimate similarity to (rowid=1) in the context of rank '
              'from p').fetchall()
          bdb.execute('select value from'
              ' (estimate correlation from pairwise columns of p)').fetchall()
          bdb.execute('infer explicit predict age with confidence 0.9'
              ' from p').fetchall()
          bdb.execute('infer explicit predict AGE with confidence 0.9'
              ' from P').fetchall()
          bdb.execute('infer explicit predict aGe with confidence 0.9'
              ' from P').fetchall()
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate predict agee with confidence 0.9 from p')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('infer explicit predict agee with confidence 0.9'
                  ' from p')
          guess.bayesdb_guess_population(bdb, 'pe', 't0',
              overrides=[
                  ('age', 'numerical'),
                  ('rank', 'numerical'),
              ])
          bdb.execute('create generator pe_cc for pe;')
          with pytest.raises(bayeslite.BQLError):
              # No models to analyze.
              bdb.execute('analyze pe_cc for 1 iteration')
          bdb.execute('initialize 1 model if not exists for pe_cc')
          bdb.execute('analyze pe_cc for 1 iteration')
          bdb.execute('estimate correlation'
              ' from pairwise columns of pe').fetchall()
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('initialize 4 models if not exists for t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('analyze t0 for 1 iteration')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate * from t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate * from columns of t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate correlation from pairwise columns of t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate similarity in the context of age '
                  'from pairwise t')
          bdb.execute('initialize 6 models if not exists for p_cc')
          bdb.execute('analyze p_cc for 1 iteration')
  
  def test_trivial_deadline():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 second')
  
  def test_parametrized():
      assert bql2sqlparam('select * from t where id = ?') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where id = :foo') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where id = $foo') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where id = @foo') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where id = ?123') == \
          'SELECT * FROM "t" WHERE ("id" = ?1);'
      assert bql2sqlparam('select * from t where a = $foo and b = ?1;') == \
          'SELECT * FROM "t" WHERE (("a" = ?1) AND ("b" = ?1));'
      assert bql2sqlparam('select * from t' +
              ' where a = ?123 and b = :foo and c = ?124') == \
          'SELECT * FROM "t" WHERE' + \
          ' ((("a" = ?1) AND ("b" = ?2)) AND ("c" = ?2));'
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          assert bql_execute(bdb, 'select count(*) from t') == [(7,)]
          assert bql_execute(bdb, 'select count(distinct division) from t') == \
              [(6,)]
          assert bql_execute(bdb, 'select * from t where height > ?', (70,)) == \
              [
                  (41, 'M', 65600, 72, 'marketing', 4),
                  (30, 'M', 70000, 73, 'sales', 4),
                  (30, 'F', 81000, 73, 'engineering', 3),
              ]
          assert bql_execute(bdb, 'select * from t where height > ?123',
                  (0,)*122 + (70,)) == \
              [
                  (41, 'M', 65600, 72, 'marketing', 4),
                  (30, 'M', 70000, 73, 'sales', 4),
                  (30, 'F', 81000, 73, 'engineering', 3),
              ]
          assert bql_execute(bdb, 'select age from t where division = :division',
                  {':division': 'sales'}) == \
              [(34,), (30,)]
          assert bql_execute(bdb, 'select division from t' +
                      ' where age < @age and rank > ?;',
                  (40, 4)) == \
              [('accounting',)]
          assert bql_execute(bdb, 'select division from t' +
                      ' where age < @age and rank > :rank;',
                  {':RANK': 4, '@aGe': 40}) == \
              [('accounting',)]
          with pytest.raises(ValueError):
              bdb.execute('select * from t where age < ? and rank > :r',
                  {':r': 4})
          def traced_execute(query, *args):
              bql = []
              def trace(string, _bindings):
                  bql.append(' '.join(string.split()))
              bdb.trace(trace)
              with bdb.savepoint():
                  bdb.execute(query, *args)
              bdb.untrace(trace)
              return bql
          def sqltraced_execute(query, *args):
              sql = []
              def trace(string, _bindings):
                  sql.append(' '.join(string.split()))
              bdb.sql_trace(trace)
              with bdb.savepoint():
                  bdb.execute(query, *args)
              bdb.sql_untrace(trace)
              return sql
          guess.bayesdb_guess_population(bdb, 'p', 't')
          bdb.execute('create generator p_cc for p;')
          bdb.execute('initialize 1 model for p_cc;')
          assert traced_execute('estimate similarity to (rowid = 1)'
                  ' in the context of (estimate * from columns of p limit 1)'
                  ' from p;') == [
              'estimate similarity to (rowid = 1)' \
                  ' in the context of (estimate * from columns of p limit 1)' \
                  ' from p;',
          ]
          assert sqltraced_execute('estimate similarity to (rowid = 1)'
                  ' in the context of (estimate * from columns of p limit 1)'
                  ' from p;') == [
              'SELECT COUNT(*) FROM bayesdb_population WHERE name = ?',
              'SELECT id FROM bayesdb_population WHERE name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'SELECT COUNT(*) FROM bayesdb_population WHERE name = ?',
              'SELECT id FROM bayesdb_population WHERE name = ?',
              'SELECT v.name AS name FROM bayesdb_variable AS v'
                  ' WHERE v.population_id = 1'
                      ' AND v.generator_id IS NULL'
                  ' LIMIT 1',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT tabname FROM bayesdb_population'
                  ' WHERE id = ?',
              'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,'
                  ' (SELECT _rowid_ FROM "t" WHERE ("rowid" = 1)), 0) FROM "t"',
              'SELECT id FROM bayesdb_generator WHERE population_id = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual '
                  'WHERE generator_id = ? AND table_rowid = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator '
                  'WHERE generator_id = ?'
              ]
  
          assert sqltraced_execute('estimate similarity to (rowid = 1)'
                  ' in the context of (estimate * from columns of p limit ?)'
                  ' from p;',
                  (1,)) == [
              'SELECT COUNT(*) FROM bayesdb_population'
                  ' WHERE name = ?',
              'SELECT id FROM bayesdb_population'
                  ' WHERE name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'SELECT COUNT(*) FROM bayesdb_population'
                  ' WHERE name = ?',
              'SELECT id FROM bayesdb_population'
                  ' WHERE name = ?',
              # ESTIMATE * FROM COLUMNS OF:
              'SELECT v.name AS name'
                  ' FROM bayesdb_variable AS v'
                  ' WHERE v.population_id = 1'
                      ' AND v.generator_id IS NULL'
                  ' LIMIT ?1',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              # ESTIMATE SIMILARITY TO (rowid=1):
              'SELECT bql_row_similarity(1, NULL, NULL, _rowid_,'
                  ' (SELECT _rowid_ FROM "t" WHERE ("rowid" = 1)), 0) FROM "t"',
              'SELECT id FROM bayesdb_generator WHERE population_id = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?'
          ]
          assert sqltraced_execute(
                  'create temp table if not exists sim as '
                  'simulate age, RANK, division '
                  'from p given gender = \'F\' limit 4') == [
              'PRAGMA table_info("sim")',
              'PRAGMA table_info("bayesdb_temp_0")',
              'SELECT COUNT(*) FROM bayesdb_population WHERE name = ?',
              'SELECT id FROM bayesdb_population WHERE name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'PRAGMA table_info("t")',
              'SELECT CAST(4 AS INTEGER), \'F\'',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'SELECT MAX(_rowid_) FROM "t"',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT id FROM bayesdb_generator'
                  ' WHERE population_id = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT tabname FROM bayesdb_generator WHERE id = ?',
              'SELECT 1 FROM "t" WHERE oid = ?',
              'SELECT 1 FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ? LIMIT 1',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT code FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND value = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ? AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'CREATE TEMP TABLE "bayesdb_temp_0"'
                  ' ("age" NUMERIC,"RANK" NUMERIC,"division" NUMERIC)',
              'INSERT INTO "bayesdb_temp_0" ("age","RANK","division")'
                  ' VALUES (?,?,?)',
              'INSERT INTO "bayesdb_temp_0" ("age","RANK","division")'
                  ' VALUES (?,?,?)',
              'INSERT INTO "bayesdb_temp_0" ("age","RANK","division")'
                  ' VALUES (?,?,?)',
              'INSERT INTO "bayesdb_temp_0" ("age","RANK","division")'
                  ' VALUES (?,?,?)',
              'CREATE TEMP TABLE IF NOT EXISTS "sim" AS'
                  ' SELECT * FROM "bayesdb_temp_0"',
              'DROP TABLE "bayesdb_temp_0"'
          ]
          assert sqltraced_execute(
                  'select * from (simulate age from p '
                  'given gender = \'F\' limit 4)') == [
              'PRAGMA table_info("bayesdb_temp_1")',
              'SELECT COUNT(*) FROM bayesdb_population WHERE name = ?',
              'SELECT id FROM bayesdb_population WHERE name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'PRAGMA table_info("t")',
              'SELECT CAST(4 AS INTEGER), \'F\'',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT COUNT(*) FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT colno FROM bayesdb_variable'
                  ' WHERE population_id = ?'
                      ' AND (generator_id IS NULL OR generator_id = ?)'
                      ' AND name = ?',
              'SELECT tabname FROM bayesdb_population WHERE id = ?',
              'SELECT MAX(_rowid_) FROM "t"',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT token FROM bayesdb_rowid_tokens',
              'SELECT id FROM bayesdb_generator WHERE population_id = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT tabname FROM bayesdb_generator WHERE id = ?',
              'SELECT 1 FROM "t" WHERE oid = ?',
              'SELECT 1 FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ? LIMIT 1',
              'SELECT cgpm_rowid FROM bayesdb_cgpm_individual'
                  ' WHERE generator_id = ? AND table_rowid = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT code FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND value = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT stattype FROM bayesdb_variable WHERE population_id = ?'
                  ' AND (generator_id IS NULL OR generator_id = ?) AND colno = ?',
              'SELECT value FROM bayesdb_cgpm_category'
                  ' WHERE generator_id = ? AND colno = ? AND code = ?',
              'CREATE TEMP TABLE "bayesdb_temp_1" ("age" NUMERIC)',
              'INSERT INTO "bayesdb_temp_1" ("age") VALUES (?)',
              'INSERT INTO "bayesdb_temp_1" ("age") VALUES (?)',
              'INSERT INTO "bayesdb_temp_1" ("age") VALUES (?)',
              'INSERT INTO "bayesdb_temp_1" ("age") VALUES (?)',
              'SELECT * FROM (SELECT * FROM "bayesdb_temp_1")',
              'DROP TABLE "bayesdb_temp_1"',
          ]
          bdb.execute('''
              create population q for t (
                  age NUMERICAL;
                  gender CATEGORICAL;   -- Not binary!
                  salary NUMERICAL;
                  height NUMERICAL;
                  division CATEGORICAL;
                  rank CATEGORICAL
              )
          ''')
          bdb.execute('create generator q_cc for q;')
          bdb.execute('initialize 1 model for q_cc;')
          assert sqltraced_execute('analyze q_cc for 1 iteration;') == [
              'SELECT COUNT(*) FROM bayesdb_generator WHERE name = ?',
              'SELECT id FROM bayesdb_generator WHERE name = ?',
              'SELECT backend FROM bayesdb_generator WHERE id = ?',
              'SELECT engine_json, engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?',
              'SELECT population_id FROM bayesdb_generator WHERE id = ?',
              'SELECT engine_stamp FROM bayesdb_cgpm_generator'
                  ' WHERE generator_id = ?',
              'UPDATE bayesdb_cgpm_generator'
                  ' SET engine_json = :engine_json, engine_stamp = :engine_stamp'
                  ' WHERE generator_id = :generator_id']
  
  def test_create_table_ifnotexists_as_simulate():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
              # If not exists table tests
              guess.bayesdb_guess_population(bdb, 'p', 't',
                  overrides=[('age', 'numerical')])
              bdb.execute('create generator p_cc for p;')
              bdb.execute('initialize 1 model for p_cc')
              bdb.execute('analyze p_cc for 1 iteration')
              bdb.execute('''
                  create table if not exists u as
                      simulate age from p limit 10
              ''')
              bdb.execute("drop table u")
              bdb.execute('''
                  create table if not exists w as simulate age from p
                      given division='sales' limit 10
              ''')
              bdb.execute("drop table w")
              bdb.execute("create table u as simulate age from p limit 10")
              x = bdb.execute("select count (*) from u").fetchvalue()
              bdb.execute('''
                  create table if not exists u as simulate age from p limit 10
              ''')
              bdb.execute('''
                  create table if not exists u as simulate age from p
                      given division='sales' limit 10
              ''')
              assert x == bdb.execute("select count (*) from u").fetchvalue()
  
  def test_createtab():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          with pytest.raises(apsw.SQLError):
              bdb.execute('drop table t')
          bdb.execute('drop table if exists t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop population p')
          bdb.execute('drop population if exists p')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop generator p_cc')
          bdb.execute('drop generator if exists p_cc')
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          with bdb.savepoint():
              # Savepoint because we don't actually want the new data to
              # be inserted.
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True, ifnotexists=True)
          guess.bayesdb_guess_population(bdb, 'p', 't',
              overrides=[('age', 'numerical')])
          bdb.execute('create generator p_cc for p;')
          with pytest.raises(bayeslite.BQLError):
              # Redefining population.
              bdb.execute('create population p for t (age numerical)')
          with pytest.raises(bayeslite.BQLError):
              # Redefining generator.
              bdb.execute('create generator p_cc for p;')
          # Make sure ignore columns work.
          #
          # XXX Also check key columns.
          guess.bayesdb_guess_population(bdb, 'p0', 't',
              overrides=[('age', 'ignore')])
          bdb.execute('drop population p0')
          population_id = core.bayesdb_get_population(bdb, 'p')
          colno = core.bayesdb_variable_number(bdb, population_id, None, 'age')
          assert core.bayesdb_variable_stattype(
              bdb, population_id, None, colno) == 'numerical'
          bdb.execute('initialize 1 model for p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop table t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop population p')
          bdb.execute('drop generator p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop generator p_cc')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('drop table t')
          bdb.execute('drop generator if exists p_cc')
          bdb.execute('drop population p')
          bdb.execute('drop population if exists p')
          bdb.execute('drop table t')
          bdb.execute('drop table if exists t')
          with open(fname, 'rU') as f:
              bayeslite.bayesdb_read_csv(bdb, 't', f, header=True, create=True)
          guess.bayesdb_guess_population(bdb, 'p', 't')
          bdb.execute("create table u as select * from t where gender = 'F'")
          assert bql_execute(bdb, 'select * from u') == [
              (23, 'F', 81000, 67, 'data science', 3),
              (36, 'F', 96000, 70, 'management', 2),
              (30, 'F', 81000, 73, 'engineering', 3),
          ]
          with pytest.raises(bayeslite.BQLError):
              bdb.execute("create table u as select * from t where gender = 'F'")
          bdb.execute('drop table u')
          with pytest.raises(apsw.SQLError):
              bql_execute(bdb, 'select * from u')
          bdb.execute("create temp table u as"
              " select * from t where gender = 'F'")
          assert bql_execute(bdb, 'select * from u') == [
              (23, 'F', 81000, 67, 'data science', 3),
              (36, 'F', 96000, 70, 'management', 2),
              (30, 'F', 81000, 73, 'engineering', 3),
          ]
          # XXX Test to make sure TEMP is passed through, and the table
          # doesn't persist on disk.
  
  def test_alterpop_addvar():
      with bayeslite.bayesdb_open() as bdb:
          bayeslite.bayesdb_read_csv(
              bdb, 't', StringIO.StringIO(test_csv.csv_data),
              header=True, create=True)
          bdb.execute('''
              create population p for t with schema(
                  age         numerical;
                  gender      nominal;
                  salary      numerical;
                  height      ignore;
                  division    ignore;
                  rank        ignore;
              )
          ''')
          population_id = core.bayesdb_get_population(bdb, 'p')
          bdb.execute('create generator m for p;')
          # Fail when variable does not exist in base table.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('alter population p add variable quux;')
          # Fail when variable already in population.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('alter population p add variable age numerical;')
          # Fail when given invalid statistical type.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('alter population p add variable heigh numr;')
          # Alter pop with stattype.
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'height')
          bdb.execute('alter population p add variable height numerical;')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'height')
          # Alter pop multiple without stattype.
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'rank')
          assert not core.bayesdb_has_variable(
              bdb, population_id, None, 'division')
          bdb.execute('''
              alter population p
                  add variable rank,
                  add variable division;
          ''')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'rank')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'division')
          # Add a new column weight to the base table.
          bdb.sql_execute('alter table t add column weight real;')
          # Fail when no values in new column.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('alter population p add variable weight numerical;')
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'weight')
          # Update a single value and update the population.
          bdb.sql_execute('update t set weight = 1 where oid = 1;')
          bdb.execute('alter population p add variable weight numerical;')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'weight')
  
  def test_txn():
      with test_csv.bayesdb_csv_file(test_csv.csv_data) as (bdb, fname):
          # Make sure rollback and commit fail outside a transaction.
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('ROLLBACK')
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('COMMIT')
  
          # Open a transaction which we'll roll back.
          bdb.execute('BEGIN')
          try:
              # Make sure transactions don't nest.  (Use savepoints.)
              with pytest.raises(bayeslite.BayesDBTxnError):
                  bdb.execute('BEGIN')
          finally:
              bdb.execute('ROLLBACK')
  
          # Make sure rollback and commit still fail outside a transaction.
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('ROLLBACK')
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('COMMIT')
  
          # Open a transaction which we'll commit.
          bdb.execute('BEGIN')
          try:
              with pytest.raises(bayeslite.BayesDBTxnError):
                  bdb.execute('BEGIN')
          finally:
              bdb.execute('COMMIT')
  
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('ROLLBACK')
          with pytest.raises(bayeslite.BayesDBTxnError):
              bdb.execute('COMMIT')
  
          # Make sure ROLLBACK undoes the effects of the transaction.
          bdb.execute('BEGIN')
          try:
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
              bdb.execute('SELECT * FROM t').fetchall()
              guess.bayesdb_guess_population(bdb, 'p', 't')
              bdb.execute('ESTIMATE * FROM p').fetchall()
          finally:
              bdb.execute('ROLLBACK')
          with pytest.raises(apsw.SQLError):
              bdb.execute('SELECT * FROM t')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('ESTIMATE * FROM p')
  
          # Make sure CREATE and DROP both work in the transaction.
          bdb.execute('BEGIN')
          try:
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
              bdb.execute('SELECT * FROM t').fetchall()
              guess.bayesdb_guess_population(bdb, 'p', 't')
              bdb.execute('ESTIMATE * FROM p').fetchall()
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('DROP TABLE t')
              bdb.execute('DROP POPULATION p')
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('ESTIMATE * FROM p')
              bdb.execute('DROP TABLE t')
              with pytest.raises(apsw.SQLError):
                  bdb.execute('SELECT * FROM t')
          finally:
              bdb.execute('ROLLBACK')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('ESTIMATE * FROM p')
          with pytest.raises(apsw.SQLError):
              bdb.execute('SELECT * FROM t')
  
          # Make sure CREATE and DROP work even if we commit.
          bdb.execute('BEGIN')
          try:
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
              bdb.execute('SELECT * FROM t').fetchall()
              guess.bayesdb_guess_population(bdb, 'p', 't')
              bdb.execute('ESTIMATE * FROM p').fetchall()
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('DROP TABLE t')
              bdb.execute('DROP POPULATION p')
              with pytest.raises(bayeslite.BQLError):
                  bdb.execute('ESTIMATE * FROM p')
              bdb.execute('DROP TABLE t')
              with pytest.raises(apsw.SQLError):
                  bdb.execute('SELECT * FROM t')
          finally:
              bdb.execute('COMMIT')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('ESTIMATE * FROM p')
          with pytest.raises(apsw.SQLError):
              bdb.execute('SELECT * FROM t')
  
          # Make sure CREATE persists if we commit.
          bdb.execute('BEGIN')
          try:
              with open(fname, 'rU') as f:
                  bayeslite.bayesdb_read_csv(bdb, 't', f, header=True,
                      create=True)
              bdb.execute('SELECT * FROM t').fetchall()
              guess.bayesdb_guess_population(bdb, 'p', 't')
              bdb.execute('ESTIMATE * FROM p').fetchall()
          finally:
              bdb.execute('COMMIT')
          bdb.execute('SELECT * FROM t').fetchall()
          bdb.execute('ESTIMATE * FROM p').fetchall()
  
          # Make sure bdb.transaction works, rolls back on exception,
          # and handles nesting correctly in the context of savepoints.
          try:
              with bdb.transaction():
                  bdb.sql_execute('create table quagga(x)')
                  raise StopIteration
          except StopIteration:
              pass
          with pytest.raises(apsw.SQLError):
              bdb.execute('select * from quagga')
          with bdb.transaction():
              with bdb.savepoint():
                  with bdb.savepoint():
                      pass
          with bdb.savepoint():
              with pytest.raises(bayeslite.BayesDBTxnError):
                  with bdb.transaction():
                      pass
  
          # XXX To do: Make sure other effects (e.g., analysis) get
          # rolled back by ROLLBACK.
  
  def test_predprob_null():
      backend = CGPM_Backend({}, multiprocess=False)
      with test_core.bayesdb(backend=backend) as bdb:
          bdb.sql_execute('''
              create table foo (
                  id integer primary key not null,
                  x numeric,
                  y numeric,
                  z numeric
              )
          ''')
          bdb.sql_execute("insert into foo values (1, 1, 'strange', 3)")
          bdb.sql_execute("insert into foo values (2, 1.2, 'strange', 1)")
          bdb.sql_execute("insert into foo values (3, 0.8, 'strange', 3)")
          bdb.sql_execute("insert into foo values (4, NULL, 'strange', 9)")
          bdb.sql_execute("insert into foo values (5, 73, 'up', 11)")
          bdb.sql_execute("insert into foo values (6, 80, 'up', -1)")
          bdb.sql_execute("insert into foo values (7, 60, 'up', NULL)")
          bdb.sql_execute("insert into foo values (8, 67, NULL, NULL)")
          bdb.sql_execute("insert into foo values (9, 3.1415926, 'down', 1)")
          bdb.sql_execute("insert into foo values (10, 1.4142135, 'down', 0)")
          bdb.sql_execute("insert into foo values (11, 2.7182818, 'down', -1)")
          bdb.sql_execute("insert into foo values (12, NULL, 'down', 10)")
          bdb.execute('''
              create population pfoo for foo (
                  id ignore;
                  x numerical;
                  y categorical;
                  z numerical;
              )
          ''')
          bdb.execute('create generator pfoo_cc for pfoo using cgpm;')
          bdb.execute('initialize 1 model for pfoo_cc')
          bdb.execute('analyze pfoo_cc for 1 iteration')
          # Null value => null predictive probability.
          assert bdb.execute('estimate predictive probability of x'
                  ' from pfoo where id = 4;').fetchall() == \
              [(None,)]
          # Nonnull value => nonnull predictive probability.
          x = bdb.execute('estimate predictive probability of x'
              ' from pfoo where id = 5').fetchall()
          assert len(x) == 1
          assert len(x[0]) == 1
          assert isinstance(x[0][0], (int, float))
          # All null values => null predictive probability.
          assert bdb.execute('estimate predictive probability of (y, z)'
                  ' from pfoo where id = 8;').fetchall() == \
              [(None,)]
          # Some nonnull values => nonnull predictive probability.
          x = bdb.execute('estimate predictive probability of (x, z)'
                  ' from pfoo where id = 8;').fetchall()
          assert len(x) == 1
          assert len(x[0]) == 1
          assert isinstance(x[0][0], (int, float))
          # All NULL constraints => same result regardless of given clause.
          c0 = bdb.execute('estimate predictive probability of x'
                  ' from pfoo where id = 8;')
          v0 = cursor_value(c0)
          assert v0 is not None
          c1 = bdb.execute('estimate predictive probability of x given (y, z)'
                  ' from pfoo where id = 8;')
          v1 = cursor_value(c1)
          assert relerr(v0, v1) < 0.0001
  
  def test_guess_all():
      with test_core.bayesdb() as bdb:
          bdb.sql_execute('create table foo (x numeric, y numeric, z numeric)')
          bdb.sql_execute('insert into foo values (1, 2, 3)')
          bdb.sql_execute('insert into foo values (4, 5, 6)')
          # XXX GUESS(*)
          guess.bayesdb_guess_population(bdb, 'pfoo', 'foo')
  
  def test_misc_errors():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('create table t1 as SELECT 1 FROM t1'
              # t1 already exists as a table.
                  ' limit 1')
          with pytest.raises(bayeslite.BQLError):
              # t1 already exists as a table.
              bdb.execute('create table t1 as simulate weight from p1'
                  ' limit 1')
          with pytest.raises(bayeslite.BQLError):
              # t1x does not exist as a population.
              bdb.execute('create table t1_sim as simulate weight from t1x'
                  ' limit 1')
          with pytest.raises(bayeslite.BQLError):
              # p1 does not have a variable waught.
              bdb.execute('create table t1_sim as simulate waught from p1'
                  ' limit 1')
          with pytest.raises(bayeslite.BQLError):
              # p1 does not have a variable agee.
              bdb.execute('create table t1_sim as simulate weight from p1'
                  ' given agee = 42 limit 1')
          with bdb.savepoint():
              bdb.sql_execute('create table t2(x)')
              with pytest.raises(bayeslite.BQLError):
                  # t1 already exists as a table.
                  bdb.execute('alter table t2 rename to t1')
          with pytest.raises(NotImplementedError):
              # Renaming columns is not yet implemented.
              bdb.execute('alter table t1 rename weight to mass')
          with pytest.raises(bayeslite.BQLError):
              # xcat does not exist as a backend.
              bdb.execute('create generator p1_xc for p1 using xcat()')
          with pytest.raises(bayeslite.BQLError):
              # p1 already exists as a population.
              bdb.execute('create generator p1_cc for p1;')
          with pytest.raises(bayeslite.BQLError):
              # multinomial is not a known statistical type.
              bdb.execute('''
                  create population q1 for t1(
                      ignore id, label, weight;
                      weight multinomial
                  )
              ''')
          with pytest.raises(bayeslite.BQLError):
              # p1_xc does not exist as a generator.
              bdb.execute('alter generator p1_xc rename to p1_xcat')
          with bdb.savepoint():
              bdb.execute('create generator p1_xc for p1;')
              with pytest.raises(bayeslite.BQLError):
                  # p1_xc already exists as a generator.
                  bdb.execute('alter generator p1_cc rename to p1_xc')
          with pytest.raises(bayeslite.BQLParseError):
              # WAIT is not allowed.
              bdb.execute('analyze p1_cc for 1 iteration wait')
          with bdb.savepoint():
              bdb.execute('initialize 1 model for p1_cc')
              bdb.execute('analyze p1_cc for 1 iteration')
              bdb.execute('initialize 1 model for p1_xc')
              bdb.execute('analyze p1_xc for 1 iteration')
              with pytest.raises(apsw.SQLError):
                  bdb.execute('select'
                      ' nonexistent((simulate age from p1 limit 1));')
          with pytest.raises(ValueError):
              bdb.execute('select :x', {'y': 42})
          with pytest.raises(ValueError):
              bdb.execute('select :x', {'x': 53, 'y': 42})
          with pytest.raises(ValueError):
              bdb.execute('select ?, ?', (1,))
          with pytest.raises(ValueError):
              bdb.execute('select ?', (1, 2))
          with pytest.raises(TypeError):
              bdb.execute('select ?', 42)
          with pytest.raises(NotImplementedError):
              bdb.execute('infer explicit predict age confidence ac, *'
                  ' from p1')
          with pytest.raises(NotImplementedError):
              bdb.execute('infer explicit predict age confidence ac,'
                  ' t1.(select age from t1 limit 1) from p1')
          with pytest.raises(bayeslite.BQLError):
              try:
                  bdb.execute('estimate similarity to (rowid=1)'
                      ' in the context of agee from p1')
              except bayeslite.BQLError as e:
                  assert 'No such columns in population:' in str(e)
                  raise
  
  def test_nested_simulate():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration')
          bdb.execute('select (simulate age from p1 limit 1),'
              ' (simulate weight from p1 limit 1)').fetchall()
          assert bdb.temp_table_name() == 'bayesdb_temp_2'
          assert not core.bayesdb_has_table(bdb, 'bayesdb_temp_0')
          assert not core.bayesdb_has_table(bdb, 'bayesdb_temp_1')
          bdb.execute('simulate weight from p1'
              ' given age = (simulate age from p1 limit 1)'
              ' limit 1').fetchall()
          # Make sure unwinding doesn't raise an exception.  Calling
          # __del__ directly, rather than via del(), has two effects:
          #
          # (a) It actually raises any exceptions in the method, unlike
          # del(), which suppresses them.
          #
          # (b) It may cause a subsequent __del__ to fail and raise an
          # exception, so that a subsequent del(), including an implicit
          # one at the end of a scope, may print a message to stderr.
          #
          # Effect (a) is what we are actually trying to test.  Effect
          # (b) is a harmless consequence as far as pytest is concerned,
          # as long as the test otherwise passes.
          bdb.execute('simulate weight from p1'
              ' given age = (simulate age from p1 limit 1)'
              ' limit 1').__del__()
  
  def test_checkpoint__ci_slow():
      with test_core.t1() as (bdb, population_id, generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 10 iterations checkpoint 1 iteration')
          # No checkpoint by seconds.
          with pytest.raises(NotImplementedError):
              bdb.execute('analyze p1_cc for 5 seconds checkpoint 1 second')
          bdb.execute('drop models from p1_cc')
          bdb.execute('initialize 1 model for p1_cc')
          # No checkpoint by seconds.
          with pytest.raises(NotImplementedError):
              bdb.execute('analyze p1_cc for 5 iterations checkpoint 1 second')
          sql = '''
              select iterations from bayesdb_generator_model
                  where generator_id = ?
          '''
          with pytest.raises(AssertionError):
              # XXX CGPM_Backend does not update bayesdb_generator_model.
              assert bdb.execute(sql, (generator_id,)).fetchvalue() == 5
          bdb.execute('drop models from p1_cc')
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration checkpoint 2 iterations')
          sql = '''
              select iterations from bayesdb_generator_model
                  where generator_id = ?
          '''
          with pytest.raises(AssertionError):
              # XXX CGPM_Backend does not update bayesdb_generator_model.
              assert bdb.execute(sql, (generator_id,)).fetchvalue() == 1
  
  def test_infer_confidence__ci_slow():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration')
          bdb.execute('infer explicit rowid, rowid as another_rowid, 4,'
              ' age, predict age as age_inf confidence age_conf'
              ' from p1').fetchall()
  
  def test_infer_as_estimate():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration')
          bdb.execute('infer explicit predictive probability of age'
              ' from p1').fetchall()
  
  def test_infer_error():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('infer explicit predict age confidence age_conf'
              ' from p1').fetchall()
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('infer explicit predict agee confidence age_conf'
                  ' from p1').fetchall()
  
  def test_estimate_by():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bdb.execute('initialize 1 model for p1_cc')
          bdb.execute('analyze p1_cc for 1 iteration')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate predictive probability of age'
                  ' by p1')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('estimate similarity to (rowid=1) '
                  'in the context of age by p1')
          def check(x, bindings=None):
              assert len(bdb.execute(x, bindings=bindings).fetchall()) == 1
          check('estimate probability density of age = 42 by p1')
          check('estimate dependence probability of age with weight by p1')
          check('estimate mutual information of age with weight by p1')
          check('estimate correlation of age with weight by p1')
          check('estimate correlation pvalue of age with weight by p1')
          rowid = bdb.execute('select min(rowid) from t1').fetchall()[0][0]
          check('''
              estimate similarity of (rowid=?) to (rowid=?)
              in the context of weight by p1
          ''', (rowid, rowid,))
  
  def test_empty_cursor():
      with bayeslite.bayesdb_open() as bdb:
          assert bdb.execute('SELECT 0').connection == bdb
          empty(bdb.execute('BEGIN'))
          empty(bdb.execute('COMMIT'))
          empty(bdb.sql_execute('CREATE TABLE t(x, y, z)'))
          empty(bdb.sql_execute('INSERT INTO t VALUES(1,2,3)'))
          empty(bdb.sql_execute('INSERT INTO t VALUES(4,5,6)'))
          empty(bdb.sql_execute('INSERT INTO t VALUES(7,8,9)'))
          empty(bdb.execute('CREATE POPULATION p FOR t '
              '(IGNORE z,y; x CATEGORICAL)'))
          empty(bdb.execute('CREATE GENERATOR p_cc FOR p;'))
          empty(bdb.execute('INITIALIZE 1 MODEL FOR p_cc'))
          empty(bdb.execute('DROP GENERATOR p_cc'))
          empty(bdb.execute('DROP POPULATION p'))
          empty(bdb.execute('DROP TABLE t'))
  
  def test_create_generator_ifnotexists():
      # XXX Test other backends too, because they have a role in ensuring that
      # this works. Their create_generator will still be called.
      #
      # [TRC 20160627: The above comment appears to be no longer true --
      # if it was ever true.]
      for using_clause in ('cgpm()',):
          with bayeslite.bayesdb_open() as bdb:
              bdb.sql_execute('CREATE TABLE t(x, y, z)')
              bdb.sql_execute('INSERT INTO t VALUES(1,2,3)')
              bdb.execute('''
                  CREATE POPULATION p FOR t (
                      x NUMERICAL;
                      y NUMERICAL;
                      z CATEGORICAL
                  )
              ''')
              for _i in (0, 1):
                  bdb.execute('CREATE GENERATOR p_cc IF NOT EXISTS FOR p USING '
                              + using_clause)
              try:
                  bdb.execute('CREATE GENERATOR p_cc FOR p USING ' + using_clause)
                  assert False  # Should have said it exists.
              except bayeslite.BQLError:
                  pass
  
  def test_bql_rand():
      with bayeslite.bayesdb_open() as bdb:
          bdb.sql_execute('CREATE TABLE frobotz(x)')
          for _ in range(10):
              bdb.sql_execute('INSERT INTO frobotz VALUES(2)')
          cursor = bdb.execute('SELECT bql_rand() FROM frobotz LIMIT 10;')
          rands = cursor.fetchall()
          # These are "the" random numbers (internal PRNG is seeded to 0)
          ans = [(0.28348770982811367,), (0.4789774612650598,), (0.07824908989551316,),
                 (0.6091223239372148,), (0.03906608409906187,), (0.3690599096081546,),
                 (0.8223420512129717,), (0.7777771914916722,), (0.061856771629497986,),
                 (0.6492586781908201,)]
          assert rands == ans
  
  def test_bql_rand2():
      seed = struct.pack('<QQQQ', 0, 0, 0, 3)
      with bayeslite.bayesdb_open(seed=seed) as bdb:
          bdb.sql_execute('CREATE TABLE frobotz(x)')
          for _ in range(10):
              bdb.sql_execute('INSERT INTO frobotz VALUES(2)')
          cursor = bdb.execute('SELECT bql_rand() FROM frobotz LIMIT 10;')
          rands = cursor.fetchall()
          ans = [(0.8351877951287725,), (0.9735099617243271,), (0.026142315910925418,),
                 (0.09380653289687524,), (0.1097050387582088,), (0.33154896906379605,),
                 (0.4579314980719317,), (0.09072802203491703,), (0.5276180968829105,),
                 (0.9993280772797679,)]
          assert rands == ans
  
  class MockTracerOneQuery(bayeslite.IBayesDBTracer):
      def __init__(self, q, qid):
          self.q = q
          self.qid = qid
          self.start_calls = 0
          self.ready_calls = 0
          self.error_calls = 0
          self.finished_calls = 0
          self.abandoned_calls = 0
      def start(self, qid, query, bindings):
          assert qid == self.qid
          assert query == self.q
          assert bindings == ()
          self.start_calls += 1
      def ready(self, qid, _cursor):
          assert qid == self.qid
          self.ready_calls += 1
      def error(self, qid, _e):
          assert qid == self.qid
          self.error_calls += 1
      def finished(self, qid):
          assert qid == self.qid
          self.finished_calls += 1
      def abandoned(self, qid):
          assert qid == self.qid
          self.abandoned_calls += 1
  
  def test_tracing_smoke():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          q = 'SELECT * FROM t1'
          tracer = MockTracerOneQuery(q, 1)
          bdb.trace(tracer)
          cursor = bdb.execute(q)
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
          cursor.fetchall()
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 1
          assert tracer.abandoned_calls == 0
          del cursor
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 1
          assert tracer.abandoned_calls == 1
          bdb.untrace(tracer)
          # XXX Make sure the whole cursor API works.
          q = 'SELECT 42'
          tracer = MockTracerOneQuery(q, 2)
          bdb.trace(tracer)
          cursor = bdb.execute(q)
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
          assert cursor.fetchvalue() == 42
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 1
          assert tracer.abandoned_calls == 0
          del cursor
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 1
          assert tracer.abandoned_calls == 1
  
  def test_tracing_error_smoke():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          q = 'SELECT * FROM wrong'
          tracer = MockTracerOneQuery(q, 1)
          bdb.trace(tracer)
          with pytest.raises(apsw.SQLError):
              bdb.execute(q)
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 0
          assert tracer.error_calls == 1
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
  
  class Boom(Exception):
      pass
  class ErroneousBackend(troll.TrollBackend):
      def __init__(self):
          self.call_ct = 0
      def name(self):
          return 'erroneous'
      def logpdf_joint(self, *_args, **_kwargs):
          if self.call_ct > 10: # Wait to avoid raising during sqlite's prefetch
              raise Boom()
          self.call_ct += 1
          return 0
  
  def test_tracing_execution_error_smoke():
      with test_core.t1() as (bdb, _population_id, _generator_id):
          bayeslite.bayesdb_register_backend(bdb, ErroneousBackend())
          bdb.execute('DROP GENERATOR p1_cc')
          bdb.execute('CREATE GENERATOR p1_err FOR p1 USING erroneous()')
          q = 'ESTIMATE PREDICTIVE PROBABILITY OF age FROM p1'
          tracer = MockTracerOneQuery(q, 1)
          bdb.trace(tracer)
          cursor = bdb.execute(q)
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 0
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
          with pytest.raises(Boom):
              cursor.fetchall()
          assert tracer.start_calls == 1
          assert tracer.ready_calls == 1
          assert tracer.error_calls == 1
          assert tracer.finished_calls == 0
          assert tracer.abandoned_calls == 0
  
  def test_pdf_var():
      with test_core.t1() as (bdb, population_id, _generator_id):
          bdb.execute('initialize 6 models for p1_cc;')
          c = bdb.execute(
              'estimate probability density of label = label from p1')
          c.fetchall()
          assert bql2sql(
                  'estimate probability density of label = label from p1') == \
              'SELECT bql_pdf_joint(1, NULL, NULL, 1, "label") FROM "t1";'
  
files: tests/test_bql.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  from __future__ import division
  
  import StringIO
  import contextlib
  import math
  import time
  
  import numpy as np
  import pytest
  
  from cgpm.cgpm import CGpm
  from cgpm.dummy.barebones import BareBonesCGpm
  from cgpm.dummy.piecewise import PieceWise
  from cgpm.dummy.trollnormal import TrollNormal
  from cgpm.factor.factor import FactorAnalysis
  from cgpm.regressions.linreg import LinearRegression
  from cgpm.utils import general as gu
  
  from bayeslite import bayesdb_nullify
  from bayeslite import bayesdb_open
  from bayeslite import bayesdb_read_csv
  from bayeslite import bayesdb_register_backend
  from bayeslite.core import bayesdb_get_generator
  from bayeslite.core import bayesdb_get_population
  from bayeslite.exception import BQLError
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  from bayeslite.util import cursor_value
  
  import test_csv
  
  @contextlib.contextmanager
  def cgpm_smoke_bdb():
      with bayesdb_open(':memory:', builtin_backends=False) as bdb:
          registry = {
              'piecewise': PieceWise,
          }
          bayesdb_register_backend(
              bdb, CGPM_Backend(registry, multiprocess=0))
  
          bdb.sql_execute('CREATE TABLE t (Output, cat, Input)')
          for i in xrange(3):
              for j in xrange(3):
                  for k in xrange(3):
                      output = i + j/(k + 1)
                      cat = -1 if (i + j*k) % 2 else +1
                      input = (i*j - k)**2
                      if i % 2:
                          output = None
                      if j % 2:
                          cat = None
                      if k % 2:
                          input = None
                      bdb.sql_execute('''
                          INSERT INTO t (output, cat, input) VALUES (?, ?, ?)
                      ''', (output, cat, input))
  
          bdb.execute('''
              CREATE POPULATION p FOR t WITH SCHEMA(
                  output  NUMERICAL;
                  input   NUMERICAL;
                  cat     NOMINAL;
              )
          ''')
  
          yield bdb
  
  @contextlib.contextmanager
  def cgpm_dummy_satellites_bdb():
      with bayesdb_open(':memory:', builtin_backends=False) as bdb:
          bdb.sql_execute('''
              CREATE TABLE satellites_ucs (
                  apogee,
                  class_of_orbit,
                  country_of_operator,
                  launch_mass,
                  perigee,
                  period
          )''')
          for l, f in [
              ('geo', lambda x, y: x + y**2),
              ('leo', lambda x, y: math.sin(x + y)),
              (None, lambda x, y: x + y**2),
              (None, lambda x, y: math.sin(x + y)),
          ]:
              for x in xrange(5):
                  for y in xrange(5):
                      countries = ['US', 'Russia', 'China', 'Bulgaria']
                      country = countries[bdb._np_prng.randint(0, len(countries))]
                      mass = bdb._np_prng.normal(1000, 50)
                      bdb.sql_execute('''
                          INSERT INTO satellites_ucs
                              (country_of_operator, launch_mass, class_of_orbit,
                                  apogee, perigee, period)
                              VALUES (?,?,?,?,?,?)
                      ''', (country, mass, l, x, y, f(x, y)))
          yield bdb
  
  def test_cgpm_no_empty_categories():
      with cgpm_smoke_bdb() as bdb:
          bdb.sql_execute('CREATE TABLE f (a, b, c);')
          rows = [['', '\'\'', 'nan'], [1.1, 3, ''], ['""""', 1, 1]]
          for row in rows:
              bdb.sql_execute('INSERT INTO f (a, b, c) VALUES (?,?,?)', row)
          bayesdb_nullify(bdb, 'f', "''")
          bayesdb_nullify(bdb, 'f', '""""')
          bayesdb_nullify(bdb, 'f', '')
          bdb.execute('''
              CREATE POPULATION q FOR f WITH SCHEMA (
                  SET STATTYPES OF a, b, c TO NOMINAL
              );
          ''')
          bdb.execute('CREATE GENERATOR h IF NOT EXISTS FOR q USING cgpm;')
          bdb.execute('INITIALIZE 1 MODEL FOR h')
          category_rows = bdb.sql_execute('''
              SELECT colno, value FROM bayesdb_cgpm_category;
          ''')
          # Assert that none of the categories are empty strings or NULL.
          expected = {
              0 : ['1.1'],       # categories for a
              1 : ['1', '3'],    # categories for b
              2 : ['nan', '1'],  # categories for c
          }
          seen = {
              0: [],
              1: [],
              2: [],
          }
          for row in category_rows:
              colno, value = row
              seen[colno].append(value)
          assert all(set(expected[c])==set(seen[c]) for c in expected)
  
  def cgpm_smoke_tests(bdb, gen, vars):
      modeledby = 'MODELED BY %s' % (gen,) if gen else ''
      for var in vars:
          bdb.execute('''
              ESTIMATE PROBABILITY DENSITY OF %s = 1 WITHIN p %s
          ''' % (var, modeledby)).fetchall()
          bdb.execute('''
              SIMULATE %s FROM p %s LIMIT 1
          ''' % (var, modeledby)).fetchall()
          bdb.execute('''
              INFER %s FROM p %s LIMIT 1
          ''' % (var, modeledby)).fetchall()
          nvars = len(bdb.execute('''
              ESTIMATE * FROM VARIABLES OF p %(modeledby)s
                  ORDER BY PROBABILITY OF
                      (MUTUAL INFORMATION WITH %(var)s USING 1 SAMPLES > 0.1)
          ''' % {'var': var, 'modeledby': modeledby}).fetchall())
          if 0 < nvars:
              c = bdb.execute('''
                  SIMULATE p.(ESTIMATE * FROM VARIABLES OF p %(modeledby)s
                                  ORDER BY PROBABILITY OF
                                      (MUTUAL INFORMATION WITH %(var)s
                                          USING 1 SAMPLES > 0.1))
                      FROM p
                      %(modeledby)s
                      LIMIT 1
              ''' % {'var': var, 'modeledby': modeledby}).fetchall()
              assert len(c) == 1
              assert len(c[0]) == nvars
  
  def test_cgpm_smoke():
      with cgpm_smoke_bdb() as bdb:
  
          # Default model.
          bdb.execute('CREATE GENERATOR g_default FOR p USING cgpm')
          bdb.execute('INITIALIZE 1 MODEL FOR g_default')
          bdb.execute('ANALYZE g_default FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_default', ['output', 'cat', 'input'])
  
          # Custom model for output and cat.
          bdb.execute('''
              CREATE GENERATOR g_manifest FOR p USING cgpm (
                  OVERRIDE MODEL FOR Output, Cat
                  GIVEN Input
                  USING piecewise
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_manifest')
          bdb.execute('ANALYZE g_manifest FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_manifest', ['output', 'cat', 'input'])
  
          # Custom model for latent output, manifest output.
          bdb.execute('''
              CREATE GENERATOR g_latout FOR p USING cgpm (
                  LATENT output_ NUMERICAL;
                  OVERRIDE MODEL FOR output_, cat GIVEN input USING piecewise;
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_latout')
          bdb.execute('ANALYZE g_latout FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_latout',
              ['output', 'output_', 'cat', 'input'])
  
          # Custom model for manifest out, latent cat.
          bdb.execute('''
              CREATE GENERATOR g_latcat FOR p USING cgpm (
                  LATENT cat_ CATEGORICAL;
                  OVERRIDE MODEL FOR output, cat_ GIVEN input USING piecewise
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_latcat')
          bdb.execute('ANALYZE g_latcat FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_latcat', ['output', 'cat', 'cat_', 'input'])
  
          # Custom chained model.
          bdb.execute('''
              CREATE GENERATOR g_chain FOR p USING cgpm (
                  LATENT midput NUMERICAL;
                  LATENT excat NUMERICAL;
                  OVERRIDE MODEL FOR midput, cat GIVEN input USING piecewise;
                  OVERRIDE MODEL FOR output, excat GIVEN midput USING piecewise
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_chain')
          bdb.execute('ANALYZE g_chain FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_chain',
              ['output', 'excat', 'midput', 'cat', 'input'])
  
          # Override the crosscat category model.
          bdb.execute('''
              CREATE GENERATOR g_category_model FOR p USING cgpm (
                  SET CATEGORY MODEL FOR output TO NORMAL;
                  OVERRIDE MODEL FOR input, cat GIVEN output USING piecewise;
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_category_model')
          bdb.execute('ANALYZE g_category_model FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_category_model',
              ['output', 'cat', 'input'])
  
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_typo FOR p USING cgpm (
                      SET CATEGORY MODEL FOR uot TO NORMAL
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_typo_manifest FOR p USING cgpm (
                      OVERRIDE MODEL FOR output, cat GIVEN ni USING piecewise
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_typo_output FOR p USING cgpm (
                      OVERRIDE MODEL FOR output, dog GIVEN input USING piecewise;
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_dup_manifest FOR p USING cgpm (
                      SET CATEGORY MODEL FOR input TO NORMAL;
                      SET CATEGORY MODEL FOR input TO LOGNORMAL
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_dup_latent FOR p USING cgpm (
                      LATENT output_error NUMERICAL;
                      LATENT output_error CATEGORICAL;
  
                      OVERRIDE MODEL FOR output_error, cat
                      GIVEN input USING piecewise;
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_latent_exists FOR p USING cgpm (
                      LATENT output_ NUMERICAL;
                      OVERRIDE MODEL FOR output_, cat GIVEN input USING piecewise;
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_latent_manifest FOR p USING cgpm (
                      LATENT output NUMERICAL;
                      OVERRIDE MODEL FOR output, cat GIVEN input USING piecewise;
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_category_override_dupe FOR p USING cgpm (
                      SET CATEGORY MODEL FOR output TO LOGNORMAL;
                      OVERRIDE MODEL FOR output, cat GIVEN input USING piecewise;
                  )
              ''')
  
          cgpm_smoke_tests(bdb, None, ['output', 'cat', 'input'])
  
          # XXX Check each operation independently: simulate, logpdf, impute.
          for var in ['output_', 'cat_', 'midput', 'excat']:
              with pytest.raises(BQLError):
                  cgpm_smoke_tests(bdb, None, [var])
  
  def test_cgpm_analysis_iteration_timed__ci_slow():
      # Test that the minimum of iterations and wall clock are used for ANALYZE.
      # The point of these tests is not for fine-grained timing control (those
      # tests exists in the python interface, there is additional bayesdb bql
      # which makes the time less predictable), but ensuring that clealry
      # extreme amounts of analysis are quickly short-circuited.
      with cgpm_smoke_bdb() as bdb:
  
          bdb.execute('CREATE GENERATOR g2 FOR p USING cgpm')
          bdb.execute('INITIALIZE 2 MODELS FOR g2')
  
          start0 = time.time()
          bdb.execute('''
              ANALYZE g2 FOR 10000 ITERATION OR 5 SECONDS
                  CHECKPOINT 1 ITERATION (QUIET);
          ''')
          assert 5 < time.time() - start0 < 15
  
          start1 = time.time()
          bdb.execute('''
              ANALYZE g2 FOR 10000 ITERATION OR 5 SECONDS (
                  OPTIMIZED; QUIET
              )
          ''')
          assert 5 < time.time() - start1 < 15
  
          start2 = time.time()
          bdb.execute('''
              ANALYZE g2 FOR 1 ITERATION OR 100 MINUTES
          ''')
          assert 0 < time.time() - start2 < 15
  
          start3 = time.time()
          bdb.execute('''
              ANALYZE g2 FOR 10 ITERATION OR 100 SECONDS
              CHECKPOINT 1 ITERATION (OPTIMIZED)
          ''')
          assert 0 < time.time() - start3 < 15
  
  
  # Use dummy, quick version of Kepler's laws.  Allow an extra
  # distribution argument to make sure it gets passed through.
  class Kepler(TrollNormal):
      def __init__(self, outputs, inputs, quagga=None, *args, **kwargs):
          assert quagga == 'eland'
          return super(Kepler, self).__init__(outputs, inputs, *args, **kwargs)
  
  def test_cgpm_kepler():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  apogee                  NUMERICAL;
                  class_of_orbit          CATEGORICAL;
                  country_of_operator     CATEGORICAL;
                  launch_mass             NUMERICAL;
                  perigee                 NUMERICAL;
                  period                  NUMERICAL
              )
          ''')
          bdb.execute('''
              ESTIMATE CORRELATION from PAIRWISE VARIABLES OF satellites
          ''').fetchall()
          registry = {
              'kepler': Kepler,
              'linreg': LinearRegression,
          }
          bayesdb_register_backend(
              bdb, CGPM_Backend(registry, multiprocess=0))
          bdb.execute('''
              CREATE GENERATOR g0 FOR satellites USING cgpm (
                  OVERRIDE GENERATIVE MODEL FOR period
                  GIVEN apogee, perigee
                  USING linreg
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g0')
          c = bdb.execute('SELECT COUNT(*) FROM bayesdb_cgpm_individual')
          n = c.fetchvalue()
          # Another generator: exponential launch mass instead of normal.
          bdb.execute('''
              CREATE GENERATOR g1 FOR satellites USING cgpm (
                  SET CATEGORY MODEL FOR launch_mass TO EXPONENTIAL;
                  OVERRIDE MODEL FOR period GIVEN apogee, perigee
                      USING kepler(quagga = eland);
                  SUBSAMPLE 20
              )
          ''')
          c_ = bdb.execute('SELECT COUNT(*) FROM bayesdb_cgpm_individual')
          n_ = c_.fetchvalue()
          assert n_ - n == 20
          bdb.execute('INITIALIZE 1 MODEL IF NOT EXISTS FOR g1')
          bdb.execute('ANALYZE g0 FOR 1 ITERATION')
          bdb.execute('ANALYZE g0 FOR 1 ITERATION (VARIABLES period)')
          bdb.execute('ANALYZE g1 FOR 1 ITERATION')
          bdb.execute('ANALYZE g1 FOR 1 ITERATION (VARIABLES period)')
          # OPTIMIZED is ignored because period is a foreign variable.
          bdb.execute('''
              ANALYZE g1 FOR 1 ITERATION (OPTIMIZED; VARIABLES period)
          ''')
          # This should fail since we have a SET CATEGORY MODEL which is not
          # compatible with lovecat. The ValueError is from cgpm not bayeslite.
          with pytest.raises(ValueError):
              bdb.execute('''
                  ANALYZE g1 FOR 1 ITERATION
                      (OPTIMIZED; VARIABLES launch_mass)
              ''')
          # Cannot use timed analysis with mixed variables.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ANALYZE g1 FOR 5 SECONDS (VARIABLES period, apogee)
              ''')
          # Cannot use timed analysis with mixed variables (period by SKIP).
          with pytest.raises(BQLError):
              bdb.execute('''
                  ANALYZE g1 FOR 5 SECONDS (SKIP apogee)
              ''')
          # OK to use iteration analysis with mixed values.
          bdb.execute('''
                  ANALYZE g1 FOR 1 ITERATION (VARIABLES period, apogee)
              ''')
          bdb.execute('''
              ESTIMATE DEPENDENCE PROBABILITY
                  FROM PAIRWISE VARIABLES OF satellites
          ''').fetchall()
          bdb.execute('''
              ESTIMATE PREDICTIVE PROBABILITY OF period FROM satellites
          ''').fetchall()
          bdb.execute('''
              ESTIMATE PROBABILITY DENSITY OF period = 42
                      GIVEN (apogee = 8 AND perigee = 7)
                  BY satellites
          ''').fetchall()
          bdb.execute('''
              SIMULATE apogee, perigee, period FROM satellites LIMIT 100
          ''').fetchall()
          bdb.execute('''
              INFER EXPLICIT
                  PREDICT apogee
                      CONFIDENCE apogee_confidence
                      USING 5 SAMPLES
              FROM satellites LIMIT 2
          ''').fetchall()
          results = bdb.execute('''
              INFER EXPLICIT
                  PREDICT class_of_orbit
                      CONFIDENCE class_of_orbit_confidence
              FROM satellites LIMIT 2
          ''').fetchall()
          assert len(results[0]) == 2
          assert isinstance(results[0][0], unicode)
          assert isinstance(results[0][1], float)
          # No CONFIDENCE specified.
          results = bdb.execute('''
              INFER EXPLICIT PREDICT class_of_orbit USING 2 SAMPLES
              FROM satellites LIMIT 2
          ''').fetchall()
          assert len(results[0]) == 1
          assert isinstance(results[0][0], unicode)
          bdb.execute('DROP MODELS FROM g0')
          bdb.execute('DROP GENERATOR g0')
          bdb.execute('DROP GENERATOR g1')
  
  def test_unknown_stattype():
      from cgpm.regressions.linreg import LinearRegression
      with cgpm_dummy_satellites_bdb() as bdb:
          # Add a column called relaunches, sum of apogee and perigee.
          bdb.sql_execute('ALTER TABLE satellites_ucs ADD COLUMN relaunches')
          n_rows = bdb.sql_execute('''
              SELECT COUNT(*) FROM satellites_ucs
          ''').next()[0]
          for rowid in xrange(n_rows):
              bdb.sql_execute('''
                  UPDATE satellites_ucs
                      SET relaunches = (SELECT apogee + perigee)
                      WHERE _rowid_ = ?
              ''', (rowid+1,))
          # Nobody will ever create a QUAGGA statistical type!
          with pytest.raises(BQLError):
              # No such statistical type at the moment.
              bdb.execute('''
                  CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                      SET STATTYPES OF apogee, perigee, launch_mass, period
                          TO NUMERICAL;
  
                      SET STATTYPE OF class_of_orbit, country_of_operator
                          TO NOMINAL;
  
                      SET STATTYPE OF relaunches
                          TO QUAGGA
                  )
              ''')
          # Invent the statistical type.
          bdb.sql_execute('INSERT INTO bayesdb_stattype VALUES (?)', ('quagga',))
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  SET STATTYPES OF apogee, perigee, launch_mass, period
                      TO NUMERICAL;
  
                  SET STATTYPES OF class_of_orbit, country_of_operator
                  TO NOMINAL;
  
                  SET STATTYPES OF relaunches
                  TO QUAGGA
              )
          ''')
          registry = {
              'kepler': Kepler,
              'linreg': LinearRegression,
          }
          bayesdb_register_backend(bdb, CGPM_Backend(registry))
          with pytest.raises(BQLError):
              # Can't model QUAGGA by default.
              bdb.execute('CREATE GENERATOR g0 FOR satellites USING cgpm')
          with pytest.raises(BQLError):
              # Can't model QUAGGA as input.
              bdb.execute('''
                  CREATE GENERATOR g0 FOR satellites USING cgpm (
                      OVERRIDE MODEL FOR relaunches GIVEN apogee USING linreg;
                      OVERRIDE MODEL FOR period GIVEN relaunches USING linreg
                  )
              ''')
          # Can model QUAGGA with an explicit distribution family.
          bdb.execute('''
              CREATE GENERATOR g0 FOR satellites USING cgpm (
                  SET CATEGORY MODEL FOR relaunches TO POISSON
              )
          ''')
          bdb.execute('''
              CREATE GENERATOR g1 FOR satellites USING cgpm (
                  SET CATEGORY MODEL FOR relaunches TO POISSON;
                  OVERRIDE MODEL FOR period GIVEN relaunches USING linreg
              )
          ''')
  
  def test_bad_analyze_vars():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  SET STATTYPE OF apogee TO NUMERICAL;
                  SET STATTYPE OF class_of_orbit TO CATEGORICAL;
                  SET STATTYPE OF country_of_operator TO CATEGORICAL;
                  SET STATTYPE OF launch_mass TO NUMERICAL;
                  SET STATTYPE OF perigee TO NUMERICAL;
                  SET STATTYPE OF period TO NUMERICAL
              )
          ''')
          registry = {
              'kepler': Kepler,
              'linreg': LinearRegression,
          }
          bayesdb_register_backend(bdb, CGPM_Backend(registry))
          bdb.execute('''
              CREATE GENERATOR satellites_cgpm FOR satellites USING cgpm
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR satellites_cgpm')
          bdb.execute('ANALYZE satellites_cgpm FOR 1 ITERATION ()')
          bdb.execute('ANALYZE satellites_cgpm FOR 1 ITERATION')
          with pytest.raises(BQLError):
              # Unknown variable `perige'.
              bdb.execute('''
                  ANALYZE satellites_cgpm FOR 1 ITERATION (
                      VARIABLES period, perige
                  )
              ''')
          with pytest.raises(BQLError):
              # Unknown variable `perige'.
              bdb.execute('''
                  ANALYZE satellites_cgpm FOR 1 ITERATION (
                      SKIP period, perige
                  )
              ''')
  
  def test_output_stattypes():
      with cgpm_dummy_satellites_bdb() as bdb:
          # Missing policy for class_of_orbit, perigee, period
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                      SET STATTYPES OF apogee, launch_mass TO NUMERICAL;
                      SET STATTYPES OF country_of_operator TO CATEGORICAL
                  )
              ''')
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  IGNORE class_of_orbit, perigee, period;
                  SET STATTYPES OF apogee, launch_mass TO NUMERICAL;
                  SET STATTYPES OF country_of_operator TO CATEGORICAL
              )
          ''')
          registry = {
              'factor_analysis': FactorAnalysis,
          }
          bayesdb_register_backend(bdb, CGPM_Backend(registry))
          # Creating factor analysis with categorical manifest should crash.
          bdb.execute('''
              CREATE GENERATOR satellites_g0 FOR satellites(
                  OVERRIDE MODEL FOR apogee, country_of_operator
                  AND EXPOSE pc_1 NUMERICAL
                  USING factor_analysis(L=1)
              )
          ''')
          with pytest.raises(ValueError):
              bdb.execute('INITIALIZE 1 MODEL FOR satellites_g0')
          with pytest.raises(BQLError):
              # Duplicate pc_2 in LATENT and EXPOSE.
              bdb.execute('''
                  CREATE GENERATOR satellites_g1 FOR satellites(
                      LATENT pc_2 CATEGORICAL,
                      OVERRIDE GENERATIVE MODEL FOR
                          apogee, launch_mass
                      AND EXPOSE pc_2 CATEGORICAL
                      USING factor_analysis(L=1)
                  )
              ''')
          # Creating factor analysis with categorical latent should crash.
          bdb.execute('''
              CREATE GENERATOR satellites_g1 FOR satellites(
                  OVERRIDE GENERATIVE MODEL FOR
                      apogee, launch_mass
                  AND EXPOSE pc_2 CATEGORICAL
                  USING factor_analysis(L=1)
              )
          ''')
          with pytest.raises(ValueError):
              bdb.execute('INITIALIZE 1 MODEL FOR satellites_g1')
          # Creating factor analysis with all numerical should be ok.
          bdb.execute('''
              CREATE GENERATOR satellites_g2 FOR satellites USING cgpm(
                  LATENT pc_3 NUMERICAL;
  
                  OVERRIDE MODEL FOR apogee, launch_mass, pc_3, pc_4
                  USING factor_analysis(L=2);
  
                  LATENT pc_4 NUMERICAL
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR satellites_g2')
          bdb.execute('ANALYZE satellites_g2 FOR 2 ITERATION')
          # Cannot transition baseline and foreign using timed analysis.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ANALYZE satellites_g2 FOR 2 SECONDS (
                      VARIABLES country_of_operator, apogee, launch_mass, pc_3);
              ''')
          bdb.execute('''
              ANALYZE satellites_g2 FOR 1 ITERATION (
                  VARIABLES apogee, launch_mass);
          ''')
          # Dependence probability of manifest with latent.
          cursor = bdb.execute('''
              ESTIMATE DEPENDENCE PROBABILITY OF apogee WITH pc_3
              BY satellites MODELED BY satellites_g2;
          ''').fetchall()
          assert cursor[0][0] == 1.
          # Dependence probability of latent with latent.
          cursor = bdb.execute('''
              ESTIMATE DEPENDENCE PROBABILITY OF pc_3 WITH pc_4
              BY satellites MODELED BY satellites_g2;
          ''').fetchall()
          assert cursor[0][0] == 1.
          # Mutual information of latent with manifest.
          cursor = bdb.execute('''
              ESTIMATE MUTUAL INFORMATION OF apogee WITH pc_4 USING 1 SAMPLES
              BY satellites MODELED BY satellites_g2;
          ''').fetchall()
          # Mutual information of latent with latent.
          cursor = bdb.execute('''
              ESTIMATE MUTUAL INFORMATION OF pc_3 WITH pc_4 USING 1 SAMPLES
              BY satellites MODELED BY satellites_g2;
          ''').fetchall()
  
  def test_initialize_with_all_nulls():
      # This test ensures that trying to initialize a generator with any
      # (manifest) column of all null variables will crash.
      # Initializing an overriden column with all null variables should not
      # be a problem in general, so we test this case as well.
  
      with bayesdb_open(':memory:', builtin_backends=False) as bdb:
          registry = {
              'barebones': BareBonesCGpm,
          }
          bayesdb_register_backend(
              bdb, CGPM_Backend(registry, multiprocess=0))
          # Create table with all missing values for a.
          bdb.sql_execute('''
              CREATE TABLE t (a REAL, b REAL, c REAL);
          ''')
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, None, 3))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, None, 1))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, None, 1))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, -2, 1))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, -5, 1))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, 2, 3))
  
          # Fail when a is numerical and modeled by crosscat.
          bdb.execute('''
              CREATE POPULATION p FOR t WITH SCHEMA(
                  SET STATTYPES OF a, b, c TO NUMERICAL
              )
          ''')
          bdb.execute('''
              CREATE GENERATOR m FOR p;
          ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  INITIALIZE 2 MODELS FOR m;
              ''')
  
          # Fail when a is nominal and modeled by crosscat.
          bdb.execute('''
              CREATE POPULATION p2 FOR t WITH SCHEMA(
                  SET STATTYPES OF a TO NOMINAL;
                  SET STATTYPES OF b, c TO NUMERICAL
              )
          ''')
          bdb.execute('CREATE GENERATOR m2 FOR p2;')
          with pytest.raises(BQLError):
              bdb.execute('INITIALIZE 2 MODELS FOR m2;')
  
          # Succeed when a is ignored.
          bdb.execute('''
              CREATE POPULATION p3 FOR t WITH SCHEMA(
                  IGNORE a;
                  SET STATTYPES OF b, c TO NUMERICAL
              )
          ''')
          bdb.execute('CREATE GENERATOR m3 FOR p3;')
          bdb.execute('INITIALIZE 2 MODELS FOR m3;')
  
  
          # Succeed when a is numerical overriden using a dummy CGPM.
          bdb.execute('''
              CREATE GENERATOR m4 FOR p(
                  OVERRIDE MODEL FOR a GIVEN b USING barebones
              )
          ''')
          bdb.execute('INITIALIZE 2 MODELS FOR m4;')
          bdb.execute('ANALYZE m4 FOR 1 ITERATION')
  
  def test_add_variable():
      with bayesdb_open() as bdb:
          bayesdb_read_csv(
              bdb, 't', StringIO.StringIO(test_csv.csv_data),
              header=True, create=True)
          bdb.execute('''
              CREATE POPULATION p FOR t WITH SCHEMA(
                  age         numerical;
                  gender      nominal;
                  salary      numerical;
                  height      ignore;
                  division    ignore;
                  rank        ignore;
              )
          ''')
          bdb.backends['cgpm'].set_multiprocess(False)
          bdb.execute('CREATE GENERATOR m0 FOR p;')
          bdb.execute('INITIALIZE 1 MODELS FOR m0;')
          bdb.execute('ANALYZE m0 FOR 5 ITERATION')
          # Run some queries on the new variable in the generator or aggregated.
          def run_queries(target, m):
              extra = 'MODELED BY %s' % (m,) if m is not None else ''
              bdb.execute('''
                  ESTIMATE PROBABILITY DENSITY OF %s = 1 BY p %s
              ''' % (target, extra,)).fetchall()
              for other in ['age', 'gender', 'salary']:
                  cursor = bdb.execute('''
                      ESTIMATE DEPENDENCE PROBABILITY OF %s WITH %s
                      BY p %s
                  ''' % (target, other, extra))
                  assert cursor_value(cursor) >= 0
              bdb.execute('''
                  ESTIMATE SIMILARITY IN THE CONTEXT OF %s
                  FROM PAIRWISE p %s;
              ''' % (target, extra,)).fetchall()
          # Fail to run quieres on height, does not exist yet.
          with pytest.raises(BQLError):
              run_queries('height', 'm0')
          # Add the height variable
          bdb.execute('ALTER POPULATION p ADD VARIABLE height numerical;')
          # Run targeted analysis on the newly included height variable.
          bdb.execute('ANALYZE m0 FOR 5 ITERATION')
          bdb.execute('ANALYZE m0 FOR 5 ITERATION (VARIABLES height);')
          # Queries should now be successful.
          run_queries('height', 'm0')
          # Create a new generator, and create a custom category model for
          # the new variable `height`.
          bdb.execute('''
              CREATE GENERATOR m1 FOR p(
                  SET CATEGORY MODEL FOR age TO exponential;
                  SET CATEGORY MODEL FOR height TO lognormal;
              )
          ''')
          bdb.execute('INITIALIZE 2 MODELS FOR m1')
          bdb.execute('ANALYZE m1 FOR 2 ITERATION')
          # Run height queries on m1.
          run_queries('height', 'm1')
          # Run height queries on population, aggregating m0 and m1.
          run_queries('height', None)
          # Add a third variable rank.
          bdb.execute('ALTER POPULATION p ADD VARIABLE rank numerical;')
          # Analyze rank on m0.
          bdb.execute('''
              ANALYZE m0 FOR 2 ITERATION (OPTIMIZED; VARIABLES rank);
          ''')
          # Analyze all except rank on m0.
          bdb.execute('''
              ANALYZE m0 FOR 2 ITERATION (OPTIMIZED; SKIP rank);
          ''')
          # Fail on m1 with OPTIMIZED, since non-standard category models.
          with pytest.raises(ValueError):
              bdb.execute('''
                  ANALYZE m1 FOR 2 ITERATION (OPTIMIZED; VARIABLES rank);
              ''')
          # Succeed analysis on non-optimized analysis.
          bdb.execute('ANALYZE m1 FOR 2 ITERATION')
          # Run queries on the new variable.
          run_queries('rank', 'm0')
          run_queries('rank', 'm1')
          run_queries('rank', None)
  
  def test_predictive_relevance():
      with cgpm_dummy_satellites_bdb() as bdb:
          bayesdb_register_backend(bdb, CGPM_Backend(cgpm_registry=dict()))
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA (
                  apogee                  NUMERICAL;
                  class_of_orbit          CATEGORICAL;
                  country_of_operator     CATEGORICAL;
                  launch_mass             NUMERICAL;
                  perigee                 NUMERICAL;
                  period                  NUMERICAL
              )
          ''')
          bdb.execute('CREATE GENERATOR m FOR satellites;')
          bdb.execute('INITIALIZE 2 MODELS FOR m;')
          bdb.execute('ANALYZE m FOR 25 ITERATION (OPTIMIZED);')
  
          # Check self-similarites, and also provide coverage of bindings.
          rowids = bdb.execute('SELECT OID from satellites_ucs;').fetchall()
          for rowid in rowids[:4]:
              cursor = bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      TO EXISTING ROWS (rowid = ?)
                      IN THE CONTEXT OF "period"
                  FROM satellites
                  WHERE rowid = ?
              ''', (1, 1,))
              assert next(cursor)[0] == 1.
  
          # A full extravaganza query, using FROM (as a 1-row).
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  TO EXISTING ROWS
                      (country_of_operator = 'Russia' AND period < 0)
                  AND HYPOTHETICAL ROWS WITH VALUES (
                      (perigee=1.0, launch_mass=120),
                      (country_of_operator='Bulgaria', perigee=2.0))
                  IN THE CONTEXT OF "country_of_operator"
              FROM satellites
              LIMIT 5
          ''').fetchall()
          assert len(cursor) == 5
          assert all(0 <= c[0] <= 1 for c in cursor)
  
          # A full extravaganza query, using BY (as a constant).
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (rowid = 1)
                  TO EXISTING ROWS
                      (country_of_operator = 'Russia' AND period < 0)
                  AND HYPOTHETICAL ROWS WITH VALUES (
                      (country_of_operator='China', perigee=1.0),
                      (country_of_operator='Bulgaria'))
                  IN THE CONTEXT OF "country_of_operator"
              BY satellites
          ''').fetchall()
          assert len(cursor) == 1
          assert all(0 <= c[0] <= 1 for c in cursor)
  
          # Hypothetical satellite with negative perigee should not be similar,
          # and use a binding to just ensure that they work.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  TO HYPOTHETICAL ROWS WITH VALUES (
                      (perigee = ?))
                  IN THE CONTEXT OF "perigee"
              FROM satellites
              LIMIT 5
          ''' , (-10000,)).fetchall()
          assert len(cursor) == 5
          assert all(np.allclose(c[0], 0) for c in cursor)
  
          # No matching target OF row.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      OF (rowid < 0) TO EXISTING ROWS (rowid = 10)
                      IN THE CONTEXT OF "launch_mass"
                  BY satellites
              ''')
  
          # Unknown CONTEXT variable "banana".
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      OF (rowid = 1) TO EXISTING ROWS (rowid = 2)
                      IN THE CONTEXT OF "banana"
                  BY satellites
              ''')
  
          # No matching EXISTING ROW.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      OF (rowid = 10) TO EXISTING ROWS (rowid < 0)
                      IN THE CONTEXT OF "launch_mass"
                  BY satellites
              ''')
  
          # Unknown categorical values 'Mongolia' in HYPOTHETICAL ROWS.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      OF (rowid = 10)
                      TO HYPOTHETICAL ROWS WITH VALUES (
                          (country_of_operator='Mongolia'),
                          (country_of_operator='Bulgaria', perigee=2.0))
                      IN THE CONTEXT OF "launch_mass"
                  BY satellites
              ''')
  
          # Create a new row.
          bdb.sql_execute('''
              INSERT INTO satellites_ucs
              (apogee, launch_mass) VALUES (12.128, 12.128)
          ''')
  
          # TARGET ROW not yet incorporated should return nan.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (apogee = 12.128)
                  TO HYPOTHETICAL ROWS WITH VALUES (
                      (country_of_operator='China', perigee=1.0))
                  IN THE CONTEXT OF "launch_mass"
              BY satellites
          ''')
          result = cursor_value(cursor)
          assert result is None
  
          # EXISTING ROW not yet incorporated should return nan, since there is
          # no hypothetical.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (rowid = 1)
                  TO EXISTING ROWS (apogee = 12.128)
                  IN THE CONTEXT OF "launch_mass"
              BY satellites
          ''')
          result = cursor_value(cursor)
          assert result is None
  
          # Although apogee = 12.128 is EXISTING but not incorporated, there are
          # other EXISTING ROWS with apogee > 0, so we should still get a result.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (rowid = 1)
                  TO EXISTING ROWS (apogee = 12.128 OR apogee > 0)
                  IN THE CONTEXT OF "launch_mass"
              BY satellites
          ''')
          result = cursor_value(cursor)
          assert result is not None
  
          # Although apogee = 12.128 is EXISTING but not incorporated, there are
          # other HYPOTHETICAL ROWS, so we should still get a result.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (rowid = 1)
                  TO EXISTING ROWS (apogee = 12.128 OR apogee > 0)
                  AND HYPOTHETICAL ROWS WITH VALUES (
                      (country_of_operator='China', perigee=1.0),
                      (country_of_operator='Bulgaria'))
                  IN THE CONTEXT OF "launch_mass"
              BY satellites
          ''')
          result = cursor_value(cursor)
          assert result is not None
  
  def test_add_drop_models():
      with cgpm_dummy_satellites_bdb() as bdb:
          bayesdb_register_backend(
              bdb, CGPM_Backend(dict(), multiprocess=0))
          bdb.execute('''
              CREATE POPULATION p FOR satellites_ucs WITH SCHEMA(
                  GUESS STATTYPES OF (*);
              )
          ''')
          bdb.execute('CREATE GENERATOR m FOR p (SUBSAMPLE 10);')
  
          # Retrieve id for testing.
          population_id = bayesdb_get_population(bdb, 'p')
          generator_id = bayesdb_get_generator(bdb, population_id, 'm')
  
          def check_modelno_mapping(lookup):
              pairs = bdb.sql_execute('''
                  SELECT modelno, cgpm_modelno FROM bayesdb_cgpm_modelno
                  WHERE generator_id = ?
              ''', (generator_id,))
              for pair in pairs:
                  assert lookup[pair[0]] == pair[1]
                  del lookup[pair[0]]
              assert len(lookup) == 0
  
          # Initialize some models.
          bdb.execute('INITIALIZE 16 MODELS FOR m')
          # Assert identity mapping initially.
          check_modelno_mapping({i:i for i in xrange(16)})
  
          bdb.execute('ANALYZE m FOR 1 ITERATION (QUIET);')
  
          # Drop some models.
          bdb.execute('DROP MODELS 1, 8-12, 14 FROM m')
          # Assert cgpm models are contiguous while bayesdb models are not, with
          # the mapping preserving the strict order.
          check_modelno_mapping({
              0: 0,
              2: 1,
              3: 2,
              4: 3,
              5: 4,
              6: 5,
              7: 6,
              13: 7,
              15: 8,
          })
  
          # Run some analysis again.
          bdb.execute('ANALYZE m FOR 1 ITERATION (OPTIMIZED; QUIET);')
  
          # Initialize 14 models if not existing.
          bdb.execute('INITIALIZE 14 MODELS IF NOT EXISTS FOR m')
          # Assert cgpm models are 0-14, while bayesdb are 0-15 excluding 14. Note
          # that INITIALIZE 14 MODELS IF NOT EXISTS does not guarantee that 14
          # MODELS in total will exist after the query, rather it will initialize
          # any non-existing modelnos with index 0-13, and any modelnos > 14
          # (modelno 15 in this test case) are untouched.
          check_modelno_mapping({
              0: 0,
              2: 1,
              3: 2,
              4: 3,
              5: 4,
              6: 5,
              7: 6,
              13: 7,
              15: 8,
              # Recreated models.
              1: 9,
              8: 10,
              9: 11,
              10: 12,
              11: 13,
              12: 14,
          })
  
          # Drop some more models, add them back with some more, and confirm
          # arithmetic and ordering remains correct.
          bdb.execute('DROP MODELS 0-1 FROM m')
          check_modelno_mapping({
              2: 0,
              3: 1,
              4: 2,
              5: 3,
              6: 4,
              7: 5,
              13: 6,
              15: 7,
              # Recreated models.
              8: 8,
              9: 9,
              10: 10,
              11: 11,
              12: 12,
          })
          bdb.execute('INITIALIZE 20 MODELS IF NOT EXISTS FOR m;')
          check_modelno_mapping({
              2: 0,
              3: 1,
              4: 2,
              5: 3,
              6: 4,
              7: 5,
              13: 6,
              15: 7,
              # Recreated models.
              8: 8,
              9: 9,
              10: 10,
              11: 11,
              12: 12,
              # Re-recreated models.
              0: 13,
              1: 14,
              # New models.
              14: 15,
              16: 16,
              17: 17,
              18: 18,
              19: 19,
          })
  
          # No such models.
          with pytest.raises(BQLError):
              bdb.execute('DROP MODELS 20-50 FROM m')
          # Drop all models.
          bdb.execute('DROP MODELS FROM m;')
          # No such models.
          with pytest.raises(BQLError):
              bdb.execute('DROP MODEL 0 FROM m')
          # Assert cgpm mapping is cleared.
          cursor = bdb.sql_execute('''
              SELECT COUNT(*) FROM bayesdb_cgpm_modelno
              WHERE generator_id = ?
          ''', (generator_id,))
          assert cursor_value(cursor) == 0
  
  def test_using_modelnos():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  SET STATTYPE OF apogee              TO NUMERICAL;
                  SET STATTYPE OF class_of_orbit      TO CATEGORICAL;
                  SET STATTYPE OF country_of_operator TO CATEGORICAL;
                  SET STATTYPE OF launch_mass         TO NUMERICAL;
                  SET STATTYPE OF perigee             TO NUMERICAL;
                  SET STATTYPE OF period              TO NUMERICAL
              )
          ''')
          bayesdb_register_backend(bdb, CGPM_Backend(dict(), multiprocess=0))
          bdb.execute('''
              CREATE GENERATOR g0 FOR satellites USING cgpm(
                  SUBSAMPLE 10
              );
          ''')
          bdb.execute('INITIALIZE 2 MODELS FOR g0')
  
          # Crash test simulate.
          bdb.execute('''
              SIMULATE apogee, class_of_orbit
              FROM satellites
              MODELED BY g0
              USING MODEL 0-1
              LIMIT 10
          ''')
          # Crash test infer explicit.
          bdb.execute('''
              INFER EXPLICIT PREDICT period, perigee
              FROM satellites
              MODELED BY g0
              USING MODEL 0
              LIMIT 2
          ''')
          # Crash test dependence probability BY.
          c = bdb.execute('''
              ESTIMATE
                  DEPENDENCE PROBABILITY OF launch_mass WITH period
              BY satellites
              MODELED BY g0
              USING MODEL 0
          ''')
          assert cursor_value(c) in [0, 1]
          # Crash test dependence probability pairwise.
          cursor = bdb.execute('''
              ESTIMATE
                  DEPENDENCE PROBABILITY
              FROM PAIRWISE VARIABLES OF satellites
              MODELED BY g0
              USING MODEL 1
          ''')
          for d in cursor:
              assert d[0] in [0, 1]
          # Crash test mutual information 1row.
          bdb.execute('''
              ESTIMATE
                  MUTUAL INFORMATION WITH (period) USING 1 SAMPLES
              FROM VARIABLES OF satellites
              USING MODEL 0
          ''').fetchall()
          # Test analyze on per-model basis.
          bdb.execute('''
              ANALYZE g0 MODEL 0 FOR 1 ITERATION CHECKPOINT 1 ITERATION
          ''')
          engine = bdb.backends['cgpm']._engine(bdb, 1)
          assert len(engine.states[0].diagnostics['logscore']) == 1
          assert len(engine.states[1].diagnostics['logscore']) == 0
          bdb.execute('''
              ANALYZE g0 MODEL 1 FOR 4 ITERATION CHECKPOINT 1 ITERATION (
                  OPTIMIZED
              );
          ''')
          assert len(engine.states[0].diagnostics['logscore']) == 1
          assert len(engine.states[1].diagnostics['logscore']) == 4
          # Some errors with bad modelnos.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ANALYZE g0 MODEL 0-3 FOR 4 ITERATION
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  SIMULATE apogee FROM satellites USING MODEL 25 LIMIT 10;
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE PROBABILITY OF period FROM satellites
                  USING MODELS 0-8 LIMIT 2;
              ''')
  
files: tests/test_cgpm.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  from __future__ import division
  
  import StringIO
  import contextlib
  import math
  import time
  
  import numpy as np
  import pytest
  
  from cgpm.cgpm import CGpm
  from cgpm.dummy.barebones import BareBonesCGpm
  from cgpm.dummy.piecewise import PieceWise
  from cgpm.dummy.trollnormal import TrollNormal
  from cgpm.factor.factor import FactorAnalysis
  from cgpm.regressions.linreg import LinearRegression
  from cgpm.utils import general as gu
  
  from bayeslite import bayesdb_nullify
  from bayeslite import bayesdb_open
  from bayeslite import bayesdb_read_csv
  from bayeslite import bayesdb_register_backend
  from bayeslite.core import bayesdb_get_generator
  from bayeslite.core import bayesdb_get_population
  from bayeslite.exception import BQLError
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  from bayeslite.util import cursor_value
  
  import test_csv
  
  @contextlib.contextmanager
  def cgpm_smoke_bdb():
      with bayesdb_open(':memory:', builtin_backends=False) as bdb:
          registry = {
              'piecewise': PieceWise,
          }
          bayesdb_register_backend(
              bdb, CGPM_Backend(registry, multiprocess=0))
  
          bdb.sql_execute('CREATE TABLE t (Output, cat, Input)')
          for i in xrange(3):
              for j in xrange(3):
                  for k in xrange(3):
                      output = i + j/(k + 1)
                      cat = -1 if (i + j*k) % 2 else +1
                      input = (i*j - k)**2
                      if i % 2:
                          output = None
                      if j % 2:
                          cat = None
                      if k % 2:
                          input = None
                      bdb.sql_execute('''
                          INSERT INTO t (output, cat, input) VALUES (?, ?, ?)
                      ''', (output, cat, input))
  
          bdb.execute('''
              CREATE POPULATION p FOR t WITH SCHEMA(
                  MODEL output, input AS NUMERICAL;
                  MODEL cat AS CATEGORICAL
              )
          ''')
  
          yield bdb
  
  @contextlib.contextmanager
  def cgpm_dummy_satellites_bdb():
      with bayesdb_open(':memory:', builtin_backends=False) as bdb:
          bdb.sql_execute('''
              CREATE TABLE satellites_ucs (
                  apogee,
                  class_of_orbit,
                  country_of_operator,
                  launch_mass,
                  perigee,
                  period
          )''')
          for l, f in [
              ('geo', lambda x, y: x + y**2),
              ('leo', lambda x, y: math.sin(x + y)),
              (None, lambda x, y: x + y**2),
              (None, lambda x, y: math.sin(x + y)),
          ]:
              for x in xrange(5):
                  for y in xrange(5):
                      countries = ['US', 'Russia', 'China', 'Bulgaria']
                      country = countries[bdb._np_prng.randint(0, len(countries))]
                      mass = bdb._np_prng.normal(1000, 50)
                      bdb.sql_execute('''
                          INSERT INTO satellites_ucs
                              (country_of_operator, launch_mass, class_of_orbit,
                                  apogee, perigee, period)
                              VALUES (?,?,?,?,?,?)
                      ''', (country, mass, l, x, y, f(x, y)))
          yield bdb
  
  def test_cgpm_no_empty_categories():
      with cgpm_smoke_bdb() as bdb:
          bdb.sql_execute('CREATE TABLE f (a, b, c);')
          rows = [['', '\'\'', 'nan'], [1.1, 3, ''], ['""""', 1, 1]]
          for row in rows:
              bdb.sql_execute('INSERT INTO f (a, b, c) VALUES (?,?,?)', row)
          bayesdb_nullify(bdb, 'f', "''")
          bayesdb_nullify(bdb, 'f', '""""')
          bayesdb_nullify(bdb, 'f', '')
          bdb.execute('''
              CREATE POPULATION q FOR f WITH SCHEMA (
                  MODEL a, b, c AS NOMINAL
              );
          ''')
          bdb.execute('CREATE GENERATOR h IF NOT EXISTS FOR q USING cgpm;')
          bdb.execute('INITIALIZE 1 MODEL FOR h')
          category_rows = bdb.sql_execute('''
              SELECT colno, value FROM bayesdb_cgpm_category;
          ''')
          # Assert that none of the categories are empty strings or NULL.
          expected = {
              0 : ['1.1'],       # categories for a
              1 : ['1', '3'],    # categories for b
              2 : ['nan', '1'],  # categories for c
          }
          seen = {
              0: [],
              1: [],
              2: [],
          }
          for row in category_rows:
              colno, value = row
              seen[colno].append(value)
          assert all(set(expected[c])==set(seen[c]) for c in expected)
  
  def cgpm_smoke_tests(bdb, gen, vars):
      modeledby = 'MODELED BY %s' % (gen,) if gen else ''
      for var in vars:
          bdb.execute('''
              ESTIMATE PROBABILITY DENSITY OF %s = 1 WITHIN p %s
          ''' % (var, modeledby)).fetchall()
          bdb.execute('''
              SIMULATE %s FROM p %s LIMIT 1
          ''' % (var, modeledby)).fetchall()
          bdb.execute('''
              INFER %s FROM p %s LIMIT 1
          ''' % (var, modeledby)).fetchall()
          nvars = len(bdb.execute('''
              ESTIMATE * FROM VARIABLES OF p %(modeledby)s
                  ORDER BY PROBABILITY OF
                      (MUTUAL INFORMATION WITH %(var)s USING 1 SAMPLES > 0.1)
          ''' % {'var': var, 'modeledby': modeledby}).fetchall())
          if 0 < nvars:
              c = bdb.execute('''
                  SIMULATE p.(ESTIMATE * FROM VARIABLES OF p %(modeledby)s
                                  ORDER BY PROBABILITY OF
                                      (MUTUAL INFORMATION WITH %(var)s
                                          USING 1 SAMPLES > 0.1))
                      FROM p
                      %(modeledby)s
                      LIMIT 1
              ''' % {'var': var, 'modeledby': modeledby}).fetchall()
              assert len(c) == 1
              assert len(c[0]) == nvars
  
  def test_cgpm_smoke():
      with cgpm_smoke_bdb() as bdb:
  
          # Default model.
          bdb.execute('CREATE GENERATOR g_default FOR p USING cgpm')
          bdb.execute('INITIALIZE 1 MODEL FOR g_default')
          bdb.execute('ANALYZE g_default FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_default', ['output', 'cat', 'input'])
  
          # Custom model for output and cat.
          bdb.execute('''
              CREATE GENERATOR g_manifest FOR p USING cgpm (
                  OVERRIDE MODEL FOR Output, Cat
                  GIVEN Input
                  USING piecewise
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_manifest')
          bdb.execute('ANALYZE g_manifest FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_manifest', ['output', 'cat', 'input'])
  
          # Custom model for latent output, manifest output.
          bdb.execute('''
              CREATE GENERATOR g_latout FOR p USING cgpm (
                  LATENT output_ NUMERICAL;
                  OVERRIDE MODEL FOR output_, cat GIVEN input USING piecewise;
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_latout')
          bdb.execute('ANALYZE g_latout FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_latout',
              ['output', 'output_', 'cat', 'input'])
  
          # Custom model for manifest out, latent cat.
          bdb.execute('''
              CREATE GENERATOR g_latcat FOR p USING cgpm (
                  LATENT cat_ CATEGORICAL;
                  OVERRIDE MODEL FOR output, cat_ GIVEN input USING piecewise
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_latcat')
          bdb.execute('ANALYZE g_latcat FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_latcat', ['output', 'cat', 'cat_', 'input'])
  
          # Custom chained model.
          bdb.execute('''
              CREATE GENERATOR g_chain FOR p USING cgpm (
                  LATENT midput NUMERICAL;
                  LATENT excat NUMERICAL;
                  OVERRIDE MODEL FOR midput, cat GIVEN input USING piecewise;
                  OVERRIDE MODEL FOR output, excat GIVEN midput USING piecewise
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_chain')
          bdb.execute('ANALYZE g_chain FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_chain',
              ['output', 'excat', 'midput', 'cat', 'input'])
  
          # Override the crosscat category model.
          bdb.execute('''
              CREATE GENERATOR g_category_model FOR p USING cgpm (
                  SET CATEGORY MODEL FOR output TO NORMAL;
                  OVERRIDE MODEL FOR input, cat GIVEN output USING piecewise;
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g_category_model')
          bdb.execute('ANALYZE g_category_model FOR 1 ITERATION')
          cgpm_smoke_tests(bdb, 'g_category_model',
              ['output', 'cat', 'input'])
  
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_typo FOR p USING cgpm (
                      SET CATEGORY MODEL FOR uot TO NORMAL
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_typo_manifest FOR p USING cgpm (
                      OVERRIDE MODEL FOR output, cat GIVEN ni USING piecewise
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_typo_output FOR p USING cgpm (
                      OVERRIDE MODEL FOR output, dog GIVEN input USING piecewise;
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_dup_manifest FOR p USING cgpm (
                      SET CATEGORY MODEL FOR input TO NORMAL;
                      SET CATEGORY MODEL FOR input TO LOGNORMAL
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_dup_latent FOR p USING cgpm (
                      LATENT output_error NUMERICAL;
                      LATENT output_error CATEGORICAL;
  
                      OVERRIDE MODEL FOR output_error, cat
                      GIVEN input USING piecewise;
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_latent_exists FOR p USING cgpm (
                      LATENT output_ NUMERICAL;
                      OVERRIDE MODEL FOR output_, cat GIVEN input USING piecewise;
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_error_latent_manifest FOR p USING cgpm (
                      LATENT output NUMERICAL;
                      OVERRIDE MODEL FOR output, cat GIVEN input USING piecewise;
                  )
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE GENERATOR g_category_override_dupe FOR p USING cgpm (
                      SET CATEGORY MODEL FOR output TO LOGNORMAL;
                      OVERRIDE MODEL FOR output, cat GIVEN input USING piecewise;
                  )
              ''')
  
          cgpm_smoke_tests(bdb, None, ['output', 'cat', 'input'])
  
          # XXX Check each operation independently: simulate, logpdf, impute.
          for var in ['output_', 'cat_', 'midput', 'excat']:
              with pytest.raises(BQLError):
                  cgpm_smoke_tests(bdb, None, [var])
  
  def test_cgpm_analysis_iteration_timed__ci_slow():
      # Test that the minimum of iterations and wall clock are used for ANALYZE.
      # The point of these tests is not for fine-grained timing control (those
      # tests exists in the python interface, there is additional bayesdb bql
      # which makes the time less predictable), but ensuring that clealry
      # extreme amounts of analysis are quickly short-circuited.
      with cgpm_smoke_bdb() as bdb:
  
          bdb.execute('CREATE GENERATOR g2 FOR p USING cgpm')
          bdb.execute('INITIALIZE 2 MODELS FOR g2')
  
          start0 = time.time()
          bdb.execute('''
              ANALYZE g2 FOR 10000 ITERATION OR 5 SECONDS
                  CHECKPOINT 1 ITERATION (QUIET);
          ''')
          assert 5 < time.time() - start0 < 15
  
          start1 = time.time()
          bdb.execute('''
              ANALYZE g2 FOR 10000 ITERATION OR 5 SECONDS (
                  OPTIMIZED; QUIET
              )
          ''')
          assert 5 < time.time() - start1 < 15
  
          start2 = time.time()
          bdb.execute('''
              ANALYZE g2 FOR 1 ITERATION OR 100 MINUTES
          ''')
          assert 0 < time.time() - start2 < 15
  
          start3 = time.time()
          bdb.execute('''
              ANALYZE g2 FOR 10 ITERATION OR 100 SECONDS
              CHECKPOINT 1 ITERATION (OPTIMIZED)
          ''')
          assert 0 < time.time() - start3 < 15
  
  
  # Use dummy, quick version of Kepler's laws.  Allow an extra
  # distribution argument to make sure it gets passed through.
  class Kepler(TrollNormal):
      def __init__(self, outputs, inputs, quagga=None, *args, **kwargs):
          assert quagga == 'eland'
          return super(Kepler, self).__init__(outputs, inputs, *args, **kwargs)
  
  def test_cgpm_kepler():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  MODEL apogee AS NUMERICAL;
                  MODEL class_of_orbit AS CATEGORICAL;
                  MODEL country_of_operator AS CATEGORICAL;
                  MODEL launch_mass AS NUMERICAL;
                  MODEL perigee AS NUMERICAL;
                  MODEL period AS NUMERICAL
              )
          ''')
          bdb.execute('''
              ESTIMATE CORRELATION from PAIRWISE VARIABLES OF satellites
          ''').fetchall()
          registry = {
              'kepler': Kepler,
              'linreg': LinearRegression,
          }
          bayesdb_register_backend(
              bdb, CGPM_Backend(registry, multiprocess=0))
          bdb.execute('''
              CREATE GENERATOR g0 FOR satellites USING cgpm (
                  OVERRIDE GENERATIVE MODEL FOR period
                  GIVEN apogee, perigee
                  USING linreg
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR g0')
          c = bdb.execute('SELECT COUNT(*) FROM bayesdb_cgpm_individual')
          n = c.fetchvalue()
          # Another generator: exponential launch mass instead of normal.
          bdb.execute('''
              CREATE GENERATOR g1 FOR satellites USING cgpm (
                  SET CATEGORY MODEL FOR launch_mass TO EXPONENTIAL;
                  OVERRIDE MODEL FOR period GIVEN apogee, perigee
                      USING kepler(quagga = eland);
                  SUBSAMPLE 20
              )
          ''')
          c_ = bdb.execute('SELECT COUNT(*) FROM bayesdb_cgpm_individual')
          n_ = c_.fetchvalue()
          assert n_ - n == 20
          bdb.execute('INITIALIZE 1 MODEL IF NOT EXISTS FOR g1')
          bdb.execute('ANALYZE g0 FOR 1 ITERATION')
          bdb.execute('ANALYZE g0 FOR 1 ITERATION (VARIABLES period)')
          bdb.execute('ANALYZE g1 FOR 1 ITERATION')
          bdb.execute('ANALYZE g1 FOR 1 ITERATION (VARIABLES period)')
          # OPTIMIZED is ignored because period is a foreign variable.
          bdb.execute('''
              ANALYZE g1 FOR 1 ITERATION (OPTIMIZED; VARIABLES period)
          ''')
          # This should fail since we have a SET CATEGORY MODEL which is not
          # compatible with lovecat. The ValueError is from cgpm not bayeslite.
          with pytest.raises(ValueError):
              bdb.execute('''
                  ANALYZE g1 FOR 1 ITERATION
                      (OPTIMIZED; VARIABLES launch_mass)
              ''')
          # Cannot use timed analysis with mixed variables.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ANALYZE g1 FOR 5 SECONDS (VARIABLES period, apogee)
              ''')
          # Cannot use timed analysis with mixed variables (period by SKIP).
          with pytest.raises(BQLError):
              bdb.execute('''
                  ANALYZE g1 FOR 5 SECONDS (SKIP apogee)
              ''')
          # OK to use iteration analysis with mixed values.
          bdb.execute('''
                  ANALYZE g1 FOR 1 ITERATION (VARIABLES period, apogee)
              ''')
          bdb.execute('''
              ESTIMATE DEPENDENCE PROBABILITY
                  FROM PAIRWISE VARIABLES OF satellites
          ''').fetchall()
          bdb.execute('''
              ESTIMATE PREDICTIVE PROBABILITY OF period FROM satellites
          ''').fetchall()
          bdb.execute('''
              ESTIMATE PROBABILITY DENSITY OF period = 42
                      GIVEN (apogee = 8 AND perigee = 7)
                  BY satellites
          ''').fetchall()
          bdb.execute('''
              SIMULATE apogee, perigee, period FROM satellites LIMIT 100
          ''').fetchall()
          bdb.execute('''
              INFER EXPLICIT
                  PREDICT apogee
                      CONFIDENCE apogee_confidence
                      USING 5 SAMPLES
              FROM satellites LIMIT 2
          ''').fetchall()
          results = bdb.execute('''
              INFER EXPLICIT
                  PREDICT class_of_orbit
                      CONFIDENCE class_of_orbit_confidence
              FROM satellites LIMIT 2
          ''').fetchall()
          assert len(results[0]) == 2
          assert isinstance(results[0][0], unicode)
          assert isinstance(results[0][1], float)
          # No CONFIDENCE specified.
          results = bdb.execute('''
              INFER EXPLICIT PREDICT class_of_orbit USING 2 SAMPLES
              FROM satellites LIMIT 2
          ''').fetchall()
          assert len(results[0]) == 1
          assert isinstance(results[0][0], unicode)
          bdb.execute('DROP MODELS FROM g0')
          bdb.execute('DROP GENERATOR g0')
          bdb.execute('DROP GENERATOR g1')
  
  def test_unknown_stattype():
      from cgpm.regressions.linreg import LinearRegression
      with cgpm_dummy_satellites_bdb() as bdb:
          # Add a column called relaunches, sum of apogee and perigee.
          bdb.sql_execute('ALTER TABLE satellites_ucs ADD COLUMN relaunches')
          n_rows = bdb.sql_execute('''
              SELECT COUNT(*) FROM satellites_ucs
          ''').next()[0]
          for rowid in xrange(n_rows):
              bdb.sql_execute('''
                  UPDATE satellites_ucs
                      SET relaunches = (SELECT apogee + perigee)
                      WHERE _rowid_ = ?
              ''', (rowid+1,))
          # Nobody will ever create a QUAGGA statistical type!
          with pytest.raises(BQLError):
              # No such statistical type at the moment.
              bdb.execute('''
                  CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                      MODEL apogee, perigee, launch_mass, period
                      AS NUMERICAL;
  
                      MODEL class_of_orbit, country_of_operator
                      AS NOMINAL;
  
                      MODEL relaunches
                      AS QUAGGA
                  )
              ''')
          # Invent the statistical type.
          bdb.sql_execute('INSERT INTO bayesdb_stattype VALUES (?)', ('quagga',))
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  MODEL apogee, perigee, launch_mass, period
                  AS NUMERICAL;
  
                  MODEL class_of_orbit, country_of_operator
                  AS NOMINAL;
  
                  MODEL relaunches
                  AS QUAGGA
              )
          ''')
          registry = {
              'kepler': Kepler,
              'linreg': LinearRegression,
          }
          bayesdb_register_backend(bdb, CGPM_Backend(registry))
          with pytest.raises(BQLError):
              # Can't model QUAGGA by default.
              bdb.execute('CREATE GENERATOR g0 FOR satellites USING cgpm')
          with pytest.raises(BQLError):
              # Can't model QUAGGA as input.
              bdb.execute('''
                  CREATE GENERATOR g0 FOR satellites USING cgpm (
                      OVERRIDE MODEL FOR relaunches GIVEN apogee USING linreg;
                      OVERRIDE MODEL FOR period GIVEN relaunches USING linreg
                  )
              ''')
          # Can model QUAGGA with an explicit distribution family.
          bdb.execute('''
              CREATE GENERATOR g0 FOR satellites USING cgpm (
                  SET CATEGORY MODEL FOR relaunches TO POISSON
              )
          ''')
          bdb.execute('''
              CREATE GENERATOR g1 FOR satellites USING cgpm (
                  SET CATEGORY MODEL FOR relaunches TO POISSON;
                  OVERRIDE MODEL FOR period GIVEN relaunches USING linreg
              )
          ''')
  
  def test_bad_analyze_vars():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  MODEL apogee AS NUMERICAL;
                  MODEL class_of_orbit AS CATEGORICAL;
                  MODEL country_of_operator AS CATEGORICAL;
                  MODEL launch_mass AS NUMERICAL;
                  MODEL perigee AS NUMERICAL;
                  MODEL period AS NUMERICAL
              )
          ''')
          registry = {
              'kepler': Kepler,
              'linreg': LinearRegression,
          }
          bayesdb_register_backend(bdb, CGPM_Backend(registry))
          bdb.execute('''
              CREATE GENERATOR satellites_cgpm FOR satellites USING cgpm
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR satellites_cgpm')
          bdb.execute('ANALYZE satellites_cgpm FOR 1 ITERATION ()')
          bdb.execute('ANALYZE satellites_cgpm FOR 1 ITERATION')
          with pytest.raises(BQLError):
              # Unknown variable `perige'.
              bdb.execute('''
                  ANALYZE satellites_cgpm FOR 1 ITERATION (
                      VARIABLES period, perige
                  )
              ''')
          with pytest.raises(BQLError):
              # Unknown variable `perige'.
              bdb.execute('''
                  ANALYZE satellites_cgpm FOR 1 ITERATION (
                      SKIP period, perige
                  )
              ''')
  
  def test_output_stattypes():
      with cgpm_dummy_satellites_bdb() as bdb:
          # Missing policy for class_of_orbit, perigee, period
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                      MODEL apogee, launch_mass AS NUMERICAL;
                      MODEL country_of_operator AS CATEGORICAL
                  )
              ''')
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  IGNORE class_of_orbit, perigee, period;
                  MODEL apogee, launch_mass AS NUMERICAL;
                  MODEL country_of_operator AS CATEGORICAL
              )
          ''')
          registry = {
              'factor_analysis': FactorAnalysis,
          }
          bayesdb_register_backend(bdb, CGPM_Backend(registry))
          # Creating factor analysis with categorical manifest should crash.
          bdb.execute('''
              CREATE GENERATOR satellites_g0 FOR satellites(
                  OVERRIDE MODEL FOR apogee, country_of_operator
                  AND EXPOSE pc_1 NUMERICAL
                  USING factor_analysis(L=1)
              )
          ''')
          with pytest.raises(ValueError):
              bdb.execute('INITIALIZE 1 MODEL FOR satellites_g0')
          with pytest.raises(BQLError):
              # Duplicate pc_2 in LATENT and EXPOSE.
              bdb.execute('''
                  CREATE GENERATOR satellites_g1 FOR satellites(
                      LATENT pc_2 CATEGORICAL,
                      OVERRIDE GENERATIVE MODEL FOR
                          apogee, launch_mass
                      AND EXPOSE pc_2 CATEGORICAL
                      USING factor_analysis(L=1)
                  )
              ''')
          # Creating factor analysis with categorical latent should crash.
          bdb.execute('''
              CREATE GENERATOR satellites_g1 FOR satellites(
                  OVERRIDE GENERATIVE MODEL FOR
                      apogee, launch_mass
                  AND EXPOSE pc_2 CATEGORICAL
                  USING factor_analysis(L=1)
              )
          ''')
          with pytest.raises(ValueError):
              bdb.execute('INITIALIZE 1 MODEL FOR satellites_g1')
          # Creating factor analysis with all numerical should be ok.
          bdb.execute('''
              CREATE GENERATOR satellites_g2 FOR satellites USING cgpm(
                  LATENT pc_3 NUMERICAL;
  
                  OVERRIDE MODEL FOR apogee, launch_mass, pc_3, pc_4
                  USING factor_analysis(L=2);
  
                  LATENT pc_4 NUMERICAL
              )
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR satellites_g2')
          bdb.execute('ANALYZE satellites_g2 FOR 2 ITERATION')
          # Cannot transition baseline and foreign using timed analysis.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ANALYZE satellites_g2 FOR 2 SECONDS (
                      VARIABLES country_of_operator, apogee, launch_mass, pc_3);
              ''')
          bdb.execute('''
              ANALYZE satellites_g2 FOR 1 ITERATION (
                  VARIABLES apogee, launch_mass);
          ''')
          # Dependence probability of manifest with latent.
          cursor = bdb.execute('''
              ESTIMATE DEPENDENCE PROBABILITY OF apogee WITH pc_3
              BY satellites MODELED BY satellites_g2;
          ''').fetchall()
          assert cursor[0][0] == 1.
          # Dependence probability of latent with latent.
          cursor = bdb.execute('''
              ESTIMATE DEPENDENCE PROBABILITY OF pc_3 WITH pc_4
              BY satellites MODELED BY satellites_g2;
          ''').fetchall()
          assert cursor[0][0] == 1.
          # Mutual information of latent with manifest.
          cursor = bdb.execute('''
              ESTIMATE MUTUAL INFORMATION OF apogee WITH pc_4 USING 1 SAMPLES
              BY satellites MODELED BY satellites_g2;
          ''').fetchall()
          # Mutual information of latent with latent.
          cursor = bdb.execute('''
              ESTIMATE MUTUAL INFORMATION OF pc_3 WITH pc_4 USING 1 SAMPLES
              BY satellites MODELED BY satellites_g2;
          ''').fetchall()
  
  def test_initialize_with_all_nulls():
      # This test ensures that trying to initialize a generator with any
      # (manifest) column of all null variables will crash.
      # Initializing an overriden column with all null variables should not
      # be a problem in general, so we test this case as well.
  
      with bayesdb_open(':memory:', builtin_backends=False) as bdb:
          registry = {
              'barebones': BareBonesCGpm,
          }
          bayesdb_register_backend(
              bdb, CGPM_Backend(registry, multiprocess=0))
          # Create table with all missing values for a.
          bdb.sql_execute('''
              CREATE TABLE t (a REAL, b REAL, c REAL);
          ''')
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, None, 3))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, None, 1))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, None, 1))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, -2, 1))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, -5, 1))
          bdb.sql_execute('INSERT INTO t VALUES (?,?,?)', (None, 2, 3))
  
          # Fail when a is numerical and modeled by crosscat.
          bdb.execute('''
              CREATE POPULATION p FOR t WITH SCHEMA(
                  MODEL a, b, c AS NUMERICAL
              )
          ''')
          bdb.execute('''
              CREATE GENERATOR m FOR p;
          ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  INITIALIZE 2 MODELS FOR m;
              ''')
  
          # Fail when a is nominal and modeled by crosscat.
          bdb.execute('''
              CREATE POPULATION p2 FOR t WITH SCHEMA(
                  MODEL a AS NOMINAL;
                  MODEL b, c AS NUMERICAL
              )
          ''')
          bdb.execute('CREATE GENERATOR m2 FOR p2;')
          with pytest.raises(BQLError):
              bdb.execute('INITIALIZE 2 MODELS FOR m2;')
  
          # Succeed when a is ignored.
          bdb.execute('''
              CREATE POPULATION p3 FOR t WITH SCHEMA(
                  IGNORE a;
                  MODEL b, c AS NUMERICAL
              )
          ''')
          bdb.execute('CREATE GENERATOR m3 FOR p3;')
          bdb.execute('INITIALIZE 2 MODELS FOR m3;')
  
  
          # Succeed when a is numerical overriden using a dummy CGPM.
          bdb.execute('''
              CREATE GENERATOR m4 FOR p(
                  OVERRIDE MODEL FOR a GIVEN b USING barebones
              )
          ''')
          bdb.execute('INITIALIZE 2 MODELS FOR m4;')
          bdb.execute('ANALYZE m4 FOR 1 ITERATION')
  
  def test_add_variable():
      with bayesdb_open() as bdb:
          bayesdb_read_csv(
              bdb, 't', StringIO.StringIO(test_csv.csv_data),
              header=True, create=True)
          bdb.execute('''
              CREATE POPULATION p FOR t WITH SCHEMA(
                  age         numerical;
                  gender      nominal;
                  salary      numerical;
                  height      ignore;
                  division    ignore;
                  rank        ignore;
              )
          ''')
          bdb.backends['cgpm'].set_multiprocess(False)
          bdb.execute('CREATE GENERATOR m0 FOR p;')
          bdb.execute('INITIALIZE 1 MODELS FOR m0;')
          bdb.execute('ANALYZE m0 FOR 5 ITERATION')
          # Run some queries on the new variable in the generator or aggregated.
          def run_queries(target, m):
              extra = 'MODELED BY %s' % (m,) if m is not None else ''
              bdb.execute('''
                  ESTIMATE PROBABILITY DENSITY OF %s = 1 BY p %s
              ''' % (target, extra,)).fetchall()
              for other in ['age', 'gender', 'salary']:
                  cursor = bdb.execute('''
                      ESTIMATE DEPENDENCE PROBABILITY OF %s WITH %s
                      BY p %s
                  ''' % (target, other, extra))
                  assert cursor_value(cursor) >= 0
              bdb.execute('''
                  ESTIMATE SIMILARITY IN THE CONTEXT OF %s
                  FROM PAIRWISE p %s;
              ''' % (target, extra,)).fetchall()
          # Fail to run quieres on height, does not exist yet.
          with pytest.raises(BQLError):
              run_queries('height', 'm0')
          # Add the height variable
          bdb.execute('ALTER POPULATION p ADD VARIABLE height numerical;')
          # Run targeted analysis on the newly included height variable.
          bdb.execute('ANALYZE m0 FOR 5 ITERATION')
          bdb.execute('ANALYZE m0 FOR 5 ITERATION (VARIABLES height);')
          # Queries should now be successful.
          run_queries('height', 'm0')
          # Create a new generator, and create a custom category model for
          # the new variable `height`.
          bdb.execute('''
              CREATE GENERATOR m1 FOR p(
                  SET CATEGORY MODEL FOR age TO exponential;
                  SET CATEGORY MODEL FOR height TO lognormal;
              )
          ''')
          bdb.execute('INITIALIZE 2 MODELS FOR m1')
          bdb.execute('ANALYZE m1 FOR 2 ITERATION')
          # Run height queries on m1.
          run_queries('height', 'm1')
          # Run height queries on population, aggregating m0 and m1.
          run_queries('height', None)
          # Add a third variable rank.
          bdb.execute('ALTER POPULATION p ADD VARIABLE rank numerical;')
          # Analyze rank on m0.
          bdb.execute('''
              ANALYZE m0 FOR 2 ITERATION (OPTIMIZED; VARIABLES rank);
          ''')
          # Analyze all except rank on m0.
          bdb.execute('''
              ANALYZE m0 FOR 2 ITERATION (OPTIMIZED; SKIP rank);
          ''')
          # Fail on m1 with OPTIMIZED, since non-standard category models.
          with pytest.raises(ValueError):
              bdb.execute('''
                  ANALYZE m1 FOR 2 ITERATION (OPTIMIZED; VARIABLES rank);
              ''')
          # Succeed analysis on non-optimized analysis.
          bdb.execute('ANALYZE m1 FOR 2 ITERATION')
          # Run queries on the new variable.
          run_queries('rank', 'm0')
          run_queries('rank', 'm1')
          run_queries('rank', None)
  
  def test_predictive_relevance():
      with cgpm_dummy_satellites_bdb() as bdb:
          bayesdb_register_backend(bdb, CGPM_Backend(cgpm_registry=dict()))
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA (
                  MODEL apogee AS NUMERICAL;
                  MODEL class_of_orbit AS CATEGORICAL;
                  MODEL country_of_operator AS CATEGORICAL;
                  MODEL launch_mass AS NUMERICAL;
                  MODEL perigee AS NUMERICAL;
                  MODEL period AS NUMERICAL
              )
          ''')
          bdb.execute('CREATE GENERATOR m FOR satellites;')
          bdb.execute('INITIALIZE 2 MODELS FOR m;')
          bdb.execute('ANALYZE m FOR 25 ITERATION (OPTIMIZED);')
  
          # Check self-similarites, and also provide coverage of bindings.
          rowids = bdb.execute('SELECT OID from satellites_ucs;').fetchall()
          for rowid in rowids[:4]:
              cursor = bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      TO EXISTING ROWS (rowid = ?)
                      IN THE CONTEXT OF "period"
                  FROM satellites
                  WHERE rowid = ?
              ''', (1, 1,))
              assert next(cursor)[0] == 1.
  
          # A full extravaganza query, using FROM (as a 1-row).
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  TO EXISTING ROWS
                      (country_of_operator = 'Russia' AND period < 0)
                  AND HYPOTHETICAL ROWS WITH VALUES (
                      (perigee=1.0, launch_mass=120),
                      (country_of_operator='Bulgaria', perigee=2.0))
                  IN THE CONTEXT OF "country_of_operator"
              FROM satellites
              LIMIT 5
          ''').fetchall()
          assert len(cursor) == 5
          assert all(0 <= c[0] <= 1 for c in cursor)
  
          # A full extravaganza query, using BY (as a constant).
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (rowid = 1)
                  TO EXISTING ROWS
                      (country_of_operator = 'Russia' AND period < 0)
                  AND HYPOTHETICAL ROWS WITH VALUES (
                      (country_of_operator='China', perigee=1.0),
                      (country_of_operator='Bulgaria'))
                  IN THE CONTEXT OF "country_of_operator"
              BY satellites
          ''').fetchall()
          assert len(cursor) == 1
          assert all(0 <= c[0] <= 1 for c in cursor)
  
          # Hypothetical satellite with negative perigee should not be similar,
          # and use a binding to just ensure that they work.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  TO HYPOTHETICAL ROWS WITH VALUES (
                      (perigee = ?))
                  IN THE CONTEXT OF "perigee"
              FROM satellites
              LIMIT 5
          ''' , (-10000,)).fetchall()
          assert len(cursor) == 5
          assert all(np.allclose(c[0], 0) for c in cursor)
  
          # No matching target OF row.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      OF (rowid < 0) TO EXISTING ROWS (rowid = 10)
                      IN THE CONTEXT OF "launch_mass"
                  BY satellites
              ''')
  
          # Unknown CONTEXT variable "banana".
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      OF (rowid = 1) TO EXISTING ROWS (rowid = 2)
                      IN THE CONTEXT OF "banana"
                  BY satellites
              ''')
  
          # No matching EXISTING ROW.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      OF (rowid = 10) TO EXISTING ROWS (rowid < 0)
                      IN THE CONTEXT OF "launch_mass"
                  BY satellites
              ''')
  
          # Unknown categorical values 'Mongolia' in HYPOTHETICAL ROWS.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE RELEVANCE
                      OF (rowid = 10)
                      TO HYPOTHETICAL ROWS WITH VALUES (
                          (country_of_operator='Mongolia'),
                          (country_of_operator='Bulgaria', perigee=2.0))
                      IN THE CONTEXT OF "launch_mass"
                  BY satellites
              ''')
  
          # Create a new row.
          bdb.sql_execute('''
              INSERT INTO satellites_ucs
              (apogee, launch_mass) VALUES (12.128, 12.128)
          ''')
  
          # TARGET ROW not yet incorporated should return nan.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (apogee = 12.128)
                  TO HYPOTHETICAL ROWS WITH VALUES (
                      (country_of_operator='China', perigee=1.0))
                  IN THE CONTEXT OF "launch_mass"
              BY satellites
          ''')
          result = cursor_value(cursor)
          assert result is None
  
          # EXISTING ROW not yet incorporated should return nan, since there is
          # no hypothetical.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (rowid = 1)
                  TO EXISTING ROWS (apogee = 12.128)
                  IN THE CONTEXT OF "launch_mass"
              BY satellites
          ''')
          result = cursor_value(cursor)
          assert result is None
  
          # Although apogee = 12.128 is EXISTING but not incorporated, there are
          # other EXISTING ROWS with apogee > 0, so we should still get a result.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (rowid = 1)
                  TO EXISTING ROWS (apogee = 12.128 OR apogee > 0)
                  IN THE CONTEXT OF "launch_mass"
              BY satellites
          ''')
          result = cursor_value(cursor)
          assert result is not None
  
          # Although apogee = 12.128 is EXISTING but not incorporated, there are
          # other HYPOTHETICAL ROWS, so we should still get a result.
          cursor = bdb.execute('''
              ESTIMATE PREDICTIVE RELEVANCE
                  OF (rowid = 1)
                  TO EXISTING ROWS (apogee = 12.128 OR apogee > 0)
                  AND HYPOTHETICAL ROWS WITH VALUES (
                      (country_of_operator='China', perigee=1.0),
                      (country_of_operator='Bulgaria'))
                  IN THE CONTEXT OF "launch_mass"
              BY satellites
          ''')
          result = cursor_value(cursor)
          assert result is not None
  
  def test_add_drop_models():
      with cgpm_dummy_satellites_bdb() as bdb:
          bayesdb_register_backend(
              bdb, CGPM_Backend(dict(), multiprocess=0))
          bdb.execute('''
              CREATE POPULATION p FOR satellites_ucs WITH SCHEMA(
                  GUESS STATTYPES FOR (*);
              )
          ''')
          bdb.execute('CREATE GENERATOR m FOR p (SUBSAMPLE 10);')
  
          # Retrieve id for testing.
          population_id = bayesdb_get_population(bdb, 'p')
          generator_id = bayesdb_get_generator(bdb, population_id, 'm')
  
          def check_modelno_mapping(lookup):
              pairs = bdb.sql_execute('''
                  SELECT modelno, cgpm_modelno FROM bayesdb_cgpm_modelno
                  WHERE generator_id = ?
              ''', (generator_id,))
              for pair in pairs:
                  assert lookup[pair[0]] == pair[1]
                  del lookup[pair[0]]
              assert len(lookup) == 0
  
          # Initialize some models.
          bdb.execute('INITIALIZE 16 MODELS FOR m')
          # Assert identity mapping initially.
          check_modelno_mapping({i:i for i in xrange(16)})
  
          bdb.execute('ANALYZE m FOR 1 ITERATION (QUIET);')
  
          # Drop some models.
          bdb.execute('DROP MODELS 1, 8-12, 14 FROM m')
          # Assert cgpm models are contiguous while bayesdb models are not, with
          # the mapping preserving the strict order.
          check_modelno_mapping({
              0: 0,
              2: 1,
              3: 2,
              4: 3,
              5: 4,
              6: 5,
              7: 6,
              13: 7,
              15: 8,
          })
  
          # Run some analysis again.
          bdb.execute('ANALYZE m FOR 1 ITERATION (OPTIMIZED; QUIET);')
  
          # Initialize 14 models if not existing.
          bdb.execute('INITIALIZE 14 MODELS IF NOT EXISTS FOR m')
          # Assert cgpm models are 0-14, while bayesdb are 0-15 excluding 14. Note
          # that INITIALIZE 14 MODELS IF NOT EXISTS does not guarantee that 14
          # MODELS in total will exist after the query, rather it will initialize
          # any non-existing modelnos with index 0-13, and any modelnos > 14
          # (modelno 15 in this test case) are untouched.
          check_modelno_mapping({
              0: 0,
              2: 1,
              3: 2,
              4: 3,
              5: 4,
              6: 5,
              7: 6,
              13: 7,
              15: 8,
              # Recreated models.
              1: 9,
              8: 10,
              9: 11,
              10: 12,
              11: 13,
              12: 14,
          })
  
          # Drop some more models, add them back with some more, and confirm
          # arithmetic and ordering remains correct.
          bdb.execute('DROP MODELS 0-1 FROM m')
          check_modelno_mapping({
              2: 0,
              3: 1,
              4: 2,
              5: 3,
              6: 4,
              7: 5,
              13: 6,
              15: 7,
              # Recreated models.
              8: 8,
              9: 9,
              10: 10,
              11: 11,
              12: 12,
          })
          bdb.execute('INITIALIZE 20 MODELS IF NOT EXISTS FOR m;')
          check_modelno_mapping({
              2: 0,
              3: 1,
              4: 2,
              5: 3,
              6: 4,
              7: 5,
              13: 6,
              15: 7,
              # Recreated models.
              8: 8,
              9: 9,
              10: 10,
              11: 11,
              12: 12,
              # Re-recreated models.
              0: 13,
              1: 14,
              # New models.
              14: 15,
              16: 16,
              17: 17,
              18: 18,
              19: 19,
          })
  
          # No such models.
          with pytest.raises(BQLError):
              bdb.execute('DROP MODELS 20-50 FROM m')
          # Drop all models.
          bdb.execute('DROP MODELS FROM m;')
          # No such models.
          with pytest.raises(BQLError):
              bdb.execute('DROP MODEL 0 FROM m')
          # Assert cgpm mapping is cleared.
          cursor = bdb.sql_execute('''
              SELECT COUNT(*) FROM bayesdb_cgpm_modelno
              WHERE generator_id = ?
          ''', (generator_id,))
          assert cursor_value(cursor) == 0
  
  def test_using_modelnos():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  MODEL apogee AS NUMERICAL;
                  MODEL class_of_orbit AS CATEGORICAL;
                  MODEL country_of_operator AS CATEGORICAL;
                  MODEL launch_mass AS NUMERICAL;
                  MODEL perigee AS NUMERICAL;
                  MODEL period AS NUMERICAL
              )
          ''')
          bayesdb_register_backend(bdb, CGPM_Backend(dict(), multiprocess=0))
          bdb.execute('''
              CREATE GENERATOR g0 FOR satellites USING cgpm(
                  SUBSAMPLE 10
              );
          ''')
          bdb.execute('INITIALIZE 2 MODELS FOR g0')
  
          # Crash test simulate.
          bdb.execute('''
              SIMULATE apogee, class_of_orbit
              FROM satellites
              MODELED BY g0
              USING MODEL 0-1
              LIMIT 10
          ''')
          # Crash test infer explicit.
          bdb.execute('''
              INFER EXPLICIT PREDICT period, perigee
              FROM satellites
              MODELED BY g0
              USING MODEL 0
              LIMIT 2
          ''')
          # Crash test dependence probability BY.
          c = bdb.execute('''
              ESTIMATE
                  DEPENDENCE PROBABILITY OF launch_mass WITH period
              BY satellites
              MODELED BY g0
              USING MODEL 0
          ''')
          assert cursor_value(c) in [0, 1]
          # Crash test dependence probability pairwise.
          cursor = bdb.execute('''
              ESTIMATE
                  DEPENDENCE PROBABILITY
              FROM PAIRWISE VARIABLES OF satellites
              MODELED BY g0
              USING MODEL 1
          ''')
          for d in cursor:
              assert d[0] in [0, 1]
          # Crash test mutual information 1row.
          bdb.execute('''
              ESTIMATE
                  MUTUAL INFORMATION WITH (period) USING 1 SAMPLES
              FROM VARIABLES OF satellites
              USING MODEL 0
          ''').fetchall()
          # Test analyze on per-model basis.
          bdb.execute('''
              ANALYZE g0 MODEL 0 FOR 1 ITERATION CHECKPOINT 1 ITERATION
          ''')
          engine = bdb.backends['cgpm']._engine(bdb, 1)
          assert len(engine.states[0].diagnostics['logscore']) == 1
          assert len(engine.states[1].diagnostics['logscore']) == 0
          bdb.execute('''
              ANALYZE g0 MODEL 1 FOR 4 ITERATION CHECKPOINT 1 ITERATION (
                  OPTIMIZED
              );
          ''')
          assert len(engine.states[0].diagnostics['logscore']) == 1
          assert len(engine.states[1].diagnostics['logscore']) == 4
          # Some errors with bad modelnos.
          with pytest.raises(BQLError):
              bdb.execute('''
                  ANALYZE g0 MODEL 0-3 FOR 4 ITERATION
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  SIMULATE apogee FROM satellites USING MODEL 25 LIMIT 10;
              ''')
          with pytest.raises(BQLError):
              bdb.execute('''
                  ESTIMATE PREDICTIVE PROBABILITY OF period FROM satellites
                  USING MODELS 0-8 LIMIT 2;
              ''')
  
files: tests/test_cgpm.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import contextlib
  import pytest
  
  import bayeslite.core
  
  from bayeslite import bayesdb_register_backend
  from bayeslite.exception import BQLError
  from bayeslite.math_util import abserr
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  
  from test_cgpm import cgpm_dummy_satellites_bdb
  
  
  @contextlib.contextmanager
  def cgpm_dummy_satellites_pop_bdb():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              create population satellites for satellites_ucs with schema(
                  apogee numerical;
                  class_of_orbit categorical;
                  country_of_operator categorical;
                  launch_mass numerical;
                  perigee numerical;
                  period numerical
              )
          ''')
          backend = CGPM_Backend(dict(), multiprocess=0)
          bayesdb_register_backend(bdb, backend)
          yield bdb
  
  
  def test_cgpm_alter_basic():
      with cgpm_dummy_satellites_pop_bdb() as bdb:
          bdb.execute('''
              create generator g0 for satellites using cgpm(
                  subsample 10
              );
          ''')
          bdb.execute('initialize 4 models for g0')
  
          population_id = bayeslite.core.bayesdb_get_population(
              bdb, 'satellites')
          generator_id = bayeslite.core.bayesdb_get_generator(
              bdb, population_id, 'g0')
  
          # Make all variables independent in models 0-1.
          bdb.execute('''
              alter generator g0 models (0,1)
                  ensure variables * independent;
          ''')
          dependencies = bdb.execute('''
              estimate dependence probability from pairwise variables of
              satellites modeled by g0 using models 0-1;
          ''').fetchall()
          for _, var0, var1, value in dependencies:
              assert value == int(var0 == var1)
  
          # Make all variables dependent in all models.
          bdb.execute('''
              alter generator g0
                  ensure variables * dependent;
          ''')
          dependencies = bdb.execute('''
              estimate dependence probability from pairwise variables of
              satellites
          ''').fetchall()
          for _, _, _, value in dependencies:
              assert value == 1
  
          # Make all variables independent again.
          bdb.execute('''
              alter generator g0
                  ensure variables * independent;
          ''')
  
          # Move apogee, perigee->view(period), launch_mass->view(class_of_orbit)
          # All other variables should remain independent.
          bdb.execute('''
              alter generator g0
                  ensure variables (apogee, period, perigee) in context of period,
                  ensure variable launch_mass in context of class_of_orbit;
          ''')
          dependencies = bdb.execute('''
              estimate dependence probability from pairwise variables of
              satellites
          ''').fetchall()
          blocks = [
              ['apogee','perigee','period'],
              ['launch_mass', 'class_of_orbit']
          ]
          for _, var0, var1, value in dependencies:
              if var0 in blocks[0] and var1 in blocks[0]:
                  assert value == 1.
              elif var0 in blocks[1] and var1 in blocks[1]:
                  assert value == 1.
              elif var0 == var1:
                  assert value == 1.
              else:
                  assert value == 0.
  
          # Move period to a singleton in model 3.
          bdb.execute('''
              alter generator g0 model (3)
                  ensure variable period in singleton view
          ''')
          dependencies = bdb.execute('''
              estimate dependence probability with period from variables of
              satellites modeled by g0 using models 0,1,2
          ''')
          for _, var0, value in dependencies:
              if var0 in blocks[0]:
                  assert value == 1
              else:
                  assert value == 0
          dependencies = bdb.execute('''
              estimate dependence probability with period from variables of
              satellites modeled by g0 using models 3
          ''')
          for _, var0, value in dependencies:
              assert value == 0
  
          # Change the column crp concentration.
          bdb.execute('''
              alter generator g0
                  set view concentration parameter to 1000
          ''')
          engine = bdb.backends['cgpm']._engine(bdb, generator_id)
          for state in engine.states:
              assert abserr(1./1000, state.alpha()) < 1e-5
  
          # Change row crp concentration in view of period.
          engine = bdb.backends['cgpm']._engine(bdb, generator_id)
          varno = bayeslite.core.bayesdb_variable_number(
              bdb, population_id, generator_id, 'period')
          initial_alphas = [s.view_for(varno).alpha() for s in engine.states]
          bdb.execute('''
              alter generator g0 models (1,3)
                  set row cluster concentration parameter
                      within view of period to 12;
          ''')
          for i, state in enumerate(engine.states):
              view_alpha = state.view_for(varno).alpha()
              if i in [1,3]:
                  assert abserr(1./12, view_alpha) < 1e-5
              else:
                  assert abserr(initial_alphas[i], view_alpha) < 1e-8
  
          # Run 10 steps of analysis.
          bdb.execute('analyze g0 for 4 iteration (optimized)')
  
          # Retrieve rows in the subsample.
          cursor = bdb.execute('''
              SELECT table_rowid FROM  bayesdb_cgpm_individual
              WHERE generator_id = ?
          ''', (generator_id,))
          subsample_rows = [c[0] for c in cursor]
  
          # Move all rows to same cluster in view of country_of_operator.
          bdb.execute('''
              alter generator g0
                  ensure rows * in cluster of row %s
                  within context of country_of_operator
          ''' % (subsample_rows[0],))
  
          similarities = bdb.execute('''
              estimate similarity in the context of country_of_operator
              from pairwise satellites
          ''')
          for row0, row1, value in similarities:
              if row0 in subsample_rows and row1 in subsample_rows:
                  assert value == 1.
              else:
                  assert value is None
  
  def test_cgpm_alter_errors():
      with cgpm_dummy_satellites_pop_bdb() as bdb:
          # Prepare the bdb.
          bdb.execute('''
              create generator g0 for satellites using cgpm(
                  subsample 10
              );
          ''')
          bdb.execute('initialize 4 models for g0')
          # Retrieve rows in the subsample.
          population_id = bayeslite.core.bayesdb_get_population(bdb, 'satellites')
          generator_id = bayeslite.core.bayesdb_get_generator(
              bdb, population_id, 'g0')
          cursor = bdb.execute('''
              SELECT table_rowid FROM  bayesdb_cgpm_individual
              WHERE generator_id = ?
          ''', (generator_id,))
          subsample_rows = [c[0] for c in cursor]
          # Invoke errors.
          with pytest.raises(BQLError):
              # ensure variables accepts * only, not named variables.
              bdb.execute('''
                  alter generator g0 models (0,1)
                      ensure variables (period_minutes, perigee) dependent;
              ''')
          with pytest.raises(BQLError):
              # Non existent target variable apogees.
              bdb.execute('''
                  alter generator g0
                      ensure variables (perigee, apogees) in context of period
              ''')
          with pytest.raises(BQLError):
              # Non existent context variable periods
              bdb.execute('''
                  alter generator g0
                      ensure variables (perigee, apogee) in context of periods
              ''')
          with pytest.raises(BQLError):
              # Non existent context variable periods
              bdb.execute('''
                  alter generator g0
                      ensure variables (perigee, apogee) in context of periods
              ''')
          with pytest.raises(BQLError):
              # Non existent target row 1000.
              bdb.execute('''
                  alter generator g0
                      ensure rows (1, 1000) in cluster of row %d
                          within context of country_of_operator
              ''' % (subsample_rows[0],))
          with pytest.raises(BQLError):
              # Non existent reference row 1000.
              bdb.execute('''
                  alter generator g0
                      ensure rows (%s) in cluster of row 1000
                          within context of country_of_operator
              ''' % (subsample_rows[0],))
          with pytest.raises(BQLError):
              # Non existent context variable country_of_operators.
              bdb.execute('''
                  alter generator g0
                      ensure rows (%s) in cluster of row %s
                          within context of country_of_operators
              ''' % (subsample_rows[0], subsample_rows[1]))
          with pytest.raises(BQLError):
              # Non existent context variable periods.
              bdb.execute('''
                  alter generator g0 models (1,3)
                      set row cluster concentration parameter
                          within view of periods to 12;
              ''')
  
files: tests/test_cgpm_alter.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import contextlib
  import pytest
  
  import bayeslite.core
  
  from bayeslite import bayesdb_register_backend
  from bayeslite.exception import BQLError
  from bayeslite.math_util import abserr
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  
  from test_cgpm import cgpm_dummy_satellites_bdb
  
  
  @contextlib.contextmanager
  def cgpm_dummy_satellites_pop_bdb():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              create population satellites for satellites_ucs with schema(
                  model apogee as numerical;
                  model class_of_orbit as categorical;
                  model country_of_operator as categorical;
                  model launch_mass as numerical;
                  model perigee as numerical;
                  model period as numerical
              )
          ''')
          backend = CGPM_Backend(dict(), multiprocess=0)
          bayesdb_register_backend(bdb, backend)
          yield bdb
  
  
  def test_cgpm_alter_basic():
      with cgpm_dummy_satellites_pop_bdb() as bdb:
          bdb.execute('''
              create generator g0 for satellites using cgpm(
                  subsample 10
              );
          ''')
          bdb.execute('initialize 4 models for g0')
  
          population_id = bayeslite.core.bayesdb_get_population(
              bdb, 'satellites')
          generator_id = bayeslite.core.bayesdb_get_generator(
              bdb, population_id, 'g0')
  
          # Make all variables independent in models 0-1.
          bdb.execute('''
              alter generator g0 models (0,1)
                  ensure variables * independent;
          ''')
          dependencies = bdb.execute('''
              estimate dependence probability from pairwise variables of
              satellites modeled by g0 using models 0-1;
          ''').fetchall()
          for _, var0, var1, value in dependencies:
              assert value == int(var0 == var1)
  
          # Make all variables dependent in all models.
          bdb.execute('''
              alter generator g0
                  ensure variables * dependent;
          ''')
          dependencies = bdb.execute('''
              estimate dependence probability from pairwise variables of
              satellites
          ''').fetchall()
          for _, _, _, value in dependencies:
              assert value == 1
  
          # Make all variables independent again.
          bdb.execute('''
              alter generator g0
                  ensure variables * independent;
          ''')
  
          # Move apogee, perigee->view(period), launch_mass->view(class_of_orbit)
          # All other variables should remain independent.
          bdb.execute('''
              alter generator g0
                  ensure variables (apogee, period, perigee) in context of period,
                  ensure variable launch_mass in context of class_of_orbit;
          ''')
          dependencies = bdb.execute('''
              estimate dependence probability from pairwise variables of
              satellites
          ''').fetchall()
          blocks = [
              ['apogee','perigee','period'],
              ['launch_mass', 'class_of_orbit']
          ]
          for _, var0, var1, value in dependencies:
              if var0 in blocks[0] and var1 in blocks[0]:
                  assert value == 1.
              elif var0 in blocks[1] and var1 in blocks[1]:
                  assert value == 1.
              elif var0 == var1:
                  assert value == 1.
              else:
                  assert value == 0.
  
          # Move period to a singleton in model 3.
          bdb.execute('''
              alter generator g0 model (3)
                  ensure variable period in singleton view
          ''')
          dependencies = bdb.execute('''
              estimate dependence probability with period from variables of
              satellites modeled by g0 using models 0,1,2
          ''')
          for _, var0, value in dependencies:
              if var0 in blocks[0]:
                  assert value == 1
              else:
                  assert value == 0
          dependencies = bdb.execute('''
              estimate dependence probability with period from variables of
              satellites modeled by g0 using models 3
          ''')
          for _, var0, value in dependencies:
              assert value == 0
  
          # Change the column crp concentration.
          bdb.execute('''
              alter generator g0
                  set view concentration parameter to 1000
          ''')
          engine = bdb.backends['cgpm']._engine(bdb, generator_id)
          for state in engine.states:
              assert abserr(1./1000, state.alpha()) < 1e-5
  
          # Change row crp concentration in view of period.
          engine = bdb.backends['cgpm']._engine(bdb, generator_id)
          varno = bayeslite.core.bayesdb_variable_number(
              bdb, population_id, generator_id, 'period')
          initial_alphas = [s.view_for(varno).alpha() for s in engine.states]
          bdb.execute('''
              alter generator g0 models (1,3)
                  set row cluster concentration parameter
                      within view of period to 12;
          ''')
          for i, state in enumerate(engine.states):
              view_alpha = state.view_for(varno).alpha()
              if i in [1,3]:
                  assert abserr(1./12, view_alpha) < 1e-5
              else:
                  assert abserr(initial_alphas[i], view_alpha) < 1e-8
  
          # Run 10 steps of analysis.
          bdb.execute('analyze g0 for 4 iteration (optimized)')
  
          # Retrieve rows in the subsample.
          cursor = bdb.execute('''
              SELECT table_rowid FROM  bayesdb_cgpm_individual
              WHERE generator_id = ?
          ''', (generator_id,))
          subsample_rows = [c[0] for c in cursor]
  
          # Move all rows to same cluster in view of country_of_operator.
          bdb.execute('''
              alter generator g0
                  ensure rows * in cluster of row %s
                  within context of country_of_operator
          ''' % (subsample_rows[0],))
  
          similarities = bdb.execute('''
              estimate similarity in the context of country_of_operator
              from pairwise satellites
          ''')
          for row0, row1, value in similarities:
              if row0 in subsample_rows and row1 in subsample_rows:
                  assert value == 1.
              else:
                  assert value is None
  
  def test_cgpm_alter_errors():
      with cgpm_dummy_satellites_pop_bdb() as bdb:
          # Prepare the bdb.
          bdb.execute('''
              create generator g0 for satellites using cgpm(
                  subsample 10
              );
          ''')
          bdb.execute('initialize 4 models for g0')
          # Retrieve rows in the subsample.
          population_id = bayeslite.core.bayesdb_get_population(bdb, 'satellites')
          generator_id = bayeslite.core.bayesdb_get_generator(
              bdb, population_id, 'g0')
          cursor = bdb.execute('''
              SELECT table_rowid FROM  bayesdb_cgpm_individual
              WHERE generator_id = ?
          ''', (generator_id,))
          subsample_rows = [c[0] for c in cursor]
          # Invoke errors.
          with pytest.raises(BQLError):
              # ensure variables accepts * only, not named variables.
              bdb.execute('''
                  alter generator g0 models (0,1)
                      ensure variables (period_minutes, perigee) dependent;
              ''')
          with pytest.raises(BQLError):
              # Non existent target variable apogees.
              bdb.execute('''
                  alter generator g0
                      ensure variables (perigee, apogees) in context of period
              ''')
          with pytest.raises(BQLError):
              # Non existent context variable periods
              bdb.execute('''
                  alter generator g0
                      ensure variables (perigee, apogee) in context of periods
              ''')
          with pytest.raises(BQLError):
              # Non existent context variable periods
              bdb.execute('''
                  alter generator g0
                      ensure variables (perigee, apogee) in context of periods
              ''')
          with pytest.raises(BQLError):
              # Non existent target row 1000.
              bdb.execute('''
                  alter generator g0
                      ensure rows (1, 1000) in cluster of row %d
                          within context of country_of_operator
              ''' % (subsample_rows[0],))
          with pytest.raises(BQLError):
              # Non existent reference row 1000.
              bdb.execute('''
                  alter generator g0
                      ensure rows (%s) in cluster of row 1000
                          within context of country_of_operator
              ''' % (subsample_rows[0],))
          with pytest.raises(BQLError):
              # Non existent context variable country_of_operators.
              bdb.execute('''
                  alter generator g0
                      ensure rows (%s) in cluster of row %s
                          within context of country_of_operators
              ''' % (subsample_rows[0], subsample_rows[1]))
          with pytest.raises(BQLError):
              # Non existent context variable periods.
              bdb.execute('''
                  alter generator g0 models (1,3)
                      set row cluster concentration parameter
                          within view of periods to 12;
              ''')
  
files: tests/test_cgpm_alter.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import pytest
  
  import bayeslite.core
  
  from bayeslite import bayesdb_register_backend
  from bayeslite.exception import BQLError
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  
  from test_cgpm import cgpm_dummy_satellites_bdb
  
  # This test suite needs expanding to confirm the subproblems are compiled to the
  # right inference kernels in cgpm_backend, but the information is quite nested
  # so it is not trivial to retrieve this information. Considering refactoring the
  # interpreter to make unit testing easier.
  
  
  def test_analysis_subproblems_basic():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  SET STATTYPE OF class_of_orbit TO CATEGORICAL;
                  SET STATTYPE OF country_of_operator TO CATEGORICAL;
                  SET STATTYPE OF launch_mass TO NUMERICAL;
                  SET STATTYPE OF perigee TO NUMERICAL;
                  SET STATTYPE OF period TO NUMERICAL
              )
          ''')
          bayesdb_register_backend(bdb, CGPM_Backend(dict(), multiprocess=0))
          bdb.execute('''
              CREATE GENERATOR g0 FOR satellites USING cgpm(
                  SUBSAMPLE 10
              );
          ''')
          bdb.execute('INITIALIZE 4 MODELS FOR g0')
  
          # Test each subproblem individually except for variable hyperparameters.
          for optimized in ['', 'OPTIMIZED;',]:
              for subproblem in [
                  'variable clustering',
                  'variable clustering concentration',
                  'row clustering',
                  'row clustering concentration',
              ]:
                  bdb.execute('''
                      ANALYZE g0 MODELS 0,1 FOR 4 ITERATION(
                          SUBPROBLEM %s;
                          %s
                      );
                  ''' % (subproblem, optimized))
  
          # Test variable hyperparameters.
          bdb.execute('''
              ANALYZE g0 FOR 1 ITERATION (
                  VARIABLES period, launch_mass;
                  SUBPROBLEM variable hyperparameters;
              )
          ''')
          with pytest.raises(BQLError):
              # OPTIMIZED backend does not support variable hyperparameters.
              bdb.execute('''
                  ANALYZE g0 FOR 1 SECONDS (
                      SUBPROBLEM variable hyperparameters;
                      OPTIMIZED;
                  )
              ''')
  
          # Test rows.
          generator_id = bayeslite.core.bayesdb_get_generator(bdb, None, 'g0')
          cursor = bdb.execute('''
              SELECT table_rowid FROM  bayesdb_cgpm_individual
              WHERE generator_id = ?
          ''', (generator_id,))
          subsample_rows = [c[0] for c in cursor]
          bad_rows = [i for i in xrange(20) if i not in subsample_rows]
          for optimized in ['', 'OPTIMIZED;']:
              bdb.execute('''
                  ANALYZE g0 MODEL 3 FOR 1 ITERATION (
                      VARIABLES class_of_orbit;
                      ROWS %s;
                      SUBPROBLEMS (
                          row clustering,
                          row clustering concentration
                      );
                      %s
              )
              ''' % (','.join(map(str, subsample_rows)), optimized))
              with pytest.raises(BQLError):
                  # Fail on rows not in the population or subsample.
                  bdb.execute('''
                      ANALYZE g0 MODEL 3 FOR 1 ITERATION (
                          VARIABLES class_of_orbit;
                          ROWS %s;
                          SUBPROBLEMS (
                              row clustering,
                              row clustering concentration
                          );
                          %s
                  )
                  ''' % (','.join(map(str, bad_rows)), optimized))
  
files: tests/test_cgpm_analysis.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import pytest
  
  import bayeslite.core
  
  from bayeslite import bayesdb_register_backend
  from bayeslite.exception import BQLError
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  
  from test_cgpm import cgpm_dummy_satellites_bdb
  
  # This test suite needs expanding to confirm the subproblems are compiled to the
  # right inference kernels in cgpm_backend, but the information is quite nested
  # so it is not trivial to retrieve this information. Considering refactoring the
  # interpreter to make unit testing easier.
  
  
  def test_analysis_subproblems_basic():
      with cgpm_dummy_satellites_bdb() as bdb:
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  MODEL apogee AS NUMERICAL;
                  MODEL class_of_orbit AS CATEGORICAL;
                  MODEL country_of_operator AS CATEGORICAL;
                  MODEL launch_mass AS NUMERICAL;
                  MODEL perigee AS NUMERICAL;
                  MODEL period AS NUMERICAL
              )
          ''')
          bayesdb_register_backend(bdb, CGPM_Backend(dict(), multiprocess=0))
          bdb.execute('''
              CREATE GENERATOR g0 FOR satellites USING cgpm(
                  SUBSAMPLE 10
              );
          ''')
          bdb.execute('INITIALIZE 4 MODELS FOR g0')
  
          # Test each subproblem individually except for variable hyperparameters.
          for optimized in ['', 'OPTIMIZED;',]:
              for subproblem in [
                  'variable clustering',
                  'variable clustering concentration',
                  'row clustering',
                  'row clustering concentration',
              ]:
                  bdb.execute('''
                      ANALYZE g0 MODELS 0,1 FOR 4 ITERATION(
                          SUBPROBLEM %s;
                          %s
                      );
                  ''' % (subproblem, optimized))
  
          # Test variable hyperparameters.
          bdb.execute('''
              ANALYZE g0 FOR 1 ITERATION (
                  VARIABLES period, launch_mass;
                  SUBPROBLEM variable hyperparameters;
              )
          ''')
          with pytest.raises(BQLError):
              # OPTIMIZED backend does not support variable hyperparameters.
              bdb.execute('''
                  ANALYZE g0 FOR 1 SECONDS (
                      SUBPROBLEM variable hyperparameters;
                      OPTIMIZED;
                  )
              ''')
  
          # Test rows.
          generator_id = bayeslite.core.bayesdb_get_generator(bdb, None, 'g0')
          cursor = bdb.execute('''
              SELECT table_rowid FROM  bayesdb_cgpm_individual
              WHERE generator_id = ?
          ''', (generator_id,))
          subsample_rows = [c[0] for c in cursor]
          bad_rows = [i for i in xrange(20) if i not in subsample_rows]
          for optimized in ['', 'OPTIMIZED;']:
              bdb.execute('''
                  ANALYZE g0 MODEL 3 FOR 1 ITERATION (
                      VARIABLES class_of_orbit;
                      ROWS %s;
                      SUBPROBLEMS (
                          row clustering,
                          row clustering concentration
                      );
                      %s
              )
              ''' % (','.join(map(str, subsample_rows)), optimized))
              with pytest.raises(BQLError):
                  # Fail on rows not in the population or subsample.
                  bdb.execute('''
                      ANALYZE g0 MODEL 3 FOR 1 ITERATION (
                          VARIABLES class_of_orbit;
                          ROWS %s;
                          SUBPROBLEMS (
                              row clustering,
                              row clustering concentration
                          );
                          %s
                  )
                  ''' % (','.join(map(str, bad_rows)), optimized))
  
files: tests/test_cgpm_analysis.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import contextlib
  import itertools
  import pytest
  
  from bayeslite import bayesdb_open
  from bayeslite.exception import BQLError
  from bayeslite.exception import BQLParseError
  
  
  '''This test provides coverage for ESTIMATE and SIMULATE of conditional mutual
  information queries for univariate and multivariate targets and conditioning
  statements.'''
  
  
  @contextlib.contextmanager
  def smoke_bdb():
      with bayesdb_open(':memory:') as bdb:
          bdb.sql_execute('CREATE TABLE t (a, b, c, d, e)')
  
          for a, b, c, d, e in itertools.product(*([range(2)]*4+[['x','y']])):
              # XXX Insert synthetic data generator here.
              bdb.sql_execute('''
                  INSERT INTO t (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)
              ''', (a, b, c, d, e))
  
          bdb.execute('''
              CREATE POPULATION p FOR t WITH SCHEMA (
                  SET STATTYPES OF a, b, c, d TO NUMERICAL;
                  SET STATTYPES OF e TO NOMINAL
              )
          ''')
  
          bdb.execute('CREATE GENERATOR m1 FOR p;')
          bdb.execute('INITIALIZE 10 MODELS FOR m1;')
  
          bdb.execute('CREATE GENERATOR m2 FOR p;')
          bdb.execute('INITIALIZE 10 MODELS FOR m2;')
          yield bdb
  
  
  def test_estimate_cmi_basic__ci_slow():
      with smoke_bdb() as bdb:
          bql = '''
              ESTIMATE MUTUAL INFORMATION OF a WITH b
              BY p
              MODELED BY m1
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_estimate_cmi_no_condition():
      with smoke_bdb() as bdb:
          # Univariate targets.
          bql = '''
              ESTIMATE MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
              BY p
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
          # Multivariate targets.
          bql = '''
              ESTIMATE MUTUAL INFORMATION OF (a, e) WITH b USING 10 SAMPLES
              BY p
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_estimate_cmi_equality_condition():
      with smoke_bdb() as bdb:
          # Univariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF a WITH b GIVEN
                      (c = 1, e = 'x') USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
          # Multivariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF (a, d) WITH b GIVEN
                      (c = 1, e = 'x') USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_estimate_cmi_marginal_condition__ci_slow():
      with smoke_bdb() as bdb:
          # Univariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF a WITH b GIVEN (d) USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
          # Multivariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF (a, b) WITH (c) GIVEN (d=1, e)
                  USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_estimate_cmi_equality_marginal_condition__ci_slow():
      with smoke_bdb() as bdb:
          # Univariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF a WITH b
                      GIVEN (d, c = 1) USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
          # Multivariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF a WITH (e, b)
                      GIVEN (d, c = 1) USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_simulate_cmi__ci_slow():
      with smoke_bdb() as bdb:
  
          # Univariate targets.
          bdb.execute('''
              CREATE TABLE f1 AS
                  SIMULATE
                      MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                          AS "mutinf(a,b)"
                  FROM MODELS OF p
          ''')
          cursor = bdb.execute('SELECT * FROM f1;')
          assert cursor.description[0][0] == 'mutinf(a,b)'
          results = cursor.fetchall()
          assert len(results) == 20
          assert all(len(r) == 1 for r in results)
  
          # Univariate and multivariate targets.
          bdb.execute('''
              CREATE TABLE IF NOT EXISTS f2 AS
                  SIMULATE
                      MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                          AS "mutinf2(a,b)",
                      MUTUAL INFORMATION OF a WITH (e, b) GIVEN (c=1, d)
                          USING 2 SAMPLES AS "mutinf2(a,bf|c=1,d)"
                  FROM MODELS OF p MODELED BY m1
          ''')
          cursor = bdb.execute('SELECT * FROM f2;')
          assert cursor.description[0][0] == 'mutinf2(a,b)'
          assert cursor.description[1][0] == 'mutinf2(a,bf|c=1,d)'
          # m1 has 10 models, so expect 10 results.
          results = cursor.fetchall()
          assert len(results) == 10
          assert all(len(r) == 2 for r in results)
  
          # f1 already exists
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE TABLE f1 AS
                      SIMULATE
                          MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                              AS "mutinf(a,b)"
                      FROM MODELS OF p MODELED BY m1
              ''')
  
          # f1 already exists, so this query should not be run. If it was
          # run then the cursor would have 10 results instead of 20 from the
          # previous invocation.
          bdb.execute('''
              CREATE TABLE IF NOT EXISTS f1 AS
                  SIMULATE
                      MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                          AS "mutinf(a,b)"
                  FROM MODELS OF p
                  MODELED BY m1
          ''')
          cursor = bdb.execute('SELECT * FROM f1;')
          assert cursor.description[0][0] == 'mutinf(a,b)'
          assert len(cursor.fetchall()) == 20
  
  
  def test_simulate_cmi_missing_table():
      with smoke_bdb() as bdb:
          bdb.execute('''
              SIMULATE MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
              FROM MODELS OF p;
          ''')
          bdb.execute('''
              SIMULATE MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
              FROM MODELS OF p
              MODELED BY m1;
          ''')
          bdb.execute('''
              SIMULATE 1 + MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
              FROM MODELS OF p
              MODELED BY m1;
          ''')
  
  def test_estimate_cmi_bound():
      with smoke_bdb() as bdb:
          bdb.execute('''
              ESTIMATE PROBABILITY OF
                      (MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES > 0.5)
                  WITHIN p
          ''')
  
  def test_simulate_cmi_missing_models_of():
      # SIMULATE of MUTUAL INFORMATION requires FROM MODELS OF, so specifying
      # a non population quantity should raise.
      with smoke_bdb() as bdb:
          with pytest.raises(BQLParseError):
              bdb.execute('''
                  SIMULATE MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                  FROM p LIMIT 10;
              ''')
  
  def test_simulate_models_population_variables():
      # SIMULATE FROM MODELS OF does not accept population variables.
      with smoke_bdb() as bdb:
          with pytest.raises(BQLParseError):
              bdb.execute('''
                  SIMULATE a, b FROM MODELS OF p LIMIT 10;
              ''')
  
files: tests/test_cmi.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import contextlib
  import itertools
  import pytest
  
  from bayeslite import bayesdb_open
  from bayeslite.exception import BQLError
  from bayeslite.exception import BQLParseError
  
  
  '''This test provides coverage for ESTIMATE and SIMULATE of conditional mutual
  information queries for univariate and multivariate targets and conditioning
  statements.'''
  
  
  @contextlib.contextmanager
  def smoke_bdb():
      with bayesdb_open(':memory:') as bdb:
          bdb.sql_execute('CREATE TABLE t (a, b, c, d, e)')
  
          for a, b, c, d, e in itertools.product(*([range(2)]*4+[['x','y']])):
              # XXX Insert synthetic data generator here.
              bdb.sql_execute('''
                  INSERT INTO t (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)
              ''', (a, b, c, d, e))
  
          bdb.execute('''
              CREATE POPULATION p FOR t WITH SCHEMA (
                  MODEL a, b, c, d AS NUMERICAL;
                  MODEL e AS NOMINAL
              )
          ''')
  
          bdb.execute('CREATE GENERATOR m1 FOR p;')
          bdb.execute('INITIALIZE 10 MODELS FOR m1;')
  
          bdb.execute('CREATE GENERATOR m2 FOR p;')
          bdb.execute('INITIALIZE 10 MODELS FOR m2;')
          yield bdb
  
  
  def test_estimate_cmi_basic__ci_slow():
      with smoke_bdb() as bdb:
          bql = '''
              ESTIMATE MUTUAL INFORMATION OF a WITH b
              BY p
              MODELED BY m1
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_estimate_cmi_no_condition():
      with smoke_bdb() as bdb:
          # Univariate targets.
          bql = '''
              ESTIMATE MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
              BY p
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
          # Multivariate targets.
          bql = '''
              ESTIMATE MUTUAL INFORMATION OF (a, e) WITH b USING 10 SAMPLES
              BY p
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_estimate_cmi_equality_condition():
      with smoke_bdb() as bdb:
          # Univariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF a WITH b GIVEN
                      (c = 1, e = 'x') USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
          # Multivariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF (a, d) WITH b GIVEN
                      (c = 1, e = 'x') USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_estimate_cmi_marginal_condition__ci_slow():
      with smoke_bdb() as bdb:
          # Univariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF a WITH b GIVEN (d) USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
          # Multivariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF (a, b) WITH (c) GIVEN (d=1, e)
                  USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_estimate_cmi_equality_marginal_condition__ci_slow():
      with smoke_bdb() as bdb:
          # Univariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF a WITH b
                      GIVEN (d, c = 1) USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
          # Multivariate targets.
          bql = '''
              ESTIMATE
                  MUTUAL INFORMATION OF a WITH (e, b)
                      GIVEN (d, c = 1) USING 10 SAMPLES
              BY p;
          '''
          result = bdb.execute(bql).fetchall()
          assert len(result) == 1
  
  
  def test_simulate_cmi__ci_slow():
      with smoke_bdb() as bdb:
  
          # Univariate targets.
          bdb.execute('''
              CREATE TABLE f1 AS
                  SIMULATE
                      MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                          AS "mutinf(a,b)"
                  FROM MODELS OF p
          ''')
          cursor = bdb.execute('SELECT * FROM f1;')
          assert cursor.description[0][0] == 'mutinf(a,b)'
          results = cursor.fetchall()
          assert len(results) == 20
          assert all(len(r) == 1 for r in results)
  
          # Univariate and multivariate targets.
          bdb.execute('''
              CREATE TABLE IF NOT EXISTS f2 AS
                  SIMULATE
                      MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                          AS "mutinf2(a,b)",
                      MUTUAL INFORMATION OF a WITH (e, b) GIVEN (c=1, d)
                          USING 2 SAMPLES AS "mutinf2(a,bf|c=1,d)"
                  FROM MODELS OF p MODELED BY m1
          ''')
          cursor = bdb.execute('SELECT * FROM f2;')
          assert cursor.description[0][0] == 'mutinf2(a,b)'
          assert cursor.description[1][0] == 'mutinf2(a,bf|c=1,d)'
          # m1 has 10 models, so expect 10 results.
          results = cursor.fetchall()
          assert len(results) == 10
          assert all(len(r) == 2 for r in results)
  
          # f1 already exists
          with pytest.raises(BQLError):
              bdb.execute('''
                  CREATE TABLE f1 AS
                      SIMULATE
                          MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                              AS "mutinf(a,b)"
                      FROM MODELS OF p MODELED BY m1
              ''')
  
          # f1 already exists, so this query should not be run. If it was
          # run then the cursor would have 10 results instead of 20 from the
          # previous invocation.
          bdb.execute('''
              CREATE TABLE IF NOT EXISTS f1 AS
                  SIMULATE
                      MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                          AS "mutinf(a,b)"
                  FROM MODELS OF p
                  MODELED BY m1
          ''')
          cursor = bdb.execute('SELECT * FROM f1;')
          assert cursor.description[0][0] == 'mutinf(a,b)'
          assert len(cursor.fetchall()) == 20
  
  
  def test_simulate_cmi_missing_table():
      with smoke_bdb() as bdb:
          bdb.execute('''
              SIMULATE MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
              FROM MODELS OF p;
          ''')
          bdb.execute('''
              SIMULATE MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
              FROM MODELS OF p
              MODELED BY m1;
          ''')
          bdb.execute('''
              SIMULATE 1 + MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
              FROM MODELS OF p
              MODELED BY m1;
          ''')
  
  def test_estimate_cmi_bound():
      with smoke_bdb() as bdb:
          bdb.execute('''
              ESTIMATE PROBABILITY OF
                      (MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES > 0.5)
                  WITHIN p
          ''')
  
  def test_simulate_cmi_missing_models_of():
      # SIMULATE of MUTUAL INFORMATION requires FROM MODELS OF, so specifying
      # a non population quantity should raise.
      with smoke_bdb() as bdb:
          with pytest.raises(BQLParseError):
              bdb.execute('''
                  SIMULATE MUTUAL INFORMATION OF a WITH b USING 10 SAMPLES
                  FROM p LIMIT 10;
              ''')
  
  def test_simulate_models_population_variables():
      # SIMULATE FROM MODELS OF does not accept population variables.
      with smoke_bdb() as bdb:
          with pytest.raises(BQLParseError):
              bdb.execute('''
                  SIMULATE a, b FROM MODELS OF p LIMIT 10;
              ''')
  
files: tests/test_cmi.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import bayeslite
  
  def test_conditional_probability_simple_inferences():
      data = [
          ['x', 'a'], ['x', 'a'], ['x', 'a'],
          ['y', 'b'], ['y', 'b'], ['y', 'b'],
      ]
      with bayeslite.bayesdb_open() as bdb:
          bdb.sql_execute('create table t(foo, bar)')
          for row in data:
              bdb.sql_execute('insert into t values (?, ?)', row)
          bdb.execute('''
              create population p for t (
                  foo categorical;
                  bar categorical;
              )
          ''')
          bdb.execute('create generator p_cc for p using cgpm;')
          bdb.execute('initialize 10 models for p_cc')
          bdb.execute('analyze p_cc for 100 iterations')
          cursor = bdb.execute('''
              estimate
                  probability density of foo = 'x',
                  probability density of foo = 'x' given (bar = 'a'),
                  probability density of foo = 'x' given (bar = 'b'),
                  probability density of foo = 'y',
                  probability density of foo = 'y' given (bar = 'a'),
                  probability density of foo = 'y' given (bar = 'b')
  
              by p
          ''').fetchall()
          px, pxa, pxb, py, pya, pyb = cursor[0]
          # Inferences on x.
          assert px < pxa
          assert pxb < px
          # Inferences on y.
          assert py < pyb
          assert pya < py
  
files: tests/test_condprob.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import bayeslite
  
  def test_conditional_probability_simple_inferences():
      data = [
          ['x', 'a'], ['x', 'a'], ['x', 'a'],
          ['y', 'b'], ['y', 'b'], ['y', 'b'],
      ]
      with bayeslite.bayesdb_open() as bdb:
          bdb.sql_execute('create table t(foo, bar)')
          for row in data:
              bdb.sql_execute('insert into t values (?, ?)', row)
          bdb.execute('''
              create population p for t (
                  model foo, bar as categorical
              )
          ''')
          bdb.execute('create generator p_cc for p using cgpm;')
          bdb.execute('initialize 10 models for p_cc')
          bdb.execute('analyze p_cc for 100 iterations')
          cursor = bdb.execute('''
              estimate
                  probability density of foo = 'x',
                  probability density of foo = 'x' given (bar = 'a'),
                  probability density of foo = 'x' given (bar = 'b'),
                  probability density of foo = 'y',
                  probability density of foo = 'y' given (bar = 'a'),
                  probability density of foo = 'y' given (bar = 'b')
  
              by p
          ''').fetchall()
          px, pxa, pxb, py, pya, pyb = cursor[0]
          # Inferences on x.
          assert px < pxa
          assert pxb < px
          # Inferences on y.
          assert py < pyb
          assert pya < py
  
files: tests/test_condprob.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import apsw
  import contextlib
  import itertools
  import json
  import pytest
  import tempfile
  
  import bayeslite
  import bayeslite.backend
  import bayeslite.bqlfn as bqlfn
  import bayeslite.core as core
  
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  
  from bayeslite import bql_quote_name
  from bayeslite.sqlite3_util import sqlite3_connection
  from bayeslite.util import cursor_value
  
  def powerset(s):
      s = list(s)
      combinations = (itertools.combinations(s, r) for r in range(len(s) + 1))
      return itertools.chain.from_iterable(combinations)
  
  @contextlib.contextmanager
  def bayesdb(backend=None, **kwargs):
      if backend is None:
          backend = CGPM_Backend(cgpm_registry={}, multiprocess=False)
      bdb = bayeslite.bayesdb_open(builtin_backends=False, **kwargs)
      bayeslite.bayesdb_register_backend(bdb, backend)
      try:
          yield bdb
      finally:
          bdb.close()
  
  def test_bayesdb_instantiation():
      # Use bayesdb_open -- don't instantiate directly.
      with pytest.raises(TypeError):
          bayeslite.BayesDB()  # pylint: disable=no-value-for-parameter
      with pytest.raises(ValueError):
          bayeslite.BayesDB(':memory:', 0xdeadbeef)
  
  def test_prng_determinism():
      bdb = bayeslite.bayesdb_open(builtin_backends=False)
      assert bdb._prng.weakrandom_uniform(1000) == 303
      assert bdb.py_prng.uniform(0, 1) == 0.6156331606142532
      assert bdb.np_prng.uniform(0, 1) == 0.28348770982811367
  
  def test_openclose():
      with bayesdb():
          pass
  
  def test_bad_db_application_id():
      with tempfile.NamedTemporaryFile(prefix='bayeslite') as f:
          with sqlite3_connection(f.name) as db:
              db.cursor().execute('PRAGMA application_id = 42')
              db.cursor().execute('PRAGMA user_version = 3')
          with pytest.raises(IOError):
              with bayesdb(pathname=f.name):
                  pass
  
  def test_bad_db_user_version():
      # XXX Would be nice to avoid a named temporary file here.  Pass
      # the sqlite3 database connection in?
      with tempfile.NamedTemporaryFile(prefix='bayeslite') as f:
          with sqlite3_connection(f.name) as db:
              db.cursor().execute('PRAGMA application_id = 1113146434')
              db.cursor().execute('PRAGMA user_version = 42')
          with pytest.raises(IOError):
              with bayesdb(pathname=f.name):
                  pass
  
  class DotdogBackend(bayeslite.backend.BayesDB_Backend):
      def name(self):
          return 'dotdog'
      def register(self, bdb):
          sql = '''
              INSERT OR IGNORE INTO bayesdb_backend
                  VALUES ('dotdog', 42)
          '''
          bdb.sql_execute(sql)
      def create_generator(self, bdb, generator_id, schema_tokens, **kwargs):
          pass
  
  def test_hackbackend():
      bdb = bayeslite.bayesdb_open(builtin_backends=False)
      bdb.sql_execute('CREATE TABLE t(a INTEGER, b TEXT)')
      bdb.sql_execute("INSERT INTO t (a, b) VALUES (42, 'fnord')")
      bdb.sql_execute('CREATE TABLE u AS SELECT * FROM t')
      bdb.execute('CREATE POPULATION p FOR t(b IGNORE; a NUMERICAL)')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_cc FOR p USING cgpm;')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_dd FOR p USING dotdog;')
      dotdog_backend = DotdogBackend()
      bayeslite.bayesdb_register_backend(bdb, dotdog_backend)
      bayeslite.bayesdb_deregister_backend(bdb, dotdog_backend)
      bayeslite.bayesdb_register_backend(bdb, dotdog_backend)
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_cc FOR p USING cgpm;')
      bdb.execute('CREATE GENERATOR p_dd FOR p USING dotdog(a NUMERICAL)')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_dd FOR p USING dotdog(a NUMERICAL)')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_cc FOR p USING cgpm;')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_dd FOR p USING dotdog(a NUMERICAL)')
      # XXX Rest of test originally exercised default backend, but
      # syntax doesn't support that now.  Not clear that's wrong either.
      bdb.execute('CREATE GENERATOR q_dd FOR p USING dotdog(a NUMERICAL)')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR q_dd FOR p USING dotdog(a NUMERICAL)')
  
  @contextlib.contextmanager
  def bayesdb_population(mkbdb, tab, pop, gen, table_schema, data, columns,
          backend_name='cgpm'):
      with mkbdb as bdb:
          table_schema(bdb)
          data(bdb)
          qt = bql_quote_name(tab)
          qp = bql_quote_name(pop)
          qg = bql_quote_name(gen)
          qmm = bql_quote_name(backend_name)
          bdb.execute('CREATE POPULATION %s FOR %s(%s)' %
              (qp, qt, ';'.join(columns)))
          bdb.execute('CREATE GENERATOR %s FOR %s USING %s;' %
              (qg, qp, qmm,))
          population_id = core.bayesdb_get_population(bdb, pop)
          generator_id = core.bayesdb_get_generator(bdb, population_id, gen)
          yield bdb, population_id, generator_id
  
  @contextlib.contextmanager
  def analyzed_bayesdb_population(mkbdb, nmodels, nsteps, max_seconds=None):
      with mkbdb as (bdb, population_id, generator_id):
          generator = core.bayesdb_generator_name(bdb, generator_id)
          qg = bql_quote_name(generator)
          bql = 'INITIALIZE %d MODELS FOR %s' % (nmodels, qg)
          bdb.execute(bql)
          # XXX Syntax currently doesn't support both nsteps and
          # max_seconds.
          duration = None
          if max_seconds:
              duration = '%d SECONDS' % (max_seconds,)
          else:
              duration = '%d ITERATIONS' % (nsteps,)
          bql = 'ANALYZE %s FOR %s' % (qg, duration)
          bdb.execute(bql)
          yield bdb, population_id, generator_id
  
  def bayesdb_maxrowid(bdb, population_id):
      table_name = core.bayesdb_population_table(bdb, population_id)
      qt = bql_quote_name(table_name)
      sql = 'SELECT MAX(_rowid_) FROM %s' % (qt,)
      return cursor_value(bdb.sql_execute(sql))
  
  def test_casefold_colname():
      def t(tname, pname, gname, sql, *args, **kwargs):
          def schema(bdb):
              bdb.sql_execute(sql)
          def data(_bdb):
              pass
          return bayesdb_population(bayesdb(), tname, pname, gname, schema, data,
              *args, **kwargs)
      with pytest.raises(apsw.SQLError):
          with t('t', 'p', 'p_cc', 'create table t(x, X)', []):
              pass
      with pytest.raises(bayeslite.BQLError):
          columns = ['x CATEGORICAL', 'x CATEGORICAL']
          with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
              pass
      with pytest.raises(bayeslite.BQLError):
          columns = ['x CATEGORICAL', 'X CATEGORICAL']
          with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
              pass
      with pytest.raises(bayeslite.BQLError):
          columns = ['x CATEGORICAL', 'X NUMERICAL']
          with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
              pass
      columns = ['x CATEGORICAL', 'y CATEGORICAL']
      with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
          pass
      columns = ['X CATEGORICAL', 'y CATEGORICAL']
      with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
          pass
      columns = ['x CATEGORICAL', 'Y NUMERICAL']
      with t('t', 'p', 'p_cc', 'CREATE TABLE T(X, Y)', columns):
          pass
  
  def t0_schema(bdb):
      bdb.sql_execute('create table t0 (id integer primary key, n integer)')
  def t0_data(bdb):
      for row in [(0, 0), (1, 1), (2, 42), (3, 87)]:
          bdb.sql_execute('insert into t0 (id, n) values (?, ?)', row)
  
  def t0():
      return bayesdb_population(
          bayesdb(), 't0', 'p0', 'p0_cc', t0_schema, t0_data,
          columns=['id IGNORE', 'n NUMERICAL'])
  
  def test_t0_badname():
      with pytest.raises(bayeslite.BQLError):
          with bayesdb_population(
                  bayesdb(), 't0', 'p0', 't0_cc', t0_schema, t0_data,
                  columns=['n CATEGORICAL', 'm CATEGORICAL']):
              pass
  
  def t1_schema(bdb):
      bdb.sql_execute('''
          create table t1 (
              id integer primary key,
              label text,
              age double,
              weight double
          )
      ''')
  def t1_data(bdb):
      for row in t1_rows:
          bdb.sql_execute('insert into t1 (label,age,weight) values (?,?,?)',
              row)
  
  t1_rows = [
      ('foo', 12, 24),
      ('bar', 14, 28),
      (None, 10, 20),
      ('baz', None, 32),
      ('quux', 4, None),
      ('zot', 8, 16),
      ('mumble', 8, 16),
      ('frotz', 8, 16),
      ('gargle', 8, 16),
      ('mumph', 8, 16),
      ('hunf', 11, 22),
      ('blort', 16, 32),
      (None, 16, 32),
      (None, 17, 34),
      (None, 18, 36),
      (None, 19, 38),
      (None, 20, 40),
      (None, 21, 42),
      (None, 22, 44),
      (None, 23, 46),
      (None, 24, 48),
      (None, 25, 50),
      (None, 26, 52),
      (None, 27, 54),
      (None, 28, 56),
      (None, 29, 58),
      (None, 30, 60),
      (None, 31, 62),
  ]
  
  def t1(*args, **kwargs):
      return bayesdb_population(
          bayesdb(*args, **kwargs), 't1', 'p1', 'p1_cc',
          t1_schema, t1_data,
          columns=['id IGNORE', 'label CATEGORICAL',
              'age NUMERICAL', 'weight NUMERICAL',])
  
  def t1_sub():
      return bayesdb_population(bayesdb(), 't1', 'p1', 'p1_sub_cc',
          t1_schema, t1_data,
          columns=['id IGNORE', 'age IGNORE', 'label CATEGORICAL',
              'weight NUMERICAL',])
  
  def t1_subcat():
      return bayesdb_population(bayesdb(), 't1', 'p1', 'p1_subcat_cc',
          t1_schema, t1_data,
          columns=['id IGNORE', 'age IGNORE','label CATEGORICAL',
              'weight CATEGORICAL'])
  
  def t1_mp():
      backend = CGPM_Backend(cgpm_registry={}, multiprocess=True)
      return bayesdb_population(bayesdb(backend=backend),
          't1', 'p1', 'p1_cc', t1_schema, t1_data,
           columns=['id IGNORE','label CATEGORICAL', 'age NUMERICAL',
              'weight NUMERICAL'])
  
  def t2_schema(bdb):
      bdb.sql_execute('''create table t2 (id, label, age, weight)''')
  
  def t2_data(bdb):
      bdb.sql_execute('''
          insert into t2 (label,age,weight) values
              ('1', '2', ?),
              ('2', '3', '1.2'),
              ('3', '48', '3e10'),
              ('4', '3', ?)
      ''', (2/3., -0.,))
  
  def t2():
      return bayesdb_population(
          bayesdb(), 't2', 'p2', 'p2_cc', t2_schema, t2_data,
          columns=['id IGNORE','label CATEGORICAL', 'age CATEGORICAL',
              'weight CATEGORICAL'])
  
  def test_t1_nokey():
      with bayesdb_population(
              bayesdb(), 't1', 'p1', 'p1_cc', t1_schema, t1_data,
              columns=['id IGNORE','label IGNORE', 'age NUMERICAL',
                  'weight NUMERICAL']):
          pass
  
  def test_t1_nocase():
      with bayesdb_population(bayesdb(), 't1', 'p1', 'p1_cc', t1_schema, t1_data,
              columns=[
                  'id IGNORE',
                  'label CATEGORICAL',
                  'age NUMERICAL',
                  'weight NUMERICAL',
              ]) \
              as (bdb, population_id, generator_id):
          bdb.execute('select id from t1').fetchall()
          bdb.execute('select ID from T1').fetchall()
          bdb.execute('select iD from T1').fetchall()
          bdb.execute('select Id from T1').fetchall()
          # bdb.execute('select id from p1_cc').fetchall()
          # bdb.execute('select ID from P1_cC').fetchall()
          # bdb.execute('select iD from P1_Cc').fetchall()
          # bdb.execute('select Id from P1_CC').fetchall()
  
  examples = {
      't0': t0,
      't1': t1,
  #    't1_mp': t1_mp,
      't1_sub': t1_sub,
      't1_subcat': t1_subcat,
      't2': t2,
  }
  
  @pytest.mark.parametrize('exname', examples.keys())
  def test_example(exname):
      with examples[exname]():
          pass
  
  @pytest.mark.parametrize('exname', examples.keys())
  def test_example_analysis0(exname):
      with analyzed_bayesdb_population(examples[exname](), 1, 0):
          pass
  
  @pytest.mark.parametrize('exname', examples.keys())
  def test_example_analysis1(exname):
      with analyzed_bayesdb_population(examples[exname](), 1, 1):
          pass
  
  # The multiprocessing engine has a large overhead, too much to try
  # every normal test with it, so we'll just run this one test to make
  # sure it doesn't crash and burn with ten models.
  def test_t1_mp_analysis():
      with analyzed_bayesdb_population(t1_mp(), 10, 2):
          pass
  
  def test_t1_mp_analysis_time_deadline():
      with analyzed_bayesdb_population(t1_mp(), 10, None, max_seconds=1):
          pass
  
  def test_t1_mp_analysis_iter_deadline__ci_slow():
      with analyzed_bayesdb_population(t1_mp(), 10, 1, max_seconds=10):
          pass
  
  def test_t1_analysis_time_deadline():
      with analyzed_bayesdb_population(t1(), 10, None, max_seconds=1):
          pass
  
  def test_t1_analysis_iter_deadline__ci_slow():
      with analyzed_bayesdb_population(t1(), 5, 1, max_seconds=10):
          pass
  
  @pytest.mark.parametrize('rowid,colno,confidence',
      [(i+1, j, conf)
          for i in range(min(5, len(t1_rows)))
          for j in range(1,3)
          for conf in [0.01, 0.5, 0.99]])
  def test_t1_predict(rowid, colno, confidence):
      with analyzed_bayesdb_population(t1(), 1, 1) as (bdb, pop_id, gen_id):
          if rowid == 0: rowid = bayesdb_maxrowid(bdb, pop_id)
          bqlfn.bql_predict(
              bdb, pop_id, None, None, rowid, colno, confidence, None)
  
  @pytest.mark.parametrize('colnos,constraints,numpredictions',
      [(colnos, constraints, numpred)
          for colnos in powerset(range(1,3))
          for constraints in [None] + list(powerset(range(1,3)))
          for numpred in range(3)])
  def test_t1_simulate(colnos, constraints, numpredictions):
      if len(colnos) == 0:
          # No need to try this or confirm it fails gracefully --
          # nothing should be trying it anyway, and bayeslite_simulate
          # is not exposed to users of the bayeslite API.
          return
      with analyzed_bayesdb_population(t1(), 1, 1) \
              as (bdb, population_id, generator_id):
          if constraints is not None:
              rowid = 1           # XXX Avoid hard-coding this.
              # Can't use t1_rows[0][i] because not all t1-based tables
              # use the same column indexing -- some use a subset of the
              # columns.
              #
              # XXX Automatically test the correct exception.
              constraints = [
                  (i, core.bayesdb_population_cell_value(
                      bdb, population_id, rowid, i))
                  for i in constraints
                  if i not in colnos
              ]
          bqlfn.bayesdb_simulate(bdb, population_id, None, None, constraints,
              colnos, numpredictions=numpredictions)
  
  @pytest.mark.parametrize('exname,colno',
      [(exname, colno)
          for exname in examples.keys()
          for colno in range(1,3)])
  def test_onecolumn(exname, colno):
      if exname == 't0' and colno > 0:
          pytest.skip('Not enough columns in %s.' % (exname,))
      if exname.startswith('t1_sub') and colno > 1:
          pytest.skip('Not enough columns in %s.' % (exname,))
      with analyzed_bayesdb_population(examples[exname](), 1, 1) \
              as (bdb, population_id, generator_id):
          bqlfn.bql_column_value_probability(
              bdb, population_id, None, None, colno, 4)
          bdb.sql_execute(
              'select bql_column_value_probability(?, NULL, NULL, ?, 4)',
              (population_id, colno)).fetchall()
  
  @pytest.mark.parametrize('exname,colno0,colno1',
      [(exname, colno0, colno1)
          for exname in examples.keys()
          for colno0 in range(1,3)
          for colno1 in range(1,3)])
  def test_twocolumn(exname, colno0, colno1):
      if exname == 't0':
          pytest.skip('Not enough columns in t0.')
      if exname.startswith('t1_sub') and (colno0 > 1 or colno1 > 1):
          pytest.skip('Not enough columns in %s.' % (exname,))
      with analyzed_bayesdb_population(examples[exname](), 1, 1) \
              as (bdb, population_id, generator_id):
          bqlfn.bql_column_correlation(
              bdb, population_id, None, None, colno0, colno1)
          bdb.sql_execute('select bql_column_correlation(?, NULL, NULL, ?, ?)',
              (population_id, colno0, colno1)).fetchall()
          bqlfn.bql_column_dependence_probability(
              bdb, population_id, None, None, colno0, colno1)
          bdb.sql_execute('select'
              ' bql_column_dependence_probability(?, NULL, NULL, ?, ?)',
              (population_id, colno0, colno1)).fetchall()
          colno0_json = json.dumps([colno0])
          colno1_json = json.dumps([colno1])
          bqlfn.bql_column_mutual_information(
              bdb, population_id, None, None, colno0_json, colno1_json, None)
          bqlfn.bql_column_mutual_information(
              bdb, population_id, None, None, colno0_json, colno1_json, None)
          bqlfn.bql_column_mutual_information(
              bdb, population_id, None, None, colno0_json, colno1_json, 1)
          bdb.sql_execute('select'
              ' bql_column_mutual_information(?, NULL, NULL, ?, ?, NULL)',
              (population_id, colno0_json, colno1_json)).fetchall()
          bdb.sql_execute('select'
              ' bql_column_mutual_information(?, NULL, NULL, ?, ?, 1)',
              (population_id, colno0_json, colno1_json)).fetchall()
          bdb.sql_execute('select'
              ' bql_column_mutual_information(?, NULL, NULL, ?, ?, 100)',
              (population_id, colno0_json, colno1_json)).fetchall()
  
  @pytest.mark.parametrize('colno,rowid',
      [(colno, rowid)
          for colno in range(1,4)
          for rowid in range(6)])
  def test_t1_column_value_probability(colno, rowid):
      with analyzed_bayesdb_population(t1(), 1, 1) \
              as (bdb, population_id, generator_id):
          if rowid == 0:
              rowid = bayesdb_maxrowid(bdb, population_id)
          value = core.bayesdb_population_cell_value(
              bdb, population_id, rowid, colno)
          bqlfn.bql_column_value_probability(
              bdb, population_id, None, None, colno, value)
          table_name = core.bayesdb_population_table(bdb, population_id)
          var = core.bayesdb_variable_name(bdb, population_id, None, colno)
          qt = bql_quote_name(table_name)
          qv = bql_quote_name(var)
          sql = '''
              select bql_column_value_probability(?, NULL, NULL, ?,
                  (select %s from %s where rowid = ?))
          ''' % (qv, qt)
          bdb.sql_execute(sql, (population_id, colno, rowid)).fetchall()
  
  @pytest.mark.parametrize('exname,rowid,colno',
      [(exname, rowid, colno)
          for exname in examples.keys()
          for rowid in range(4)
          for colno in range(1,3)])
  def test_row_column_predictive_probability(exname, rowid, colno):
      if exname == 't0' and colno > 1:
          pytest.skip('Not enough columns in t0.')
      if exname.startswith('t1_sub') and colno > 1:
          pytest.skip('Not enough columns in %s.' % (exname,))
      with analyzed_bayesdb_population(examples[exname](), 1, 1) \
              as (bdb, population_id, generator_id):
          if rowid == 0:
              rowid = bayesdb_maxrowid(bdb, population_id)
          targets = json.dumps([colno])
          constraints = json.dumps([])
          bqlfn.bql_row_column_predictive_probability(
              bdb, population_id, None, None, rowid, targets, constraints)
          bdb.sql_execute('''
              select bql_row_column_predictive_probability(
                  ?, NULL, NULL, ?, \'%s\', \'%s\')
              ''' % (targets, constraints), (population_id, rowid)
          ).fetchall()
  
  def test_bayesdb_population_fresh_row_id():
      with bayesdb_population(
              bayesdb(), 't1', 'p1', 'p1_cc', t1_schema, lambda x: 0,\
              columns=['id IGNORE','label CATEGORICAL', 'age NUMERICAL',
                  'weight NUMERICAL'])\
              as (bdb, population_id, generator_id):
          assert core.bayesdb_population_fresh_row_id(bdb, population_id) == 1
          t1_data(bdb)
          assert core.bayesdb_population_fresh_row_id(bdb, population_id) == \
              len(t1_rows) + 1
  
  def test_bayesdb_population_add_variable():
      with bayesdb() as bdb:
          bdb.sql_execute('create table t (a real, b ignore, c real)')
          bdb.execute('''
              create population p for t with schema(
                  set stattypes of a, c to numerical;
                  b ignore;
              );
          ''')
          population_id = core.bayesdb_get_population(bdb, 'p')
          # Checks column a.
          assert core.bayesdb_has_variable(bdb, population_id, None, 'a')
          assert core.bayesdb_table_column_number(bdb, 't', 'a') == 0
          assert core.bayesdb_variable_number(bdb, population_id, None, 'a') == 0
          # Checks column b, which is not in the population yet.
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'b')
          assert core.bayesdb_table_column_number(bdb, 't', 'b') == 1
          # Checks column c.
          assert core.bayesdb_has_variable(bdb, population_id, None, 'c')
          assert core.bayesdb_table_column_number(bdb, 't', 'c') == 2
          assert core.bayesdb_variable_number(bdb, population_id, None, 'c') == 2
          # Cannot add variable 'c', already exists.
          with pytest.raises(apsw.ConstraintError):
              core.bayesdb_add_variable(bdb, population_id, 'c', 'nominal')
          # Cannot add variable 'b' with a bad stattype.
          with pytest.raises(apsw.ConstraintError):
              core.bayesdb_add_variable(bdb, population_id, 'b', 'quzz')
          # Now add column b to the population.
          core.bayesdb_add_variable(bdb, population_id, 'b', 'nominal')
          assert core.bayesdb_variable_number(bdb, population_id, None, 'b') == 1
          # Add a new column q to table t, then add it to population p.
          bdb.sql_execute('alter table t add column q real;')
          assert core.bayesdb_table_column_number(bdb, 't', 'q') == 3
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'q')
          core.bayesdb_add_variable(bdb, population_id, 'q', 'numerical')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'q')
          assert core.bayesdb_variable_number(bdb, population_id, None, 'q') == 3
  
files: tests/test_core.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import apsw
  import contextlib
  import itertools
  import json
  import pytest
  import tempfile
  
  import bayeslite
  import bayeslite.backend
  import bayeslite.bqlfn as bqlfn
  import bayeslite.core as core
  
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  
  from bayeslite import bql_quote_name
  from bayeslite.sqlite3_util import sqlite3_connection
  from bayeslite.util import cursor_value
  
  def powerset(s):
      s = list(s)
      combinations = (itertools.combinations(s, r) for r in range(len(s) + 1))
      return itertools.chain.from_iterable(combinations)
  
  @contextlib.contextmanager
  def bayesdb(backend=None, **kwargs):
      if backend is None:
          backend = CGPM_Backend(cgpm_registry={}, multiprocess=False)
      bdb = bayeslite.bayesdb_open(builtin_backends=False, **kwargs)
      bayeslite.bayesdb_register_backend(bdb, backend)
      try:
          yield bdb
      finally:
          bdb.close()
  
  def test_bayesdb_instantiation():
      # Use bayesdb_open -- don't instantiate directly.
      with pytest.raises(TypeError):
          bayeslite.BayesDB()  # pylint: disable=no-value-for-parameter
      with pytest.raises(ValueError):
          bayeslite.BayesDB(':memory:', 0xdeadbeef)
  
  def test_prng_determinism():
      bdb = bayeslite.bayesdb_open(builtin_backends=False)
      assert bdb._prng.weakrandom_uniform(1000) == 303
      assert bdb.py_prng.uniform(0, 1) == 0.6156331606142532
      assert bdb.np_prng.uniform(0, 1) == 0.28348770982811367
  
  def test_openclose():
      with bayesdb():
          pass
  
  def test_bad_db_application_id():
      with tempfile.NamedTemporaryFile(prefix='bayeslite') as f:
          with sqlite3_connection(f.name) as db:
              db.cursor().execute('PRAGMA application_id = 42')
              db.cursor().execute('PRAGMA user_version = 3')
          with pytest.raises(IOError):
              with bayesdb(pathname=f.name):
                  pass
  
  def test_bad_db_user_version():
      # XXX Would be nice to avoid a named temporary file here.  Pass
      # the sqlite3 database connection in?
      with tempfile.NamedTemporaryFile(prefix='bayeslite') as f:
          with sqlite3_connection(f.name) as db:
              db.cursor().execute('PRAGMA application_id = 1113146434')
              db.cursor().execute('PRAGMA user_version = 42')
          with pytest.raises(IOError):
              with bayesdb(pathname=f.name):
                  pass
  
  class DotdogBackend(bayeslite.backend.BayesDB_Backend):
      def name(self):
          return 'dotdog'
      def register(self, bdb):
          sql = '''
              INSERT OR IGNORE INTO bayesdb_backend
                  VALUES ('dotdog', 42)
          '''
          bdb.sql_execute(sql)
      def create_generator(self, bdb, generator_id, schema_tokens, **kwargs):
          pass
  
  def test_hackbackend():
      bdb = bayeslite.bayesdb_open(builtin_backends=False)
      bdb.sql_execute('CREATE TABLE t(a INTEGER, b TEXT)')
      bdb.sql_execute("INSERT INTO t (a, b) VALUES (42, 'fnord')")
      bdb.sql_execute('CREATE TABLE u AS SELECT * FROM t')
      bdb.execute('CREATE POPULATION p FOR t(b IGNORE; a NUMERICAL)')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_cc FOR p USING cgpm;')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_dd FOR p USING dotdog;')
      dotdog_backend = DotdogBackend()
      bayeslite.bayesdb_register_backend(bdb, dotdog_backend)
      bayeslite.bayesdb_deregister_backend(bdb, dotdog_backend)
      bayeslite.bayesdb_register_backend(bdb, dotdog_backend)
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_cc FOR p USING cgpm;')
      bdb.execute('CREATE GENERATOR p_dd FOR p USING dotdog(a NUMERICAL)')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_dd FOR p USING dotdog(a NUMERICAL)')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_cc FOR p USING cgpm;')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR p_dd FOR p USING dotdog(a NUMERICAL)')
      # XXX Rest of test originally exercised default backend, but
      # syntax doesn't support that now.  Not clear that's wrong either.
      bdb.execute('CREATE GENERATOR q_dd FOR p USING dotdog(a NUMERICAL)')
      with pytest.raises(bayeslite.BQLError):
          bdb.execute('CREATE GENERATOR q_dd FOR p USING dotdog(a NUMERICAL)')
  
  @contextlib.contextmanager
  def bayesdb_population(mkbdb, tab, pop, gen, table_schema, data, columns,
          backend_name='cgpm'):
      with mkbdb as bdb:
          table_schema(bdb)
          data(bdb)
          qt = bql_quote_name(tab)
          qp = bql_quote_name(pop)
          qg = bql_quote_name(gen)
          qmm = bql_quote_name(backend_name)
          bdb.execute('CREATE POPULATION %s FOR %s(%s)' %
              (qp, qt, ';'.join(columns)))
          bdb.execute('CREATE GENERATOR %s FOR %s USING %s;' %
              (qg, qp, qmm,))
          population_id = core.bayesdb_get_population(bdb, pop)
          generator_id = core.bayesdb_get_generator(bdb, population_id, gen)
          yield bdb, population_id, generator_id
  
  @contextlib.contextmanager
  def analyzed_bayesdb_population(mkbdb, nmodels, nsteps, max_seconds=None):
      with mkbdb as (bdb, population_id, generator_id):
          generator = core.bayesdb_generator_name(bdb, generator_id)
          qg = bql_quote_name(generator)
          bql = 'INITIALIZE %d MODELS FOR %s' % (nmodels, qg)
          bdb.execute(bql)
          # XXX Syntax currently doesn't support both nsteps and
          # max_seconds.
          duration = None
          if max_seconds:
              duration = '%d SECONDS' % (max_seconds,)
          else:
              duration = '%d ITERATIONS' % (nsteps,)
          bql = 'ANALYZE %s FOR %s' % (qg, duration)
          bdb.execute(bql)
          yield bdb, population_id, generator_id
  
  def bayesdb_maxrowid(bdb, population_id):
      table_name = core.bayesdb_population_table(bdb, population_id)
      qt = bql_quote_name(table_name)
      sql = 'SELECT MAX(_rowid_) FROM %s' % (qt,)
      return cursor_value(bdb.sql_execute(sql))
  
  def test_casefold_colname():
      def t(tname, pname, gname, sql, *args, **kwargs):
          def schema(bdb):
              bdb.sql_execute(sql)
          def data(_bdb):
              pass
          return bayesdb_population(bayesdb(), tname, pname, gname, schema, data,
              *args, **kwargs)
      with pytest.raises(apsw.SQLError):
          with t('t', 'p', 'p_cc', 'create table t(x, X)', []):
              pass
      with pytest.raises(bayeslite.BQLError):
          columns = ['x CATEGORICAL', 'x CATEGORICAL']
          with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
              pass
      with pytest.raises(bayeslite.BQLError):
          columns = ['x CATEGORICAL', 'X CATEGORICAL']
          with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
              pass
      with pytest.raises(bayeslite.BQLError):
          columns = ['x CATEGORICAL', 'X NUMERICAL']
          with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
              pass
      columns = ['x CATEGORICAL', 'y CATEGORICAL']
      with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
          pass
      columns = ['X CATEGORICAL', 'y CATEGORICAL']
      with t('t', 'p', 'p_cc', 'create table t(x, y)', columns):
          pass
      columns = ['x CATEGORICAL', 'Y NUMERICAL']
      with t('t', 'p', 'p_cc', 'CREATE TABLE T(X, Y)', columns):
          pass
  
  def t0_schema(bdb):
      bdb.sql_execute('create table t0 (id integer primary key, n integer)')
  def t0_data(bdb):
      for row in [(0, 0), (1, 1), (2, 42), (3, 87)]:
          bdb.sql_execute('insert into t0 (id, n) values (?, ?)', row)
  
  def t0():
      return bayesdb_population(
          bayesdb(), 't0', 'p0', 'p0_cc', t0_schema, t0_data,
          columns=['id IGNORE', 'n NUMERICAL'])
  
  def test_t0_badname():
      with pytest.raises(bayeslite.BQLError):
          with bayesdb_population(
                  bayesdb(), 't0', 'p0', 't0_cc', t0_schema, t0_data,
                  columns=['n CATEGORICAL', 'm CATEGORICAL']):
              pass
  
  def t1_schema(bdb):
      bdb.sql_execute('''
          create table t1 (
              id integer primary key,
              label text,
              age double,
              weight double
          )
      ''')
  def t1_data(bdb):
      for row in t1_rows:
          bdb.sql_execute('insert into t1 (label,age,weight) values (?,?,?)',
              row)
  
  t1_rows = [
      ('foo', 12, 24),
      ('bar', 14, 28),
      (None, 10, 20),
      ('baz', None, 32),
      ('quux', 4, None),
      ('zot', 8, 16),
      ('mumble', 8, 16),
      ('frotz', 8, 16),
      ('gargle', 8, 16),
      ('mumph', 8, 16),
      ('hunf', 11, 22),
      ('blort', 16, 32),
      (None, 16, 32),
      (None, 17, 34),
      (None, 18, 36),
      (None, 19, 38),
      (None, 20, 40),
      (None, 21, 42),
      (None, 22, 44),
      (None, 23, 46),
      (None, 24, 48),
      (None, 25, 50),
      (None, 26, 52),
      (None, 27, 54),
      (None, 28, 56),
      (None, 29, 58),
      (None, 30, 60),
      (None, 31, 62),
  ]
  
  def t1(*args, **kwargs):
      return bayesdb_population(
          bayesdb(*args, **kwargs), 't1', 'p1', 'p1_cc',
          t1_schema, t1_data,
          columns=['id IGNORE', 'label CATEGORICAL',
              'age NUMERICAL', 'weight NUMERICAL',])
  
  def t1_sub():
      return bayesdb_population(bayesdb(), 't1', 'p1', 'p1_sub_cc',
          t1_schema, t1_data,
          columns=['id IGNORE', 'age IGNORE', 'label CATEGORICAL',
              'weight NUMERICAL',])
  
  def t1_subcat():
      return bayesdb_population(bayesdb(), 't1', 'p1', 'p1_subcat_cc',
          t1_schema, t1_data,
          columns=['id IGNORE', 'age IGNORE','label CATEGORICAL',
              'weight CATEGORICAL'])
  
  def t1_mp():
      backend = CGPM_Backend(cgpm_registry={}, multiprocess=True)
      return bayesdb_population(bayesdb(backend=backend),
          't1', 'p1', 'p1_cc', t1_schema, t1_data,
           columns=['id IGNORE','label CATEGORICAL', 'age NUMERICAL',
              'weight NUMERICAL'])
  
  def t2_schema(bdb):
      bdb.sql_execute('''create table t2 (id, label, age, weight)''')
  
  def t2_data(bdb):
      bdb.sql_execute('''
          insert into t2 (label,age,weight) values
              ('1', '2', ?),
              ('2', '3', '1.2'),
              ('3', '48', '3e10'),
              ('4', '3', ?)
      ''', (2/3., -0.,))
  
  def t2():
      return bayesdb_population(
          bayesdb(), 't2', 'p2', 'p2_cc', t2_schema, t2_data,
          columns=['id IGNORE','label CATEGORICAL', 'age CATEGORICAL',
              'weight CATEGORICAL'])
  
  def test_t1_nokey():
      with bayesdb_population(
              bayesdb(), 't1', 'p1', 'p1_cc', t1_schema, t1_data,
              columns=['id IGNORE','label IGNORE', 'age NUMERICAL',
                  'weight NUMERICAL']):
          pass
  
  def test_t1_nocase():
      with bayesdb_population(bayesdb(), 't1', 'p1', 'p1_cc', t1_schema, t1_data,
              columns=[
                  'id IGNORE',
                  'label CATEGORICAL',
                  'age NUMERICAL',
                  'weight NUMERICAL',
              ]) \
              as (bdb, population_id, generator_id):
          bdb.execute('select id from t1').fetchall()
          bdb.execute('select ID from T1').fetchall()
          bdb.execute('select iD from T1').fetchall()
          bdb.execute('select Id from T1').fetchall()
          # bdb.execute('select id from p1_cc').fetchall()
          # bdb.execute('select ID from P1_cC').fetchall()
          # bdb.execute('select iD from P1_Cc').fetchall()
          # bdb.execute('select Id from P1_CC').fetchall()
  
  examples = {
      't0': t0,
      't1': t1,
  #    't1_mp': t1_mp,
      't1_sub': t1_sub,
      't1_subcat': t1_subcat,
      't2': t2,
  }
  
  @pytest.mark.parametrize('exname', examples.keys())
  def test_example(exname):
      with examples[exname]():
          pass
  
  @pytest.mark.parametrize('exname', examples.keys())
  def test_example_analysis0(exname):
      with analyzed_bayesdb_population(examples[exname](), 1, 0):
          pass
  
  @pytest.mark.parametrize('exname', examples.keys())
  def test_example_analysis1(exname):
      with analyzed_bayesdb_population(examples[exname](), 1, 1):
          pass
  
  # The multiprocessing engine has a large overhead, too much to try
  # every normal test with it, so we'll just run this one test to make
  # sure it doesn't crash and burn with ten models.
  def test_t1_mp_analysis():
      with analyzed_bayesdb_population(t1_mp(), 10, 2):
          pass
  
  def test_t1_mp_analysis_time_deadline():
      with analyzed_bayesdb_population(t1_mp(), 10, None, max_seconds=1):
          pass
  
  def test_t1_mp_analysis_iter_deadline__ci_slow():
      with analyzed_bayesdb_population(t1_mp(), 10, 1, max_seconds=10):
          pass
  
  def test_t1_analysis_time_deadline():
      with analyzed_bayesdb_population(t1(), 10, None, max_seconds=1):
          pass
  
  def test_t1_analysis_iter_deadline__ci_slow():
      with analyzed_bayesdb_population(t1(), 5, 1, max_seconds=10):
          pass
  
  @pytest.mark.parametrize('rowid,colno,confidence',
      [(i+1, j, conf)
          for i in range(min(5, len(t1_rows)))
          for j in range(1,3)
          for conf in [0.01, 0.5, 0.99]])
  def test_t1_predict(rowid, colno, confidence):
      with analyzed_bayesdb_population(t1(), 1, 1) as (bdb, pop_id, gen_id):
          if rowid == 0: rowid = bayesdb_maxrowid(bdb, pop_id)
          bqlfn.bql_predict(
              bdb, pop_id, None, None, rowid, colno, confidence, None)
  
  @pytest.mark.parametrize('colnos,constraints,numpredictions',
      [(colnos, constraints, numpred)
          for colnos in powerset(range(1,3))
          for constraints in [None] + list(powerset(range(1,3)))
          for numpred in range(3)])
  def test_t1_simulate(colnos, constraints, numpredictions):
      if len(colnos) == 0:
          # No need to try this or confirm it fails gracefully --
          # nothing should be trying it anyway, and bayeslite_simulate
          # is not exposed to users of the bayeslite API.
          return
      with analyzed_bayesdb_population(t1(), 1, 1) \
              as (bdb, population_id, generator_id):
          if constraints is not None:
              rowid = 1           # XXX Avoid hard-coding this.
              # Can't use t1_rows[0][i] because not all t1-based tables
              # use the same column indexing -- some use a subset of the
              # columns.
              #
              # XXX Automatically test the correct exception.
              constraints = [
                  (i, core.bayesdb_population_cell_value(
                      bdb, population_id, rowid, i))
                  for i in constraints
                  if i not in colnos
              ]
          bqlfn.bayesdb_simulate(bdb, population_id, None, None, constraints,
              colnos, numpredictions=numpredictions)
  
  @pytest.mark.parametrize('exname,colno',
      [(exname, colno)
          for exname in examples.keys()
          for colno in range(1,3)])
  def test_onecolumn(exname, colno):
      if exname == 't0' and colno > 0:
          pytest.skip('Not enough columns in %s.' % (exname,))
      if exname.startswith('t1_sub') and colno > 1:
          pytest.skip('Not enough columns in %s.' % (exname,))
      with analyzed_bayesdb_population(examples[exname](), 1, 1) \
              as (bdb, population_id, generator_id):
          bqlfn.bql_column_value_probability(
              bdb, population_id, None, None, colno, 4)
          bdb.sql_execute(
              'select bql_column_value_probability(?, NULL, NULL, ?, 4)',
              (population_id, colno)).fetchall()
  
  @pytest.mark.parametrize('exname,colno0,colno1',
      [(exname, colno0, colno1)
          for exname in examples.keys()
          for colno0 in range(1,3)
          for colno1 in range(1,3)])
  def test_twocolumn(exname, colno0, colno1):
      if exname == 't0':
          pytest.skip('Not enough columns in t0.')
      if exname.startswith('t1_sub') and (colno0 > 1 or colno1 > 1):
          pytest.skip('Not enough columns in %s.' % (exname,))
      with analyzed_bayesdb_population(examples[exname](), 1, 1) \
              as (bdb, population_id, generator_id):
          bqlfn.bql_column_correlation(
              bdb, population_id, None, None, colno0, colno1)
          bdb.sql_execute('select bql_column_correlation(?, NULL, NULL, ?, ?)',
              (population_id, colno0, colno1)).fetchall()
          bqlfn.bql_column_dependence_probability(
              bdb, population_id, None, None, colno0, colno1)
          bdb.sql_execute('select'
              ' bql_column_dependence_probability(?, NULL, NULL, ?, ?)',
              (population_id, colno0, colno1)).fetchall()
          colno0_json = json.dumps([colno0])
          colno1_json = json.dumps([colno1])
          bqlfn.bql_column_mutual_information(
              bdb, population_id, None, None, colno0_json, colno1_json, None)
          bqlfn.bql_column_mutual_information(
              bdb, population_id, None, None, colno0_json, colno1_json, None)
          bqlfn.bql_column_mutual_information(
              bdb, population_id, None, None, colno0_json, colno1_json, 1)
          bdb.sql_execute('select'
              ' bql_column_mutual_information(?, NULL, NULL, ?, ?, NULL)',
              (population_id, colno0_json, colno1_json)).fetchall()
          bdb.sql_execute('select'
              ' bql_column_mutual_information(?, NULL, NULL, ?, ?, 1)',
              (population_id, colno0_json, colno1_json)).fetchall()
          bdb.sql_execute('select'
              ' bql_column_mutual_information(?, NULL, NULL, ?, ?, 100)',
              (population_id, colno0_json, colno1_json)).fetchall()
  
  @pytest.mark.parametrize('colno,rowid',
      [(colno, rowid)
          for colno in range(1,4)
          for rowid in range(6)])
  def test_t1_column_value_probability(colno, rowid):
      with analyzed_bayesdb_population(t1(), 1, 1) \
              as (bdb, population_id, generator_id):
          if rowid == 0:
              rowid = bayesdb_maxrowid(bdb, population_id)
          value = core.bayesdb_population_cell_value(
              bdb, population_id, rowid, colno)
          bqlfn.bql_column_value_probability(
              bdb, population_id, None, None, colno, value)
          table_name = core.bayesdb_population_table(bdb, population_id)
          var = core.bayesdb_variable_name(bdb, population_id, None, colno)
          qt = bql_quote_name(table_name)
          qv = bql_quote_name(var)
          sql = '''
              select bql_column_value_probability(?, NULL, NULL, ?,
                  (select %s from %s where rowid = ?))
          ''' % (qv, qt)
          bdb.sql_execute(sql, (population_id, colno, rowid)).fetchall()
  
  @pytest.mark.parametrize('exname,rowid,colno',
      [(exname, rowid, colno)
          for exname in examples.keys()
          for rowid in range(4)
          for colno in range(1,3)])
  def test_row_column_predictive_probability(exname, rowid, colno):
      if exname == 't0' and colno > 1:
          pytest.skip('Not enough columns in t0.')
      if exname.startswith('t1_sub') and colno > 1:
          pytest.skip('Not enough columns in %s.' % (exname,))
      with analyzed_bayesdb_population(examples[exname](), 1, 1) \
              as (bdb, population_id, generator_id):
          if rowid == 0:
              rowid = bayesdb_maxrowid(bdb, population_id)
          targets = json.dumps([colno])
          constraints = json.dumps([])
          bqlfn.bql_row_column_predictive_probability(
              bdb, population_id, None, None, rowid, targets, constraints)
          bdb.sql_execute('''
              select bql_row_column_predictive_probability(
                  ?, NULL, NULL, ?, \'%s\', \'%s\')
              ''' % (targets, constraints), (population_id, rowid)
          ).fetchall()
  
  def test_bayesdb_population_fresh_row_id():
      with bayesdb_population(
              bayesdb(), 't1', 'p1', 'p1_cc', t1_schema, lambda x: 0,\
              columns=['id IGNORE','label CATEGORICAL', 'age NUMERICAL',
                  'weight NUMERICAL'])\
              as (bdb, population_id, generator_id):
          assert core.bayesdb_population_fresh_row_id(bdb, population_id) == 1
          t1_data(bdb)
          assert core.bayesdb_population_fresh_row_id(bdb, population_id) == \
              len(t1_rows) + 1
  
  def test_bayesdb_population_add_variable():
      with bayesdb() as bdb:
          bdb.sql_execute('create table t (a real, b ignore, c real)')
          bdb.execute('''
              create population p for t with schema(
                  model a, c as numerical;
                  b ignore;
              );
          ''')
          population_id = core.bayesdb_get_population(bdb, 'p')
          # Checks column a.
          assert core.bayesdb_has_variable(bdb, population_id, None, 'a')
          assert core.bayesdb_table_column_number(bdb, 't', 'a') == 0
          assert core.bayesdb_variable_number(bdb, population_id, None, 'a') == 0
          # Checks column b, which is not in the population yet.
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'b')
          assert core.bayesdb_table_column_number(bdb, 't', 'b') == 1
          # Checks column c.
          assert core.bayesdb_has_variable(bdb, population_id, None, 'c')
          assert core.bayesdb_table_column_number(bdb, 't', 'c') == 2
          assert core.bayesdb_variable_number(bdb, population_id, None, 'c') == 2
          # Cannot add variable 'c', already exists.
          with pytest.raises(apsw.ConstraintError):
              core.bayesdb_add_variable(bdb, population_id, 'c', 'nominal')
          # Cannot add variable 'b' with a bad stattype.
          with pytest.raises(apsw.ConstraintError):
              core.bayesdb_add_variable(bdb, population_id, 'b', 'quzz')
          # Now add column b to the population.
          core.bayesdb_add_variable(bdb, population_id, 'b', 'nominal')
          assert core.bayesdb_variable_number(bdb, population_id, None, 'b') == 1
          # Add a new column q to table t, then add it to population p.
          bdb.sql_execute('alter table t add column q real;')
          assert core.bayesdb_table_column_number(bdb, 't', 'q') == 3
          assert not core.bayesdb_has_variable(bdb, population_id, None, 'q')
          core.bayesdb_add_variable(bdb, population_id, 'q', 'numerical')
          assert core.bayesdb_has_variable(bdb, population_id, None, 'q')
          assert core.bayesdb_variable_number(bdb, population_id, None, 'q') == 3
  
files: tests/test_core.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  '''Ensures that INFER EXPLICIT PREDICT col for a _rowid_ which exists in the
  base table, but not incorporated into the generator (i.e. did not exist in the
  table during INITIALIZE) correctly retrieves all other values in the row. The
  test cases creates a population of (0,1) and (1,0) -- the complement function --
  and ensures its recovery on a held-out test set.'''
  
  import pytest
  
  from bayeslite import bayesdb_open
  
  from bayeslite.core import bayesdb_get_generator
  from bayeslite.core import bayesdb_get_population
  from bayeslite.core import bayesdb_population_fresh_row_id
  from bayeslite.util import cursor_value
  
  
  @pytest.fixture(scope='module')
  def bdb():
      bdb = bayesdb_open(':memory:')
  
      # Create the population of complements.
      bdb.sql_execute('CREATE TABLE t (a TEXT, b TEXT)')
      for _ in xrange(20):
          bdb.sql_execute('INSERT INTO t (a, b) VALUES (0,1)')
      for _ in xrange(20):
          bdb.sql_execute('INSERT INTO t (a, b) VALUES (1,0)')
  
      # Create the population and generator on the existing rows.
      bdb.execute('CREATE POPULATION p FOR t (SET STATTYPES OF a, b TO NOMINAL)')
      bdb.execute('CREATE GENERATOR m FOR p;')
      bdb.execute('INITIALIZE 1 MODELS FOR m;')
      bdb.execute('ANALYZE m FOR 1000 ITERATION (OPTIMIZED);')
  
      # Add new 'hypothetical' rows into the base table to serve as out-of-
      # sample probe points; only zeros, only ones, and nothing.
      for _ in xrange(40, 50):
          bdb.sql_execute('INSERT INTO t (a) VALUES (0)')
      for _ in xrange(50, 60):
          bdb.sql_execute('INSERT INTO t (b) VALUES (1)')
      for _ in xrange(60, 80):
          bdb.sql_execute('INSERT INTO t (a,b) VALUES (NULL, NULL)')
  
      # Make sure fresh_row_id 80 from the base table, not generator.
      population_id = bayesdb_get_population(bdb, 'p')
      assert bayesdb_population_fresh_row_id(bdb, population_id) == 81
  
      # Make sure the cgpm only has 40 rowids incorporated.
      generator_id = bayesdb_get_generator(bdb, population_id, 'm')
      cursor = bdb.sql_execute('''
          SELECT MAX(table_rowid) FROM bayesdb_cgpm_individual
          WHERE generator_id = ?
      ''', (generator_id,))
      assert cursor_value(cursor) == 40
  
      # Turn off multiprocessing for sequence of queries.
      bdb.backends['cgpm'].set_multiprocess(False)
      return bdb
  
  
  def test_infer_ones(bdb):
      # All the b's should be one (check 90%).
      b_ones = bdb.execute('''
          INFER EXPLICIT
              a,
              PREDICT b CONFIDENCE conf USING 100 SAMPLES
          FROM p WHERE oid BETWEEN 41 AND 50
      ''').fetchall()
      assert all(r[0] == '0' for r in b_ones)
      assert len([r for r in b_ones if r[1] == '1' and r[2] > 0.8]) >= 9
  
  
  def test_infer_zeros(bdb):
      # All the a's should be zero (check 90%).
      a_zeros = bdb.execute('''
          INFER EXPLICIT
              PREDICT a CONFIDENCE conf USING 100 SAMPLES,
              b
          FROM p WHERE oid BETWEEN 51 AND 60
      ''').fetchall()
      assert all(r[2] == '1' for r in a_zeros)
      assert len([r for r in a_zeros if r[0] == '0' and r[1] > 0.8]) >= 9
  
  
  def test_infer_uniform_marginal(bdb):
      # None of these queries should illustrate a dominant pattern of 0s or 1s. We
      # test heuristically not statistically.
  
      uniform_a = bdb.execute('''
          INFER EXPLICIT PREDICT a CONFIDENCE conf USING 100 SAMPLES
          FROM p WHERE oid BETWEEN 61 AND 80
      ''').fetchall()
      assert len([r for r in uniform_a if r[0] == '1' and r[1] > 0.55]) < 15
      assert len([r for r in uniform_a if r[0] == '0' and r[1] > 0.55]) < 15
  
      uniform_b = bdb.execute('''
          INFER EXPLICIT PREDICT b CONFIDENCE conf USING 100 SAMPLES
          FROM p WHERE oid BETWEEN 61 AND 80
      ''').fetchall()
      assert len([r for r in uniform_a if r[0] == '1' and r[1] > 0.55]) < 15
      assert len([r for r in uniform_a if r[0] == '0' and r[1] > 0.55]) < 15
  
  
  def test_infer_uniform_joint(bdb):
      # Should be roughly independent.
      uniform_ab = bdb.execute('''
          INFER EXPLICIT
              PREDICT a CONFIDENCE confa USING 10 SAMPLES,
              PREDICT b CONFIDENCE confb USING 10 SAMPLES
          FROM p WHERE oid BETWEEN 61 AND 80
      ''').fetchall()
      a0b0 = [r for r in uniform_ab if r[0] == '0' and r[1] == '0']
      a0b1 = [r for r in uniform_ab if r[0] == '0' and r[1] == '1']
      a1b0 = [r for r in uniform_ab if r[0] == '1' and r[1] == '0']
      a1b1 = [r for r in uniform_ab if r[0] == '1' and r[1] == '1']
  
      # None of these should comprise more than 50% of the samples.
      assert all([len(s) < 10 for s in [a0b0, a0b1, a1b0, a1b1]])
  
files: tests/test_infer_hypothetical.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  '''Ensures that INFER EXPLICIT PREDICT col for a _rowid_ which exists in the
  base table, but not incorporated into the generator (i.e. did not exist in the
  table during INITIALIZE) correctly retrieves all other values in the row. The
  test cases creates a population of (0,1) and (1,0) -- the complement function --
  and ensures its recovery on a held-out test set.'''
  
  import pytest
  
  from bayeslite import bayesdb_open
  
  from bayeslite.core import bayesdb_get_generator
  from bayeslite.core import bayesdb_get_population
  from bayeslite.core import bayesdb_population_fresh_row_id
  from bayeslite.util import cursor_value
  
  
  @pytest.fixture(scope='module')
  def bdb():
      bdb = bayesdb_open(':memory:')
  
      # Create the population of complements.
      bdb.sql_execute('CREATE TABLE t (a TEXT, b TEXT)')
      for _ in xrange(20):
          bdb.sql_execute('INSERT INTO t (a, b) VALUES (0,1)')
      for _ in xrange(20):
          bdb.sql_execute('INSERT INTO t (a, b) VALUES (1,0)')
  
      # Create the population and generator on the existing rows.
      bdb.execute('CREATE POPULATION p FOR t (MODEL a, b AS NOMINAL)')
      bdb.execute('CREATE GENERATOR m FOR p;')
      bdb.execute('INITIALIZE 1 MODELS FOR m;')
      bdb.execute('ANALYZE m FOR 1000 ITERATION (OPTIMIZED);')
  
      # Add new 'hypothetical' rows into the base table to serve as out-of-
      # sample probe points; only zeros, only ones, and nothing.
      for _ in xrange(40, 50):
          bdb.sql_execute('INSERT INTO t (a) VALUES (0)')
      for _ in xrange(50, 60):
          bdb.sql_execute('INSERT INTO t (b) VALUES (1)')
      for _ in xrange(60, 80):
          bdb.sql_execute('INSERT INTO t (a,b) VALUES (NULL, NULL)')
  
      # Make sure fresh_row_id 80 from the base table, not generator.
      population_id = bayesdb_get_population(bdb, 'p')
      assert bayesdb_population_fresh_row_id(bdb, population_id) == 81
  
      # Make sure the cgpm only has 40 rowids incorporated.
      generator_id = bayesdb_get_generator(bdb, population_id, 'm')
      cursor = bdb.sql_execute('''
          SELECT MAX(table_rowid) FROM bayesdb_cgpm_individual
          WHERE generator_id = ?
      ''', (generator_id,))
      assert cursor_value(cursor) == 40
  
      # Turn off multiprocessing for sequence of queries.
      bdb.backends['cgpm'].set_multiprocess(False)
      return bdb
  
  
  def test_infer_ones(bdb):
      # All the b's should be one (check 90%).
      b_ones = bdb.execute('''
          INFER EXPLICIT
              a,
              PREDICT b CONFIDENCE conf USING 100 SAMPLES
          FROM p WHERE oid BETWEEN 41 AND 50
      ''').fetchall()
      assert all(r[0] == '0' for r in b_ones)
      assert len([r for r in b_ones if r[1] == '1' and r[2] > 0.8]) >= 9
  
  
  def test_infer_zeros(bdb):
      # All the a's should be zero (check 90%).
      a_zeros = bdb.execute('''
          INFER EXPLICIT
              PREDICT a CONFIDENCE conf USING 100 SAMPLES,
              b
          FROM p WHERE oid BETWEEN 51 AND 60
      ''').fetchall()
      assert all(r[2] == '1' for r in a_zeros)
      assert len([r for r in a_zeros if r[0] == '0' and r[1] > 0.8]) >= 9
  
  
  def test_infer_uniform_marginal(bdb):
      # None of these queries should illustrate a dominant pattern of 0s or 1s. We
      # test heuristically not statistically.
  
      uniform_a = bdb.execute('''
          INFER EXPLICIT PREDICT a CONFIDENCE conf USING 100 SAMPLES
          FROM p WHERE oid BETWEEN 61 AND 80
      ''').fetchall()
      assert len([r for r in uniform_a if r[0] == '1' and r[1] > 0.55]) < 15
      assert len([r for r in uniform_a if r[0] == '0' and r[1] > 0.55]) < 15
  
      uniform_b = bdb.execute('''
          INFER EXPLICIT PREDICT b CONFIDENCE conf USING 100 SAMPLES
          FROM p WHERE oid BETWEEN 61 AND 80
      ''').fetchall()
      assert len([r for r in uniform_a if r[0] == '1' and r[1] > 0.55]) < 15
      assert len([r for r in uniform_a if r[0] == '0' and r[1] > 0.55]) < 15
  
  
  def test_infer_uniform_joint(bdb):
      # Should be roughly independent.
      uniform_ab = bdb.execute('''
          INFER EXPLICIT
              PREDICT a CONFIDENCE confa USING 10 SAMPLES,
              PREDICT b CONFIDENCE confb USING 10 SAMPLES
          FROM p WHERE oid BETWEEN 61 AND 80
      ''').fetchall()
      a0b0 = [r for r in uniform_ab if r[0] == '0' and r[1] == '0']
      a0b1 = [r for r in uniform_ab if r[0] == '0' and r[1] == '1']
      a1b0 = [r for r in uniform_ab if r[0] == '1' and r[1] == '0']
      a1b1 = [r for r in uniform_ab if r[0] == '1' and r[1] == '1']
  
      # None of these should comprise more than 50% of the samples.
      assert all([len(s) < 10 for s in [a0b0, a0b1, a1b0, a1b1]])
  
files: tests/test_infer_hypothetical.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import pytest
  
  import bayeslite.core as core
  
  from bayeslite import BQLError
  from bayeslite import bayesdb_open
  from bayeslite import bayesdb_register_backend
  from bayeslite.backends.nig_normal import NIGNormalBackend
  
  def test_nig_normal_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x) values(?)', (x,))
          bdb.execute('create population p for t(x numerical)')
          bdb.execute('create generator g for p using nig_normal')
          bdb.execute('initialize 1 model for g')
          bdb.execute('analyze g for 1 iteration')
          bdb.execute('estimate probability density of x = 50 from p').fetchall()
          bdb.execute('simulate x from p limit 1').fetchall()
          bdb.execute('drop models from g')
          bdb.execute('drop generator g')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_numbering():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(id integer primary key, x, y)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x, y) values(?, ?)', (x, x*x - 100))
          bdb.execute('''
              create population p for t(
                  id ignore;
                  set stattypes of x,y to numerical;
              )
          ''')
          assert core.bayesdb_has_population(bdb, 'p')
          pid = core.bayesdb_get_population(bdb, 'p')
          assert core.bayesdb_variable_numbers(bdb, pid, None) == [1, 2]
  
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
  
          assert core.bayesdb_has_generator(bdb, pid, 'g0')
          g0 = core.bayesdb_get_generator(bdb, pid, 'g0')
          assert core.bayesdb_has_generator(bdb, pid, 'g1')
          g1 = core.bayesdb_get_generator(bdb, pid, 'g1')
          assert core.bayesdb_variable_numbers(bdb, pid, None) == [1, 2]
          assert core.bayesdb_variable_numbers(bdb, pid, g0) == [1, 2]
          assert core.bayesdb_variable_numbers(bdb, pid, g1) == [-1, 1, 2]
  
  def test_nig_normal_latent_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x) values(?)', (x,))
          bdb.execute('create population p for t(x numerical)')
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # PROBABILITY DENSITY OF x = v
          bdb.execute('estimate probability density of x = 50 within p') \
              .fetchall()
          with pytest.raises(BQLError):
              bdb.execute('estimate probability density of xe = 1 within p') \
                  .fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 within p modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 1 within p modeled by g1
          ''').fetchall()
  
          # PREDICTIVE PROBABILITY OF x
          bdb.execute('estimate predictive probability of x from p').fetchall()
          with pytest.raises(BQLError):
              bdb.execute(
                  'estimate predictive probability of xe from p').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate predictive probability of xe from p modeled by g0
              ''').fetchall()
          for r, p_xe in bdb.execute('''
              estimate rowid, predictive probability of xe from p modeled by g1
          '''):
              assert p_xe is None, 'rowid %r p(xe) %r' % (r, p_xe)
  
          # INFER/PREDICT
          bdb.execute(
              'INFER EXPLICIT PREDICT x CONFIDENCE x_c FROM p').fetchall()
          with pytest.raises(BQLError):
              bdb.execute(
                  'INFER EXPLICIT PREDICT xe CONFIDENCE xe_c FROM p').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  INFER EXPLICIT PREDICT xe CONFIDENCE xe_c FROM p
                      MODELED BY g0
              ''').fetchall()
          bdb.execute('''
              INFER EXPLICIT PREDICT xe CONFIDENCE xe_c FROM p
                  MODELED BY g1
          ''').fetchall()
  
          # SIMULATE x
          bdb.execute('simulate x from p limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate x, xe from p limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute(
                  'simulate x, xe from p modeled by g0 limit 1').fetchall()
          bdb.execute('simulate x, xe from p modeled by g1 limit 1').fetchall()
  
          assert 100 == len(bdb.execute('''
              estimate similarity in the context of x from pairwise p limit 100
          ''').fetchall())
          assert 1 == len(bdb.execute('''
              estimate similarity in the context of x
              from pairwise p modeled by g0 limit 1
          ''').fetchall())
                  # No such column xe in g0.
          with pytest.raises(BQLError):
              assert 1 == len(bdb.execute('''
                  estimate similarity in the context of xe
                      from pairwise p modeled by g0 limit 1
              ''').fetchall())
          # Column xe exists in g1.
          assert 1 == len(bdb.execute('''
              estimate similarity in the context of xe
                  from pairwise p modeled by g1 limit 1
          ''').fetchall())
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_conditional_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x) values(?)', (x,))
          bdb.execute('create population p for t(x numerical)')
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # observed given observed
          bdb.execute('''
              estimate probability density of x = 50 given (x = 50) within p
          ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (x = 50) within p
                  modeled by g0
          ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (x = 50) within p
                  modeled by g1
          ''').fetchall()
  
          # observed given latent
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of x = 50 given (xe = 50) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of x = 50 given (xe = 50) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (xe = 50) within p
                  modeled by g1
          ''').fetchall()
  
          # latent given observed
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 50 given (x = 50) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 50 given (x = 50) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 50 given (x = 50) within p
                  modeled by g1
          ''').fetchall()
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_2var_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x, y)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x, y) values(?, ?)',
                  (x, x*x - 100))
          bdb.execute('create population p for t(x numerical; y numerical)')
  
          # CORRELATION, CORRELATION PVALUE, without generators.
          assert 4 == len(bdb.execute('''
              estimate correlation, correlation pvalue
                  from pairwise variables of p
          ''').fetchall())
  
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # CORRELATION, CORRELATION PVALUE, with generators.
          assert 4 == len(bdb.execute('''
              estimate correlation, correlation pvalue
                  from pairwise variables of p
          ''').fetchall())
          assert 4 == len(bdb.execute('''
              estimate correlation, correlation pvalue
                  from pairwise variables of p modeled by g0
          ''').fetchall())
          with pytest.raises(BQLError):
              # g1 has a latent variable xe.
              assert 4 == len(bdb.execute('''
                  estimate correlation, correlation pvalue
                      from pairwise variables of p modeled by g1
              ''').fetchall())
  
          # DEPENDENCE PROBABILITY, MUTUAL INFORMATION
          assert 4 == len(bdb.execute('''
              estimate dependence probability, mutual information
                  from pairwise variables of p
          ''').fetchall())
          assert 4 == len(bdb.execute('''
              estimate dependence probability, mutual information
                  from pairwise variables of p modeled by g0
          ''').fetchall())
          assert 9 == len(bdb.execute('''
              estimate dependence probability, mutual information
                  from pairwise variables of p modeled by g1
          ''').fetchall())
  
          # SIMULATE LATENT VARIABLE
          assert 10 == len(bdb.execute('''
              simulate xe from p modeled by g1 limit 10;
          ''').fetchall())
          assert 10 == len(bdb.execute('''
              simulate y, xe from p modeled by g1 limit 10;
          ''').fetchall())
          # Cannot simulate the latent xe from the population p.
          with pytest.raises(BQLError):
              assert 10 == len(bdb.execute('''
                  simulate xe from p limit 10;
              ''').fetchall())
          # Cannot simulate the latent xe from the generator g0.
          with pytest.raises(BQLError):
              assert 10 == len(bdb.execute('''
                  simulate xe from p modeled by g0 limit 10;
              ''').fetchall())
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_2var_conditional_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x, y)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x, y) values(?, ?)',
                  (x, x*x - 100))
          bdb.execute('create population p for t(x numerical; y numerical)')
  
          # CORRELATION, CORRELATION PVALUE, without generators.
          assert 4 == len(bdb.execute('''
              estimate correlation, correlation pvalue
                  from pairwise variables of p
          ''').fetchall())
  
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # observed given other observed
          bdb.execute('''
              estimate probability density of x = 50 given (y = 49) within p
          ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (y = 49) within p
                  modeled by g0
          ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (y = 49) within p
                  modeled by g1
          ''').fetchall()
          bdb.execute('simulate x from p given y = 49 limit 1').fetchall()
          bdb.execute('''
              simulate x from p modeled by g0 given y = 49 limit 1
          ''').fetchall()
          bdb.execute('''
              simulate x from p modeled by g1 given y = 49 limit 1
          ''').fetchall()
  
          # observed given related latent
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of x = 50 given (xe = 1) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of x = 50 given (xe = 1) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (xe = 1) within p
                  modeled by g1
          ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate x from p given xe = 1 limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate x from p modeled by g0 given xe = 1 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate x from p modeled by g1 given xe = 1 limit 1
          ''').fetchall()
  
          # observed given unrelated latent
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of y = 50 given (xe = 1) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of y = 50 given (xe = 1) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of y = 50 given (xe = 1) within p
                  modeled by g1
          ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate y from p given xe = 1 limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate y from p modeled by g0 given xe = 1 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate y from p modeled by g1 given xe = 1 limit 1
          ''').fetchall()
  
          # latent given related observed
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (x = 50) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (x = 50) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 1 given (x = 50) within p
                  modeled by g1
          ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate xe from p given x = 50 limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate xe from p modeled by g0 given x = 50 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate xe from p modeled by g1 given x = 50 limit 1
          ''').fetchall()
  
          # latent given unrelated observed
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (y = 50) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (y = 50) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 1 given (y = 50) within p
                  modeled by g1
          ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate xe from p given y = 50 limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate xe from p modeled by g0 given y = 50 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate xe from p modeled by g1 given y = 50 limit 1
          ''').fetchall()
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_2var2lat_conditional_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x, y)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x, y) values(?, ?)',
                  (x, x*x - 100))
          bdb.execute('create population p for t(x numerical; y numerical)')
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(
                  xe deviation(x),
                  ye deviation(y)
              )
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # latent given latent
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (ye = -1) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (ye = -1) within p
                       modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 1 given (ye = -1) within p
                   modeled by g1
          ''').fetchall()
  
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate xe from p given ye = -1 limit 1
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate xe from p modeled by g0 given ye = -1 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate xe from p modeled by g1 given ye = -1 limit 1
          ''').fetchall()
  
          with pytest.raises(BQLError):
              bdb.execute(
                  'estimate dependence probability of xe with ye within p')
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate dependence probability of xe with ye within p
                      modeled by g0
              ''')
          bdb.execute('''
              estimate dependence probability of xe with ye within p
                  modeled by g1
          ''')
  
          with pytest.raises(BQLError):
              bdb.execute(
                  'estimate mutual information of xe with ye within p')
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate mutual information of xe with ye within p
                      modeled by g0
              ''')
          bdb.execute('''
              estimate mutual information of xe with ye within p
                  modeled by g1
          ''')
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
files: tests/test_nig_normal.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import pytest
  
  import bayeslite.core as core
  
  from bayeslite import BQLError
  from bayeslite import bayesdb_open
  from bayeslite import bayesdb_register_backend
  from bayeslite.backends.nig_normal import NIGNormalBackend
  
  def test_nig_normal_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x) values(?)', (x,))
          bdb.execute('create population p for t(x numerical)')
          bdb.execute('create generator g for p using nig_normal')
          bdb.execute('initialize 1 model for g')
          bdb.execute('analyze g for 1 iteration')
          bdb.execute('estimate probability density of x = 50 from p').fetchall()
          bdb.execute('simulate x from p limit 1').fetchall()
          bdb.execute('drop models from g')
          bdb.execute('drop generator g')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_numbering():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(id integer primary key, x, y)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x, y) values(?, ?)', (x, x*x - 100))
          bdb.execute('''
              create population p for t(id ignore; model x,y as numerical)
          ''')
          assert core.bayesdb_has_population(bdb, 'p')
          pid = core.bayesdb_get_population(bdb, 'p')
          assert core.bayesdb_variable_numbers(bdb, pid, None) == [1, 2]
  
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
  
          assert core.bayesdb_has_generator(bdb, pid, 'g0')
          g0 = core.bayesdb_get_generator(bdb, pid, 'g0')
          assert core.bayesdb_has_generator(bdb, pid, 'g1')
          g1 = core.bayesdb_get_generator(bdb, pid, 'g1')
          assert core.bayesdb_variable_numbers(bdb, pid, None) == [1, 2]
          assert core.bayesdb_variable_numbers(bdb, pid, g0) == [1, 2]
          assert core.bayesdb_variable_numbers(bdb, pid, g1) == [-1, 1, 2]
  
  def test_nig_normal_latent_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x) values(?)', (x,))
          bdb.execute('create population p for t(x numerical)')
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # PROBABILITY DENSITY OF x = v
          bdb.execute('estimate probability density of x = 50 within p') \
              .fetchall()
          with pytest.raises(BQLError):
              bdb.execute('estimate probability density of xe = 1 within p') \
                  .fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 within p modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 1 within p modeled by g1
          ''').fetchall()
  
          # PREDICTIVE PROBABILITY OF x
          bdb.execute('estimate predictive probability of x from p').fetchall()
          with pytest.raises(BQLError):
              bdb.execute(
                  'estimate predictive probability of xe from p').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate predictive probability of xe from p modeled by g0
              ''').fetchall()
          for r, p_xe in bdb.execute('''
              estimate rowid, predictive probability of xe from p modeled by g1
          '''):
              assert p_xe is None, 'rowid %r p(xe) %r' % (r, p_xe)
  
          # INFER/PREDICT
          bdb.execute(
              'INFER EXPLICIT PREDICT x CONFIDENCE x_c FROM p').fetchall()
          with pytest.raises(BQLError):
              bdb.execute(
                  'INFER EXPLICIT PREDICT xe CONFIDENCE xe_c FROM p').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  INFER EXPLICIT PREDICT xe CONFIDENCE xe_c FROM p
                      MODELED BY g0
              ''').fetchall()
          bdb.execute('''
              INFER EXPLICIT PREDICT xe CONFIDENCE xe_c FROM p
                  MODELED BY g1
          ''').fetchall()
  
          # SIMULATE x
          bdb.execute('simulate x from p limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate x, xe from p limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute(
                  'simulate x, xe from p modeled by g0 limit 1').fetchall()
          bdb.execute('simulate x, xe from p modeled by g1 limit 1').fetchall()
  
          assert 100 == len(bdb.execute('''
              estimate similarity in the context of x from pairwise p limit 100
          ''').fetchall())
          assert 1 == len(bdb.execute('''
              estimate similarity in the context of x
              from pairwise p modeled by g0 limit 1
          ''').fetchall())
                  # No such column xe in g0.
          with pytest.raises(BQLError):
              assert 1 == len(bdb.execute('''
                  estimate similarity in the context of xe
                      from pairwise p modeled by g0 limit 1
              ''').fetchall())
          # Column xe exists in g1.
          assert 1 == len(bdb.execute('''
              estimate similarity in the context of xe
                  from pairwise p modeled by g1 limit 1
          ''').fetchall())
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_conditional_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x) values(?)', (x,))
          bdb.execute('create population p for t(x numerical)')
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # observed given observed
          bdb.execute('''
              estimate probability density of x = 50 given (x = 50) within p
          ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (x = 50) within p
                  modeled by g0
          ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (x = 50) within p
                  modeled by g1
          ''').fetchall()
  
          # observed given latent
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of x = 50 given (xe = 50) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of x = 50 given (xe = 50) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (xe = 50) within p
                  modeled by g1
          ''').fetchall()
  
          # latent given observed
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 50 given (x = 50) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 50 given (x = 50) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 50 given (x = 50) within p
                  modeled by g1
          ''').fetchall()
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_2var_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x, y)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x, y) values(?, ?)',
                  (x, x*x - 100))
          bdb.execute('create population p for t(x numerical; y numerical)')
  
          # CORRELATION, CORRELATION PVALUE, without generators.
          assert 4 == len(bdb.execute('''
              estimate correlation, correlation pvalue
                  from pairwise variables of p
          ''').fetchall())
  
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # CORRELATION, CORRELATION PVALUE, with generators.
          assert 4 == len(bdb.execute('''
              estimate correlation, correlation pvalue
                  from pairwise variables of p
          ''').fetchall())
          assert 4 == len(bdb.execute('''
              estimate correlation, correlation pvalue
                  from pairwise variables of p modeled by g0
          ''').fetchall())
          with pytest.raises(BQLError):
              # g1 has a latent variable xe.
              assert 4 == len(bdb.execute('''
                  estimate correlation, correlation pvalue
                      from pairwise variables of p modeled by g1
              ''').fetchall())
  
          # DEPENDENCE PROBABILITY, MUTUAL INFORMATION
          assert 4 == len(bdb.execute('''
              estimate dependence probability, mutual information
                  from pairwise variables of p
          ''').fetchall())
          assert 4 == len(bdb.execute('''
              estimate dependence probability, mutual information
                  from pairwise variables of p modeled by g0
          ''').fetchall())
          assert 9 == len(bdb.execute('''
              estimate dependence probability, mutual information
                  from pairwise variables of p modeled by g1
          ''').fetchall())
  
          # SIMULATE LATENT VARIABLE
          assert 10 == len(bdb.execute('''
              simulate xe from p modeled by g1 limit 10;
          ''').fetchall())
          assert 10 == len(bdb.execute('''
              simulate y, xe from p modeled by g1 limit 10;
          ''').fetchall())
          # Cannot simulate the latent xe from the population p.
          with pytest.raises(BQLError):
              assert 10 == len(bdb.execute('''
                  simulate xe from p limit 10;
              ''').fetchall())
          # Cannot simulate the latent xe from the generator g0.
          with pytest.raises(BQLError):
              assert 10 == len(bdb.execute('''
                  simulate xe from p modeled by g0 limit 10;
              ''').fetchall())
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_2var_conditional_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x, y)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x, y) values(?, ?)',
                  (x, x*x - 100))
          bdb.execute('create population p for t(x numerical; y numerical)')
  
          # CORRELATION, CORRELATION PVALUE, without generators.
          assert 4 == len(bdb.execute('''
              estimate correlation, correlation pvalue
                  from pairwise variables of p
          ''').fetchall())
  
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(xe deviation(x))
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # observed given other observed
          bdb.execute('''
              estimate probability density of x = 50 given (y = 49) within p
          ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (y = 49) within p
                  modeled by g0
          ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (y = 49) within p
                  modeled by g1
          ''').fetchall()
          bdb.execute('simulate x from p given y = 49 limit 1').fetchall()
          bdb.execute('''
              simulate x from p modeled by g0 given y = 49 limit 1
          ''').fetchall()
          bdb.execute('''
              simulate x from p modeled by g1 given y = 49 limit 1
          ''').fetchall()
  
          # observed given related latent
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of x = 50 given (xe = 1) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of x = 50 given (xe = 1) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of x = 50 given (xe = 1) within p
                  modeled by g1
          ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate x from p given xe = 1 limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate x from p modeled by g0 given xe = 1 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate x from p modeled by g1 given xe = 1 limit 1
          ''').fetchall()
  
          # observed given unrelated latent
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of y = 50 given (xe = 1) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of y = 50 given (xe = 1) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of y = 50 given (xe = 1) within p
                  modeled by g1
          ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate y from p given xe = 1 limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate y from p modeled by g0 given xe = 1 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate y from p modeled by g1 given xe = 1 limit 1
          ''').fetchall()
  
          # latent given related observed
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (x = 50) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (x = 50) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 1 given (x = 50) within p
                  modeled by g1
          ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate xe from p given x = 50 limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate xe from p modeled by g0 given x = 50 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate xe from p modeled by g1 given x = 50 limit 1
          ''').fetchall()
  
          # latent given unrelated observed
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (y = 50) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (y = 50) within p
                      modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 1 given (y = 50) within p
                  modeled by g1
          ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('simulate xe from p given y = 50 limit 1').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate xe from p modeled by g0 given y = 50 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate xe from p modeled by g1 given y = 50 limit 1
          ''').fetchall()
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
  def test_nig_normal_latent_2var2lat_conditional_smoke():
      with bayesdb_open(':memory:') as bdb:
          bayesdb_register_backend(bdb, NIGNormalBackend())
          bdb.sql_execute('create table t(x, y)')
          for x in xrange(100):
              bdb.sql_execute('insert into t(x, y) values(?, ?)',
                  (x, x*x - 100))
          bdb.execute('create population p for t(x numerical; y numerical)')
          bdb.execute('create generator g0 for p using nig_normal')
          bdb.execute('''
              create generator g1 for p using nig_normal(
                  xe deviation(x),
                  ye deviation(y)
              )
          ''')
          bdb.execute('initialize 1 model for g0')
          bdb.execute('analyze g0 for 1 iteration')
          bdb.execute('initialize 1 model for g1')
          bdb.execute('analyze g1 for 1 iteration')
  
          # latent given latent
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (ye = -1) within p
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate probability density of xe = 1 given (ye = -1) within p
                       modeled by g0
              ''').fetchall()
          bdb.execute('''
              estimate probability density of xe = 1 given (ye = -1) within p
                   modeled by g1
          ''').fetchall()
  
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate xe from p given ye = -1 limit 1
              ''').fetchall()
          with pytest.raises(BQLError):
              bdb.execute('''
                  simulate xe from p modeled by g0 given ye = -1 limit 1
              ''').fetchall()
          bdb.execute('''
              simulate xe from p modeled by g1 given ye = -1 limit 1
          ''').fetchall()
  
          with pytest.raises(BQLError):
              bdb.execute(
                  'estimate dependence probability of xe with ye within p')
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate dependence probability of xe with ye within p
                      modeled by g0
              ''')
          bdb.execute('''
              estimate dependence probability of xe with ye within p
                  modeled by g1
          ''')
  
          with pytest.raises(BQLError):
              bdb.execute(
                  'estimate mutual information of xe with ye within p')
          with pytest.raises(BQLError):
              bdb.execute('''
                  estimate mutual information of xe with ye within p
                      modeled by g0
              ''')
          bdb.execute('''
              estimate mutual information of xe with ye within p
                  modeled by g1
          ''')
  
          bdb.execute('drop models from g0')
          bdb.execute('drop generator g0')
          bdb.execute('drop models from g1')
          bdb.execute('drop generator g1')
          bdb.execute('drop population p')
          bdb.execute('drop table t')
  
files: tests/test_nig_normal.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import contextlib
  import itertools
  import pytest
  
  import bayeslite
  import bayeslite.ast as ast
  import bayeslite.parse as parse
  
  def parse_bql_string(string):
      phrases = list(parse.parse_bql_string(string))
      phrase_pos = list(parse.parse_bql_string_pos(string))
      assert len(phrases) == len(phrase_pos)
      start = 0
      for i in range(len(phrase_pos)):
          phrase, pos = phrase_pos[i]
          assert phrases[i] == phrase
          substring = buffer(string, start, len(string) - start)
          phrase0, pos0 = parse.parse_bql_string_pos_1(substring)
          assert phrase0 == phrase
          assert pos0 == pos - start
          start = pos
      return phrases
  
  def test_empty():
      assert [] == parse_bql_string('')
      assert [] == parse_bql_string(';')
      assert [] == parse_bql_string(';;')
      assert [] == parse_bql_string(' ;')
      assert [] == parse_bql_string('; ')
      assert [] == parse_bql_string(' ; ')
      assert [] == parse_bql_string(' ; ; ')
  
  def test_multiquery():
      assert parse_bql_string('select 0; select 1;') == [
          ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None),
          ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(1)), None)],
              None, None, None, None, None),
      ]
      assert parse_bql_string('select 0; select 1') == [
          ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None),
          ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(1)), None)],
              None, None, None, None, None),
      ]
  
  def test_select_trivial():
      assert parse_bql_string('select null;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitNull(None)), None)],
              None, None, None, None, None)]
      assert parse_bql_string("select 'x';") == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitString('x')), None)],
              None, None, None, None, None)]
      assert parse_bql_string("select 'x''y';") == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitString("x'y")), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select "x";') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'x'), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select "x""y";') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'x"y'), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 0.;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select .0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 0.0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1e0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1e+1;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(10)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1e-1;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(.1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1.e0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select .1e0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(.1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select .1e1;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1.e10;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(1e10)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select all 0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select distinct 0;') == \
          [ast.Select(ast.SELQUANT_DISTINCT,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 0 as z;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), 'z')],
              None, None, None, None, None)]
      assert parse_bql_string('select * from t;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select t1.* from t1;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll('t1')],
              [ast.SelTab('t1', None)], None, None, None, None)]
      assert parse_bql_string('select c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'c'), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select c as d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'c'), 'd')],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select t.c as d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol('t', 'c'), 'd')],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select t.c as d, p as q, x from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpCol('t', 'c'), 'd'),
                  ast.SelColExp(ast.ExpCol(None, 'p'), 'q'),
                  ast.SelColExp(ast.ExpCol(None, 'x'), None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select * from t, u;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None), ast.SelTab('u', None)],
              None, None, None, None)]
      assert parse_bql_string('select * from t as u;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', 'u')],
              None, None, None, None)]
      assert parse_bql_string('select * where x;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, ast.ExpCol(None, 'x'), None, None, None)]
      assert parse_bql_string('select * from t where x;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None)],
              ast.ExpCol(None, 'x'), None, None, None)]
      assert parse_bql_string('select * group by x;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, ast.Grouping([ast.ExpCol(None, 'x')], None),
              None, None)]
      assert parse_bql_string('select * from t where x group by y;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None)],
              ast.ExpCol(None, 'x'),
              ast.Grouping([ast.ExpCol(None, 'y')], None), None, None)]
      assert parse_bql_string('select * from t where x group by y, z;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None)],
              ast.ExpCol(None, 'x'),
              ast.Grouping([ast.ExpCol(None, 'y'), ast.ExpCol(None, 'z')], None),
              None, None)]
      assert parse_bql_string('select * order by x;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * order by x asc;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * order by x desc;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_DESC)],
              None)]
      assert parse_bql_string('select * order by x, y;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None,
              [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_ASC),
               ast.Ord(ast.ExpCol(None, 'y'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * order by x desc, y;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None,
              [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_DESC),
               ast.Ord(ast.ExpCol(None, 'y'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * order by x, y asc;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None,
              [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_ASC),
               ast.Ord(ast.ExpCol(None, 'y'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * limit 32;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, None,
              ast.Lim(ast.ExpLit(ast.LitInt(32)), None))]
      assert parse_bql_string('select * limit 32 offset 16;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, None,
              ast.Lim(ast.ExpLit(ast.LitInt(32)), ast.ExpLit(ast.LitInt(16))))]
      assert parse_bql_string('select * limit 16, 32;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, None,
              ast.Lim(ast.ExpLit(ast.LitInt(32)), ast.ExpLit(ast.LitInt(16))))]
      assert parse_bql_string('select (select0);') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'select0'), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select (select 0);') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpSub(ast.Select(ast.SELQUANT_ALL,
                      [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
                      None, None, None, None, None)
                  ),
                  None,
              )],
              None, None, None, None, None)]
      assert parse_bql_string('select f(f(), f(x), f(*), f(distinct x), y);') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpApp(False, 'f', [
                      ast.ExpApp(False, 'f', []),
                      ast.ExpApp(False, 'f', [ast.ExpCol(None, 'x')]),
                      ast.ExpAppStar('f'),
                      ast.ExpApp(True, 'f', [ast.ExpCol(None, 'x')]),
                      ast.ExpCol(None, 'y'),
                  ]),
                  None,
              )],
              None, None, None, None, None)]
  
  def test_select_bql():
      assert parse_bql_string('select predictive probability of c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLPredProb(
                  [ast.ColListLit(['c'])], []), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select predictive probability of (c) from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLPredProb(
                  [ast.ColListLit(['c'])], []), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select predictive probability of c, * from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpBQLPredProb(
                      [ast.ColListLit(['c'])], []), None),
                  ast.SelColAll(None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select predictive probability of (*), * from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(
                      ast.ExpBQLPredProb([ast.ColListAll()], []), None),
                  ast.SelColAll(None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
          'select c, predictive probability of d given (c, f) from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpCol(None, 'c'), None),
                  ast.SelColExp(ast.ExpBQLPredProb(
                      [ast.ColListLit(['d'])],
                      [ast.ColListLit(['c']), ast.ColListLit(['f'])]),
                      None
                  ),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
          'select c, predictive probability of (d, w, *) given (c, *) from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpCol(None, 'c'), None),
                  ast.SelColExp(ast.ExpBQLPredProb(
                      [ast.ColListLit(['d']), ast.ColListLit(['w']),
                          ast.ColListAll()],
                      [ast.ColListLit(['c']), ast.ColListAll()]),
                      None
                  ),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select predictive probability of c, d from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpBQLPredProb(
                      [ast.ColListLit(['c'])], []), None),
                  ast.SelColExp(ast.ExpCol(None, 'd'), None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select probability density of c = 42'
              ' from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLProbDensity([('c', ast.ExpLit(ast.LitInt(42)))],
                      []),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      # Using * for context will later cause a crash in the compiler!
      assert parse_bql_string(
          'select similarity in the context of * from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(None, None, [ast.ColListAll()]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
          'select similarity to (rowid=8) in the context of s from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      None,
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(8))
                      )),
                      [ast.ColListLit(['s'])]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      with pytest.raises(parse.BQLParseError):
          # Cannot use similarity of without to.
          parse_bql_string(
              'select similarity of (rowid=8) in the context of r from t')
      assert parse_bql_string(
          'select similarity in the context of c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLSim(None, None, [ast.ColListLit(['c'])]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select similarity to (rowid=5) in the context of age from t1;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      None,
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(5)),
                      )),
                      [ast.ColListLit(['age'])]),
                  None)],
              [ast.SelTab('t1', None)], None, None, None, None)]
      assert parse_bql_string(
              'select similarity to (rowid=8) in the context of c, d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(
                      ast.ExpBQLSim(
                          None,
                          ast.ExpOp(ast.OP_EQ, (
                              ast.ExpCol(None, 'rowid'),
                              ast.ExpLit(ast.LitInt(8)),
                          )),
                          [ast.ColListLit(['c'])]),
                      None),
                  ast.SelColExp(ast.ExpCol(None, 'd'), None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select similarity to (rowid=8) in the context of' +
              ' (estimate * from columns of t order by ' +
              '  probability density of value 4 limit 1)' +
              ' from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      None,
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(8)),
                      )),
                      [ast.ColListSub(
                          ast.EstCols(
                              [ast.SelColAll(None)], 't', None, None, None,
                              [ast.Ord(ast.ExpBQLProbDensityFn(
                                      ast.ExpLit(ast.LitInt(4)),
                                      []),
                                  ast.ORD_ASC)],
                              ast.Lim(ast.ExpLit(ast.LitInt(1)), None))
                      )]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      # Required TO variable.
      with pytest.raises(parse.BQLParseError):
          parse_bql_string(
              'select similarity of ("name" = \'Bar\') from t;')
      # Missing context variable.
      with pytest.raises(bayeslite.BQLParseError):
          parse_bql_string(
              'select similarity of ("name" = \'Bar\') to (rowid=8) '
                  'AS "sim_bar_8" from t;')
      assert parse_bql_string(
              'select similarity of ("name" = \'Bar\') to (rowid=8) '
                  'in the context of c AS "sim_bar_8" from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'name'),
                          ast.ExpLit(ast.LitString('Bar')),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(8)),
                      )),
                      [ast.ColListLit(['c'])]),
                  'sim_bar_8')],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select similarity of ("name" = \'Bar\') to (rowid=8)' +
              'in the context of (estimate * from columns of t ' +
                  'order by probability density of value 4 limit 1)' +
              'from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'name'),
                          ast.ExpLit(ast.LitString('Bar')),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(8)),
                      )),
                      [ast.ColListSub(
                          ast.EstCols(
                              [ast.SelColAll(None)], 't', None, None, None,
                              [ast.Ord(ast.ExpBQLProbDensityFn(
                                      ast.ExpLit(ast.LitInt(4)),
                                      []),
                                  ast.ORD_ASC)],
                              ast.Lim(ast.ExpLit(ast.LitInt(1)), None))
                      )]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select dependence probability with c from t;') ==\
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLDepProb('c', None), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select dependence probability of c with d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLDepProb('c', 'd'), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select mutual information with c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLMutInf(['c'], None, None, None), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select mutual information with (c) from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLMutInf(['c'], None, None, None), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select mutual information of c with (d) from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLMutInf(['c'], ['d'], None, None),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select mutual information of (a, b, q) with (d, r) '
              'given (f, z=2, w) from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLMutInf(
                  ['a', 'b', 'q'], ['d', 'r'],
                  [('f', ast.ExpLit(ast.LitNull(0))),
                      ('z',ast.ExpLit(ast.LitInt(2))),
                      ('w', ast.ExpLit(ast.LitNull(0)))],
                  None),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select mutual information of c with d' +
              ' using (1+2) samples from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLMutInf(
                      ['c'], ['d'], None,
                      ast.op(
                          ast.OP_ADD, ast.ExpLit(ast.LitInt(1)),
                          ast.ExpLit(ast.LitInt(2)))),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
              select mutual information with c given (d, a=1) using
              10 samples from t;
              ''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLMutInf(
                      ['c'], None,
                      [('d', ast.ExpLit(ast.LitNull(0))),
                          ('a',ast.ExpLit(ast.LitInt(1)))],
                      ast.ExpLit(ast.LitInt(10))
                  ),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
              select mutual information with (c, f) given (d, a=1) using
              10 samples from t;
              ''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLMutInf(
                      ['c', 'f'],
                      None,
                      [('d', ast.ExpLit(ast.LitNull(0))),
                          ('a',ast.ExpLit(ast.LitInt(1)))],
                      ast.ExpLit(ast.LitInt(10))
                  ),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
              select mutual information of b with c
              given (d, a=1, e, r=2) from t;''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLMutInf(
                      ['b'],
                      ['c'],
                      [
                          ('d', ast.ExpLit(ast.LitNull(0))),
                          ('a',ast.ExpLit(ast.LitInt(1))),
                          ('e', ast.ExpLit(ast.LitNull(0))),
                          ('r', ast.ExpLit(ast.LitInt(2))),
                      ],
                      None
                  ),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select correlation with c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLCorrel('c', None), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select correlation of c with d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLCorrel('c', 'd'), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      # XXX This got broken a while ago: parenthesization in PROBABILITY
      # OF X = E is too permissive.  I didn't notice because before I
      # introduced BQLParseError, this simply caught Exception -- which
      # covered the AssertionError that this turned into.
      #
      # with pytest.raises(parse.BQLParseError):
      #     parse_bql_string('select probability density of x = 1 -' +
      #         ' probability density of y = 0 from t;')
      #     # XXX Should really be this test, but getting the grammar to
      #     # admit this unambiguously is too much of a pain at the
      #     # moment.
      #     assert parse_bql_string('select probability density of x = 1 -' +
      #             ' probability density of y = 0 from t;') == \
      #         [ast.Select(ast.SELQUANT_ALL,
      #             [ast.SelColExp(ast.ExpBQLProbDensity([('x',
      #                         ast.ExpOp(ast.OP_SUB, (
      #                             ast.ExpLit(ast.LitInt(1)),
      #                             ast.ExpBQLProbDensity([('y',
      #                                     ast.ExpLit(ast.LitInt(0)))],
      #                                 []),
      #                         )))],
      #                     []),
      #                 None)],
      #             [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select probability density of c1 = f(c2)'
              ' from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLProbDensity([('c1',
                          ast.ExpApp(False, 'f', [ast.ExpCol(None, 'c2')]))],
                      []),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select key, t.(estimate * from columns of t'
              ' order by dependence probability with c desc limit 4)'
              ' from t order by key asc') == \
          [ast.Select(ast.SELQUANT_ALL, [
                  ast.SelColExp(ast.ExpCol(None, 'key'), None),
                  ast.SelColSub('t',
                      ast.EstCols([ast.SelColAll(None)], 't', None, None, None,
                          [ast.Ord(ast.ExpBQLDepProb('c', None), ast.ORD_DESC)],
                          ast.Lim(ast.ExpLit(ast.LitInt(4)), None)))
              ],
              [ast.SelTab('t', None)],
              None, None,
              [ast.Ord(ast.ExpCol(None, 'key'), ast.ORD_ASC)],
              None)]
  
  def test_predictive_relevance():
      with pytest.raises(parse.BQLParseError):
          # No ofcondition, tocondition, or constraints.
          parse_bql_string(
              'select predictive relevance in the context of f from t;')
      with pytest.raises(parse.BQLParseError):
          # No tocondition, or constraints.
          parse_bql_string(
              'select predictive relevance of (rowid=8) '
              'in the context of q from t')
      assert parse_bql_string(
          'select predictive relevance to existing rows (rowid=8 AND age < 10) '
          'in the context of "s" from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLPredRel(
                      ofcondition=None,
                      tocondition=ast.ExpOp(ast.OP_BOOLAND, (
                          ast.ExpOp(ast.OP_EQ, (
                              ast.ExpCol(None, 'rowid'),
                              ast.ExpLit(ast.LitInt(8))
                          )),
                          ast.ExpOp(ast.OP_LT, (
                              ast.ExpCol(None, 'age'),
                              ast.ExpLit(ast.LitInt(10))
                          )),
                      )),
                      hypotheticals=None,
                      column=[ast.ColListLit(['s'])]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
          select predictive relevance
              of (name = 'Uganda')
              to hypothetical rows with values (
                  ("gdp_per_capita" = 82, "mortality" = 14),
                  ("gdp_per_capita" = 74, continent = 'Europe', "mortality" = 7)
              )
              in the context of
                "gdp_per_capita"
          from t
          ''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLPredRel(
                      ofcondition=ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'name'),
                          ast.ExpLit(ast.LitString('Uganda'))
                      )),
                      tocondition=None,
                      hypotheticals=[[
                          ('gdp_per_capita',ast.ExpLit(ast.LitInt(82))),
                          ('mortality',ast.ExpLit(ast.LitInt(14)))
                      ],
                      [
                          ('gdp_per_capita',ast.ExpLit(ast.LitInt(74))),
                          ('continent',ast.ExpLit(ast.LitString('Europe'))),
                          ('mortality',ast.ExpLit(ast.LitInt(7))),
                      ]],
                      column=[ast.ColListLit(['gdp_per_capita'])],
                  ),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
          select predictive relevance
              of (name = 'Uganda')
              to existing rows (rowid between 1 AND 100)
              and hypothetical rows with values (
                  ("gdp_per_capita" = 82, "mortality" = 14),
                  ("gdp_per_capita" = 74, continent = 'Europe', "mortality" = 7)
              )
              in the context of
                "gdp_per_capita"
          from t
          ''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLPredRel(
                      ofcondition=ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'name'),
                          ast.ExpLit(ast.LitString('Uganda'))
                      )),
                      tocondition=ast.ExpOp(ast.OP_BETWEEN,
                          (ast.ExpCol(None, 'rowid'),
                              ast.ExpLit(ast.LitInt(1)),
                              ast.ExpLit(ast.LitInt(100)))),
                      hypotheticals=[[
                          ('gdp_per_capita',ast.ExpLit(ast.LitInt(82))),
                          ('mortality',ast.ExpLit(ast.LitInt(14)))
                      ],
                      [
                          ('gdp_per_capita',ast.ExpLit(ast.LitInt(74))),
                          ('continent',ast.ExpLit(ast.LitString('Europe'))),
                          ('mortality',ast.ExpLit(ast.LitInt(7))),
                      ]],
                      column=[ast.ColListLit(['gdp_per_capita'])],
                  ),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
  
  def test_trivial_scan_error():
      with pytest.raises(parse.BQLParseError):
          parse_bql_string('select 0c;')
      with pytest.raises(parse.BQLParseError):
          parse_bql_string('select 1.0p1;')
  
  def test_trivial_precedence_error():
      with pytest.raises(parse.BQLParseError):
          parse_bql_string('select similarity to similarity to 0' +
              ' in the context of c from t;')
  
  def test_trivial_commands():
      assert parse_bql_string('''
          create population satellites for satellites_ucs (
              guess(*);
          )
      ''') == \
          [ast.CreatePop(False, 'satellites', 'satellites_ucs', [
              ast.PopGuessVars('*'),
          ])]
      assert parse_bql_string('''
          create population satellites for satellites_ucs (
              guess stattypes of launch_site, "contracto=r";
              set stattype of country_of_operator, orbit_type to categorical;
              set stattype of launch_mass to numerical;
              set stattype of perigee to numerical;
              set stattype of apogee, period to numerical;
          )
      ''') == \
          [ast.CreatePop(False, 'satellites', 'satellites_ucs', [
              ast.PopGuessVars(['launch_site', 'contracto=r']),
              ast.PopModelVars(
                  ['country_of_operator', 'orbit_type'], 'categorical'),
              ast.PopModelVars(['launch_mass'], 'numerical'),
              ast.PopModelVars(['perigee'], 'numerical'),
              ast.PopModelVars(['apogee', 'period'], 'numerical'),
          ])]
      assert parse_bql_string('''
          create population satellites for satellites_ucs (
              set stattype of country_of_operator, orbit_type to categorical;;
              set stattype of apogee, period to numerical;;
          )
      ''') == \
          [ast.CreatePop(False, 'satellites', 'satellites_ucs', [
              ast.PopModelVars(
                  ['country_of_operator', 'orbit_type'], 'categorical'),
              ast.PopModelVars(['apogee', 'period'], 'numerical'),
          ])]
      assert parse_bql_string('''
          create population satellites for satellites_ucs (
              country_of_operator categorical;
              orbit_type          categorical;
              launch_mass         numerical;
              perigee             numerical;
              apogee              numerical;
              period              numerical;
          )
      ''') == \
          [ast.CreatePop(False, 'satellites', 'satellites_ucs', [
              ast.PopModelVars(['country_of_operator'], 'categorical'),
              ast.PopModelVars(['orbit_type'], 'categorical'),
              ast.PopModelVars(['launch_mass'], 'numerical'),
              ast.PopModelVars(['perigee'], 'numerical'),
              ast.PopModelVars(['apogee'], 'numerical'),
              ast.PopModelVars(['period'], 'numerical'),
          ])]
      assert parse_bql_string('drop population satellites') == \
          [ast.DropPop(False, 'satellites')]
      assert parse_bql_string('create generator t_cc for t using cgpm'
              '(xyz numerical, pqr categorical, lmn cyclic)') == \
          [ast.CreateGen('t_cc', False, 't', 'cgpm', [
              ['xyz', 'numerical'],
              ['pqr', 'categorical'],
              ['lmn', 'cyclic'],
          ])]
      assert parse_bql_string('create generator t_cc for t'
              '(xyz numerical, pqr categorical, lmn cyclic)') == \
          [ast.CreateGen(
              't_cc', False, 't',
              None,       # Backend defaults to cgpm.
              [
                  ['xyz', 'numerical'],
                  ['pqr', 'categorical'],
                  ['lmn', 'cyclic'],
          ])]
      assert parse_bql_string('create generator t_cc if not exists'
              ' for t using cgpm'
              '(xyz numerical, pqr categorical, lmn cyclic)') == \
          [ast.CreateGen('t_cc', True, 't', 'cgpm', [
              ['xyz', 'numerical'],
              ['pqr', 'categorical'],
              ['lmn', 'cyclic'],
          ])]
      assert parse_bql_string('create generator if not exists t_cc'
              ' for t using cgpm'
              '(xyz numerical, pqr categorical, lmn cyclic)') == \
          [ast.CreateGen('t_cc', True, 't', 'cgpm', [
              ['xyz', 'numerical'],
              ['pqr', 'categorical'],
              ['lmn', 'cyclic'],
          ])]
      # XXX Schema of [[]] instead of [] is kinda wacky.  Fix?  (But
      # make sure the empty-parens and no-parens cases are equivalent.)
      assert parse_bql_string('create generator t_cc'
              ' for t using cgpm()') == \
          [ast.CreateGen('t_cc', False, 't', 'cgpm', [[]])]
      assert parse_bql_string('create generator t_cc'
              ' for t using cgpm') == \
          [ast.CreateGen('t_cc', False, 't', 'cgpm', [[]])]
      assert parse_bql_string('initialize 1 model for t;') == \
          [ast.InitModels(False, 't', 1)]
      assert parse_bql_string('initialize 1 model if not exists for t;') == \
          [ast.InitModels(True, 't', 1)]
      assert parse_bql_string('initialize 2 models for t;') == \
          [ast.InitModels(False, 't', 2)]
      assert parse_bql_string('initialize 2 models if not exists for t;') == \
          [ast.InitModels(True, 't', 2)]
      assert parse_bql_string('create temporary table tx as'
              ' infer explicit x, predict x as xi confidence xc from t_cc') == \
          [ast.CreateTabAs(True, False, 'tx',
              ast.InferExplicit(
                  [
                      ast.SelColExp(ast.ExpCol(None, 'x'), None),
                      ast.PredCol('x', 'xi', 'xc', None),
                  ],
                  't_cc', None, None, None, None, None, None,
              ))]
  
  def test_analyze():
      assert parse_bql_string('analyze t for 1 iteration;') == \
          [ast.AnalyzeModels('t', None, 1, None, None, None, None)]
      assert parse_bql_string('analyze t for 7 seconds or 1 iteration;') == \
          [ast.AnalyzeModels('t', None, 1, 7, None, None, None)]
      assert parse_bql_string('analyze t for 1 iteration;') == \
          [ast.AnalyzeModels('t', None, 1, None, None, None, None)]
      assert parse_bql_string('analyze t for 1 minute;') == \
          [ast.AnalyzeModels('t', None, None, 60, None, None, None)]
      assert parse_bql_string('analyze t for 1 minute;') == \
          [ast.AnalyzeModels('t', None, None, 60, None, None, None)]
      assert parse_bql_string('analyze t for 2 minutes;') == \
          [ast.AnalyzeModels('t', None, None, 120, None, None, None)]
      assert parse_bql_string('analyze t for 100 iterations or 2 minutes;') == \
          [ast.AnalyzeModels('t', None, 100, 120, None, None, None)]
      assert parse_bql_string('analyze t for 2 minutes;') == \
          [ast.AnalyzeModels('t', None, None, 120, None, None, None)]
      assert parse_bql_string('analyze t for 1 second;') == \
          [ast.AnalyzeModels('t', None, None, 1, None, None, None)]
      assert parse_bql_string('analyze t for 1 second;') == \
          [ast.AnalyzeModels('t', None, None, 1, None, None, None)]
      assert parse_bql_string('analyze t for 2 seconds;') == \
          [ast.AnalyzeModels('t', None, None, 2, None, None, None)]
      assert parse_bql_string('analyze t for 2 seconds;') == \
          [ast.AnalyzeModels('t', None, None, 2, None, None, None)]
      assert parse_bql_string('analyze t model 1 for 1 iteration;') == \
          [ast.AnalyzeModels('t', [1], 1, None, None, None, None)]
      assert parse_bql_string('analyze t models 1,2,3 for 1 iteration;') == \
          [ast.AnalyzeModels('t', [1,2,3], 1, None, None, None, None)]
      assert parse_bql_string('analyze t models 1-3,5 for 1 iteration;') == \
          [ast.AnalyzeModels('t', [1,2,3,5], 1, None, None, None, None)]
      assert parse_bql_string('analyze t for 10 iterations'
              ' checkpoint 3 iterations') == \
          [ast.AnalyzeModels('t', None, 10, None, 3, None, None)]
      assert parse_bql_string('analyze t for 10 iterations'
              ' (mh(default, one, 10))') == \
          [ast.AnalyzeModels('t', None, 10, None, None, None, [
              'mh', '(', 'default', ',', 'one', ',', 10, ')'
          ])]
      assert parse_bql_string('analyze t for 10 seconds'
              ' checkpoint 3 seconds') == \
          [ast.AnalyzeModels('t', None, None, 10, None, 3, None)]
      assert parse_bql_string('analyze t for 1 minute or 10 minutes'
              ' checkpoint 3 seconds') == \
          [ast.AnalyzeModels('t', None, None, 60, None, 3, None)]
      assert parse_bql_string('analyze t for 100 iterations or 10 iterations'
              ' checkpoint 3 seconds') == \
          [ast.AnalyzeModels('t', None, 10, None, None, 3, None)]
  
  def test_altergen():
      assert parse_bql_string('alter generator g '
              'rename to rumba') == \
          [ast.AlterGen(
              generator='g',
              modelnos=None,
              commands=[ast.AlterGenRenameGen('rumba')]
          )]
      assert parse_bql_string('alter generator g models (1, 2, 4) '
              'rename to rumba') == \
          [ast.AlterGen(
              generator='g',
              modelnos=[1,2,4],
              commands=[ast.AlterGenRenameGen('rumba')]
          )]
      assert parse_bql_string('alter generator g '
              'rename to rumba, generic cmd (a,c), generic cmd2') == \
          [ast.AlterGen(
              generator='g',
              modelnos=None,
              commands=[
                  ast.AlterGenRenameGen('rumba'),
                  ast.AlterGenGeneric(['generic', 'cmd', ['a', ',', 'c']]),
                  ast.AlterGenGeneric(['generic', 'cmd2']),
              ],
          )]
      assert parse_bql_string('alter generator g models (1-4) '
              'set variable clustering of (*) to independent, '
              'set variable clustering of (*) to dependent, '
              'set variable cluster of bar to cluster of baz, '
              'set variable cluster of (roger, martin) to singleton, '
              'set variable clustering hyperparameter to 100, '
              'set row cluster of 1 to cluster of 2, '
              'set row clustering hyperparameter of cluster of variable K to 11, '
              'set row clustering hyperparameter of variable K to -1 '
          ) == \
          [ast.AlterGen(
              generator='g',
              modelnos=[1, 2, 3, 4],
              commands=[
                  ast.AlterGenGeneric([
                      'set', 'variable', 'clustering', 'of', ['*'],
                      'to', 'independent']),
                  ast.AlterGenGeneric([
                      'set', 'variable', 'clustering', 'of', ['*'],
                      'to', 'dependent']),
                  ast.AlterGenGeneric([
                      'set', 'variable', 'cluster', 'of', 'bar', 'to',
                      'cluster', 'of', 'baz']),
                  ast.AlterGenGeneric([
                      'set', 'variable', 'cluster', 'of',
                      ['roger', ',', 'martin'], 'to', 'singleton']),
                  ast.AlterGenGeneric([
                      'set', 'variable', 'clustering', 'hyperparameter',
                      'to', 100]),
                  ast.AlterGenGeneric(
                      ['set', 'row', 'cluster', 'of', 1, 'to',
                      'cluster', 'of', 2]),
                  ast.AlterGenGeneric([
                      'set', 'row', 'clustering', 'hyperparameter', 'of',
                      'cluster', 'of', 'variable', 'K', 'to', 11]),
                  ast.AlterGenGeneric([
                      'set', 'row', 'clustering', 'hyperparameter', 'of',
                      'variable', 'K', 'to', '-', 1])
          ])]
  
  def test_create_tab_csv():
      assert parse_bql_string('create temp table if not exists f '
              'from \'foo.csv\'') == \
          [ast.CreateTabCsv(True, True, 'f', 'foo.csv')]
      assert parse_bql_string('create table if not exists f '
              'from \'foo.csv\'') == \
          [ast.CreateTabCsv(False, True, 'f', 'foo.csv')]
      assert parse_bql_string('create table f '
              'from \'foo.csv\'') == \
          [ast.CreateTabCsv(False, False, 'f', 'foo.csv')]
      # Using double quotes for csv pathname should raise.
      with pytest.raises(parse.BQLParseError):
          parse_bql_string('create temp table if not exists f '
              'from "foo.csv"')
  
  def test_alterpop_stattype():
      assert parse_bql_string('alter population p '
              'set stattypes for a to normal') == \
          [ast.AlterPop('p',
              [ast.AlterPopStatType(['a'], 'normal')]
          )]
      assert parse_bql_string('alter population g '
              'set stattypes for a, b to BETA') == \
          [ast.AlterPop('g', [ast.AlterPopStatType(['a', 'b'], 'BETA')])]
      assert parse_bql_string('alter population p '
              'set stattypes for a, b to beta, '
              'set stattypes for c to nominal') == \
          [ast.AlterPop('p', [
              ast.AlterPopStatType(['a', 'b'], 'beta'),
              ast.AlterPopStatType(['c'], 'nominal'),]
          )]
  
  def test_alterpop_addvar():
      assert parse_bql_string('alter population p '
              'add variable r') == \
          [ast.AlterPop('p',
              [ast.AlterPopAddVar('r', None)]
          )]
      assert parse_bql_string('alter population g '
              'add variable r nominal') == \
          [ast.AlterPop('g',
              [ast.AlterPopAddVar('r', 'nominal')]
          )]
      assert parse_bql_string('alter population p '
              'add variable a, '
              'set stattypes for a to nominal, '
              'add variable b numerical') == \
          [ast.AlterPop('p', [
              ast.AlterPopAddVar('a', None),
              ast.AlterPopStatType(['a'], 'nominal'),
              ast.AlterPopAddVar('b', 'numerical'),]
          )]
      with pytest.raises(bayeslite.BQLParseError):
          parse_bql_string('alter population v add variable;')
  
  def test_infer_trivial():
      assert parse_bql_string('infer x from p') == \
          [ast.InferAuto([ast.InfColOne('x', None)], ast.ExpLit(ast.LitInt(0)),
              None, 'p', None, None, None, None, None, None)]
  
  def test_infer_conf():
      assert parse_bql_string('infer x with confidence 0.9 from p') == \
          [ast.InferAuto([ast.InfColOne('x', None)],
              ast.ExpLit(ast.LitFloat(0.9)), None, 'p', None, None, None, None,
              None, None)]
  
  def test_infer_samples():
      assert parse_bql_string('infer x using 42 samples from p') == \
          [ast.InferAuto([ast.InfColOne('x', None)],
              ast.ExpLit(ast.LitInt(0)), ast.ExpLit(ast.LitInt(42)), 'p',
              None, None, None, None, None, None)]
  
  def test_infer_conf_samples():
      assert parse_bql_string('infer x with confidence 0.9 using 42 samples'
              ' from p') == \
          [ast.InferAuto([ast.InfColOne('x', None)],
              ast.ExpLit(ast.LitInt(.9)), ast.ExpLit(ast.LitInt(42)), 'p', None,
              None, None, None, None, None)]
  
  def test_infer_explicit():
      assert parse_bql_string('infer explicit x, predict y with confidence 0.9,'
              ' predict p with confidence 0.8 as q, predict u confidence v,'
              ' predict a as b confidence c,'
              ' predict h confidence k using 42 samples'
              ' from p') == \
          [ast.InferExplicit([
                  ast.SelColExp(ast.ExpCol(None, 'x'), None),
                  ast.SelColExp(
                      ast.ExpBQLPredict('y', ast.ExpLit(ast.LitFloat(.9)), None),
                      None,
                  ),
                  ast.SelColExp(
                      ast.ExpBQLPredict('p', ast.ExpLit(ast.LitFloat(.8)), None),
                      'q',
                  ),
                  ast.PredCol('u', None, 'v', None),
                  ast.PredCol('a', 'b', 'c', None),
                  ast.PredCol('h', None, 'k', ast.ExpLit(ast.LitInt(42))),
              ],
              'p', None, None, None, None, None, None)]
  
  def test_infer_explicit_samples():
      assert parse_bql_string('infer explicit x, predict y with confidence 0.9,'
              ' predict p with confidence 0.8 as q, predict u confidence v,'
              ' predict a as b confidence c,'
              ' predict h confidence k using 42 samples'
              ' from p') == \
          [ast.InferExplicit([
                  ast.SelColExp(ast.ExpCol(None, 'x'), None),
                  ast.SelColExp(
                      ast.ExpBQLPredict('y', ast.ExpLit(ast.LitFloat(.9)), None),
                      None,
                  ),
                  ast.SelColExp(
                      ast.ExpBQLPredict('p', ast.ExpLit(ast.LitFloat(.8)), None),
                      'q',
                  ),
                  ast.PredCol('u', None, 'v', None),
                  ast.PredCol('a', 'b', 'c', None),
                  ast.PredCol('h', None, 'k', ast.ExpLit(ast.LitInt(42))),
              ],
              'p', None, None, None, None, None, None)]
  
  def test_parametrized():
      assert parse_bql_string('select * from t where id = ?;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_EQ, (
                      ast.ExpCol(None, 'id'),
                      ast.ExpNumpar(1),
                  )),
                  None, None, None),
              1, {})]
      assert parse_bql_string('select * from t where id = ?123;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_EQ, (
                      ast.ExpCol(None, 'id'),
                      ast.ExpNumpar(123),
                  )),
                  None, None, None),
              123, {})]
      assert parse_bql_string('select * from t where id = :foo;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_EQ, (
                      ast.ExpCol(None, 'id'),
                      ast.ExpNampar(1, ':foo'),
                  )),
                  None, None, None),
              1, {':foo': 1})]
      assert parse_bql_string('select * from t where a = :foo and b = @foo;') \
          == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_BOOLAND, (
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'a'),
                          ast.ExpNampar(1, ':foo'),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'b'),
                          ast.ExpNampar(2, '@foo'),
                      )),
                  )),
                  None, None, None),
              2, {':foo': 1, '@foo': 2})]
      assert parse_bql_string('select * from t where a = $foo and b = ?1;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_BOOLAND, (
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'a'),
                          ast.ExpNampar(1, '$foo'),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'b'),
                          ast.ExpNumpar(1),
                      )),
                  )),
                  None, None, None),
              1, {'$foo': 1})]
      assert parse_bql_string('select * from t' +
              ' where a = ?123 and b = :foo and c = ?124;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_BOOLAND, (
                      ast.ExpOp(ast.OP_BOOLAND, (
                          ast.ExpOp(ast.OP_EQ, (
                              ast.ExpCol(None, 'a'),
                              ast.ExpNumpar(123),
                          )),
                          ast.ExpOp(ast.OP_EQ, (
                              ast.ExpCol(None, 'b'),
                              ast.ExpNampar(124, ':foo'),
                          )),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'c'),
                          ast.ExpNumpar(124),
                      )),
                  )),
                  None, None, None),
              124, {':foo': 124})]
  
  def test_complete():
      assert parse.bql_string_complete_p('')
      assert parse.bql_string_complete_p(';')
      assert parse.bql_string_complete_p(';;;')
      assert parse.bql_string_complete_p('\n;\n;;;\n;\n')
      assert not parse.bql_string_complete_p('select 0')
      assert parse.bql_string_complete_p('select 0;')
      assert not parse.bql_string_complete_p('select 0\nfrom t')
      assert parse.bql_string_complete_p('select 0\nfrom t;')
      assert not parse.bql_string_complete_p('select 0;select 1')
      assert parse.bql_string_complete_p('select 0;select 1;')
      assert not parse.bql_string_complete_p('select 0;\nselect 1')
      assert parse.bql_string_complete_p('select 0;\nselect 1;')
  
  def test_simulate():
      with pytest.raises(parse.BQLParseError):
          # Need limit.
          parse_bql_string('create table s as simulate x from t')
      with pytest.raises(parse.BQLParseError):
          # Need limit.
          parse_bql_string('create table s as simulate x from t given y = 0')
      assert parse_bql_string('create table s as'
              ' simulate x from t limit 10') == \
          [ast.CreateTabAs(False, False, 's',
              ast.Simulate(
                  [ast.SelColExp(ast.ExpCol(None, 'x'), None)],
                  't',
                  None,
                  None,
                  [],
                  ast.ExpLit(ast.LitInt(10)),
                  None)
          )]
      assert parse_bql_string('create table if not exists s as'
              ' simulate x, y from t given z = 0 limit 10 accuracy 2') == \
          [ast.CreateTabAs(False, True, 's',
              ast.Simulate(
                  [
                      ast.SelColExp(ast.ExpCol(None, 'x'), None),
                      ast.SelColExp(ast.ExpCol(None, 'y'), None),
                  ],
                  't',
                  None,
                  None,
                  [('z', ast.ExpLit(ast.LitInt(0)))],
                  ast.ExpLit(ast.LitInt(10)),
                  2)
          )]
      assert parse_bql_string('create temp table s as'
              ' simulate x, y from t given z = 0 limit 10') == \
          [ast.CreateTabAs(True, False, 's',
              ast.Simulate(
                  [
                      ast.SelColExp(ast.ExpCol(None, 'x'), None),
                      ast.SelColExp(ast.ExpCol(None, 'y'), None),
                  ],
                  't',
                  None,
                  None,
                  [('z', ast.ExpLit(ast.LitInt(0)))],
                  ast.ExpLit(ast.LitInt(10)),
                  None),
          )]
      assert parse_bql_string(
              'create temp table if not exists s as'
              ' simulate x, y from t given z = 0, w = 1'
              ' limit 10 accuracy 19') == \
          [ast.CreateTabAs(True, True, 's',
              ast.Simulate(
                  [
                      ast.SelColExp(ast.ExpCol(None, 'x'), None),
                      ast.SelColExp(ast.ExpCol(None, 'y'), None),
                  ],
                  't',
                  None,
                  None,
                  [
                      ('z', ast.ExpLit(ast.LitInt(0))),
                      ('w', ast.ExpLit(ast.LitInt(1))),
                  ],
                  ast.ExpLit(ast.LitInt(10)),
                  19)
          )]
      # Specifying a quantity other than a variable should raise.
      with pytest.raises(parse.BQLParseError):
          parse_bql_string(
              'simulate a, dependence probability of a with b from t limit 10;')
      with pytest.raises(parse.BQLParseError):
          parse_bql_string(
              'create table f as simulate a, dependence probability of a with b '
              'from t limit 10;')
  
  def test_simulate_models():
      assert parse_bql_string(
          'simulate dependence probability of a with b from models of t;') == [
              ast.SimulateModels(
                  [
                      ast.SelColExp(ast.ExpBQLDepProb('a', 'b'), None),
                  ],
                  't', None
              )
      ]
      assert parse_bql_string(
          'simulate dependence probability of a with b AS q, '
          'mutual information of c with d given (e, r=2.7) '
              'using 100 samples as g '
          'from models of p modeled by z') == [
              ast.SimulateModels(
                  [
                      ast.SelColExp(
                          ast.ExpBQLDepProb('a', 'b'), 'q'),
                      ast.SelColExp(
                          ast.ExpBQLMutInf(
                              ['c'],
                              ['d'],
                              [
                                  ('e', ast.ExpLit(ast.LitNull(0))),
                                  ('r', ast.ExpLit(ast.LitFloat(2.7)))
                              ],
                              ast.ExpLit(ast.LitInt(100))),
                          'g'
                      ),
                  ],
                  'p', 'z'
              )
      ]
      assert parse_bql_string(
          'simulate dependence probability of a with b AS q, '
          'mutual information of (c) with (d, r) given (e, r=2.7) '
              'using 100 samples as g '
          'from models of p modeled by z') == [
              ast.SimulateModels(
                  [
                      ast.SelColExp(
                          ast.ExpBQLDepProb('a', 'b'), 'q'),
                      ast.SelColExp(
                          ast.ExpBQLMutInf(
                              ['c'],
                              ['d', 'r'],
                              [
                                  ('e', ast.ExpLit(ast.LitNull(0))),
                                  ('r', ast.ExpLit(ast.LitFloat(2.7)))
                              ],
                              ast.ExpLit(ast.LitInt(100))),
                          'g'
                      ),
                  ],
                  'p', 'z'
              )
      ]
      assert parse_bql_string(
          'simulate probability density of (a=2, c=1.1) given (b=0.5) '
          'from models of p') == [
              ast.SimulateModels(
                  [
                      ast.SelColExp(
                          ast.ExpBQLProbDensity(
                              [
                                  ('a', ast.ExpLit(ast.LitInt(2))),
                                  ('c', ast.ExpLit(ast.LitFloat(1.1)))
                              ],
                              [('b', ast.ExpLit(ast.LitFloat(0.5)))]),
                          None
                      ),
                  ],
                  'p', None
              )
      ]
      for temp, ifnotexists in itertools.product(
              ('temp', ''), ('if not exists', '')):
          assert parse_bql_string(
              'create %s table %s f as '
              'simulate dependence probability of a with b AS q, '
              'mutual information of "bad-col" with d '
                  'given (e, r=2.7) using 100 samples as g '
              'from models of p modeled by z' % (temp, ifnotexists)) == [
                  ast.CreateTabAs(
                      bool(temp),
                      bool(ifnotexists),
                      'f',
                      ast.SimulateModels(
                          [
                              ast.SelColExp(
                                  ast.ExpBQLDepProb('a', 'b'),
                                  'q'
                              ),
                              ast.SelColExp(
                                  ast.ExpBQLMutInf(
                                      ['bad-col'],
                                      ['d'],
                                      [
                                          ('e', ast.ExpLit(ast.LitNull(0))),
                                          ('r', ast.ExpLit(ast.LitFloat(2.7)))
                                      ],
                                      ast.ExpLit(ast.LitInt(100))),
                                  'g'
                              ),
                          ],
                          'p', 'z'
                      )
              )
          ]
  
  def test_is_bql():
      assert ast.is_bql(ast.ExpLit(ast.LitInt(0))) == False
      assert ast.is_bql(ast.ExpNumpar(0)) == False
      assert ast.is_bql(ast.ExpNampar(0, 'x')) == False
      assert ast.is_bql(ast.ExpCol('t', 'c')) == False
      # ...
      assert ast.is_bql(ast.ExpBQLPredProb([ast.ColListLit(['c'])], []))
      assert ast.is_bql(
          ast.ExpBQLProbDensity([('c', ast.ExpLit(ast.LitInt(0)))], []))
      assert ast.is_bql(ast.ExpBQLProbDensityFn(ast.ExpLit(ast.LitInt(0)), []))
      assert ast.is_bql(ast.ExpBQLSim(None, ast.ExpLit(ast.LitInt(0)), []))
      assert ast.is_bql(ast.ExpBQLDepProb('c0', 'c1'))
      assert ast.is_bql(ast.ExpBQLMutInf('c0', 'c1', None, 100))
      assert ast.is_bql(ast.ExpBQLCorrel('c0', 'c1'))
      assert ast.is_bql(ast.ExpBQLPredict('c', ast.ExpLit(ast.LitInt(.5)), None))
      assert ast.is_bql(ast.ExpBQLPredictConf('c', None))
  
  def test_regress():
      assert parse_bql_string('''
          regress t given (y) using 10 samples by pop;
      ''') == [
          ast.Regress(
              target='t',
              givens=[
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='y'), name=None),],
              nsamp=ast.ExpLit(value=ast.LitInt(value=10)),
              population='pop',
              generator=None,
              modelnos=None,
      )]
      assert parse_bql_string('''
          regress t given (y, x) using 10 samples by pop modeled by m
          using models 1, 7;
          ''') == [
          ast.Regress(
              target='t',
              givens=[
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='y'), name=None),
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='x'), name=None)],
              nsamp=ast.ExpLit(value=ast.LitInt(value=10)),
              population='pop',
              generator='m',
              modelnos=[1,7],
      )]
      assert parse_bql_string('regress t given (*) by pop;') == [
          ast.Regress(
              target='t',
              givens=[ast.SelColAll(None)],
              nsamp=None,
              population='pop',
              generator=None,
              modelnos=None,
      )]
      # Disallow this query in the compiler, mixing * with u.
      assert parse_bql_string('''
          regress t given (*, u) using 10 samples by pop modeled by m
          using models 1-3;
      ''') == [
          ast.Regress(
              target='t',
              givens=[
                  ast.SelColAll(None),
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='u'), name=None)],
              nsamp=ast.ExpLit(value=ast.LitInt(value=10)),
              population='pop',
              generator='m',
              modelnos=[1,2,3],
      )]
      # Disallow this query in the compiler, mixing subquery.
      assert parse_bql_string('''
          regress t given (
              pop.(estimate * from variables of pop limit 2),
              a, y)
          using 10 samples by pop modeled by f;
      ''') == [
          ast.Regress(
              target='t',
              givens=[
                  ast.SelColSub(table='pop',
                      query=ast.EstCols(
                          columns=[ast.SelColAll(table=None)],
                          population='pop',
                          generator=None,
                          modelnos=None,
                          condition=None,
                          order=None,
                          limit=ast.Lim(
                              limit=ast.ExpLit(value=ast.LitInt(value=2)),
                              offset=None
                  ))),
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='a'), name=None),
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='y'), name=None)
              ],
              nsamp=ast.ExpLit(value=ast.LitInt(value=10)),
              population='pop',
              generator='f',
              modelnos=None,
      )]
  
      with pytest.raises(bayeslite.BQLParseError):
          # Missing given.
          assert parse_bql_string('regress t using 10 samples by pop;')
      with pytest.raises(bayeslite.BQLParseError):
          # Missing parenthesis.
          assert parse_bql_string('regress t given * using 10 samples by pop;')
  
  def test_using_model():
      assert parse_bql_string('simulate x from t using model 42'
              ' limit 10') == [
          ast.Simulate(
              columns=[ast.SelColExp(ast.ExpCol(None, 'x'), None)],
              population='t',
              generator=None,
              modelnos=[42],
              constraints=[],
              nsamples=ast.ExpLit(ast.LitInt(10)),
              accuracy=None)
      ]
      with pytest.raises(parse.BQLParseError):
          assert parse_bql_string('simulate x from t'
                  ' using model (87)') == [
              ast.Simulate(
                  columns=[ast.SelColExp(ast.ExpCol(None, 'x'), None)],
                  population='t',
                  generator=None,
                  modelnos=[87],
                  constraints=[],
                  nsamples=ast.ExpLit(ast.LitInt(10)),
                  accuracy=None)
          ]
      assert parse_bql_string('estimate x from t modeled by g '
              'using models 1, 2') == [
          ast.Estimate(
              quantifier=ast.SELQUANT_ALL,
              columns=[ast.SelColExp(ast.ExpCol(None, 'x'), None)],
              population='t',
              generator='g',
              modelnos=[1,2],
              condition=None,
              grouping=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('estimate * from columns of t modeled by z'
              ' using models 1-3, 5, 12-14') == [
          ast.EstCols(
              columns=[ast.SelColAll(None)],
              population='t',
              generator='z',
              modelnos=[1,2,3,5,12,13,14],
              condition=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('estimate 42 from pairwise columns of t'
              ' using models 0, 7') == [
          ast.EstPairCols(
              columns=[(ast.ExpLit(ast.LitInt(42)), None)],
              population='t',
              subcolumns=None,
              generator=None,
              modelnos=[0,7],
              condition=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('estimate similarity in the context of h '
              'from pairwise t modeled by g using models 8-10') == [
          ast.EstPairRow(
              columns=[ast.SelColExp(
                  ast.ExpBQLSim(
                      None, None, [ast.ColListLit(columns=['h'])]), name=None)],
              population='t',
              generator='g',
              modelnos=[8, 9, 10],
              condition=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('infer x from t using models 7') == [
          ast.InferAuto(
              columns=[ast.InfColOne('x', None)],
              confidence=ast.ExpLit(ast.LitInt(0)),
              nsamples=None,
              population='t',
              generator=None,
              modelnos=[7],
              condition=None,
              grouping=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('infer explicit x from t using models 7, 14') == [
          ast.InferExplicit(
              columns=[ast.SelColExp(ast.ExpCol(None, 'x'), None)],
              population='t',
              generator=None,
              modelnos=[7,14],
              condition=None,
              grouping=None,
              order=None,
              limit=None)
      ]
  
  @contextlib.contextmanager
  def raises_str(klass, string):
      with pytest.raises(klass):
          try:
              yield
          except klass as e:
              assert string in str(e)
              raise
  
  def test_estimate_pairwise_deprecation():
      with raises_str(bayeslite.BQLParseError, "deprecated `ESTIMATE COLUMNS'"):
          parse_bql_string('estimate columns from t')
      with raises_str(bayeslite.BQLParseError, "deprecated `ESTIMATE PAIRWISE'"):
          parse_bql_string('estimate pairwise dependence probability from t')
      with raises_str(bayeslite.BQLParseError,
              "deprecated `ESTIMATE PAIRWISE ROW'"):
          parse_bql_string('estimate pairwise row similarity from t')
  
  def test_parse_error_with_context():
      with raises_str(bayeslite.BQLParseError, "select ( 1 +"):
          parse_bql_string('select (1 +')
      with raises_str(bayeslite.BQLParseError,
                      "Syntax error near [] after [select]"):
          parse_bql_string('select')
  
files: tests/test_parse.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import contextlib
  import itertools
  import pytest
  
  import bayeslite
  import bayeslite.ast as ast
  import bayeslite.parse as parse
  
  def parse_bql_string(string):
      phrases = list(parse.parse_bql_string(string))
      phrase_pos = list(parse.parse_bql_string_pos(string))
      assert len(phrases) == len(phrase_pos)
      start = 0
      for i in range(len(phrase_pos)):
          phrase, pos = phrase_pos[i]
          assert phrases[i] == phrase
          substring = buffer(string, start, len(string) - start)
          phrase0, pos0 = parse.parse_bql_string_pos_1(substring)
          assert phrase0 == phrase
          assert pos0 == pos - start
          start = pos
      return phrases
  
  def test_empty():
      assert [] == parse_bql_string('')
      assert [] == parse_bql_string(';')
      assert [] == parse_bql_string(';;')
      assert [] == parse_bql_string(' ;')
      assert [] == parse_bql_string('; ')
      assert [] == parse_bql_string(' ; ')
      assert [] == parse_bql_string(' ; ; ')
  
  def test_multiquery():
      assert parse_bql_string('select 0; select 1;') == [
          ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None),
          ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(1)), None)],
              None, None, None, None, None),
      ]
      assert parse_bql_string('select 0; select 1') == [
          ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None),
          ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(1)), None)],
              None, None, None, None, None),
      ]
  
  def test_select_trivial():
      assert parse_bql_string('select null;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitNull(None)), None)],
              None, None, None, None, None)]
      assert parse_bql_string("select 'x';") == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitString('x')), None)],
              None, None, None, None, None)]
      assert parse_bql_string("select 'x''y';") == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitString("x'y")), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select "x";') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'x'), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select "x""y";') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'x"y'), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 0.;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select .0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 0.0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1e0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1e+1;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(10)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1e-1;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(.1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1.e0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select .1e0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(.1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select .1e1;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(1)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 1.e10;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitFloat(1e10)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select all 0;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select distinct 0;') == \
          [ast.Select(ast.SELQUANT_DISTINCT,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select 0 as z;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), 'z')],
              None, None, None, None, None)]
      assert parse_bql_string('select * from t;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select t1.* from t1;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll('t1')],
              [ast.SelTab('t1', None)], None, None, None, None)]
      assert parse_bql_string('select c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'c'), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select c as d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'c'), 'd')],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select t.c as d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol('t', 'c'), 'd')],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select t.c as d, p as q, x from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpCol('t', 'c'), 'd'),
                  ast.SelColExp(ast.ExpCol(None, 'p'), 'q'),
                  ast.SelColExp(ast.ExpCol(None, 'x'), None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select * from t, u;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None), ast.SelTab('u', None)],
              None, None, None, None)]
      assert parse_bql_string('select * from t as u;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', 'u')],
              None, None, None, None)]
      assert parse_bql_string('select * where x;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, ast.ExpCol(None, 'x'), None, None, None)]
      assert parse_bql_string('select * from t where x;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None)],
              ast.ExpCol(None, 'x'), None, None, None)]
      assert parse_bql_string('select * group by x;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, ast.Grouping([ast.ExpCol(None, 'x')], None),
              None, None)]
      assert parse_bql_string('select * from t where x group by y;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None)],
              ast.ExpCol(None, 'x'),
              ast.Grouping([ast.ExpCol(None, 'y')], None), None, None)]
      assert parse_bql_string('select * from t where x group by y, z;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              [ast.SelTab('t', None)],
              ast.ExpCol(None, 'x'),
              ast.Grouping([ast.ExpCol(None, 'y'), ast.ExpCol(None, 'z')], None),
              None, None)]
      assert parse_bql_string('select * order by x;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * order by x asc;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * order by x desc;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_DESC)],
              None)]
      assert parse_bql_string('select * order by x, y;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None,
              [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_ASC),
               ast.Ord(ast.ExpCol(None, 'y'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * order by x desc, y;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None,
              [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_DESC),
               ast.Ord(ast.ExpCol(None, 'y'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * order by x, y asc;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None,
              [ast.Ord(ast.ExpCol(None, 'x'), ast.ORD_ASC),
               ast.Ord(ast.ExpCol(None, 'y'), ast.ORD_ASC)],
              None)]
      assert parse_bql_string('select * limit 32;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, None,
              ast.Lim(ast.ExpLit(ast.LitInt(32)), None))]
      assert parse_bql_string('select * limit 32 offset 16;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, None,
              ast.Lim(ast.ExpLit(ast.LitInt(32)), ast.ExpLit(ast.LitInt(16))))]
      assert parse_bql_string('select * limit 16, 32;') == \
          [ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
              None, None, None, None,
              ast.Lim(ast.ExpLit(ast.LitInt(32)), ast.ExpLit(ast.LitInt(16))))]
      assert parse_bql_string('select (select0);') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpCol(None, 'select0'), None)],
              None, None, None, None, None)]
      assert parse_bql_string('select (select 0);') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpSub(ast.Select(ast.SELQUANT_ALL,
                      [ast.SelColExp(ast.ExpLit(ast.LitInt(0)), None)],
                      None, None, None, None, None)
                  ),
                  None,
              )],
              None, None, None, None, None)]
      assert parse_bql_string('select f(f(), f(x), f(*), f(distinct x), y);') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpApp(False, 'f', [
                      ast.ExpApp(False, 'f', []),
                      ast.ExpApp(False, 'f', [ast.ExpCol(None, 'x')]),
                      ast.ExpAppStar('f'),
                      ast.ExpApp(True, 'f', [ast.ExpCol(None, 'x')]),
                      ast.ExpCol(None, 'y'),
                  ]),
                  None,
              )],
              None, None, None, None, None)]
  
  def test_select_bql():
      assert parse_bql_string('select predictive probability of c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLPredProb(
                  [ast.ColListLit(['c'])], []), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select predictive probability of (c) from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLPredProb(
                  [ast.ColListLit(['c'])], []), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select predictive probability of c, * from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpBQLPredProb(
                      [ast.ColListLit(['c'])], []), None),
                  ast.SelColAll(None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select predictive probability of (*), * from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(
                      ast.ExpBQLPredProb([ast.ColListAll()], []), None),
                  ast.SelColAll(None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
          'select c, predictive probability of d given (c, f) from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpCol(None, 'c'), None),
                  ast.SelColExp(ast.ExpBQLPredProb(
                      [ast.ColListLit(['d'])],
                      [ast.ColListLit(['c']), ast.ColListLit(['f'])]),
                      None
                  ),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
          'select c, predictive probability of (d, w, *) given (c, *) from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpCol(None, 'c'), None),
                  ast.SelColExp(ast.ExpBQLPredProb(
                      [ast.ColListLit(['d']), ast.ColListLit(['w']),
                          ast.ColListAll()],
                      [ast.ColListLit(['c']), ast.ColListAll()]),
                      None
                  ),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select predictive probability of c, d from t;') \
          == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(ast.ExpBQLPredProb(
                      [ast.ColListLit(['c'])], []), None),
                  ast.SelColExp(ast.ExpCol(None, 'd'), None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select probability density of c = 42'
              ' from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLProbDensity([('c', ast.ExpLit(ast.LitInt(42)))],
                      []),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      # Using * for context will later cause a crash in the compiler!
      assert parse_bql_string(
          'select similarity in the context of * from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(None, None, [ast.ColListAll()]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
          'select similarity to (rowid=8) in the context of s from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      None,
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(8))
                      )),
                      [ast.ColListLit(['s'])]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      with pytest.raises(parse.BQLParseError):
          # Cannot use similarity of without to.
          parse_bql_string(
              'select similarity of (rowid=8) in the context of r from t')
      assert parse_bql_string(
          'select similarity in the context of c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLSim(None, None, [ast.ColListLit(['c'])]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select similarity to (rowid=5) in the context of age from t1;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      None,
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(5)),
                      )),
                      [ast.ColListLit(['age'])]),
                  None)],
              [ast.SelTab('t1', None)], None, None, None, None)]
      assert parse_bql_string(
              'select similarity to (rowid=8) in the context of c, d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [
                  ast.SelColExp(
                      ast.ExpBQLSim(
                          None,
                          ast.ExpOp(ast.OP_EQ, (
                              ast.ExpCol(None, 'rowid'),
                              ast.ExpLit(ast.LitInt(8)),
                          )),
                          [ast.ColListLit(['c'])]),
                      None),
                  ast.SelColExp(ast.ExpCol(None, 'd'), None),
              ],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select similarity to (rowid=8) in the context of' +
              ' (estimate * from columns of t order by ' +
              '  probability density of value 4 limit 1)' +
              ' from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      None,
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(8)),
                      )),
                      [ast.ColListSub(
                          ast.EstCols(
                              [ast.SelColAll(None)], 't', None, None, None,
                              [ast.Ord(ast.ExpBQLProbDensityFn(
                                      ast.ExpLit(ast.LitInt(4)),
                                      []),
                                  ast.ORD_ASC)],
                              ast.Lim(ast.ExpLit(ast.LitInt(1)), None))
                      )]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      # Required TO variable.
      with pytest.raises(parse.BQLParseError):
          parse_bql_string(
              'select similarity of ("name" = \'Bar\') from t;')
      # Missing context variable.
      with pytest.raises(bayeslite.BQLParseError):
          parse_bql_string(
              'select similarity of ("name" = \'Bar\') to (rowid=8) '
                  'AS "sim_bar_8" from t;')
      assert parse_bql_string(
              'select similarity of ("name" = \'Bar\') to (rowid=8) '
                  'in the context of c AS "sim_bar_8" from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'name'),
                          ast.ExpLit(ast.LitString('Bar')),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(8)),
                      )),
                      [ast.ColListLit(['c'])]),
                  'sim_bar_8')],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select similarity of ("name" = \'Bar\') to (rowid=8)' +
              'in the context of (estimate * from columns of t ' +
                  'order by probability density of value 4 limit 1)' +
              'from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLSim(
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'name'),
                          ast.ExpLit(ast.LitString('Bar')),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'rowid'),
                          ast.ExpLit(ast.LitInt(8)),
                      )),
                      [ast.ColListSub(
                          ast.EstCols(
                              [ast.SelColAll(None)], 't', None, None, None,
                              [ast.Ord(ast.ExpBQLProbDensityFn(
                                      ast.ExpLit(ast.LitInt(4)),
                                      []),
                                  ast.ORD_ASC)],
                              ast.Lim(ast.ExpLit(ast.LitInt(1)), None))
                      )]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select dependence probability with c from t;') ==\
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLDepProb('c', None), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select dependence probability of c with d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLDepProb('c', 'd'), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select mutual information with c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLMutInf(['c'], None, None, None), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select mutual information with (c) from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLMutInf(['c'], None, None, None), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select mutual information of c with (d) from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLMutInf(['c'], ['d'], None, None),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string(
              'select mutual information of (a, b, q) with (d, r) '
              'given (f, z=2, w) from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLMutInf(
                  ['a', 'b', 'q'], ['d', 'r'],
                  [('f', ast.ExpLit(ast.LitNull(0))),
                      ('z',ast.ExpLit(ast.LitInt(2))),
                      ('w', ast.ExpLit(ast.LitNull(0)))],
                  None),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select mutual information of c with d' +
              ' using (1+2) samples from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLMutInf(
                      ['c'], ['d'], None,
                      ast.op(
                          ast.OP_ADD, ast.ExpLit(ast.LitInt(1)),
                          ast.ExpLit(ast.LitInt(2)))),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
              select mutual information with c given (d, a=1) using
              10 samples from t;
              ''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLMutInf(
                      ['c'], None,
                      [('d', ast.ExpLit(ast.LitNull(0))),
                          ('a',ast.ExpLit(ast.LitInt(1)))],
                      ast.ExpLit(ast.LitInt(10))
                  ),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
              select mutual information with (c, f) given (d, a=1) using
              10 samples from t;
              ''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLMutInf(
                      ['c', 'f'],
                      None,
                      [('d', ast.ExpLit(ast.LitNull(0))),
                          ('a',ast.ExpLit(ast.LitInt(1)))],
                      ast.ExpLit(ast.LitInt(10))
                  ),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
              select mutual information of b with c
              given (d, a=1, e, r=2) from t;''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLMutInf(
                      ['b'],
                      ['c'],
                      [
                          ('d', ast.ExpLit(ast.LitNull(0))),
                          ('a',ast.ExpLit(ast.LitInt(1))),
                          ('e', ast.ExpLit(ast.LitNull(0))),
                          ('r', ast.ExpLit(ast.LitInt(2))),
                      ],
                      None
                  ),
              None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select correlation with c from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLCorrel('c', None), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select correlation of c with d from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLCorrel('c', 'd'), None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      # XXX This got broken a while ago: parenthesization in PROBABILITY
      # OF X = E is too permissive.  I didn't notice because before I
      # introduced BQLParseError, this simply caught Exception -- which
      # covered the AssertionError that this turned into.
      #
      # with pytest.raises(parse.BQLParseError):
      #     parse_bql_string('select probability density of x = 1 -' +
      #         ' probability density of y = 0 from t;')
      #     # XXX Should really be this test, but getting the grammar to
      #     # admit this unambiguously is too much of a pain at the
      #     # moment.
      #     assert parse_bql_string('select probability density of x = 1 -' +
      #             ' probability density of y = 0 from t;') == \
      #         [ast.Select(ast.SELQUANT_ALL,
      #             [ast.SelColExp(ast.ExpBQLProbDensity([('x',
      #                         ast.ExpOp(ast.OP_SUB, (
      #                             ast.ExpLit(ast.LitInt(1)),
      #                             ast.ExpBQLProbDensity([('y',
      #                                     ast.ExpLit(ast.LitInt(0)))],
      #                                 []),
      #                         )))],
      #                     []),
      #                 None)],
      #             [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select probability density of c1 = f(c2)'
              ' from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(ast.ExpBQLProbDensity([('c1',
                          ast.ExpApp(False, 'f', [ast.ExpCol(None, 'c2')]))],
                      []),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('select key, t.(estimate * from columns of t'
              ' order by dependence probability with c desc limit 4)'
              ' from t order by key asc') == \
          [ast.Select(ast.SELQUANT_ALL, [
                  ast.SelColExp(ast.ExpCol(None, 'key'), None),
                  ast.SelColSub('t',
                      ast.EstCols([ast.SelColAll(None)], 't', None, None, None,
                          [ast.Ord(ast.ExpBQLDepProb('c', None), ast.ORD_DESC)],
                          ast.Lim(ast.ExpLit(ast.LitInt(4)), None)))
              ],
              [ast.SelTab('t', None)],
              None, None,
              [ast.Ord(ast.ExpCol(None, 'key'), ast.ORD_ASC)],
              None)]
  
  def test_predictive_relevance():
      with pytest.raises(parse.BQLParseError):
          # No ofcondition, tocondition, or constraints.
          parse_bql_string(
              'select predictive relevance in the context of f from t;')
      with pytest.raises(parse.BQLParseError):
          # No tocondition, or constraints.
          parse_bql_string(
              'select predictive relevance of (rowid=8) '
              'in the context of q from t')
      assert parse_bql_string(
          'select predictive relevance to existing rows (rowid=8 AND age < 10) '
          'in the context of "s" from t;') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLPredRel(
                      ofcondition=None,
                      tocondition=ast.ExpOp(ast.OP_BOOLAND, (
                          ast.ExpOp(ast.OP_EQ, (
                              ast.ExpCol(None, 'rowid'),
                              ast.ExpLit(ast.LitInt(8))
                          )),
                          ast.ExpOp(ast.OP_LT, (
                              ast.ExpCol(None, 'age'),
                              ast.ExpLit(ast.LitInt(10))
                          )),
                      )),
                      hypotheticals=None,
                      column=[ast.ColListLit(['s'])]),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
          select predictive relevance
              of (name = 'Uganda')
              to hypothetical rows with values (
                  ("gdp_per_capita" = 82, "mortality" = 14),
                  ("gdp_per_capita" = 74, continent = 'Europe', "mortality" = 7)
              )
              in the context of
                "gdp_per_capita"
          from t
          ''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLPredRel(
                      ofcondition=ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'name'),
                          ast.ExpLit(ast.LitString('Uganda'))
                      )),
                      tocondition=None,
                      hypotheticals=[[
                          ('gdp_per_capita',ast.ExpLit(ast.LitInt(82))),
                          ('mortality',ast.ExpLit(ast.LitInt(14)))
                      ],
                      [
                          ('gdp_per_capita',ast.ExpLit(ast.LitInt(74))),
                          ('continent',ast.ExpLit(ast.LitString('Europe'))),
                          ('mortality',ast.ExpLit(ast.LitInt(7))),
                      ]],
                      column=[ast.ColListLit(['gdp_per_capita'])],
                  ),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
      assert parse_bql_string('''
          select predictive relevance
              of (name = 'Uganda')
              to existing rows (rowid between 1 AND 100)
              and hypothetical rows with values (
                  ("gdp_per_capita" = 82, "mortality" = 14),
                  ("gdp_per_capita" = 74, continent = 'Europe', "mortality" = 7)
              )
              in the context of
                "gdp_per_capita"
          from t
          ''') == \
          [ast.Select(ast.SELQUANT_ALL,
              [ast.SelColExp(
                  ast.ExpBQLPredRel(
                      ofcondition=ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'name'),
                          ast.ExpLit(ast.LitString('Uganda'))
                      )),
                      tocondition=ast.ExpOp(ast.OP_BETWEEN,
                          (ast.ExpCol(None, 'rowid'),
                              ast.ExpLit(ast.LitInt(1)),
                              ast.ExpLit(ast.LitInt(100)))),
                      hypotheticals=[[
                          ('gdp_per_capita',ast.ExpLit(ast.LitInt(82))),
                          ('mortality',ast.ExpLit(ast.LitInt(14)))
                      ],
                      [
                          ('gdp_per_capita',ast.ExpLit(ast.LitInt(74))),
                          ('continent',ast.ExpLit(ast.LitString('Europe'))),
                          ('mortality',ast.ExpLit(ast.LitInt(7))),
                      ]],
                      column=[ast.ColListLit(['gdp_per_capita'])],
                  ),
                  None)],
              [ast.SelTab('t', None)], None, None, None, None)]
  
  def test_trivial_scan_error():
      with pytest.raises(parse.BQLParseError):
          parse_bql_string('select 0c;')
      with pytest.raises(parse.BQLParseError):
          parse_bql_string('select 1.0p1;')
  
  def test_trivial_precedence_error():
      with pytest.raises(parse.BQLParseError):
          parse_bql_string('select similarity to similarity to 0' +
              ' in the context of c from t;')
  
  def test_trivial_commands():
      assert parse_bql_string('''
          create population satellites for satellites_ucs (
              MODEL country_of_operator, orbit_type AS categorical;
              MODEL launch_mass AS numerical;
              MODEL perigee AS numerical;
              MODEL apogee, period AS numerical
          )
      ''') == \
          [ast.CreatePop(False, 'satellites', 'satellites_ucs', [
              ast.PopModelVars(
                  ['country_of_operator', 'orbit_type'], 'categorical'),
              ast.PopModelVars(['launch_mass'], 'numerical'),
              ast.PopModelVars(['perigee'], 'numerical'),
              ast.PopModelVars(['apogee', 'period'], 'numerical'),
          ])]
      assert parse_bql_string('''
          create population satellites for satellites_ucs (
              MODEL country_of_operator, orbit_type AS categorical;;
              MODEL apogee, period AS numerical;;
          )
      ''') == \
          [ast.CreatePop(False, 'satellites', 'satellites_ucs', [
              ast.PopModelVars(
                  ['country_of_operator', 'orbit_type'], 'categorical'),
              ast.PopModelVars(['apogee', 'period'], 'numerical'),
          ])]
      assert parse_bql_string('drop population satellites') == \
          [ast.DropPop(False, 'satellites')]
      assert parse_bql_string('create generator t_cc for t using cgpm'
              '(xyz numerical, pqr categorical, lmn cyclic)') == \
          [ast.CreateGen('t_cc', False, 't', 'cgpm', [
              ['xyz', 'numerical'],
              ['pqr', 'categorical'],
              ['lmn', 'cyclic'],
          ])]
      assert parse_bql_string('create generator t_cc for t'
              '(xyz numerical, pqr categorical, lmn cyclic)') == \
          [ast.CreateGen(
              't_cc', False, 't',
              None,       # Backend defaults to cgpm.
              [
                  ['xyz', 'numerical'],
                  ['pqr', 'categorical'],
                  ['lmn', 'cyclic'],
          ])]
      assert parse_bql_string('create generator t_cc if not exists'
              ' for t using cgpm'
              '(xyz numerical, pqr categorical, lmn cyclic)') == \
          [ast.CreateGen('t_cc', True, 't', 'cgpm', [
              ['xyz', 'numerical'],
              ['pqr', 'categorical'],
              ['lmn', 'cyclic'],
          ])]
      assert parse_bql_string('create generator if not exists t_cc'
              ' for t using cgpm'
              '(xyz numerical, pqr categorical, lmn cyclic)') == \
          [ast.CreateGen('t_cc', True, 't', 'cgpm', [
              ['xyz', 'numerical'],
              ['pqr', 'categorical'],
              ['lmn', 'cyclic'],
          ])]
      # XXX Schema of [[]] instead of [] is kinda wacky.  Fix?  (But
      # make sure the empty-parens and no-parens cases are equivalent.)
      assert parse_bql_string('create generator t_cc'
              ' for t using cgpm()') == \
          [ast.CreateGen('t_cc', False, 't', 'cgpm', [[]])]
      assert parse_bql_string('create generator t_cc'
              ' for t using cgpm') == \
          [ast.CreateGen('t_cc', False, 't', 'cgpm', [[]])]
      assert parse_bql_string('initialize 1 model for t;') == \
          [ast.InitModels(False, 't', 1)]
      assert parse_bql_string('initialize 1 model if not exists for t;') == \
          [ast.InitModels(True, 't', 1)]
      assert parse_bql_string('initialize 2 models for t;') == \
          [ast.InitModels(False, 't', 2)]
      assert parse_bql_string('initialize 2 models if not exists for t;') == \
          [ast.InitModels(True, 't', 2)]
      assert parse_bql_string('create temporary table tx as'
              ' infer explicit x, predict x as xi confidence xc from t_cc') == \
          [ast.CreateTabAs(True, False, 'tx',
              ast.InferExplicit(
                  [
                      ast.SelColExp(ast.ExpCol(None, 'x'), None),
                      ast.PredCol('x', 'xi', 'xc', None),
                  ],
                  't_cc', None, None, None, None, None, None,
              ))]
  
  def test_analyze():
      assert parse_bql_string('analyze t for 1 iteration;') == \
          [ast.AnalyzeModels('t', None, 1, None, None, None, None)]
      assert parse_bql_string('analyze t for 7 seconds or 1 iteration;') == \
          [ast.AnalyzeModels('t', None, 1, 7, None, None, None)]
      assert parse_bql_string('analyze t for 1 iteration;') == \
          [ast.AnalyzeModels('t', None, 1, None, None, None, None)]
      assert parse_bql_string('analyze t for 1 minute;') == \
          [ast.AnalyzeModels('t', None, None, 60, None, None, None)]
      assert parse_bql_string('analyze t for 1 minute;') == \
          [ast.AnalyzeModels('t', None, None, 60, None, None, None)]
      assert parse_bql_string('analyze t for 2 minutes;') == \
          [ast.AnalyzeModels('t', None, None, 120, None, None, None)]
      assert parse_bql_string('analyze t for 100 iterations or 2 minutes;') == \
          [ast.AnalyzeModels('t', None, 100, 120, None, None, None)]
      assert parse_bql_string('analyze t for 2 minutes;') == \
          [ast.AnalyzeModels('t', None, None, 120, None, None, None)]
      assert parse_bql_string('analyze t for 1 second;') == \
          [ast.AnalyzeModels('t', None, None, 1, None, None, None)]
      assert parse_bql_string('analyze t for 1 second;') == \
          [ast.AnalyzeModels('t', None, None, 1, None, None, None)]
      assert parse_bql_string('analyze t for 2 seconds;') == \
          [ast.AnalyzeModels('t', None, None, 2, None, None, None)]
      assert parse_bql_string('analyze t for 2 seconds;') == \
          [ast.AnalyzeModels('t', None, None, 2, None, None, None)]
      assert parse_bql_string('analyze t model 1 for 1 iteration;') == \
          [ast.AnalyzeModels('t', [1], 1, None, None, None, None)]
      assert parse_bql_string('analyze t models 1,2,3 for 1 iteration;') == \
          [ast.AnalyzeModels('t', [1,2,3], 1, None, None, None, None)]
      assert parse_bql_string('analyze t models 1-3,5 for 1 iteration;') == \
          [ast.AnalyzeModels('t', [1,2,3,5], 1, None, None, None, None)]
      assert parse_bql_string('analyze t for 10 iterations'
              ' checkpoint 3 iterations') == \
          [ast.AnalyzeModels('t', None, 10, None, 3, None, None)]
      assert parse_bql_string('analyze t for 10 iterations'
              ' (mh(default, one, 10))') == \
          [ast.AnalyzeModels('t', None, 10, None, None, None, [
              'mh', '(', 'default', ',', 'one', ',', 10, ')'
          ])]
      assert parse_bql_string('analyze t for 10 seconds'
              ' checkpoint 3 seconds') == \
          [ast.AnalyzeModels('t', None, None, 10, None, 3, None)]
      assert parse_bql_string('analyze t for 1 minute or 10 minutes'
              ' checkpoint 3 seconds') == \
          [ast.AnalyzeModels('t', None, None, 60, None, 3, None)]
      assert parse_bql_string('analyze t for 100 iterations or 10 iterations'
              ' checkpoint 3 seconds') == \
          [ast.AnalyzeModels('t', None, 10, None, None, 3, None)]
  
  def test_altergen():
      assert parse_bql_string('alter generator g '
              'rename to rumba') == \
          [ast.AlterGen(
              generator='g',
              modelnos=None,
              commands=[ast.AlterGenRenameGen('rumba')]
          )]
      assert parse_bql_string('alter generator g models (1, 2, 4) '
              'rename to rumba') == \
          [ast.AlterGen(
              generator='g',
              modelnos=[1,2,4],
              commands=[ast.AlterGenRenameGen('rumba')]
          )]
      assert parse_bql_string('alter generator g '
              'rename to rumba, generic cmd (a,c), generic cmd2') == \
          [ast.AlterGen(
              generator='g',
              modelnos=None,
              commands=[
                  ast.AlterGenRenameGen('rumba'),
                  ast.AlterGenGeneric(['generic', 'cmd', ['a', ',', 'c']]),
                  ast.AlterGenGeneric(['generic', 'cmd2']),
              ],
          )]
      assert parse_bql_string('alter generator g models (1-4) '
              'set variable clustering of (*) to independent, '
              'set variable clustering of (*) to dependent, '
              'set variable cluster of bar to cluster of baz, '
              'set variable cluster of (roger, martin) to singleton, '
              'set variable clustering hyperparameter to 100, '
              'set row cluster of 1 to cluster of 2, '
              'set row clustering hyperparameter of cluster of variable K to 11, '
              'set row clustering hyperparameter of variable K to -1 '
          ) == \
          [ast.AlterGen(
              generator='g',
              modelnos=[1, 2, 3, 4],
              commands=[
                  ast.AlterGenGeneric([
                      'set', 'variable', 'clustering', 'of', ['*'],
                      'to', 'independent']),
                  ast.AlterGenGeneric([
                      'set', 'variable', 'clustering', 'of', ['*'],
                      'to', 'dependent']),
                  ast.AlterGenGeneric([
                      'set', 'variable', 'cluster', 'of', 'bar', 'to',
                      'cluster', 'of', 'baz']),
                  ast.AlterGenGeneric([
                      'set', 'variable', 'cluster', 'of',
                      ['roger', ',', 'martin'], 'to', 'singleton']),
                  ast.AlterGenGeneric([
                      'set', 'variable', 'clustering', 'hyperparameter',
                      'to', 100]),
                  ast.AlterGenGeneric(
                      ['set', 'row', 'cluster', 'of', 1, 'to',
                      'cluster', 'of', 2]),
                  ast.AlterGenGeneric([
                      'set', 'row', 'clustering', 'hyperparameter', 'of',
                      'cluster', 'of', 'variable', 'K', 'to', 11]),
                  ast.AlterGenGeneric([
                      'set', 'row', 'clustering', 'hyperparameter', 'of',
                      'variable', 'K', 'to', '-', 1])
          ])]
  
  def test_create_tab_csv():
      assert parse_bql_string('create temp table if not exists f '
              'from \'foo.csv\'') == \
          [ast.CreateTabCsv(True, True, 'f', 'foo.csv')]
      assert parse_bql_string('create table if not exists f '
              'from \'foo.csv\'') == \
          [ast.CreateTabCsv(False, True, 'f', 'foo.csv')]
      assert parse_bql_string('create table f '
              'from \'foo.csv\'') == \
          [ast.CreateTabCsv(False, False, 'f', 'foo.csv')]
      # Using double quotes for csv pathname should raise.
      with pytest.raises(parse.BQLParseError):
          parse_bql_string('create temp table if not exists f '
              'from "foo.csv"')
  
  def test_alterpop_stattype():
      assert parse_bql_string('alter population p '
              'set stattypes for a to normal') == \
          [ast.AlterPop('p',
              [ast.AlterPopStatType(['a'], 'normal')]
          )]
      assert parse_bql_string('alter population g '
              'set stattypes for a, b to BETA') == \
          [ast.AlterPop('g', [ast.AlterPopStatType(['a', 'b'], 'BETA')])]
      assert parse_bql_string('alter population p '
              'set stattypes for a, b to beta, '
              'set stattypes for c to nominal') == \
          [ast.AlterPop('p', [
              ast.AlterPopStatType(['a', 'b'], 'beta'),
              ast.AlterPopStatType(['c'], 'nominal'),]
          )]
  
  def test_alterpop_addvar():
      assert parse_bql_string('alter population p '
              'add variable r') == \
          [ast.AlterPop('p',
              [ast.AlterPopAddVar('r', None)]
          )]
      assert parse_bql_string('alter population g '
              'add variable r nominal') == \
          [ast.AlterPop('g',
              [ast.AlterPopAddVar('r', 'nominal')]
          )]
      assert parse_bql_string('alter population p '
              'add variable a, '
              'set stattypes for a to nominal, '
              'add variable b numerical') == \
          [ast.AlterPop('p', [
              ast.AlterPopAddVar('a', None),
              ast.AlterPopStatType(['a'], 'nominal'),
              ast.AlterPopAddVar('b', 'numerical'),]
          )]
      with pytest.raises(bayeslite.BQLParseError):
          parse_bql_string('alter population v add variable;')
  
  def test_infer_trivial():
      assert parse_bql_string('infer x from p') == \
          [ast.InferAuto([ast.InfColOne('x', None)], ast.ExpLit(ast.LitInt(0)),
              None, 'p', None, None, None, None, None, None)]
  
  def test_infer_conf():
      assert parse_bql_string('infer x with confidence 0.9 from p') == \
          [ast.InferAuto([ast.InfColOne('x', None)],
              ast.ExpLit(ast.LitFloat(0.9)), None, 'p', None, None, None, None,
              None, None)]
  
  def test_infer_samples():
      assert parse_bql_string('infer x using 42 samples from p') == \
          [ast.InferAuto([ast.InfColOne('x', None)],
              ast.ExpLit(ast.LitInt(0)), ast.ExpLit(ast.LitInt(42)), 'p',
              None, None, None, None, None, None)]
  
  def test_infer_conf_samples():
      assert parse_bql_string('infer x with confidence 0.9 using 42 samples'
              ' from p') == \
          [ast.InferAuto([ast.InfColOne('x', None)],
              ast.ExpLit(ast.LitInt(.9)), ast.ExpLit(ast.LitInt(42)), 'p', None,
              None, None, None, None, None)]
  
  def test_infer_explicit():
      assert parse_bql_string('infer explicit x, predict y with confidence 0.9,'
              ' predict p with confidence 0.8 as q, predict u confidence v,'
              ' predict a as b confidence c,'
              ' predict h confidence k using 42 samples'
              ' from p') == \
          [ast.InferExplicit([
                  ast.SelColExp(ast.ExpCol(None, 'x'), None),
                  ast.SelColExp(
                      ast.ExpBQLPredict('y', ast.ExpLit(ast.LitFloat(.9)), None),
                      None,
                  ),
                  ast.SelColExp(
                      ast.ExpBQLPredict('p', ast.ExpLit(ast.LitFloat(.8)), None),
                      'q',
                  ),
                  ast.PredCol('u', None, 'v', None),
                  ast.PredCol('a', 'b', 'c', None),
                  ast.PredCol('h', None, 'k', ast.ExpLit(ast.LitInt(42))),
              ],
              'p', None, None, None, None, None, None)]
  
  def test_infer_explicit_samples():
      assert parse_bql_string('infer explicit x, predict y with confidence 0.9,'
              ' predict p with confidence 0.8 as q, predict u confidence v,'
              ' predict a as b confidence c,'
              ' predict h confidence k using 42 samples'
              ' from p') == \
          [ast.InferExplicit([
                  ast.SelColExp(ast.ExpCol(None, 'x'), None),
                  ast.SelColExp(
                      ast.ExpBQLPredict('y', ast.ExpLit(ast.LitFloat(.9)), None),
                      None,
                  ),
                  ast.SelColExp(
                      ast.ExpBQLPredict('p', ast.ExpLit(ast.LitFloat(.8)), None),
                      'q',
                  ),
                  ast.PredCol('u', None, 'v', None),
                  ast.PredCol('a', 'b', 'c', None),
                  ast.PredCol('h', None, 'k', ast.ExpLit(ast.LitInt(42))),
              ],
              'p', None, None, None, None, None, None)]
  
  def test_parametrized():
      assert parse_bql_string('select * from t where id = ?;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_EQ, (
                      ast.ExpCol(None, 'id'),
                      ast.ExpNumpar(1),
                  )),
                  None, None, None),
              1, {})]
      assert parse_bql_string('select * from t where id = ?123;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_EQ, (
                      ast.ExpCol(None, 'id'),
                      ast.ExpNumpar(123),
                  )),
                  None, None, None),
              123, {})]
      assert parse_bql_string('select * from t where id = :foo;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_EQ, (
                      ast.ExpCol(None, 'id'),
                      ast.ExpNampar(1, ':foo'),
                  )),
                  None, None, None),
              1, {':foo': 1})]
      assert parse_bql_string('select * from t where a = :foo and b = @foo;') \
          == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_BOOLAND, (
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'a'),
                          ast.ExpNampar(1, ':foo'),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'b'),
                          ast.ExpNampar(2, '@foo'),
                      )),
                  )),
                  None, None, None),
              2, {':foo': 1, '@foo': 2})]
      assert parse_bql_string('select * from t where a = $foo and b = ?1;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_BOOLAND, (
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'a'),
                          ast.ExpNampar(1, '$foo'),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'b'),
                          ast.ExpNumpar(1),
                      )),
                  )),
                  None, None, None),
              1, {'$foo': 1})]
      assert parse_bql_string('select * from t' +
              ' where a = ?123 and b = :foo and c = ?124;') == \
          [ast.Parametrized(ast.Select(ast.SELQUANT_ALL, [ast.SelColAll(None)],
                  [ast.SelTab('t', None)],
                  ast.ExpOp(ast.OP_BOOLAND, (
                      ast.ExpOp(ast.OP_BOOLAND, (
                          ast.ExpOp(ast.OP_EQ, (
                              ast.ExpCol(None, 'a'),
                              ast.ExpNumpar(123),
                          )),
                          ast.ExpOp(ast.OP_EQ, (
                              ast.ExpCol(None, 'b'),
                              ast.ExpNampar(124, ':foo'),
                          )),
                      )),
                      ast.ExpOp(ast.OP_EQ, (
                          ast.ExpCol(None, 'c'),
                          ast.ExpNumpar(124),
                      )),
                  )),
                  None, None, None),
              124, {':foo': 124})]
  
  def test_complete():
      assert parse.bql_string_complete_p('')
      assert parse.bql_string_complete_p(';')
      assert parse.bql_string_complete_p(';;;')
      assert parse.bql_string_complete_p('\n;\n;;;\n;\n')
      assert not parse.bql_string_complete_p('select 0')
      assert parse.bql_string_complete_p('select 0;')
      assert not parse.bql_string_complete_p('select 0\nfrom t')
      assert parse.bql_string_complete_p('select 0\nfrom t;')
      assert not parse.bql_string_complete_p('select 0;select 1')
      assert parse.bql_string_complete_p('select 0;select 1;')
      assert not parse.bql_string_complete_p('select 0;\nselect 1')
      assert parse.bql_string_complete_p('select 0;\nselect 1;')
  
  def test_simulate():
      with pytest.raises(parse.BQLParseError):
          # Need limit.
          parse_bql_string('create table s as simulate x from t')
      with pytest.raises(parse.BQLParseError):
          # Need limit.
          parse_bql_string('create table s as simulate x from t given y = 0')
      assert parse_bql_string('create table s as'
              ' simulate x from t limit 10') == \
          [ast.CreateTabAs(False, False, 's',
              ast.Simulate(
                  [ast.SelColExp(ast.ExpCol(None, 'x'), None)],
                  't',
                  None,
                  None,
                  [],
                  ast.ExpLit(ast.LitInt(10)),
                  None)
          )]
      assert parse_bql_string('create table if not exists s as'
              ' simulate x, y from t given z = 0 limit 10 accuracy 2') == \
          [ast.CreateTabAs(False, True, 's',
              ast.Simulate(
                  [
                      ast.SelColExp(ast.ExpCol(None, 'x'), None),
                      ast.SelColExp(ast.ExpCol(None, 'y'), None),
                  ],
                  't',
                  None,
                  None,
                  [('z', ast.ExpLit(ast.LitInt(0)))],
                  ast.ExpLit(ast.LitInt(10)),
                  2)
          )]
      assert parse_bql_string('create temp table s as'
              ' simulate x, y from t given z = 0 limit 10') == \
          [ast.CreateTabAs(True, False, 's',
              ast.Simulate(
                  [
                      ast.SelColExp(ast.ExpCol(None, 'x'), None),
                      ast.SelColExp(ast.ExpCol(None, 'y'), None),
                  ],
                  't',
                  None,
                  None,
                  [('z', ast.ExpLit(ast.LitInt(0)))],
                  ast.ExpLit(ast.LitInt(10)),
                  None),
          )]
      assert parse_bql_string(
              'create temp table if not exists s as'
              ' simulate x, y from t given z = 0, w = 1'
              ' limit 10 accuracy 19') == \
          [ast.CreateTabAs(True, True, 's',
              ast.Simulate(
                  [
                      ast.SelColExp(ast.ExpCol(None, 'x'), None),
                      ast.SelColExp(ast.ExpCol(None, 'y'), None),
                  ],
                  't',
                  None,
                  None,
                  [
                      ('z', ast.ExpLit(ast.LitInt(0))),
                      ('w', ast.ExpLit(ast.LitInt(1))),
                  ],
                  ast.ExpLit(ast.LitInt(10)),
                  19)
          )]
      # Specifying a quantity other than a variable should raise.
      with pytest.raises(parse.BQLParseError):
          parse_bql_string(
              'simulate a, dependence probability of a with b from t limit 10;')
      with pytest.raises(parse.BQLParseError):
          parse_bql_string(
              'create table f as simulate a, dependence probability of a with b '
              'from t limit 10;')
  
  def test_simulate_models():
      assert parse_bql_string(
          'simulate dependence probability of a with b from models of t;') == [
              ast.SimulateModels(
                  [
                      ast.SelColExp(ast.ExpBQLDepProb('a', 'b'), None),
                  ],
                  't', None
              )
      ]
      assert parse_bql_string(
          'simulate dependence probability of a with b AS q, '
          'mutual information of c with d given (e, r=2.7) '
              'using 100 samples as g '
          'from models of p modeled by z') == [
              ast.SimulateModels(
                  [
                      ast.SelColExp(
                          ast.ExpBQLDepProb('a', 'b'), 'q'),
                      ast.SelColExp(
                          ast.ExpBQLMutInf(
                              ['c'],
                              ['d'],
                              [
                                  ('e', ast.ExpLit(ast.LitNull(0))),
                                  ('r', ast.ExpLit(ast.LitFloat(2.7)))
                              ],
                              ast.ExpLit(ast.LitInt(100))),
                          'g'
                      ),
                  ],
                  'p', 'z'
              )
      ]
      assert parse_bql_string(
          'simulate dependence probability of a with b AS q, '
          'mutual information of (c) with (d, r) given (e, r=2.7) '
              'using 100 samples as g '
          'from models of p modeled by z') == [
              ast.SimulateModels(
                  [
                      ast.SelColExp(
                          ast.ExpBQLDepProb('a', 'b'), 'q'),
                      ast.SelColExp(
                          ast.ExpBQLMutInf(
                              ['c'],
                              ['d', 'r'],
                              [
                                  ('e', ast.ExpLit(ast.LitNull(0))),
                                  ('r', ast.ExpLit(ast.LitFloat(2.7)))
                              ],
                              ast.ExpLit(ast.LitInt(100))),
                          'g'
                      ),
                  ],
                  'p', 'z'
              )
      ]
      assert parse_bql_string(
          'simulate probability density of (a=2, c=1.1) given (b=0.5) '
          'from models of p') == [
              ast.SimulateModels(
                  [
                      ast.SelColExp(
                          ast.ExpBQLProbDensity(
                              [
                                  ('a', ast.ExpLit(ast.LitInt(2))),
                                  ('c', ast.ExpLit(ast.LitFloat(1.1)))
                              ],
                              [('b', ast.ExpLit(ast.LitFloat(0.5)))]),
                          None
                      ),
                  ],
                  'p', None
              )
      ]
      for temp, ifnotexists in itertools.product(
              ('temp', ''), ('if not exists', '')):
          assert parse_bql_string(
              'create %s table %s f as '
              'simulate dependence probability of a with b AS q, '
              'mutual information of "bad-col" with d '
                  'given (e, r=2.7) using 100 samples as g '
              'from models of p modeled by z' % (temp, ifnotexists)) == [
                  ast.CreateTabAs(
                      bool(temp),
                      bool(ifnotexists),
                      'f',
                      ast.SimulateModels(
                          [
                              ast.SelColExp(
                                  ast.ExpBQLDepProb('a', 'b'),
                                  'q'
                              ),
                              ast.SelColExp(
                                  ast.ExpBQLMutInf(
                                      ['bad-col'],
                                      ['d'],
                                      [
                                          ('e', ast.ExpLit(ast.LitNull(0))),
                                          ('r', ast.ExpLit(ast.LitFloat(2.7)))
                                      ],
                                      ast.ExpLit(ast.LitInt(100))),
                                  'g'
                              ),
                          ],
                          'p', 'z'
                      )
              )
          ]
  
  def test_is_bql():
      assert ast.is_bql(ast.ExpLit(ast.LitInt(0))) == False
      assert ast.is_bql(ast.ExpNumpar(0)) == False
      assert ast.is_bql(ast.ExpNampar(0, 'x')) == False
      assert ast.is_bql(ast.ExpCol('t', 'c')) == False
      # ...
      assert ast.is_bql(ast.ExpBQLPredProb([ast.ColListLit(['c'])], []))
      assert ast.is_bql(
          ast.ExpBQLProbDensity([('c', ast.ExpLit(ast.LitInt(0)))], []))
      assert ast.is_bql(ast.ExpBQLProbDensityFn(ast.ExpLit(ast.LitInt(0)), []))
      assert ast.is_bql(ast.ExpBQLSim(None, ast.ExpLit(ast.LitInt(0)), []))
      assert ast.is_bql(ast.ExpBQLDepProb('c0', 'c1'))
      assert ast.is_bql(ast.ExpBQLMutInf('c0', 'c1', None, 100))
      assert ast.is_bql(ast.ExpBQLCorrel('c0', 'c1'))
      assert ast.is_bql(ast.ExpBQLPredict('c', ast.ExpLit(ast.LitInt(.5)), None))
      assert ast.is_bql(ast.ExpBQLPredictConf('c', None))
  
  def test_regress():
      assert parse_bql_string('''
          regress t given (y) using 10 samples by pop;
      ''') == [
          ast.Regress(
              target='t',
              givens=[
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='y'), name=None),],
              nsamp=ast.ExpLit(value=ast.LitInt(value=10)),
              population='pop',
              generator=None,
              modelnos=None,
      )]
      assert parse_bql_string('''
          regress t given (y, x) using 10 samples by pop modeled by m
          using models 1, 7;
          ''') == [
          ast.Regress(
              target='t',
              givens=[
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='y'), name=None),
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='x'), name=None)],
              nsamp=ast.ExpLit(value=ast.LitInt(value=10)),
              population='pop',
              generator='m',
              modelnos=[1,7],
      )]
      assert parse_bql_string('regress t given (*) by pop;') == [
          ast.Regress(
              target='t',
              givens=[ast.SelColAll(None)],
              nsamp=None,
              population='pop',
              generator=None,
              modelnos=None,
      )]
      # Disallow this query in the compiler, mixing * with u.
      assert parse_bql_string('''
          regress t given (*, u) using 10 samples by pop modeled by m
          using models 1-3;
      ''') == [
          ast.Regress(
              target='t',
              givens=[
                  ast.SelColAll(None),
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='u'), name=None)],
              nsamp=ast.ExpLit(value=ast.LitInt(value=10)),
              population='pop',
              generator='m',
              modelnos=[1,2,3],
      )]
      # Disallow this query in the compiler, mixing subquery.
      assert parse_bql_string('''
          regress t given (
              pop.(estimate * from variables of pop limit 2),
              a, y)
          using 10 samples by pop modeled by f;
      ''') == [
          ast.Regress(
              target='t',
              givens=[
                  ast.SelColSub(table='pop',
                      query=ast.EstCols(
                          columns=[ast.SelColAll(table=None)],
                          population='pop',
                          generator=None,
                          modelnos=None,
                          condition=None,
                          order=None,
                          limit=ast.Lim(
                              limit=ast.ExpLit(value=ast.LitInt(value=2)),
                              offset=None
                  ))),
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='a'), name=None),
                  ast.SelColExp(
                      expression=ast.ExpCol(table=None, column='y'), name=None)
              ],
              nsamp=ast.ExpLit(value=ast.LitInt(value=10)),
              population='pop',
              generator='f',
              modelnos=None,
      )]
  
      with pytest.raises(bayeslite.BQLParseError):
          # Missing given.
          assert parse_bql_string('regress t using 10 samples by pop;')
      with pytest.raises(bayeslite.BQLParseError):
          # Missing parenthesis.
          assert parse_bql_string('regress t given * using 10 samples by pop;')
  
  def test_using_model():
      assert parse_bql_string('simulate x from t using model 42'
              ' limit 10') == [
          ast.Simulate(
              columns=[ast.SelColExp(ast.ExpCol(None, 'x'), None)],
              population='t',
              generator=None,
              modelnos=[42],
              constraints=[],
              nsamples=ast.ExpLit(ast.LitInt(10)),
              accuracy=None)
      ]
      with pytest.raises(parse.BQLParseError):
          assert parse_bql_string('simulate x from t'
                  ' using model (87)') == [
              ast.Simulate(
                  columns=[ast.SelColExp(ast.ExpCol(None, 'x'), None)],
                  population='t',
                  generator=None,
                  modelnos=[87],
                  constraints=[],
                  nsamples=ast.ExpLit(ast.LitInt(10)),
                  accuracy=None)
          ]
      assert parse_bql_string('estimate x from t modeled by g '
              'using models 1, 2') == [
          ast.Estimate(
              quantifier=ast.SELQUANT_ALL,
              columns=[ast.SelColExp(ast.ExpCol(None, 'x'), None)],
              population='t',
              generator='g',
              modelnos=[1,2],
              condition=None,
              grouping=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('estimate * from columns of t modeled by z'
              ' using models 1-3, 5, 12-14') == [
          ast.EstCols(
              columns=[ast.SelColAll(None)],
              population='t',
              generator='z',
              modelnos=[1,2,3,5,12,13,14],
              condition=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('estimate 42 from pairwise columns of t'
              ' using models 0, 7') == [
          ast.EstPairCols(
              columns=[(ast.ExpLit(ast.LitInt(42)), None)],
              population='t',
              subcolumns=None,
              generator=None,
              modelnos=[0,7],
              condition=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('estimate similarity in the context of h '
              'from pairwise t modeled by g using models 8-10') == [
          ast.EstPairRow(
              columns=[ast.SelColExp(
                  ast.ExpBQLSim(
                      None, None, [ast.ColListLit(columns=['h'])]), name=None)],
              population='t',
              generator='g',
              modelnos=[8, 9, 10],
              condition=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('infer x from t using models 7') == [
          ast.InferAuto(
              columns=[ast.InfColOne('x', None)],
              confidence=ast.ExpLit(ast.LitInt(0)),
              nsamples=None,
              population='t',
              generator=None,
              modelnos=[7],
              condition=None,
              grouping=None,
              order=None,
              limit=None)
      ]
      assert parse_bql_string('infer explicit x from t using models 7, 14') == [
          ast.InferExplicit(
              columns=[ast.SelColExp(ast.ExpCol(None, 'x'), None)],
              population='t',
              generator=None,
              modelnos=[7,14],
              condition=None,
              grouping=None,
              order=None,
              limit=None)
      ]
  
  @contextlib.contextmanager
  def raises_str(klass, string):
      with pytest.raises(klass):
          try:
              yield
          except klass as e:
              assert string in str(e)
              raise
  
  def test_estimate_pairwise_deprecation():
      with raises_str(bayeslite.BQLParseError, "deprecated `ESTIMATE COLUMNS'"):
          parse_bql_string('estimate columns from t')
      with raises_str(bayeslite.BQLParseError, "deprecated `ESTIMATE PAIRWISE'"):
          parse_bql_string('estimate pairwise dependence probability from t')
      with raises_str(bayeslite.BQLParseError,
              "deprecated `ESTIMATE PAIRWISE ROW'"):
          parse_bql_string('estimate pairwise row similarity from t')
  
  def test_parse_error_with_context():
      with raises_str(bayeslite.BQLParseError, "select ( 1 +"):
          parse_bql_string('select (1 +')
      with raises_str(bayeslite.BQLParseError,
                      "Syntax error near [] after [select]"):
          parse_bql_string('select')
  
files: tests/test_parse.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import pytest
  
  from bayeslite import BQLError
  from bayeslite import bayesdb_register_backend
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  
  from test_cgpm import cgpm_dummy_satellites_bdb
  
  
  def test_regress_bonanza__ci_integration():
      with cgpm_dummy_satellites_bdb() as bdb:
          bayesdb_register_backend(
              bdb, CGPM_Backend(dict(), multiprocess=0))
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  apogee                  NUMERICAL;
                  class_of_orbit          NOMINAL;
                  country_of_operator     NOMINAL;
                  launch_mass             NUMERICAL;
                  perigee                 NUMERICAL;
                  period                  NUMERICAL;
              )
          ''')
          bdb.execute('''
              CREATE GENERATOR m FOR satellites;
          ''')
          bdb.execute('INITIALIZE 2 MODELS FOR m;')
  
          def check_regression_variables(results, numericals, nominals):
              seen = set()
              for r in results:
                  assert len(r) == 2
                  variable = r[0]
                  assert variable not in seen
                  assert variable in numericals or \
                      any(variable.startswith('%s_dum_' % (nominal,))
                          for nominal in nominals)
                  seen.add(variable)
  
          # Regression on 1 numerical variable.
          results = bdb.execute('''
              REGRESS apogee GIVEN (perigee) USING 12 SAMPLES BY satellites;
          ''').fetchall()
          assert len(results) == 2
          check_regression_variables(results, ['intercept', 'perigee'], [])
  
          # Regression on 1 nominal variable.
          results = bdb.execute('''
              REGRESS apogee GIVEN (country_of_operator)
              USING 12 SAMPLES BY satellites;
          ''').fetchall()
          check_regression_variables(
              results, ['intercept'], ['country_of_operator'])
  
          # Regression on 1 nominal + 1 numerical variable.
          bdb.execute('''
              REGRESS apogee GIVEN (perigee, country_of_operator)
              USING 12 SAMPLES BY satellites;
          ''').fetchall()
          check_regression_variables(
              results, ['intercept', 'perigee'], ['country_of_operator'])
  
          # Regression on all variables.
          results = bdb.execute('''
              REGRESS apogee GIVEN (*) USING 12 SAMPLES BY satellites;
          ''', (3,)).fetchall()
          check_regression_variables(
              results,
              ['intercept', 'perigee', 'launch_mass', 'period',],
              ['country_of_operator', 'class_of_orbit',],
          )
  
          # Regression on column selector subexpression with a binding.
          results = bdb.execute('''
              REGRESS apogee GIVEN (
                  satellites.(
                      ESTIMATE * FROM VARIABLES OF satellites
                      ORDER BY dependence probability with apogee DESC
                      LIMIT ?
                  )
              )
              USING 12 SAMPLES BY satellites MODELED BY m USING MODEL 1;
          ''', (3,)).fetchall()
  
          cursor = bdb.execute('''
              ESTIMATE * FROM VARIABLES OF satellites
                  ORDER BY dependence probability with apogee DESC
                  LIMIT ?
          ''', (3,)).fetchall()
          top_variables = [c[0] for c in cursor]
          nominals = [
              var for var in top_variables
              if var in ['country_of_operator', 'class_of_orbit',]
          ]
          numericals = [var for var in top_variables if var not in nominals]
          check_regression_variables(
              results, numericals + ['intercept'], nominals)
  
          # Cannot mix * with other variables.
          with pytest.raises(BQLError):
              bdb.execute('''
                  REGRESS apogee GIVEN (*, class_of_orbit)
                  USING 1 SAMPLES BY satellites;
              ''').fetchall()
  
          # Not enough data for regression, 1 unique nominal variable.
          with pytest.raises(ValueError):
              bdb.execute('''
                  REGRESS apogee GIVEN (class_of_orbit)
                  USING 1 SAMPLES BY satellites;
              ''').fetchall()
  
files: tests/test_regress.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import pytest
  
  from bayeslite import BQLError
  from bayeslite import bayesdb_register_backend
  from bayeslite.backends.cgpm_backend import CGPM_Backend
  
  from test_cgpm import cgpm_dummy_satellites_bdb
  
  
  def test_regress_bonanza__ci_integration():
      with cgpm_dummy_satellites_bdb() as bdb:
          bayesdb_register_backend(
              bdb, CGPM_Backend(dict(), multiprocess=0))
          bdb.execute('''
              CREATE POPULATION satellites FOR satellites_ucs WITH SCHEMA(
                  MODEL apogee AS NUMERICAL;
                  MODEL class_of_orbit AS NOMINAL;
                  MODEL country_of_operator AS NOMINAL;
                  MODEL launch_mass AS NUMERICAL;
                  MODEL perigee AS NUMERICAL;
                  MODEL period AS NUMERICAL
              )
          ''')
          bdb.execute('''
              CREATE GENERATOR m FOR satellites;
          ''')
          bdb.execute('INITIALIZE 2 MODELS FOR m;')
  
          def check_regression_variables(results, numericals, nominals):
              seen = set()
              for r in results:
                  assert len(r) == 2
                  variable = r[0]
                  assert variable not in seen
                  assert variable in numericals or \
                      any(variable.startswith('%s_dum_' % (nominal,))
                          for nominal in nominals)
                  seen.add(variable)
  
          # Regression on 1 numerical variable.
          results = bdb.execute('''
              REGRESS apogee GIVEN (perigee) USING 12 SAMPLES BY satellites;
          ''').fetchall()
          assert len(results) == 2
          check_regression_variables(results, ['intercept', 'perigee'], [])
  
          # Regression on 1 nominal variable.
          results = bdb.execute('''
              REGRESS apogee GIVEN (country_of_operator)
              USING 12 SAMPLES BY satellites;
          ''').fetchall()
          check_regression_variables(
              results, ['intercept'], ['country_of_operator'])
  
          # Regression on 1 nominal + 1 numerical variable.
          bdb.execute('''
              REGRESS apogee GIVEN (perigee, country_of_operator)
              USING 12 SAMPLES BY satellites;
          ''').fetchall()
          check_regression_variables(
              results, ['intercept', 'perigee'], ['country_of_operator'])
  
          # Regression on all variables.
          results = bdb.execute('''
              REGRESS apogee GIVEN (*) USING 12 SAMPLES BY satellites;
          ''', (3,)).fetchall()
          check_regression_variables(
              results,
              ['intercept', 'perigee', 'launch_mass', 'period',],
              ['country_of_operator', 'class_of_orbit',],
          )
  
          # Regression on column selector subexpression with a binding.
          results = bdb.execute('''
              REGRESS apogee GIVEN (
                  satellites.(
                      ESTIMATE * FROM VARIABLES OF satellites
                      ORDER BY dependence probability with apogee DESC
                      LIMIT ?
                  )
              )
              USING 12 SAMPLES BY satellites MODELED BY m USING MODEL 1;
          ''', (3,)).fetchall()
  
          cursor = bdb.execute('''
              ESTIMATE * FROM VARIABLES OF satellites
                  ORDER BY dependence probability with apogee DESC
                  LIMIT ?
          ''', (3,)).fetchall()
          top_variables = [c[0] for c in cursor]
          nominals = [
              var for var in top_variables
              if var in ['country_of_operator', 'class_of_orbit',]
          ]
          numericals = [var for var in top_variables if var not in nominals]
          check_regression_variables(
              results, numericals + ['intercept'], nominals)
  
          # Cannot mix * with other variables.
          with pytest.raises(BQLError):
              bdb.execute('''
                  REGRESS apogee GIVEN (*, class_of_orbit)
                  USING 1 SAMPLES BY satellites;
              ''').fetchall()
  
          # Not enough data for regression, 1 unique nominal variable.
          with pytest.raises(ValueError):
              bdb.execute('''
                  REGRESS apogee GIVEN (class_of_orbit)
                  USING 1 SAMPLES BY satellites;
              ''').fetchall()
  
files: tests/test_regress.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import os
  
  import pytest
  
  import bayeslite
  import bayeslite.read_csv as read_csv
  
  from bayeslite.guess import bayesdb_guess_population
  
  
  root = os.path.dirname(os.path.abspath(__file__))
  dha_csv = os.path.join(root, 'dha.csv')
  
  
  # Test that simulating a column constrained to have a specific value
  # returns an error.
  #
  # XXX This should be a backend-independent test.
  def test_simulate_drawconstraint_error__ci_slow():
      with bayeslite.bayesdb_open() as bdb:
          with open(dha_csv, 'rU') as f:
              read_csv.bayesdb_read_csv(bdb, 'dha', f, header=True, create=True)
          bdb.backends['cgpm'].set_multiprocess(False)
          bayesdb_guess_population(
              bdb, 'hospital', 'dha', overrides=[('name', 'key')])
          bdb.execute(
              'CREATE GENERATOR hospital_cc FOR hospital USING cgpm;')
          bdb.execute('INITIALIZE 1 MODEL FOR hospital_cc')
          bdb.execute('ANALYZE hospital_cc FOR 1 ITERATION (OPTIMIZED);')
          with pytest.raises(ValueError):
              bdb.execute('''
                  SIMULATE ttl_mdcr_spnd, n_death_ill FROM hospital
                      GIVEN TTL_MDCR_SPND = 40000
                      LIMIT 100
              ''').fetchall()
          samples = bdb.execute('''
              SIMULATE n_death_ill FROM hospital
                  GIVEN TTL_MDCR_SPND = 40000
                  LIMIT 100
          ''').fetchall()
          assert len(samples) == 100
          assert all(len(s) == 1 for s in samples)
  
  
  data = [
      ('foo', 56),
      ('bar', 0),
      ('baz', 1),
      ('quux', 1),
      ('zot', 0),
      ('mumble', 2),
      ('frotz', 0),
      ('gargle', 0),
      ('mumph', 1),
      ('hunf', 3),
      ('blort', 0)
  ]
  
  
  def test_simulate_given_rowid():
      # Test simulation of a variable given a rowid. Uses synthetic data with
      # one variable, in which one value of the variable is different from the
      # others by an order of magnitude. Thus, simulating the value for that row
      # should produce values that are significantly different from simulated
      # values of the variable for another row.
      with bayeslite.bayesdb_open() as bdb:
          bdb.backends['cgpm'].set_multiprocess(False)
          bdb.sql_execute('CREATE TABLE t(x TEXT, y NUMERIC)')
          for row in data:
              bdb.sql_execute('INSERT INTO t (x, y) VALUES (?, ?)', row)
          bdb.execute('''
              CREATE POPULATION t_p FOR t WITH SCHEMA {
                  IGNORE x;
                  y NUMERICAL;
              }
          ''')
          bdb.execute('''
              CREATE GENERATOR t_g FOR t_p;
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR t_g')
          bdb.execute('ANALYZE t_g FOR 3 ITERATION')
          bdb.execute('''
              CREATE TABLE row1 AS
                  SIMULATE y FROM t_p
                  GIVEN _rowid_ = 1
              LIMIT 100
          ''')
          bdb.execute('''
              CREATE TABLE row5 AS
                  SIMULATE y FROM t_p
                  GIVEN oid = 5
              LIMIT 100
          ''')
          row1_avg = bdb.execute('SELECT AVG(y) FROM row1').fetchall()[0][0]
          row5_avg = bdb.execute('SELECT AVG(y) FROM row5').fetchall()[0][0]
          # Mean of simulations for row 1 should be "significantly" larger.
          assert row5_avg + 10 < row1_avg
  
          # Multiple specified rowids should produce an error.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('''
                  SIMULATE y FROM t_p
                  GIVEN oid = 5, rowid = 2 LIMIT 10;
              ''')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('''
                  SIMULATE y FROM t_p
                  GIVEN _rowid_ = 5, OID = 1, w = 3 LIMIT 10;
              ''')
  
  
  data_multivariate = [
      ('foo',     6,      7,      None),      # rowid = 1
      ('bar',     1,      1,      2),         # rowid = 2
      ('baz',     100,    100,    200),       # rowid = 3
      ('quux',    1000,   2000,   3000),      # rowid = 4
      ('zot',     0,      2,      2),         # rowid = 5
      ('mumble',  20,     10,     30),        # rowid = 6
      ('frotz',   4,      13,     17),        # rowid = 7
      ('gargle',  34,     2,      36),        # rowid = 8
      ('mumph',   78,     4,      82),        # rowid = 9
      ('hunf',    90,     1,      91),        # rowid = 10
      ('blort',   80,     80,     160),       # rowid = 11
      ('wip',     None,   9,      5),         # rowid = 12
  ]
  
  
  def test_simulate_given_rowid_multivariate():
      # Test that GIVEN statement can accept a multivariate constraint clause in
      # which one of the constraints is on _rowid_.
      with bayeslite.bayesdb_open() as bdb:
          bdb.backends['cgpm'].set_multiprocess(False)
          bdb.sql_execute(
              'CREATE TABLE t(x TEXT, y NUMERIC, z NUMERIC, w NUMERIC)')
          for row in data_multivariate:
              bdb.sql_execute(
                  'INSERT INTO t (x, y, z, w) VALUES (?, ?, ?, ?)', row)
          bdb.execute('''
              CREATE POPULATION t_p FOR t WITH SCHEMA {
                  SET STATTYPES OF y, z, w TO NUMERICAL;
                  IGNORE x
              }
          ''')
          bdb.execute('CREATE GENERATOR t_g FOR t_p;')
          bdb.execute('INITIALIZE 1 MODEL FOR t_g')
          bdb.execute('ANALYZE t_g FOR 100 ITERATION (OPTIMIZED)')
          bdb.execute('''
              CREATE TABLE row1_1 AS
                  SIMULATE y FROM t_p
                  GIVEN _rowid_ = 1, w = 3000
              LIMIT 1500
          ''')
          bdb.execute('''
              CREATE TABLE row1_2 AS
                  SIMULATE y FROM t_p
                  GIVEN ROWID = 1, w = 1
              LIMIT 1500
          ''')
          row1_1_avg = bdb.execute('SELECT AVG(y) FROM row1_1').fetchall()[0][0]
          row1_2_avg = bdb.execute('SELECT AVG(y) FROM row1_2').fetchall()[0][0]
          # We expect these values to be close to each other, because conditioning
          # on _rowid_ decouples the dependencies between other variables in the
          # CrossCat generator, so the additional condition on w should have no
          # effect.
          assert abs(row1_1_avg - row1_2_avg) < 2
  
          bdb.execute('''
              CREATE TABLE row_w3000 AS
                  SIMULATE y FROM t_p
                  GIVEN w = 3000
              LIMIT 100
          ''')
          bdb.execute('''
              CREATE TABLE row_w1 AS
                  SIMULATE y FROM t_p
                  GIVEN w = 1
              LIMIT 100
          ''')
          row_w1 = bdb.execute('SELECT AVG(y) FROM row_w1').fetchall()[0][0]
          row_w3000 = bdb.execute('SELECT AVG(y) FROM row_w3000').fetchall()[0][0]
          # We expect these values to be far from each other, because the
          # condition on w should specify very different posteriors for y.
          assert abs(row_w1 - row_w3000) > 100
  
          # A call to SIMULATE without CREATE TABLE. Since oid 1 has w = None, the
          # constraint specification w = 3 is legal.
          result = bdb.execute('''
              SIMULATE y FROM t_p
              GIVEN oid = 1, w = 3
              LIMIT 10;
          ''').fetchall()
          assert len(result) == 10
  
  
  def test_simulate_given_rowid_unincorporated():
      '''Ensure specifying rowid loads constraints for unincorporated rows'''
      with bayeslite.bayesdb_open() as bdb:
          bdb.backends['cgpm'].set_multiprocess(False)
          bdb.sql_execute(
              'CREATE TABLE t(x TEXT, y NUMERIC, z NUMERIC, w NUMERIC)')
          for row in data_multivariate[:-5]:
              bdb.sql_execute(
                  'INSERT INTO t (x, y, z, w) VALUES (?, ?, ?, ?)', row)
          bdb.execute('''
              CREATE POPULATION t_p FOR t WITH SCHEMA {
                  SET STATTYPES OF y, z, w TO NUMERICAL;
                  IGNORE x
              }
          ''')
          bdb.execute('CREATE GENERATOR t_g FOR t_p;')
          bdb.execute('INITIALIZE 1 MODEL FOR t_g')
          bdb.execute('ANALYZE t_g FOR 20 ITERATION (OPTIMIZED)')
  
          # User cannot override values in incorporated rowids. A ValueError is
          # captured because checking for observed rowids is performed by cgpm.
          with pytest.raises(ValueError):
              bdb.execute('''
                  SIMULATE y FROM t_p
                  GIVEN rowid = 3, z = 99
                  LIMIT 10
              ''')
  
          # Insert remaining five rows into base table without incorporating
          # the data into the generator.
          for row in data_multivariate[-5:]:
              bdb.sql_execute(
                  'INSERT INTO t (x, y, z, w) VALUES (?, ?, ?, ?)', row)
  
          # Since rowid = 12 has y = None, the override to y = 1 is legal.
          bdb.execute('''
              SIMULATE z FROM t_p
              GIVEN rowid = 12, y = 1
              LIMIT 10
          ''').fetchall()
  
files: tests/test_simulate.py
files: # -*- coding: utf-8 -*-
  
  #   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import os
  
  import pytest
  
  import bayeslite
  import bayeslite.read_csv as read_csv
  
  from bayeslite.guess import bayesdb_guess_population
  
  
  root = os.path.dirname(os.path.abspath(__file__))
  dha_csv = os.path.join(root, 'dha.csv')
  
  
  # Test that simulating a column constrained to have a specific value
  # returns an error.
  #
  # XXX This should be a backend-independent test.
  def test_simulate_drawconstraint_error__ci_slow():
      with bayeslite.bayesdb_open() as bdb:
          with open(dha_csv, 'rU') as f:
              read_csv.bayesdb_read_csv(bdb, 'dha', f, header=True, create=True)
          bdb.backends['cgpm'].set_multiprocess(False)
          bayesdb_guess_population(
              bdb, 'hospital', 'dha', overrides=[('name', 'key')])
          bdb.execute(
              'CREATE GENERATOR hospital_cc FOR hospital USING cgpm;')
          bdb.execute('INITIALIZE 1 MODEL FOR hospital_cc')
          bdb.execute('ANALYZE hospital_cc FOR 1 ITERATION (OPTIMIZED);')
          with pytest.raises(ValueError):
              bdb.execute('''
                  SIMULATE ttl_mdcr_spnd, n_death_ill FROM hospital
                      GIVEN TTL_MDCR_SPND = 40000
                      LIMIT 100
              ''').fetchall()
          samples = bdb.execute('''
              SIMULATE n_death_ill FROM hospital
                  GIVEN TTL_MDCR_SPND = 40000
                  LIMIT 100
          ''').fetchall()
          assert len(samples) == 100
          assert all(len(s) == 1 for s in samples)
  
  
  data = [
      ('foo', 56),
      ('bar', 0),
      ('baz', 1),
      ('quux', 1),
      ('zot', 0),
      ('mumble', 2),
      ('frotz', 0),
      ('gargle', 0),
      ('mumph', 1),
      ('hunf', 3),
      ('blort', 0)
  ]
  
  
  def test_simulate_given_rowid():
      # Test simulation of a variable given a rowid. Uses synthetic data with
      # one variable, in which one value of the variable is different from the
      # others by an order of magnitude. Thus, simulating the value for that row
      # should produce values that are significantly different from simulated
      # values of the variable for another row.
      with bayeslite.bayesdb_open() as bdb:
          bdb.backends['cgpm'].set_multiprocess(False)
          bdb.sql_execute('CREATE TABLE t(x TEXT, y NUMERIC)')
          for row in data:
              bdb.sql_execute('INSERT INTO t (x, y) VALUES (?, ?)', row)
          bdb.execute('''
              CREATE POPULATION t_p FOR t WITH SCHEMA {
                  IGNORE x;
                  MODEL y AS NUMERICAL;
              }
          ''')
          bdb.execute('''
              CREATE GENERATOR t_g FOR t_p;
          ''')
          bdb.execute('INITIALIZE 1 MODEL FOR t_g')
          bdb.execute('ANALYZE t_g FOR 3 ITERATION')
          bdb.execute('''
              CREATE TABLE row1 AS
                  SIMULATE y FROM t_p
                  GIVEN _rowid_ = 1
              LIMIT 100
          ''')
          bdb.execute('''
              CREATE TABLE row5 AS
                  SIMULATE y FROM t_p
                  GIVEN oid = 5
              LIMIT 100
          ''')
          row1_avg = bdb.execute('SELECT AVG(y) FROM row1').fetchall()[0][0]
          row5_avg = bdb.execute('SELECT AVG(y) FROM row5').fetchall()[0][0]
          # Mean of simulations for row 1 should be "significantly" larger.
          assert row5_avg + 10 < row1_avg
  
          # Multiple specified rowids should produce an error.
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('''
                  SIMULATE y FROM t_p
                  GIVEN oid = 5, rowid = 2 LIMIT 10;
              ''')
          with pytest.raises(bayeslite.BQLError):
              bdb.execute('''
                  SIMULATE y FROM t_p
                  GIVEN _rowid_ = 5, OID = 1, w = 3 LIMIT 10;
              ''')
  
  
  data_multivariate = [
      ('foo',     6,      7,      None),      # rowid = 1
      ('bar',     1,      1,      2),         # rowid = 2
      ('baz',     100,    100,    200),       # rowid = 3
      ('quux',    1000,   2000,   3000),      # rowid = 4
      ('zot',     0,      2,      2),         # rowid = 5
      ('mumble',  20,     10,     30),        # rowid = 6
      ('frotz',   4,      13,     17),        # rowid = 7
      ('gargle',  34,     2,      36),        # rowid = 8
      ('mumph',   78,     4,      82),        # rowid = 9
      ('hunf',    90,     1,      91),        # rowid = 10
      ('blort',   80,     80,     160),       # rowid = 11
      ('wip',     None,   9,      5),         # rowid = 12
  ]
  
  
  def test_simulate_given_rowid_multivariate():
      # Test that GIVEN statement can accept a multivariate constraint clause in
      # which one of the constraints is on _rowid_.
      with bayeslite.bayesdb_open() as bdb:
          bdb.backends['cgpm'].set_multiprocess(False)
          bdb.sql_execute(
              'CREATE TABLE t(x TEXT, y NUMERIC, z NUMERIC, w NUMERIC)')
          for row in data_multivariate:
              bdb.sql_execute(
                  'INSERT INTO t (x, y, z, w) VALUES (?, ?, ?, ?)', row)
          bdb.execute('''
              CREATE POPULATION t_p FOR t WITH SCHEMA {
                  MODEL y, z, w AS NUMERICAL;
                  IGNORE x
              }
          ''')
          bdb.execute('CREATE GENERATOR t_g FOR t_p;')
          bdb.execute('INITIALIZE 1 MODEL FOR t_g')
          bdb.execute('ANALYZE t_g FOR 100 ITERATION (OPTIMIZED)')
          bdb.execute('''
              CREATE TABLE row1_1 AS
                  SIMULATE y FROM t_p
                  GIVEN _rowid_ = 1, w = 3000
              LIMIT 1500
          ''')
          bdb.execute('''
              CREATE TABLE row1_2 AS
                  SIMULATE y FROM t_p
                  GIVEN ROWID = 1, w = 1
              LIMIT 1500
          ''')
          row1_1_avg = bdb.execute('SELECT AVG(y) FROM row1_1').fetchall()[0][0]
          row1_2_avg = bdb.execute('SELECT AVG(y) FROM row1_2').fetchall()[0][0]
          # We expect these values to be close to each other, because conditioning
          # on _rowid_ decouples the dependencies between other variables in the
          # CrossCat generator, so the additional condition on w should have no
          # effect.
          assert abs(row1_1_avg - row1_2_avg) < 2
  
          bdb.execute('''
              CREATE TABLE row_w3000 AS
                  SIMULATE y FROM t_p
                  GIVEN w = 3000
              LIMIT 100
          ''')
          bdb.execute('''
              CREATE TABLE row_w1 AS
                  SIMULATE y FROM t_p
                  GIVEN w = 1
              LIMIT 100
          ''')
          row_w1 = bdb.execute('SELECT AVG(y) FROM row_w1').fetchall()[0][0]
          row_w3000 = bdb.execute('SELECT AVG(y) FROM row_w3000').fetchall()[0][0]
          # We expect these values to be far from each other, because the
          # condition on w should specify very different posteriors for y.
          assert abs(row_w1 - row_w3000) > 100
  
          # A call to SIMULATE without CREATE TABLE. Since oid 1 has w = None, the
          # constraint specification w = 3 is legal.
          result = bdb.execute('''
              SIMULATE y FROM t_p
              GIVEN oid = 1, w = 3
              LIMIT 10;
          ''').fetchall()
          assert len(result) == 10
  
  
  def test_simulate_given_rowid_unincorporated():
      '''Ensure specifying rowid loads constraints for unincorporated rows'''
      with bayeslite.bayesdb_open() as bdb:
          bdb.backends['cgpm'].set_multiprocess(False)
          bdb.sql_execute(
              'CREATE TABLE t(x TEXT, y NUMERIC, z NUMERIC, w NUMERIC)')
          for row in data_multivariate[:-5]:
              bdb.sql_execute(
                  'INSERT INTO t (x, y, z, w) VALUES (?, ?, ?, ?)', row)
          bdb.execute('''
              CREATE POPULATION t_p FOR t WITH SCHEMA {
                  MODEL y, z, w AS NUMERICAL;
                  IGNORE x
              }
          ''')
          bdb.execute('CREATE GENERATOR t_g FOR t_p;')
          bdb.execute('INITIALIZE 1 MODEL FOR t_g')
          bdb.execute('ANALYZE t_g FOR 20 ITERATION (OPTIMIZED)')
  
          # User cannot override values in incorporated rowids. A ValueError is
          # captured because checking for observed rowids is performed by cgpm.
          with pytest.raises(ValueError):
              bdb.execute('''
                  SIMULATE y FROM t_p
                  GIVEN rowid = 3, z = 99
                  LIMIT 10
              ''')
  
          # Insert remaining five rows into base table without incorporating
          # the data into the generator.
          for row in data_multivariate[-5:]:
              bdb.sql_execute(
                  'INSERT INTO t (x, y, z, w) VALUES (?, ?, ?, ?)', row)
  
          # Since rowid = 12 has y = None, the override to y = 1 is legal.
          bdb.execute('''
              SIMULATE z FROM t_p
              GIVEN rowid = 12, y = 1
              LIMIT 10
          ''').fetchall()
  
files: tests/test_simulate.py